diff --git a/target-arm/cpu-qom.h b/target-arm/cpu-qom.h
index bb6722f..2a0f3f4 100644
--- a/target-arm/cpu-qom.h
+++ b/target-arm/cpu-qom.h
@@ -136,6 +136,8 @@ typedef struct ARMCPU {
     uint32_t id_pfr0;
     uint32_t id_pfr1;
     uint32_t id_dfr0;
+    uint32_t pmceid0;
+    uint32_t pmceid1;
     uint32_t id_afr0;
     uint32_t id_mmfr0;
     uint32_t id_mmfr1;
diff --git a/target-arm/cpu.c b/target-arm/cpu.c
index 87d0772..6a728d9 100644
--- a/target-arm/cpu.c
+++ b/target-arm/cpu.c
@@ -938,6 +938,8 @@ static void cortex_a15_initfn(Object *obj)
     cpu->id_pfr0 = 0x00001131;
     cpu->id_pfr1 = 0x00011011;
     cpu->id_dfr0 = 0x02010555;
+    cpu->pmceid0 = 0x00000481; /* PMUv3 events 0x0, 0x8, and 0x11 */
+    cpu->pmceid1 = 0x00000000;
     cpu->id_afr0 = 0x00000000;
     cpu->id_mmfr0 = 0x10201105;
     cpu->id_mmfr1 = 0x20000000;
diff --git a/target-arm/cpu.h b/target-arm/cpu.h
index 44084a5..f6857fa 100644
--- a/target-arm/cpu.h
+++ b/target-arm/cpu.h
@@ -112,11 +112,22 @@ typedef struct ARMGenericTimer {
     uint64_t ctl; /* Timer Control register */
 } ARMGenericTimer;
 
+/* Indices into ARMCPU.ppi_outputs (and .gt_timer for timers) */
 #define NUM_GTIMERS 2
 #define GTIMER_PHYS 0
 #define GTIMER_VIRT 1
 #define PMU_IDX     2
 
+enum pmu_counter_type {
+    PMU_COUNTER_TYPE_SWINC = 0x000,
+    PMU_COUNTER_TYPE_INSTRUCTIONS = 0x008,
+    PMU_COUNTER_TYPE_CYCLES = 0x011
+};
+
+/* Performance monitor event counter state */
+#define NUM_PMU_COUNTERS 4 /* 0-30, inclusive, doesn't count cycle counter */
+#define PMU_COUNTER_MASK 0x8000000F /* Mask of bits allowed for PMINTEN{SET|CLR}*/
+
 typedef struct {
     uint64_t raw_tcr;
     uint32_t mask;
@@ -287,12 +298,13 @@ typedef struct CPUARMState {
         };
         uint32_t c9_insn; /* Cache lockdown registers.  */
         uint32_t c9_data;
-        uint64_t c9_pmcr; /* performance monitor control register */
-        uint64_t c9_pmcnten; /* perf monitor counter enables */
+        uint32_t c9_pmcr; /* performance monitor control register */
+        uint64_t c9_pmccntr;
+        uint32_t c9_pmcnten; /* perf monitor counter enables */
         uint32_t c9_pmovsr; /* perf monitor overflow status */
-        uint32_t c9_pmxevtyper; /* perf monitor event type */
         uint32_t c9_pmuserenr; /* perf monitor user enable */
         uint32_t c9_pminten; /* perf monitor interrupt enables */
+        uint32_t c9_pmselr; /* perf monitor event counter selection */
         union { /* Memory attribute redirection */
             struct {
 #ifdef HOST_WORDS_BIGENDIAN
@@ -354,6 +366,9 @@ typedef struct CPUARMState {
             uint64_t tpidruro_ns;
             uint64_t tpidrro_el[1];
         };
+        uint32_t c14_pmccfiltr; /* Performance Monitor Filter Register */
+        uint32_t c14_pmevcntr[NUM_PMU_COUNTERS];
+        uint32_t c14_pmevtyper[NUM_PMU_COUNTERS];
         uint64_t c14_cntfrq; /* Counter Frequency register */
         uint64_t c14_cntkctl; /* Timer Control register */
         ARMGenericTimer c14_timer[NUM_GTIMERS];
@@ -371,11 +386,6 @@ typedef struct CPUARMState {
         uint64_t dbgwvr[16]; /* watchpoint value registers */
         uint64_t dbgwcr[16]; /* watchpoint control registers */
         uint64_t mdscr_el1;
-        /* If the counter is enabled, this stores the last time the counter
-         * was reset. Otherwise it stores the counter value
-         */
-        uint64_t c15_ccnt;
-        uint64_t pmccfiltr_el0; /* Performance Monitor Filter Register */
     } cp15;
 
     struct {
@@ -508,17 +518,6 @@ int cpu_arm_signal_handler(int host_signum, void *pinfo,
 int arm_cpu_handle_mmu_fault(CPUState *cpu, vaddr address, int rw,
                              int mmu_idx);
 
-/**
- * pmccntr_sync
- * @env: CPUARMState
- *
- * Synchronises the counter in the PMCCNTR. This must always be called twice,
- * once before any action that might affect the timer and again afterwards.
- * The function is used to swap the state of the register if required.
- * This only happens when not in user mode (!CONFIG_USER_ONLY)
- */
-void pmccntr_sync(CPUARMState *env);
-
 /* SCTLR bit meanings. Several bits have been reused in newer
  * versions of the architecture; in that case we define constants
  * for both old and new bit meanings. Code which tests against those
diff --git a/target-arm/cpu64.c b/target-arm/cpu64.c
index 270bc2f..b2a3a74 100644
--- a/target-arm/cpu64.c
+++ b/target-arm/cpu64.c
@@ -132,6 +132,8 @@ static void aarch64_a57_initfn(Object *obj)
     cpu->id_isar5 = 0x00011121;
     cpu->id_aa64pfr0 = 0x00002222;
     cpu->id_aa64dfr0 = 0x10305106;
+    cpu->pmceid0 = 0x00000481; /* PMUv3 events 0x0, 0x8, and 0x11 */
+    cpu->pmceid1 = 0x00000000;
     cpu->id_aa64isar0 = 0x00011120;
     cpu->id_aa64mmfr0 = 0x00001124;
     cpu->dbgdidr = 0x3516d000;
diff --git a/target-arm/helper.c b/target-arm/helper.c
index be3ad01..a659e67 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -21,11 +21,6 @@ static inline int get_phys_addr(CPUARMState *env, target_ulong address,
                                 int access_type, ARMMMUIdx mmu_idx,
                                 hwaddr *phys_ptr, MemTxAttrs *attrs, int *prot,
                                 target_ulong *page_size);
-
-/* Definitions for the PMCCNTR and PMCR registers */
-#define PMCRD   0x8
-#define PMCRC   0x4
-#define PMCRE   0x1
 #endif
 
 #ifdef TARGET_AARCH64
@@ -660,150 +655,337 @@ static CPAccessResult pmreg_access(CPUARMState *env, const ARMCPRegInfo *ri)
     return CP_ACCESS_OK;
 }
 
-#ifndef CONFIG_USER_ONLY
+/* Definitions for the PMU CP registers */
+#define PMCR_LC  0x40
+#define PMCR_D   0x8
+#define PMCR_C   0x4
+#define PMCR_E   0x1
+
+#define PMCNTEN_C 0x80000000
+
+#define PMCCFILTR_NSH 0x08000000
+#define PMCCFILTR_P 0x80000000
+#define PMCCFILTR_U 0x40000000
 
-static inline bool arm_ccnt_enabled(CPUARMState *env)
+#define PMEVTYPER_NSH 0x08000000
+#define PMEVTYPER_P 0x80000000
+#define PMEVTYPER_U 0x40000000
+#define PMEVTYPER_EVTCOUNT 0x000003ff
+
+#define PMXEVTYPER_P 0x80000000
+#define PMXEVTYPER_U 0x40000000
+
+static void pmccfiltr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                            uint64_t value)
 {
-    /* This does not support checking PMCCFILTR_EL0 register */
+    env->cp15.c14_pmccfiltr = value & 0xfc000000;
+}
 
-    if (!(env->cp15.c9_pmcr & PMCRE)) {
-        return false;
+static uint64_t pmevcntr_read(CPUARMState *env, const ARMCPRegInfo *ri,
+                              const uint8_t idx)
+{
+    if (idx >= NUM_PMU_COUNTERS) {
+        return arm_cp_read_zero(env, ri);
     }
+    return env->cp15.c14_pmevcntr[idx];
+}
 
-    return true;
+static uint64_t pmevcntr_readfn(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+    uint8_t idx = ((ri->crm & 3) << 3) | (ri->opc2 & 7);
+    return pmevcntr_read(env, ri, idx);
 }
 
-/* Called by anything that wants to be an input for event counts to the PMU
- * (except for SWINC, event 0x000, since its events can target specific
- * counters)
- */
-static void pmevcntr_increment(CPUARMState *env, uint8_t event_type,
-                               uint64_t increment_by)
+static void pmevcntr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                             uint64_t value, const uint8_t idx)
 {
+    if (idx >= NUM_PMU_COUNTERS) {
+        arm_cp_write_ignore(env, ri, value);
+    } else {
+        env->cp15.c14_pmevcntr[idx] = value;
+    }
 }
 
-void pmccntr_sync(CPUARMState *env)
+static void pmevcntr_writefn(CPUARMState *env, const ARMCPRegInfo *ri,
+                             uint64_t value)
 {
-    uint64_t temp_ticks;
+    uint8_t idx = ((ri->crm & 3) << 3) | (ri->opc2 & 7);
+    pmevcntr_write(env, ri, value, idx);
 
-    temp_ticks = muldiv64(qemu_clock_get_us(QEMU_CLOCK_VIRTUAL),
-                          get_ticks_per_sec(), 1000000);
+}
 
-    if (env->cp15.c9_pmcr & PMCRD) {
-        /* Increment once every 64 processor clock cycles */
-        temp_ticks /= 64;
+static uint64_t pmevtyper_read(CPUARMState *env, const ARMCPRegInfo *ri,
+                               const uint8_t idx)
+{
+    if (idx >= NUM_PMU_COUNTERS) {
+        return arm_cp_read_zero(env, ri);
     }
+    return env->cp15.c14_pmevtyper[idx];
+}
+
+static uint64_t pmevtyper_readfn(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+    uint8_t idx = ((ri->crm & 3) << 3) | (ri->opc2 & 7);
+    return pmevtyper_read(env, ri, idx);
+}
 
-    if (arm_ccnt_enabled(env)) {
-        env->cp15.c15_ccnt = temp_ticks - env->cp15.c15_ccnt;
+static void pmevtyper_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                           uint64_t value, const uint8_t idx)
+{
+    if (idx >= NUM_PMU_COUNTERS) {
+        arm_cp_write_ignore(env, ri, value);
+    } else {
+        env->cp15.c14_pmevtyper[idx] = value & 0xf80003ff;
     }
 }
 
-static void pmcr_write(CPUARMState *env, const ARMCPRegInfo *ri,
-                       uint64_t value)
+static void pmevtyper_writefn(CPUARMState *env, const ARMCPRegInfo *ri,
+                              uint64_t value)
 {
-    pmccntr_sync(env);
+    uint8_t idx = ((ri->crm & 3) << 3) | (ri->opc2 & 7);
+    pmevtyper_write(env, ri, value, idx);
+}
 
-    if (value & PMCRC) {
-        /* The counter has been reset */
-        env->cp15.c15_ccnt = 0;
+static void pmselr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                           uint64_t value)
+{
+    env->cp15.c9_pmselr = value & 31;
+}
+
+static uint64_t pmxevcntr_read(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+    return pmevcntr_read(env, ri, env->cp15.c9_pmselr & 31);
+}
+
+static void pmxevcntr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                            uint64_t value)
+{
+    pmevcntr_write(env, ri, value, env->cp15.c9_pmselr & 31);
+}
+
+static uint64_t pmxevtyper_read(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+    uint8_t idx = env->cp15.c9_pmselr & 31;
+    if (idx == 31)
+        return env->cp15.c14_pmccfiltr;
+    return pmevtyper_read(env, ri, env->cp15.c9_pmselr & 31);
+}
+
+static void pmxevtyper_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                             uint64_t value)
+{
+    uint8_t idx = env->cp15.c9_pmselr & 31;
+    if (idx == 31)
+        pmccfiltr_write(env, ri, value);
+    else
+        pmevtyper_write(env, ri, value, env->cp15.c9_pmselr & 31);
+}
+
+static inline bool pmccntr_enabled(CPUARMState *env)
+{
+    /* This does not check PMCR.E to see if all events are disabled, it is
+     * assumed that is being checked externally, and doesn't support checking
+     * for the secure/non-secure components of the PMCCFILTR_EL0 register.
+     */
+
+    if (!(env->cp15.c9_pmcnten & PMCNTEN_C)) {
+        return false;
     }
 
-    /* only the DP, X, D and E bits are writable */
-    env->cp15.c9_pmcr &= ~0x39;
-    env->cp15.c9_pmcr |= (value & 0x39);
+    switch (arm_current_el(env)) {
+    case 2:
+        if (!(env->cp15.c14_pmccfiltr & PMCCFILTR_NSH)) {
+            return false;
+        } else {
+          break;
+        }
+    case 1:
+        if (env->cp15.c14_pmccfiltr & PMCCFILTR_P) {
+            return false;
+        } else {
+            break;
+        }
+    case 0:
+        if (env->cp15.c14_pmccfiltr & PMCCFILTR_U) {
+            return false;
+        } else {
+            break;
+        }
+    }
 
-    pmccntr_sync(env);
+    return true;
 }
 
-static uint64_t pmccntr_read(CPUARMState *env, const ARMCPRegInfo *ri)
+static inline bool pmevcntr_enabled(CPUARMState *env, uint8_t idx)
 {
-    uint64_t total_ticks;
+    /* This does not check PMCR.E to see if all events are disabled, it is
+     * assumed that is being checked externally, and doesn't support checking
+     * for the secure/non-secure components of the PMEVTYPER<n>_EL0 registers
+     */
+
+    if (!(env->cp15.c9_pmcnten & (1U << idx))) {
+        return false;
+    }
 
-    if (!arm_ccnt_enabled(env)) {
-        /* Counter is disabled, do not change value */
-        return env->cp15.c15_ccnt;
+    switch (arm_current_el(env)) {
+    case 2:
+        if (!(env->cp15.c14_pmevtyper[idx] & PMEVTYPER_NSH)) {
+            return false;
+        } else {
+          break;
+        }
+    case 1:
+        if (env->cp15.c14_pmevtyper[idx] & PMEVTYPER_P) {
+            return false;
+        } else {
+            break;
+        }
+    case 0:
+        if (env->cp15.c14_pmevtyper[idx] & PMEVTYPER_U) {
+            return false;
+        } else {
+            break;
+        }
     }
 
-    total_ticks = muldiv64(qemu_clock_get_us(QEMU_CLOCK_VIRTUAL),
-                           get_ticks_per_sec(), 1000000);
+    return true;
+}
+
+static void pmu_update_irq(CPUARMState *env)
+{
+    ARMCPU *cpu = arm_env_get_cpu(env);
+    qemu_set_irq(cpu->ppi_outputs[PMU_IDX], (env->cp15.c9_pmcr & PMCR_E) &&
+        (env->cp15.c9_pminten & env->cp15.c9_pmovsr));
+}
+
+static void pmccntr_increment(CPUARMState *env, uint64_t increment_by)
+{
+    if (pmccntr_enabled(env)) {
+        uint64_t new_pmccntr = env->cp15.c9_pmccntr + increment_by;
+        unsigned int overflow_bit = (env->cp15.c9_pmcr & PMCR_LC) ? 63 : 31;
 
-    if (env->cp15.c9_pmcr & PMCRD) {
-        /* Increment once every 64 processor clock cycles */
-        total_ticks /= 64;
+        if (!(new_pmccntr & (1 << overflow_bit)) &&
+              env->cp15.c9_pmccntr & (1 << overflow_bit)) {
+            env->cp15.c9_pmovsr |= (1 << 31);
+            pmu_update_irq(env);
+        }
+        env->cp15.c9_pmccntr = new_pmccntr;
     }
-    return total_ticks - env->cp15.c15_ccnt;
 }
 
-static void pmccntr_write(CPUARMState *env, const ARMCPRegInfo *ri,
-                        uint64_t value)
+/* Called by anything that wants to be an input for event counts to the PMU
+ * (except for SWINC, event 0x000 since its events can target specific counters
+ */
+static void pmevcntr_increment(CPUARMState *env, uint8_t event_type,
+                               uint64_t increment_by)
 {
-    uint64_t total_ticks;
+    unsigned int i;
 
-    if (!arm_ccnt_enabled(env)) {
-        /* Counter is disabled, set the absolute value */
-        env->cp15.c15_ccnt = value;
+    //early out if no counters are enabled
+    if (!(env->cp15.c9_pmcr & PMCR_E) || !env->cp15.c9_pmcnten)
         return;
-    }
 
-    total_ticks = muldiv64(qemu_clock_get_us(QEMU_CLOCK_VIRTUAL),
-                           get_ticks_per_sec(), 1000000);
+    if (event_type == PMU_COUNTER_TYPE_CYCLES)
+        pmccntr_increment(env, increment_by);
+
+    for (i = 0; i < NUM_PMU_COUNTERS; i++) {
+        if (pmevcntr_enabled(env, i) &&
+              (env->cp15.c14_pmevtyper[i] & PMEVTYPER_EVTCOUNT) == event_type) {
+            uint32_t new_pmevcntr = env->cp15.c14_pmevcntr[i] + increment_by;
 
-    if (env->cp15.c9_pmcr & PMCRD) {
-        /* Increment once every 64 processor clock cycles */
-        total_ticks /= 64;
+            if (!(new_pmevcntr & (1 << 31)) &&
+                  (env->cp15.c14_pmevcntr[i] & (1 << 31))) {
+                env->cp15.c9_pmovsr |= (1 << i);
+                pmu_update_irq(env);
+            }
+            env->cp15.c14_pmevcntr[i] = new_pmevcntr;
+        }
     }
-    env->cp15.c15_ccnt = total_ticks - value;
 }
 
-static void pmccntr_write32(CPUARMState *env, const ARMCPRegInfo *ri,
-                            uint64_t value)
+static void pmswinc_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                          uint64_t value)
 {
-    uint64_t cur_val = pmccntr_read(env, NULL);
+    unsigned int i;
 
-    pmccntr_write(env, ri, deposit64(cur_val, 0, 32, value));
+    for (i = 0; i < NUM_PMU_COUNTERS; i++) {
+        if (value & (1 << i) &&
+              pmevcntr_enabled(env, i) &&
+              (env->cp15.c14_pmevtyper[i] & PMEVTYPER_EVTCOUNT) == PMU_COUNTER_TYPE_SWINC) {
+            uint32_t new_pmevcntr = env->cp15.c14_pmevcntr[i] + 1;
+
+            if (!(new_pmevcntr & (1 << 31)) &&
+                  (env->cp15.c14_pmevcntr[i] & (1 << 31))) {
+                env->cp15.c9_pmovsr |= (1 << i);
+                pmu_update_irq(env);
+            }
+            env->cp15.c14_pmevcntr[i] = new_pmevcntr;
+        }
+    }
 }
 
-#else /* CONFIG_USER_ONLY */
+static void pmcr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                       uint64_t value)
+{
+    if (value & PMCR_C) {
+        /* The counter has been reset */
+        env->cp15.c9_pmccntr = 0;
+    }
+
+    /* only the DP, X, D and E bits are writable */
+    env->cp15.c9_pmcr &= ~0x39;
+    env->cp15.c9_pmcr |= (value & 0x39);
+}
 
-void pmccntr_sync(CPUARMState *env)
+static uint64_t pmccntr_read(CPUARMState *env, const ARMCPRegInfo *ri)
 {
+    return env->cp15.c9_pmccntr;
 }
 
-#endif
+static void pmccntr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                          uint64_t value)
+{
+    env->cp15.c9_pmccntr = value;
+}
 
-static void pmccfiltr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+static void pmccntr_write32(CPUARMState *env, const ARMCPRegInfo *ri,
                             uint64_t value)
 {
-    pmccntr_sync(env);
-    env->cp15.pmccfiltr_el0 = value & 0x7E000000;
-    pmccntr_sync(env);
+    uint64_t cur_val = pmccntr_read(env, NULL);
+
+    pmccntr_write(env, ri, deposit64(cur_val, 0, 32, value));
 }
 
 static void pmcntenset_write(CPUARMState *env, const ARMCPRegInfo *ri,
                             uint64_t value)
 {
-    value &= (1 << 31);
+    value &= PMU_COUNTER_MASK;
     env->cp15.c9_pmcnten |= value;
+    pmu_update_irq(env);
 }
 
 static void pmcntenclr_write(CPUARMState *env, const ARMCPRegInfo *ri,
                              uint64_t value)
 {
-    value &= (1 << 31);
+    value &= PMU_COUNTER_MASK;
     env->cp15.c9_pmcnten &= ~value;
+    pmu_update_irq(env);
 }
 
-static void pmovsr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+static void pmovsset_write(CPUARMState *env, const ARMCPRegInfo *ri,
                          uint64_t value)
 {
-    env->cp15.c9_pmovsr &= ~value;
+    value &= PMU_COUNTER_MASK;
+    env->cp15.c9_pmovsr |= value;
+    pmu_update_irq(env);
 }
 
-static void pmxevtyper_write(CPUARMState *env, const ARMCPRegInfo *ri,
-                             uint64_t value)
+static void pmovsr_write(CPUARMState *env, const ARMCPRegInfo *ri,
+                         uint64_t value)
 {
-    env->cp15.c9_pmxevtyper = value & 0xff;
+    value &= PMU_COUNTER_MASK;
+    env->cp15.c9_pmovsr &= ~value;
+    pmu_update_irq(env);
 }
 
 static void pmuserenr_write(CPUARMState *env, const ARMCPRegInfo *ri,
@@ -815,16 +997,17 @@ static void pmuserenr_write(CPUARMState *env, const ARMCPRegInfo *ri,
 static void pmintenset_write(CPUARMState *env, const ARMCPRegInfo *ri,
                              uint64_t value)
 {
-    /* We have no event counters so only the C bit can be changed */
-    value &= (1 << 31);
+    value &= PMU_COUNTER_MASK;
     env->cp15.c9_pminten |= value;
+    pmu_update_irq(env);
 }
 
 static void pmintenclr_write(CPUARMState *env, const ARMCPRegInfo *ri,
                              uint64_t value)
 {
-    value &= (1 << 31);
+    value &= PMU_COUNTER_MASK;
     env->cp15.c9_pminten &= ~value;
+    pmu_update_irq(env);
 }
 
 static void vbar_write(CPUARMState *env, const ARMCPRegInfo *ri,
@@ -905,11 +1088,7 @@ static const ARMCPRegInfo v7_cp_reginfo[] = {
     /* the old v6 WFI, UNPREDICTABLE in v7 but we choose to NOP */
     { .name = "NOP", .cp = 15, .crn = 7, .crm = 0, .opc1 = 0, .opc2 = 4,
       .access = PL1_W, .type = ARM_CP_NOP },
-    /* Performance monitors are implementation defined in v7,
-     * but with an ARM recommended set of registers, which we
-     * follow (although we don't actually implement any counters)
-     *
-     * Performance registers fall into three categories:
+    /* Performance monitor registers fall into three categories:
      *  (a) always UNDEF in PL0, RW in PL1 (PMINTENSET, PMINTENCLR)
      *  (b) RO in PL0 (ie UNDEF on write), RW in PL1 (PMUSERENR)
      *  (c) UNDEF in PL0 if PMUSERENR.EN==0, otherwise accessible (all others)
@@ -918,7 +1097,7 @@ static const ARMCPRegInfo v7_cp_reginfo[] = {
      */
     { .name = "PMCNTENSET", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 1,
       .access = PL0_RW, .type = ARM_CP_ALIAS,
-      .fieldoffset = offsetoflow32(CPUARMState, cp15.c9_pmcnten),
+      .fieldoffset = offsetof(CPUARMState, cp15.c9_pmcnten),
       .writefn = pmcntenset_write,
       .accessfn = pmreg_access,
       .raw_writefn = raw_write },
@@ -929,71 +1108,117 @@ static const ARMCPRegInfo v7_cp_reginfo[] = {
       .writefn = pmcntenset_write, .raw_writefn = raw_write },
     { .name = "PMCNTENCLR", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 2,
       .access = PL0_RW,
-      .fieldoffset = offsetoflow32(CPUARMState, cp15.c9_pmcnten),
+      .fieldoffset = offsetof(CPUARMState, cp15.c9_pmcnten),
       .accessfn = pmreg_access,
-      .writefn = pmcntenclr_write,
+      .writefn = pmcntenclr_write, .raw_writefn = raw_write,
       .type = ARM_CP_ALIAS },
     { .name = "PMCNTENCLR_EL0", .state = ARM_CP_STATE_AA64,
       .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 12, .opc2 = 2,
       .access = PL0_RW, .accessfn = pmreg_access,
       .type = ARM_CP_ALIAS,
       .fieldoffset = offsetof(CPUARMState, cp15.c9_pmcnten),
-      .writefn = pmcntenclr_write },
+      .writefn = pmcntenclr_write, .raw_writefn = raw_write },
+    { .name = "PMOVSSET_EL0", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 14, .opc2 = 3,
+      .access = PL0_RW, .accessfn = pmreg_access,
+      .fieldoffset = offsetof(CPUARMState, cp15.c9_pmovsr), .resetvalue = 0,
+      .writefn = pmovsset_write, .raw_writefn = raw_write },
     { .name = "PMOVSR", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 3,
       .access = PL0_RW, .fieldoffset = offsetof(CPUARMState, cp15.c9_pmovsr),
-      .accessfn = pmreg_access,
+      .accessfn = pmreg_access, .type = ARM_CP_ALIAS,
+      .writefn = pmovsr_write,
+      .raw_writefn = raw_write },
+    { .name = "PMOVSCLR_EL0", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 12, .opc2 = 3,
+      .access = PL0_RW, .accessfn = pmreg_access,
+      .type = ARM_CP_ALIAS,
+      .fieldoffset = offsetof(CPUARMState, cp15.c9_pmovsr),
       .writefn = pmovsr_write,
       .raw_writefn = raw_write },
     /* Unimplemented so WI. */
     { .name = "PMSWINC", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 4,
-      .access = PL0_W, .accessfn = pmreg_access, .type = ARM_CP_NOP },
-    /* Since we don't implement any events, writing to PMSELR is UNPREDICTABLE.
-     * We choose to RAZ/WI.
-     */
+      .access = PL0_W, .accessfn = pmreg_access, .type = ARM_CP_NO_RAW,
+      .writefn = pmswinc_write },
+    { .name = "PMSWINC_EL0", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 12, .opc2 = 4,
+      .access = PL0_W, .accessfn = pmreg_access, .type = ARM_CP_NO_RAW,
+      .writefn = pmswinc_write },
     { .name = "PMSELR", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 5,
-      .access = PL0_RW, .type = ARM_CP_CONST, .resetvalue = 0,
-      .accessfn = pmreg_access },
-#ifndef CONFIG_USER_ONLY
+      .access = PL0_RW, .type = ARM_CP_ALIAS,
+      .accessfn = pmreg_access, .writefn = pmselr_write,
+      .fieldoffset = offsetof(CPUARMState, cp15.c9_pmselr) },
+    { .name = "PMSELR_EL0", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 12, .opc2 = 5,
+      .access = PL0_RW, .accessfn = pmreg_access,
+      .fieldoffset = offsetof(CPUARMState, cp15.c9_pmselr),
+      .writefn = pmselr_write, .resetvalue = 0 },
+    { .name = "PMXEVCNTR", .cp = 15, .crn = 9, .crm = 13, .opc1 = 0, .opc2 = 2,
+      .access = PL0_RW, .type = ARM_CP_ALIAS,
+      .accessfn = pmreg_access, .writefn = pmxevcntr_write,
+      .readfn = pmxevcntr_read },
+    { .name = "PMXEVCNTR_EL0", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 13, .opc2 = 2,
+      .access = PL0_RW, .type = ARM_CP_ALIAS,
+      .accessfn = pmreg_access, .writefn = pmxevcntr_write,
+      .readfn = pmxevcntr_read },
+    { .name = "PMXEVTYPER", .cp = 15, .crn = 9, .crm = 13, .opc1 = 0, .opc2 = 1,
+      .access = PL0_RW, .type = ARM_CP_ALIAS,
+      .accessfn = pmreg_access, .writefn = pmxevtyper_write,
+      .readfn = pmxevtyper_read },
+    { .name = "PMXEVTYPER_EL0", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 13, .opc2 = 1,
+      .access = PL0_RW, .type = ARM_CP_ALIAS,
+      .accessfn = pmreg_access, .writefn = pmxevtyper_write,
+      .readfn = pmxevtyper_read },
     { .name = "PMCCNTR", .cp = 15, .crn = 9, .crm = 13, .opc1 = 0, .opc2 = 0,
-      .access = PL0_RW, .resetvalue = 0, .type = ARM_CP_IO,
+      .access = PL0_RW, .resetvalue = 0,
       .readfn = pmccntr_read, .writefn = pmccntr_write32,
       .accessfn = pmreg_access },
     { .name = "PMCCNTR_EL0", .state = ARM_CP_STATE_AA64,
       .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 13, .opc2 = 0,
       .access = PL0_RW, .accessfn = pmreg_access,
-      .type = ARM_CP_IO,
-      .readfn = pmccntr_read, .writefn = pmccntr_write, },
-#endif
+      .type = ARM_CP_ALIAS,
+      .readfn = pmccntr_read, .writefn = pmccntr_write },
+    { .name = "PMCCFILTR", .cp = 15, .crn = 14, .crm = 15, .opc1 = 0, .opc2 = 7,
+      .access = PL0_RW, .accessfn = pmreg_access,
+      .writefn = pmccfiltr_write,
+      .fieldoffset = offsetof(CPUARMState, cp15.c14_pmccfiltr),
+      .resetvalue = 0 },
     { .name = "PMCCFILTR_EL0", .state = ARM_CP_STATE_AA64,
       .opc0 = 3, .opc1 = 3, .crn = 14, .crm = 15, .opc2 = 7,
       .writefn = pmccfiltr_write,
       .access = PL0_RW, .accessfn = pmreg_access,
-      .type = ARM_CP_IO,
-      .fieldoffset = offsetof(CPUARMState, cp15.pmccfiltr_el0),
-      .resetvalue = 0, },
-    { .name = "PMXEVTYPER", .cp = 15, .crn = 9, .crm = 13, .opc1 = 0, .opc2 = 1,
-      .access = PL0_RW,
-      .fieldoffset = offsetof(CPUARMState, cp15.c9_pmxevtyper),
-      .accessfn = pmreg_access, .writefn = pmxevtyper_write,
-      .raw_writefn = raw_write },
-    /* Unimplemented, RAZ/WI. */
-    { .name = "PMXEVCNTR", .cp = 15, .crn = 9, .crm = 13, .opc1 = 0, .opc2 = 2,
-      .access = PL0_RW, .type = ARM_CP_CONST, .resetvalue = 0,
-      .accessfn = pmreg_access },
+      .type = ARM_CP_ALIAS,
+      .fieldoffset = offsetof(CPUARMState, cp15.c14_pmccfiltr) },
     { .name = "PMUSERENR", .cp = 15, .crn = 9, .crm = 14, .opc1 = 0, .opc2 = 0,
+      .access = PL0_R | PL1_RW, .type = ARM_CP_ALIAS,
+      .fieldoffset = offsetof(CPUARMState, cp15.c9_pmuserenr),
+      .writefn = pmuserenr_write, .raw_writefn = raw_write },
+    { .name = "PMUSERENR_EL0", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 14, .opc2 = 0,
       .access = PL0_R | PL1_RW,
       .fieldoffset = offsetof(CPUARMState, cp15.c9_pmuserenr),
       .resetvalue = 0,
       .writefn = pmuserenr_write, .raw_writefn = raw_write },
     { .name = "PMINTENSET", .cp = 15, .crn = 9, .crm = 14, .opc1 = 0, .opc2 = 1,
-      .access = PL1_RW,
+      .access = PL1_RW, .type = ARM_CP_ALIAS,
       .fieldoffset = offsetof(CPUARMState, cp15.c9_pminten),
       .resetvalue = 0,
       .writefn = pmintenset_write, .raw_writefn = raw_write },
+    { .name = "PMINTENSET_EL1", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 0, .crn = 9, .crm = 14, .opc2 = 1,
+      .access = PL1_RW,
+      .fieldoffset = offsetof(CPUARMState, cp15.c9_pminten), .resetvalue = 0,
+      .writefn = pmintenset_write, .raw_writefn = raw_write },
     { .name = "PMINTENCLR", .cp = 15, .crn = 9, .crm = 14, .opc1 = 0, .opc2 = 2,
       .access = PL1_RW, .type = ARM_CP_ALIAS,
       .fieldoffset = offsetof(CPUARMState, cp15.c9_pminten),
       .resetvalue = 0, .writefn = pmintenclr_write, },
+    { .name = "PMINTENCLR_EL1", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 0, .crn = 9, .crm = 14, .opc2 = 2,
+      .access = PL1_RW, .type = ARM_CP_ALIAS,
+      .fieldoffset = offsetof(CPUARMState, cp15.c9_pminten),
+      .writefn = pmintenclr_write },
     { .name = "VBAR", .state = ARM_CP_STATE_BOTH,
       .opc0 = 3, .crn = 12, .crm = 0, .opc1 = 0, .opc2 = 0,
       .access = PL1_RW, .writefn = vbar_write,
@@ -3024,6 +3249,7 @@ static void define_debug_regs(ARMCPU *cpu)
 
 void register_cp_regs_for_features(ARMCPU *cpu)
 {
+    unsigned int i;
     /* Register all the coprocessor registers based on feature bits */
     CPUARMState *env = &cpu->env;
     if (arm_feature(env, ARM_FEATURE_M)) {
@@ -3120,15 +3346,14 @@ void register_cp_regs_for_features(ARMCPU *cpu)
     }
     if (arm_feature(env, ARM_FEATURE_V7)) {
         /* v7 performance monitor control register: same implementor
-         * field as main ID register, and we implement only the cycle
-         * count register.
+         * field as main ID register.
          */
-#ifndef CONFIG_USER_ONLY
         ARMCPRegInfo pmcr = {
             .name = "PMCR", .cp = 15, .crn = 9, .crm = 12, .opc1 = 0, .opc2 = 0,
             .access = PL0_RW,
-            .type = ARM_CP_IO | ARM_CP_ALIAS,
-            .fieldoffset = offsetoflow32(CPUARMState, cp15.c9_pmcr),
+            .type = ARM_CP_ALIAS,
+            .fieldoffset = offsetof(CPUARMState, cp15.c9_pmcr),
+            .resetvalue = (cpu->midr & 0xff000000) | (NUM_PMU_COUNTERS << 11),
             .accessfn = pmreg_access, .writefn = pmcr_write,
             .raw_writefn = raw_write,
         };
@@ -3136,14 +3361,47 @@ void register_cp_regs_for_features(ARMCPU *cpu)
             .name = "PMCR_EL0", .state = ARM_CP_STATE_AA64,
             .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 12, .opc2 = 0,
             .access = PL0_RW, .accessfn = pmreg_access,
-            .type = ARM_CP_IO,
             .fieldoffset = offsetof(CPUARMState, cp15.c9_pmcr),
-            .resetvalue = cpu->midr & 0xff000000,
+            .resetvalue = (cpu->midr & 0xff000000) | (NUM_PMU_COUNTERS << 11),
             .writefn = pmcr_write, .raw_writefn = raw_write,
         };
         define_one_arm_cp_reg(cpu, &pmcr);
         define_one_arm_cp_reg(cpu, &pmcr64);
-#endif
+
+        for (i = 0; i < 31; i++) {
+            char *pmevcntr_name = g_strdup_printf("PMEVCNTR%d", i);
+            char *pmevcntr_el0_name = g_strdup_printf("PMEVCNTR%d_EL0", i);
+            char *pmevtyper_name = g_strdup_printf("PMEVTYPER%d", i);
+            char *pmevtyper_el0_name = g_strdup_printf("PMEVTYPER%d_EL0", i);
+            ARMCPRegInfo pmcntr_regs[] = {
+                { .name = pmevcntr_name, .cp = 15, .crn = 15,
+                  .crm = 8 | (3 & (i >> 3)), .opc1 = 0, .opc2 = i & 7,
+                  .access = PL0_RW, .resetvalue = 0, .type = ARM_CP_ALIAS,
+                  .readfn = pmevcntr_readfn, .writefn = pmevcntr_writefn,
+                  .accessfn = pmreg_access },
+                { .name = pmevcntr_el0_name, .state = ARM_CP_STATE_AA64,
+                  .opc0 = 3, .opc1 = 3, .crn = 15, .crm = 8 | (3 & (i >> 3)),
+                  .opc2 = i & 7, .access = PL0_RW, .accessfn = pmreg_access,
+                  .resetvalue = 0,
+                  .readfn = pmevcntr_readfn, .writefn = pmevcntr_writefn },
+                { .name = pmevtyper_name, .cp = 15, .crn = 15,
+                  .crm = 12 | (3 & (i >> 3)), .opc1 = 0, .opc2 = i & 7,
+                  .access = PL0_RW, .resetvalue = 0, .type = ARM_CP_ALIAS,
+                  .readfn = pmevtyper_readfn, .writefn = pmevtyper_writefn,
+                  .accessfn = pmreg_access },
+                { .name = pmevtyper_el0_name, .state = ARM_CP_STATE_AA64,
+                  .opc0 = 3, .opc1 = 3, .crn = 15, .crm = 12 | (3 & (i >> 3)),
+                  .opc2 = i & 7, .access = PL0_RW, .accessfn = pmreg_access,
+                  .resetvalue = 0,
+                  .readfn = pmevtyper_readfn, .writefn = pmevtyper_writefn },
+                REGINFO_SENTINEL
+            };
+            define_arm_cp_regs(cpu, pmcntr_regs);
+            g_free(pmevcntr_name);
+            g_free(pmevcntr_el0_name);
+            g_free(pmevtyper_name);
+            g_free(pmevtyper_el0_name);
+        }
         ARMCPRegInfo clidr = {
             .name = "CLIDR", .state = ARM_CP_STATE_BOTH,
             .opc0 = 3, .crn = 0, .crm = 0, .opc1 = 1, .opc2 = 1,
@@ -3169,16 +3427,19 @@ void register_cp_regs_for_features(ARMCPU *cpu)
             { .name = "ID_AA64DFR0_EL1", .state = ARM_CP_STATE_AA64,
               .opc0 = 3, .opc1 = 0, .crn = 0, .crm = 5, .opc2 = 0,
               .access = PL1_R, .type = ARM_CP_CONST,
-              /* We mask out the PMUVer field, because we don't currently
-               * implement the PMU. Not advertising it prevents the guest
-               * from trying to use it and getting UNDEFs on registers we
-               * don't implement.
-               */
-              .resetvalue = cpu->id_aa64dfr0 & ~0xf00 },
+              .resetvalue = cpu->id_aa64dfr0 },
             { .name = "ID_AA64DFR1_EL1", .state = ARM_CP_STATE_AA64,
               .opc0 = 3, .opc1 = 0, .crn = 0, .crm = 5, .opc2 = 1,
               .access = PL1_R, .type = ARM_CP_CONST,
               .resetvalue = cpu->id_aa64dfr1 },
+            { .name = "PMCEID0_EL0", .state = ARM_CP_STATE_AA64,
+              .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 12, .opc2 = 6,
+              .access = PL1_R, .type = ARM_CP_CONST,
+              .resetvalue = cpu->pmceid0},
+            { .name = "PMCEID1_EL0", .state = ARM_CP_STATE_AA64,
+              .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 12, .opc2 = 7,
+              .access = PL1_R, .type = ARM_CP_CONST,
+              .resetvalue = cpu->pmceid1},
             { .name = "ID_AA64AFR0_EL1", .state = ARM_CP_STATE_AA64,
               .opc0 = 3, .opc1 = 0, .crn = 0, .crm = 5, .opc2 = 4,
               .access = PL1_R, .type = ARM_CP_CONST,
@@ -4073,14 +4334,17 @@ void HELPER(context_check_pid)(CPUARMState *env)
 void HELPER(update_instruction_count)(CPUARMState *env)
 {
     if (bbtrace_initialized()) {
-      /*
-       * If the bbv plugin is compiled in and enabled, we must account for the
-       * fact that bbv_profile needs to see prof_ic before we clear it.
-       * However, it doesn't always clear the counter every time this gets
-       * called, so we must keep track of the last value seen to ensure we
-       * update the instruction counter correctly in that case.
-       */
-        increment_instruction_counters(env->prof_ic - env->prof_ic_last);
+        /*
+         * If the bbv plugin is compiled in and enabled, we must account for the
+         * fact that bbv_profile needs to see prof_ic before we clear it.
+         * However, it doesn't always clear the counter every time this gets
+         * called, so we must keep track of the last value seen to ensure we
+         * update the instruction counter correctly in that case.
+         */
+        pmevcntr_increment(env, PMU_COUNTER_TYPE_INSTRUCTIONS,
+                           env->prof_ic - env->prof_ic_last);
+        pmevcntr_increment(env, PMU_COUNTER_TYPE_CYCLES,
+                           env->prof_ic - env->prof_ic_last);
         if (env->prof_pc && env->prof_is_jmp) {
             // If this is the end of a basic block, zero out last_seen counter too
             env->prof_ic_last = 0;
