Message ID | 20240910174747.148141-3-alexei.filippov@syntacore.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | target/riscv: Add support for machine specific pmu's events | expand |
On Wed, Sep 11, 2024 at 3:50 AM Alexei Filippov <alexei.filippov@syntacore.com> wrote: > > Add support of hardware performance monitor into sifive_u machine. > HPM support was made along to [SiFive FU740-C000 manual v1p6] sec. 3.8. > All described events for now counting by the same algorithm. Each event > counting could be implemented separately in read/write call backs, also > events combination are working, but right counting for them should be > implemented separatly. > > Signed-off-by: Alexei Filippov <alexei.filippov@syntacore.com> > --- > hw/misc/meson.build | 1 + > hw/misc/sifive_u_pmu.c | 384 +++++++++++++++++++++++++++++++++ > hw/riscv/sifive_u.c | 14 ++ > include/hw/misc/sifive_u_pmu.h | 24 +++ > target/riscv/cpu.c | 20 +- > 5 files changed, 442 insertions(+), 1 deletion(-) > create mode 100644 hw/misc/sifive_u_pmu.c > create mode 100644 include/hw/misc/sifive_u_pmu.h > > diff --git a/hw/misc/meson.build b/hw/misc/meson.build > index 2ca8717be2..ea93a38268 100644 > --- a/hw/misc/meson.build > +++ b/hw/misc/meson.build > @@ -34,6 +34,7 @@ system_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c')) > system_ss.add(when: 'CONFIG_SIFIVE_E_AON', if_true: files('sifive_e_aon.c')) > system_ss.add(when: 'CONFIG_SIFIVE_U_OTP', if_true: files('sifive_u_otp.c')) > system_ss.add(when: 'CONFIG_SIFIVE_U_PRCI', if_true: files('sifive_u_prci.c')) > +specific_ss.add(when: 'CONFIG_SIFIVE_U', if_true: files('sifive_u_pmu.c')) > > subdir('macio') > > diff --git a/hw/misc/sifive_u_pmu.c b/hw/misc/sifive_u_pmu.c > new file mode 100644 > index 0000000000..929a2517cc > --- /dev/null > +++ b/hw/misc/sifive_u_pmu.c > @@ -0,0 +1,384 @@ > +/* > + * RISC-V SiFive U PMU emulation. > + * > + * Copyright (c) 2024 Alexei Filippov <alexei.filippov@syntacore.com>. > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms and conditions of the GNU General Public License, > + * version 2 or later, as published by the Free Software Foundation. > + * > + * This program is distributed in the hope it will be useful, but WITHOUT > + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or > + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for > + * more details. > + * > + * You should have received a copy of the GNU General Public License along with > + * this program. If not, see <http://www.gnu.org/licenses/>. > + */ > + > +#include "qemu/osdep.h" > +#include "qemu/log.h" > +#include "qemu/error-report.h" > +#include "target/riscv/cpu.h" > +#include "target/riscv/pmu.h" > +#include "include/hw/misc/sifive_u_pmu.h" > +#include "sysemu/cpu-timers.h" > +#include "sysemu/device_tree.h" > + > +REG32(SIFIVE_U_PMU_MHPMEVENT, 0x323) > + FIELD(SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, 0, 8) > + FIELD(SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, 8, 18) > + > + /* > + * Support all PMU events described in > + * https://sifive.cdn.prismic.io/sifive/1a82e600-1f93-4f41-b2d8-86ed8b16acba_fu740-c000-manual-v1p6.pdf > + * FU740-C000 Manual sec. 3.8 "Hardware Performace Monitor", all We don't explicitly support the FU740 CPU, so this is a little strange. What about when the FU740 v2 comes out and it has new events or the FU540 (which I assume has less)? Alistair > + * events trigger irq by counter overflow, by default all caunters > + * caunted identically, special behavior, combining events for example, > + * must be described separately in write/read and trigger irq functions. > + */ > + > +#define SIFIVE_U_PMU_INST { \ > + X(RISCV_SIFIVE_U_EVENT_EXCEPTION_TAKEN, 0x00001), \ > + X(RISCV_SIFIVE_U_EVENT_INTEGER_LOAD_RETIRED, 0x00002), \ > + X(RISCV_SIFIVE_U_EVENT_INTEGER_STORE_RETIRED, 0x00004), \ > + X(RISCV_SIFIVE_U_EVENT_ATOMIC_MEMORY_RETIRED, 0x00008), \ > + X(RISCV_SIFIVE_U_EVENT_SYSTEM_INSTRUCTION_RETIRED, 0x00010), \ > + X(RISCV_SIFIVE_U_EVENT_INTEGER_ARITHMETIC_RETIRED, 0x00020), \ > + X(RISCV_SIFIVE_U_EVENT_CONDITIONAL_BRANCH_RETIRED, 0x00040), \ > + X(RISCV_SIFIVE_U_EVENT_JAL_INSTRUCTION_RETIRED, 0x00080), \ > + X(RISCV_SIFIVE_U_EVENT_JALR_INSTRUCTION_RETIRED, 0x00100), \ > + X(RISCV_SIFIVE_U_EVENT_INTEGER_MULTIPLICATION_RETIRED, 0x00200), \ > + X(RISCV_SIFIVE_U_EVENT_INTEGER_DIVISION_RETIRED, 0x00400), \ > + X(RISCV_SIFIVE_U_EVENT_FP_LOAD_RETIRED, 0x00800), \ > + X(RISCV_SIFIVE_U_EVENT_FP_STORE_RETIRED, 0x01000), \ > + X(RISCV_SIFIVE_U_EVENT_FP_ADDITION_RETIRED, 0x02000), \ > + X(RISCV_SIFIVE_U_EVENT_FP_MULTIPLICATION_RETIRED, 0x04000), \ > + X(RISCV_SIFIVE_U_EVENT_FP_FUSEDMADD_RETIRED, 0x08000), \ > + X(RISCV_SIFIVE_U_EVENT_FP_DIV_SQRT_RETIRED, 0x10000), \ > + X(RISCV_SIFIVE_U_EVENT_OTHER_FP_RETIRED, 0x20000), } > + > +#define SIFIVE_U_PMU_MICROARCH { \ > + X(RISCV_SIFIVE_U_EVENT_ADDRESSGEN_INTERLOCK, 0x00001), \ > + X(RISCV_SIFIVE_U_EVENT_LONGLAT_INTERLOCK, 0x00002), \ > + X(RISCV_SIFIVE_U_EVENT_CSR_READ_INTERLOCK, 0x00004), \ > + X(RISCV_SIFIVE_U_EVENT_ICACHE_ITIM_BUSY, 0x00008), \ > + X(RISCV_SIFIVE_U_EVENT_DCACHE_DTIM_BUSY, 0x00010), \ > + X(RISCV_SIFIVE_U_EVENT_BRANCH_DIRECTION_MISPREDICTION, 0x00020), \ > + X(RISCV_SIFIVE_U_EVENT_BRANCH_TARGET_MISPREDICTION, 0x00040), \ > + X(RISCV_SIFIVE_U_EVENT_PIPE_FLUSH_CSR_WRITE, 0x00080), \ > + X(RISCV_SIFIVE_U_EVENT_PIPE_FLUSH_OTHER_EVENT, 0x00100), \ > + X(RISCV_SIFIVE_U_EVENT_INTEGER_MULTIPLICATION_INTERLOCK, 0x00200), \ > + X(RISCV_SIFIVE_U_EVENT_FP_INTERLOCK, 0x00400), } > + > +#define SIFIVE_U_PMU_MEM { \ > + X(RISCV_SIFIVE_U_EVENT_ICACHE_RETIRED, 0x00001), \ > + X(RISCV_SIFIVE_U_EVENT_DCACHE_MISS_MMIO_ACCESSES, 0x00002), \ > + X(RISCV_SIFIVE_U_EVENT_DCACHE_WRITEBACK, 0x00004), \ > + X(RISCV_SIFIVE_U_EVENT_INST_TLB_MISS, 0x00008), \ > + X(RISCV_SIFIVE_U_EVENT_DATA_TLB_MISS, 0x00010), \ > + X(RISCV_SIFIVE_U_EVENT_UTLB_MISS, 0x00020), } > + > +#define X(a, b) a = b > + enum SIFIVE_U_PMU_INST; > + enum SIFIVE_U_PMU_MEM; > + enum SIFIVE_U_PMU_MICROARCH; > +#undef X > + > +#define X(a, b) a > + const uint32_t > + riscv_sifive_u_pmu_events[RISCV_SIFIVE_U_CLASS_MAX][RISCV_SIFIVE_U_MASK_MAX] = { > + SIFIVE_U_PMU_INST, > + SIFIVE_U_PMU_MICROARCH, > + SIFIVE_U_PMU_MEM, > + }; > +#undef X > + > +void sifive_u_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name) > +{ > + uint32_t fdt_event_mhpmevent_map[10 * 3] = {}; > + uint32_t fdt_event_mhpmctr_map[6 * 4] = {}; > + uint32_t event_idx; > + > + /* > + * SBI_PMU_HW_CACHE_REFERENCES: 0x3 -> Instruction cache/ITIM busy | > + * Data cache/DTIM busy > + * result: < 0x3 0x0 1801 > > + */ > + fdt_event_mhpmevent_map[0] = cpu_to_be32(0x3); > + fdt_event_mhpmevent_map[1] = cpu_to_be32(0x0); > + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, > + RISCV_SIFIVE_U_EVENT_ICACHE_ITIM_BUSY | > + RISCV_SIFIVE_U_EVENT_DCACHE_DTIM_BUSY); > + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, > + RISCV_SIFIVE_U_CLASS_MICROARCH); > + fdt_event_mhpmevent_map[2] = cpu_to_be32(event_idx); > + > + > + /* > + * SBI_PMU_HW_CACHE_MISSES: 0x4 -> Instruction cache miss | > + * Data cache miss or mem-mapped I/O access > + * result: < 0x4 0x0 0x302 > > + */ > + fdt_event_mhpmevent_map[3] = cpu_to_be32(0x4); > + fdt_event_mhpmevent_map[4] = cpu_to_be32(0x0); > + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, > + RISCV_SIFIVE_U_EVENT_ICACHE_RETIRED | > + RISCV_SIFIVE_U_EVENT_DCACHE_MISS_MMIO_ACCESSES); > + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, > + RISCV_SIFIVE_U_CLASS_MEM); > + fdt_event_mhpmevent_map[5] = cpu_to_be32(event_idx); > + > + /* > + * SBI_PMU_HW_BRANCH_INSTRUCTIONS: 0x5 -> Conditional branch retired > + * result: < 0x5 0x0 0x4000 > > + */ > + fdt_event_mhpmevent_map[6] = cpu_to_be32(0x5); > + fdt_event_mhpmevent_map[7] = cpu_to_be32(0x0); > + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, > + RISCV_SIFIVE_U_EVENT_CONDITIONAL_BRANCH_RETIRED); > + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, > + RISCV_SIFIVE_U_CLASS_INST); > + fdt_event_mhpmevent_map[8] = cpu_to_be32(event_idx); > + > + /* > + * SBI_PMU_HW_BRANCH_MISSES: 0x6 -> Branch direction misprediction | > + * Branch/jump target misprediction > + * result: < 0x6 0x0 0x6001 > > + */ > + fdt_event_mhpmevent_map[9] = cpu_to_be32(0x6); > + fdt_event_mhpmevent_map[10] = cpu_to_be32(0x0); > + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, > + RISCV_SIFIVE_U_EVENT_BRANCH_DIRECTION_MISPREDICTION | > + RISCV_SIFIVE_U_EVENT_BRANCH_TARGET_MISPREDICTION); > + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, > + RISCV_SIFIVE_U_CLASS_MICROARCH); > + fdt_event_mhpmevent_map[11] = cpu_to_be32(event_idx); > + > + /* > + * L1D_READ_MISS: 0x10001 -> Data cache miss or memory-mapped I/O access > + * result: < 0x10001 0x0 0x202 > > + */ > + fdt_event_mhpmevent_map[12] = cpu_to_be32(0x10001); > + fdt_event_mhpmevent_map[13] = cpu_to_be32(0x0); > + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, > + RISCV_SIFIVE_U_EVENT_DCACHE_MISS_MMIO_ACCESSES); > + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, > + RISCV_SIFIVE_U_CLASS_MEM); > + fdt_event_mhpmevent_map[14] = cpu_to_be32(event_idx); > + > + /* > + * L1D_WRITE_ACCESS: 0x10002 -> Data cache write back > + * result: < 0x10002 0x0 0x402 > > + */ > + fdt_event_mhpmevent_map[15] = cpu_to_be32(0x10002); > + fdt_event_mhpmevent_map[16] = cpu_to_be32(0x0); > + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, > + RISCV_SIFIVE_U_EVENT_DCACHE_WRITEBACK); > + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, > + RISCV_SIFIVE_U_CLASS_MEM); > + fdt_event_mhpmevent_map[17] = cpu_to_be32(event_idx); > + > + /* > + * L1I_READ_ACCESS: 0x10009 -> Instruction cache miss > + * result: < 0x10009 0x0 0x102 > > + */ > + fdt_event_mhpmevent_map[18] = cpu_to_be32(0x10009); > + fdt_event_mhpmevent_map[19] = cpu_to_be32(0x0); > + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, > + RISCV_SIFIVE_U_EVENT_ICACHE_RETIRED); > + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, > + RISCV_SIFIVE_U_CLASS_MEM); > + fdt_event_mhpmevent_map[20] = cpu_to_be32(event_idx); > + > + /* > + * LL_READ_MISS: 0x10011 -> UTLB miss > + * result: < 0x10011 0x0 0x2002 > > + */ > + fdt_event_mhpmevent_map[21] = cpu_to_be32(0x10011); > + fdt_event_mhpmevent_map[22] = cpu_to_be32(0x0); > + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, > + RISCV_SIFIVE_U_EVENT_UTLB_MISS); > + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, > + RISCV_SIFIVE_U_CLASS_MEM); > + fdt_event_mhpmevent_map[23] = cpu_to_be32(event_idx); > + > + /* > + * DTLB_READ_MISS: 0x10019 -> Data TLB miss > + * result: < 0x10019 0x0 0x1002 > > + */ > + fdt_event_mhpmevent_map[24] = cpu_to_be32(0x10019); > + fdt_event_mhpmevent_map[25] = cpu_to_be32(0x0); > + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, > + RISCV_SIFIVE_U_EVENT_DATA_TLB_MISS); > + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, > + RISCV_SIFIVE_U_CLASS_MEM); > + fdt_event_mhpmevent_map[26] = cpu_to_be32(event_idx); > + > + /* > + * DTLB_READ_MISS: 0x10021 -> Data TLB miss > + * result: < 0x10019 0x0 0x802 > > + */ > + fdt_event_mhpmevent_map[27] = cpu_to_be32(0x10021); > + fdt_event_mhpmevent_map[28] = cpu_to_be32(0x0); > + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, > + RISCV_SIFIVE_U_EVENT_INST_TLB_MISS); > + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, > + RISCV_SIFIVE_U_CLASS_MEM); > + fdt_event_mhpmevent_map[29] = cpu_to_be32(event_idx); > + > + fdt_event_mhpmctr_map[0] = cpu_to_be32(0x00003); > + fdt_event_mhpmctr_map[1] = cpu_to_be32(0x00006); > + fdt_event_mhpmctr_map[2] = cpu_to_be32(cmask); > + > + fdt_event_mhpmctr_map[3] = cpu_to_be32(0x10001); > + fdt_event_mhpmctr_map[4] = cpu_to_be32(0x10002); > + fdt_event_mhpmctr_map[5] = cpu_to_be32(cmask); > + > + fdt_event_mhpmctr_map[6] = cpu_to_be32(0x10009); > + fdt_event_mhpmctr_map[7] = cpu_to_be32(0x10009); > + fdt_event_mhpmctr_map[8] = cpu_to_be32(cmask); > + > + fdt_event_mhpmctr_map[9] = cpu_to_be32(0x10011); > + fdt_event_mhpmctr_map[10] = cpu_to_be32(0x10011); > + fdt_event_mhpmctr_map[11] = cpu_to_be32(cmask); > + > + fdt_event_mhpmctr_map[12] = cpu_to_be32(0x10019); > + fdt_event_mhpmctr_map[13] = cpu_to_be32(0x10019); > + fdt_event_mhpmctr_map[14] = cpu_to_be32(cmask); > + > + fdt_event_mhpmctr_map[15] = cpu_to_be32(0x10021); > + fdt_event_mhpmctr_map[16] = cpu_to_be32(0x10021); > + fdt_event_mhpmctr_map[17] = cpu_to_be32(cmask); > + > + fdt_event_mhpmctr_map[18] = cpu_to_be32(0x1); > + fdt_event_mhpmctr_map[19] = cpu_to_be32(0x1); > + fdt_event_mhpmctr_map[20] = cpu_to_be32(cmask | 1 << 0); > + > + fdt_event_mhpmctr_map[21] = cpu_to_be32(0x2); > + fdt_event_mhpmctr_map[22] = cpu_to_be32(0x2); > + fdt_event_mhpmctr_map[23] = cpu_to_be32(cmask | 1 << 2); > + > + /* This a OpenSBI specific DT property documented in OpenSBI docs */ > + qemu_fdt_setprop(fdt, pmu_name, "riscv,event-to-mhpmevent", > + fdt_event_mhpmevent_map, sizeof(fdt_event_mhpmevent_map)); > + qemu_fdt_setprop(fdt, pmu_name, "riscv,event-to-mhpmcounters", > + fdt_event_mhpmctr_map, sizeof(fdt_event_mhpmctr_map)); > + > +} > + > +bool riscv_sifive_u_supported_events(uint32_t event_idx) > +{ > + uint32_t group = FIELD_EX32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS); > + uint32_t event_mask = FIELD_EX32(event_idx, SIFIVE_U_PMU_MHPMEVENT, > + EVENT_MASK); > + uint32_t idx = 32 - clz32(event_mask); > + > + if (group >= RISCV_SIFIVE_U_CLASS_MAX || idx > RISCV_SIFIVE_U_MASK_MAX) { > + return 0; > + } > + > + bool event_match = true; > + if (!idx) { > + event_match = false; > + } > + while (event_match && idx) { > + if (!riscv_sifive_u_pmu_events[group][idx - 1]) { > + event_match = false; > + } > + event_mask = event_mask & (~(1 << (idx - 1))); > + idx = 32 - clz32(event_mask); > + } > + return event_match; > +} > + > +static target_ulong get_ticks(bool icnt, bool high_half) > +{ > + int64_t val; > + target_ulong res; > + > + if (icnt && icount_enabled()) { > + val = icount_get_raw(); > + } else { > + val = cpu_get_host_ticks(); > + } > + > + if (high_half) { > + res = val >> 32; > + } else { > + res = val; > + } > + > + return res; > +} > + > +target_ulong riscv_sifive_u_pmu_ctr_read(PMUCTRState *counter, > + uint32_t event_idx, bool high_half) > +{ > + target_ulong ctrl_val = high_half ? counter->mhpmcounterh_val : > + counter->mhpmcounter_val; > + uint32_t event_class_field = FIELD_EX32(event_idx, > + SIFIVE_U_PMU_MHPMEVENT, > + EVENT_CLASS); > + uint32_t event_mask_field = FIELD_EX32(event_idx, > + SIFIVE_U_PMU_MHPMEVENT, > + EVENT_MASK); > + > + if (event_class_field >= RISCV_SIFIVE_U_CLASS_MAX || > + (32 - clz32(event_mask_field)) >= RISCV_SIFIVE_U_MASK_MAX) { > + return ctrl_val; > + } > + > + switch (event_class_field) { > + /* If we want to handle some events separately */ > + > + /* fall through */ > + default: > + /* In case we do not want handle it separately */ > + if (riscv_sifive_u_supported_events(event_idx)) { > + return get_ticks(false, high_half); > + } > + /* Did not find event in supported */ > + return ctrl_val; > + } > + > + g_assert_not_reached(); /* unreachable */ > + return 0; > +} > + > +void riscv_sifive_u_pmu_ctr_write(PMUCTRState *counter, uint32_t event_idx, > + target_ulong val, bool high_half) > +{ > + target_ulong *ctr_prev = high_half ? &counter->mhpmcounterh_prev : > + &counter->mhpmcounter_prev; > + uint32_t event_class_field = FIELD_EX32(event_idx, > + SIFIVE_U_PMU_MHPMEVENT, > + EVENT_CLASS); > + uint32_t event_mask_field = FIELD_EX32(event_idx, > + SIFIVE_U_PMU_MHPMEVENT, > + EVENT_MASK); > + > + if (event_class_field >= RISCV_SIFIVE_U_CLASS_MAX || > + (32 - clz32(event_mask_field)) >= RISCV_SIFIVE_U_MASK_MAX) { > + *ctr_prev = val; > + return; > + } > + > + switch (event_class_field) { > + /* If we want to handle some events separately */ > + > + /* fall through */ > + default: > + /* In case we do not want handle it separately */ > + if (riscv_sifive_u_supported_events(event_idx)) { > + *ctr_prev = get_ticks(false, high_half); > + return; > + } > + /* Did not find event in supported */ > + *ctr_prev = val; > + return; > + } > + > + g_assert_not_reached(); /* unreachable */ > + return; > +} > diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c > index af5f923f54..2771102fbe 100644 > --- a/hw/riscv/sifive_u.c > +++ b/hw/riscv/sifive_u.c > @@ -60,6 +60,7 @@ > #include "sysemu/device_tree.h" > #include "sysemu/runstate.h" > #include "sysemu/sysemu.h" > +#include "include/hw/misc/sifive_u_pmu.h" > > #include <libfdt.h> > > @@ -93,6 +94,17 @@ static const MemMapEntry sifive_u_memmap[] = { > #define OTP_SERIAL 1 > #define GEM_REVISION 0x10070109 > > +static void create_fdt_pmu(MachineState *s) > +{ > + g_autofree char *pmu_name = g_strdup_printf("/pmu"); > + MachineState *ms = MACHINE(s); > + RISCVCPU *hart = RISCV_CPU(qemu_get_cpu(0)); > + > + qemu_fdt_add_subnode(ms->fdt, pmu_name); > + qemu_fdt_setprop_string(ms->fdt, pmu_name, "compatible", "riscv,pmu"); > + sifive_u_pmu_generate_fdt_node(ms->fdt, hart->pmu_avail_ctrs, pmu_name); > +} > + > static void create_fdt(SiFiveUState *s, const MemMapEntry *memmap, > bool is_32_bit) > { > @@ -499,6 +511,8 @@ static void create_fdt(SiFiveUState *s, const MemMapEntry *memmap, > qemu_fdt_setprop_string(fdt, "/aliases", "serial0", nodename); > > g_free(nodename); > + > + create_fdt_pmu(ms); > } > > static void sifive_u_machine_reset(void *opaque, int n, int level) > diff --git a/include/hw/misc/sifive_u_pmu.h b/include/hw/misc/sifive_u_pmu.h > new file mode 100644 > index 0000000000..8877c2ba46 > --- /dev/null > +++ b/include/hw/misc/sifive_u_pmu.h > @@ -0,0 +1,24 @@ > +#ifndef RISCV_SIFIVE_U_PMU_H > +#define RISCV_SIFIVE_U_PMU_H > + > +#include "target/riscv/cpu.h" > +#include "qapi/error.h" > + > +/* Maximum events per class */ > +#define RISCV_SIFIVE_U_MASK_MAX 18 > + > +enum riscv_sifive_u_pmu_classes { > + RISCV_SIFIVE_U_CLASS_INST = 0x0, > + RISCV_SIFIVE_U_CLASS_MICROARCH, > + RISCV_SIFIVE_U_CLASS_MEM, > + RISCV_SIFIVE_U_CLASS_MAX = 0x3 > +}; > + > +bool riscv_sifive_u_supported_events(uint32_t event_idx); > +void riscv_sifive_u_pmu_ctr_write(PMUCTRState *counter, uint32_t event_idx, > + target_ulong val, bool high_half); > +target_ulong riscv_sifive_u_pmu_ctr_read(PMUCTRState *counter, > + uint32_t event_idx, bool high_half); > +void sifive_u_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name); > + > +#endif /* RISCV_SCR_PMU_H */ > diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c > index a90808a3ba..2ae43c7658 100644 > --- a/target/riscv/cpu.c > +++ b/target/riscv/cpu.c > @@ -38,6 +38,7 @@ > #include "kvm/kvm_riscv.h" > #include "tcg/tcg-cpu.h" > #include "tcg/tcg.h" > +#include "include/hw/misc/sifive_u_pmu.h" > > /* RISC-V CPU definitions */ > static const char riscv_single_letter_exts[] = "IEMAFDQCBPVH"; > @@ -477,6 +478,15 @@ static void riscv_max_cpu_init(Object *obj) > #endif > } > > +#ifndef CONFIG_USER_ONLY > +static void riscv_sifive_u_hart_reg_pmu_cb(CPURISCVState *env) > +{ > + env->pmu_vendor_support = riscv_sifive_u_supported_events; > + env->pmu_ctr_write = riscv_sifive_u_pmu_ctr_write; > + env->pmu_ctr_read = riscv_sifive_u_pmu_ctr_read; > +} > +#endif > + > #if defined(TARGET_RISCV64) > static void rv64_base_cpu_init(Object *obj) > { > @@ -498,9 +508,12 @@ static void rv64_sifive_u_cpu_init(Object *obj) > RISCVCPU *cpu = RISCV_CPU(obj); > CPURISCVState *env = &cpu->env; > riscv_cpu_set_misa_ext(env, RVI | RVM | RVA | RVF | RVD | RVC | RVS | RVU); > - env->priv_ver = PRIV_VERSION_1_10_0; > + env->priv_ver = PRIV_VERSION_1_12_0; > #ifndef CONFIG_USER_ONLY > set_satp_mode_max_supported(RISCV_CPU(obj), VM_1_10_SV39); > + if (!kvm_enabled()) { > + riscv_sifive_u_hart_reg_pmu_cb(env); > + } > #endif > > /* inherited from parent obj via riscv_cpu_init() */ > @@ -508,6 +521,8 @@ static void rv64_sifive_u_cpu_init(Object *obj) > cpu->cfg.ext_zicsr = true; > cpu->cfg.mmu = true; > cpu->cfg.pmp = true; > + cpu->cfg.ext_sscofpmf = true; > + cpu->cfg.pmu_mask = MAKE_64BIT_MASK(3, 2); > } > > static void rv64_sifive_e_cpu_init(Object *obj) > @@ -660,6 +675,9 @@ static void rv32_sifive_u_cpu_init(Object *obj) > env->priv_ver = PRIV_VERSION_1_10_0; > #ifndef CONFIG_USER_ONLY > set_satp_mode_max_supported(RISCV_CPU(obj), VM_1_10_SV32); > + if (!kvm_enabled()) { > + riscv_sifive_u_hart_reg_pmu_cb(env); > + } > #endif > > /* inherited from parent obj via riscv_cpu_init() */ > -- > 2.34.1 > >
diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 2ca8717be2..ea93a38268 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -34,6 +34,7 @@ system_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c')) system_ss.add(when: 'CONFIG_SIFIVE_E_AON', if_true: files('sifive_e_aon.c')) system_ss.add(when: 'CONFIG_SIFIVE_U_OTP', if_true: files('sifive_u_otp.c')) system_ss.add(when: 'CONFIG_SIFIVE_U_PRCI', if_true: files('sifive_u_prci.c')) +specific_ss.add(when: 'CONFIG_SIFIVE_U', if_true: files('sifive_u_pmu.c')) subdir('macio') diff --git a/hw/misc/sifive_u_pmu.c b/hw/misc/sifive_u_pmu.c new file mode 100644 index 0000000000..929a2517cc --- /dev/null +++ b/hw/misc/sifive_u_pmu.c @@ -0,0 +1,384 @@ +/* + * RISC-V SiFive U PMU emulation. + * + * Copyright (c) 2024 Alexei Filippov <alexei.filippov@syntacore.com>. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/error-report.h" +#include "target/riscv/cpu.h" +#include "target/riscv/pmu.h" +#include "include/hw/misc/sifive_u_pmu.h" +#include "sysemu/cpu-timers.h" +#include "sysemu/device_tree.h" + +REG32(SIFIVE_U_PMU_MHPMEVENT, 0x323) + FIELD(SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, 0, 8) + FIELD(SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, 8, 18) + + /* + * Support all PMU events described in + * https://sifive.cdn.prismic.io/sifive/1a82e600-1f93-4f41-b2d8-86ed8b16acba_fu740-c000-manual-v1p6.pdf + * FU740-C000 Manual sec. 3.8 "Hardware Performace Monitor", all + * events trigger irq by counter overflow, by default all caunters + * caunted identically, special behavior, combining events for example, + * must be described separately in write/read and trigger irq functions. + */ + +#define SIFIVE_U_PMU_INST { \ + X(RISCV_SIFIVE_U_EVENT_EXCEPTION_TAKEN, 0x00001), \ + X(RISCV_SIFIVE_U_EVENT_INTEGER_LOAD_RETIRED, 0x00002), \ + X(RISCV_SIFIVE_U_EVENT_INTEGER_STORE_RETIRED, 0x00004), \ + X(RISCV_SIFIVE_U_EVENT_ATOMIC_MEMORY_RETIRED, 0x00008), \ + X(RISCV_SIFIVE_U_EVENT_SYSTEM_INSTRUCTION_RETIRED, 0x00010), \ + X(RISCV_SIFIVE_U_EVENT_INTEGER_ARITHMETIC_RETIRED, 0x00020), \ + X(RISCV_SIFIVE_U_EVENT_CONDITIONAL_BRANCH_RETIRED, 0x00040), \ + X(RISCV_SIFIVE_U_EVENT_JAL_INSTRUCTION_RETIRED, 0x00080), \ + X(RISCV_SIFIVE_U_EVENT_JALR_INSTRUCTION_RETIRED, 0x00100), \ + X(RISCV_SIFIVE_U_EVENT_INTEGER_MULTIPLICATION_RETIRED, 0x00200), \ + X(RISCV_SIFIVE_U_EVENT_INTEGER_DIVISION_RETIRED, 0x00400), \ + X(RISCV_SIFIVE_U_EVENT_FP_LOAD_RETIRED, 0x00800), \ + X(RISCV_SIFIVE_U_EVENT_FP_STORE_RETIRED, 0x01000), \ + X(RISCV_SIFIVE_U_EVENT_FP_ADDITION_RETIRED, 0x02000), \ + X(RISCV_SIFIVE_U_EVENT_FP_MULTIPLICATION_RETIRED, 0x04000), \ + X(RISCV_SIFIVE_U_EVENT_FP_FUSEDMADD_RETIRED, 0x08000), \ + X(RISCV_SIFIVE_U_EVENT_FP_DIV_SQRT_RETIRED, 0x10000), \ + X(RISCV_SIFIVE_U_EVENT_OTHER_FP_RETIRED, 0x20000), } + +#define SIFIVE_U_PMU_MICROARCH { \ + X(RISCV_SIFIVE_U_EVENT_ADDRESSGEN_INTERLOCK, 0x00001), \ + X(RISCV_SIFIVE_U_EVENT_LONGLAT_INTERLOCK, 0x00002), \ + X(RISCV_SIFIVE_U_EVENT_CSR_READ_INTERLOCK, 0x00004), \ + X(RISCV_SIFIVE_U_EVENT_ICACHE_ITIM_BUSY, 0x00008), \ + X(RISCV_SIFIVE_U_EVENT_DCACHE_DTIM_BUSY, 0x00010), \ + X(RISCV_SIFIVE_U_EVENT_BRANCH_DIRECTION_MISPREDICTION, 0x00020), \ + X(RISCV_SIFIVE_U_EVENT_BRANCH_TARGET_MISPREDICTION, 0x00040), \ + X(RISCV_SIFIVE_U_EVENT_PIPE_FLUSH_CSR_WRITE, 0x00080), \ + X(RISCV_SIFIVE_U_EVENT_PIPE_FLUSH_OTHER_EVENT, 0x00100), \ + X(RISCV_SIFIVE_U_EVENT_INTEGER_MULTIPLICATION_INTERLOCK, 0x00200), \ + X(RISCV_SIFIVE_U_EVENT_FP_INTERLOCK, 0x00400), } + +#define SIFIVE_U_PMU_MEM { \ + X(RISCV_SIFIVE_U_EVENT_ICACHE_RETIRED, 0x00001), \ + X(RISCV_SIFIVE_U_EVENT_DCACHE_MISS_MMIO_ACCESSES, 0x00002), \ + X(RISCV_SIFIVE_U_EVENT_DCACHE_WRITEBACK, 0x00004), \ + X(RISCV_SIFIVE_U_EVENT_INST_TLB_MISS, 0x00008), \ + X(RISCV_SIFIVE_U_EVENT_DATA_TLB_MISS, 0x00010), \ + X(RISCV_SIFIVE_U_EVENT_UTLB_MISS, 0x00020), } + +#define X(a, b) a = b + enum SIFIVE_U_PMU_INST; + enum SIFIVE_U_PMU_MEM; + enum SIFIVE_U_PMU_MICROARCH; +#undef X + +#define X(a, b) a + const uint32_t + riscv_sifive_u_pmu_events[RISCV_SIFIVE_U_CLASS_MAX][RISCV_SIFIVE_U_MASK_MAX] = { + SIFIVE_U_PMU_INST, + SIFIVE_U_PMU_MICROARCH, + SIFIVE_U_PMU_MEM, + }; +#undef X + +void sifive_u_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name) +{ + uint32_t fdt_event_mhpmevent_map[10 * 3] = {}; + uint32_t fdt_event_mhpmctr_map[6 * 4] = {}; + uint32_t event_idx; + + /* + * SBI_PMU_HW_CACHE_REFERENCES: 0x3 -> Instruction cache/ITIM busy | + * Data cache/DTIM busy + * result: < 0x3 0x0 1801 > + */ + fdt_event_mhpmevent_map[0] = cpu_to_be32(0x3); + fdt_event_mhpmevent_map[1] = cpu_to_be32(0x0); + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, + RISCV_SIFIVE_U_EVENT_ICACHE_ITIM_BUSY | + RISCV_SIFIVE_U_EVENT_DCACHE_DTIM_BUSY); + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, + RISCV_SIFIVE_U_CLASS_MICROARCH); + fdt_event_mhpmevent_map[2] = cpu_to_be32(event_idx); + + + /* + * SBI_PMU_HW_CACHE_MISSES: 0x4 -> Instruction cache miss | + * Data cache miss or mem-mapped I/O access + * result: < 0x4 0x0 0x302 > + */ + fdt_event_mhpmevent_map[3] = cpu_to_be32(0x4); + fdt_event_mhpmevent_map[4] = cpu_to_be32(0x0); + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, + RISCV_SIFIVE_U_EVENT_ICACHE_RETIRED | + RISCV_SIFIVE_U_EVENT_DCACHE_MISS_MMIO_ACCESSES); + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, + RISCV_SIFIVE_U_CLASS_MEM); + fdt_event_mhpmevent_map[5] = cpu_to_be32(event_idx); + + /* + * SBI_PMU_HW_BRANCH_INSTRUCTIONS: 0x5 -> Conditional branch retired + * result: < 0x5 0x0 0x4000 > + */ + fdt_event_mhpmevent_map[6] = cpu_to_be32(0x5); + fdt_event_mhpmevent_map[7] = cpu_to_be32(0x0); + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, + RISCV_SIFIVE_U_EVENT_CONDITIONAL_BRANCH_RETIRED); + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, + RISCV_SIFIVE_U_CLASS_INST); + fdt_event_mhpmevent_map[8] = cpu_to_be32(event_idx); + + /* + * SBI_PMU_HW_BRANCH_MISSES: 0x6 -> Branch direction misprediction | + * Branch/jump target misprediction + * result: < 0x6 0x0 0x6001 > + */ + fdt_event_mhpmevent_map[9] = cpu_to_be32(0x6); + fdt_event_mhpmevent_map[10] = cpu_to_be32(0x0); + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, + RISCV_SIFIVE_U_EVENT_BRANCH_DIRECTION_MISPREDICTION | + RISCV_SIFIVE_U_EVENT_BRANCH_TARGET_MISPREDICTION); + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, + RISCV_SIFIVE_U_CLASS_MICROARCH); + fdt_event_mhpmevent_map[11] = cpu_to_be32(event_idx); + + /* + * L1D_READ_MISS: 0x10001 -> Data cache miss or memory-mapped I/O access + * result: < 0x10001 0x0 0x202 > + */ + fdt_event_mhpmevent_map[12] = cpu_to_be32(0x10001); + fdt_event_mhpmevent_map[13] = cpu_to_be32(0x0); + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, + RISCV_SIFIVE_U_EVENT_DCACHE_MISS_MMIO_ACCESSES); + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, + RISCV_SIFIVE_U_CLASS_MEM); + fdt_event_mhpmevent_map[14] = cpu_to_be32(event_idx); + + /* + * L1D_WRITE_ACCESS: 0x10002 -> Data cache write back + * result: < 0x10002 0x0 0x402 > + */ + fdt_event_mhpmevent_map[15] = cpu_to_be32(0x10002); + fdt_event_mhpmevent_map[16] = cpu_to_be32(0x0); + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, + RISCV_SIFIVE_U_EVENT_DCACHE_WRITEBACK); + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, + RISCV_SIFIVE_U_CLASS_MEM); + fdt_event_mhpmevent_map[17] = cpu_to_be32(event_idx); + + /* + * L1I_READ_ACCESS: 0x10009 -> Instruction cache miss + * result: < 0x10009 0x0 0x102 > + */ + fdt_event_mhpmevent_map[18] = cpu_to_be32(0x10009); + fdt_event_mhpmevent_map[19] = cpu_to_be32(0x0); + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, + RISCV_SIFIVE_U_EVENT_ICACHE_RETIRED); + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, + RISCV_SIFIVE_U_CLASS_MEM); + fdt_event_mhpmevent_map[20] = cpu_to_be32(event_idx); + + /* + * LL_READ_MISS: 0x10011 -> UTLB miss + * result: < 0x10011 0x0 0x2002 > + */ + fdt_event_mhpmevent_map[21] = cpu_to_be32(0x10011); + fdt_event_mhpmevent_map[22] = cpu_to_be32(0x0); + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, + RISCV_SIFIVE_U_EVENT_UTLB_MISS); + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, + RISCV_SIFIVE_U_CLASS_MEM); + fdt_event_mhpmevent_map[23] = cpu_to_be32(event_idx); + + /* + * DTLB_READ_MISS: 0x10019 -> Data TLB miss + * result: < 0x10019 0x0 0x1002 > + */ + fdt_event_mhpmevent_map[24] = cpu_to_be32(0x10019); + fdt_event_mhpmevent_map[25] = cpu_to_be32(0x0); + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, + RISCV_SIFIVE_U_EVENT_DATA_TLB_MISS); + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, + RISCV_SIFIVE_U_CLASS_MEM); + fdt_event_mhpmevent_map[26] = cpu_to_be32(event_idx); + + /* + * DTLB_READ_MISS: 0x10021 -> Data TLB miss + * result: < 0x10019 0x0 0x802 > + */ + fdt_event_mhpmevent_map[27] = cpu_to_be32(0x10021); + fdt_event_mhpmevent_map[28] = cpu_to_be32(0x0); + event_idx = FIELD_DP32(0, SIFIVE_U_PMU_MHPMEVENT, EVENT_MASK, + RISCV_SIFIVE_U_EVENT_INST_TLB_MISS); + event_idx = FIELD_DP32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS, + RISCV_SIFIVE_U_CLASS_MEM); + fdt_event_mhpmevent_map[29] = cpu_to_be32(event_idx); + + fdt_event_mhpmctr_map[0] = cpu_to_be32(0x00003); + fdt_event_mhpmctr_map[1] = cpu_to_be32(0x00006); + fdt_event_mhpmctr_map[2] = cpu_to_be32(cmask); + + fdt_event_mhpmctr_map[3] = cpu_to_be32(0x10001); + fdt_event_mhpmctr_map[4] = cpu_to_be32(0x10002); + fdt_event_mhpmctr_map[5] = cpu_to_be32(cmask); + + fdt_event_mhpmctr_map[6] = cpu_to_be32(0x10009); + fdt_event_mhpmctr_map[7] = cpu_to_be32(0x10009); + fdt_event_mhpmctr_map[8] = cpu_to_be32(cmask); + + fdt_event_mhpmctr_map[9] = cpu_to_be32(0x10011); + fdt_event_mhpmctr_map[10] = cpu_to_be32(0x10011); + fdt_event_mhpmctr_map[11] = cpu_to_be32(cmask); + + fdt_event_mhpmctr_map[12] = cpu_to_be32(0x10019); + fdt_event_mhpmctr_map[13] = cpu_to_be32(0x10019); + fdt_event_mhpmctr_map[14] = cpu_to_be32(cmask); + + fdt_event_mhpmctr_map[15] = cpu_to_be32(0x10021); + fdt_event_mhpmctr_map[16] = cpu_to_be32(0x10021); + fdt_event_mhpmctr_map[17] = cpu_to_be32(cmask); + + fdt_event_mhpmctr_map[18] = cpu_to_be32(0x1); + fdt_event_mhpmctr_map[19] = cpu_to_be32(0x1); + fdt_event_mhpmctr_map[20] = cpu_to_be32(cmask | 1 << 0); + + fdt_event_mhpmctr_map[21] = cpu_to_be32(0x2); + fdt_event_mhpmctr_map[22] = cpu_to_be32(0x2); + fdt_event_mhpmctr_map[23] = cpu_to_be32(cmask | 1 << 2); + + /* This a OpenSBI specific DT property documented in OpenSBI docs */ + qemu_fdt_setprop(fdt, pmu_name, "riscv,event-to-mhpmevent", + fdt_event_mhpmevent_map, sizeof(fdt_event_mhpmevent_map)); + qemu_fdt_setprop(fdt, pmu_name, "riscv,event-to-mhpmcounters", + fdt_event_mhpmctr_map, sizeof(fdt_event_mhpmctr_map)); + +} + +bool riscv_sifive_u_supported_events(uint32_t event_idx) +{ + uint32_t group = FIELD_EX32(event_idx, SIFIVE_U_PMU_MHPMEVENT, EVENT_CLASS); + uint32_t event_mask = FIELD_EX32(event_idx, SIFIVE_U_PMU_MHPMEVENT, + EVENT_MASK); + uint32_t idx = 32 - clz32(event_mask); + + if (group >= RISCV_SIFIVE_U_CLASS_MAX || idx > RISCV_SIFIVE_U_MASK_MAX) { + return 0; + } + + bool event_match = true; + if (!idx) { + event_match = false; + } + while (event_match && idx) { + if (!riscv_sifive_u_pmu_events[group][idx - 1]) { + event_match = false; + } + event_mask = event_mask & (~(1 << (idx - 1))); + idx = 32 - clz32(event_mask); + } + return event_match; +} + +static target_ulong get_ticks(bool icnt, bool high_half) +{ + int64_t val; + target_ulong res; + + if (icnt && icount_enabled()) { + val = icount_get_raw(); + } else { + val = cpu_get_host_ticks(); + } + + if (high_half) { + res = val >> 32; + } else { + res = val; + } + + return res; +} + +target_ulong riscv_sifive_u_pmu_ctr_read(PMUCTRState *counter, + uint32_t event_idx, bool high_half) +{ + target_ulong ctrl_val = high_half ? counter->mhpmcounterh_val : + counter->mhpmcounter_val; + uint32_t event_class_field = FIELD_EX32(event_idx, + SIFIVE_U_PMU_MHPMEVENT, + EVENT_CLASS); + uint32_t event_mask_field = FIELD_EX32(event_idx, + SIFIVE_U_PMU_MHPMEVENT, + EVENT_MASK); + + if (event_class_field >= RISCV_SIFIVE_U_CLASS_MAX || + (32 - clz32(event_mask_field)) >= RISCV_SIFIVE_U_MASK_MAX) { + return ctrl_val; + } + + switch (event_class_field) { + /* If we want to handle some events separately */ + + /* fall through */ + default: + /* In case we do not want handle it separately */ + if (riscv_sifive_u_supported_events(event_idx)) { + return get_ticks(false, high_half); + } + /* Did not find event in supported */ + return ctrl_val; + } + + g_assert_not_reached(); /* unreachable */ + return 0; +} + +void riscv_sifive_u_pmu_ctr_write(PMUCTRState *counter, uint32_t event_idx, + target_ulong val, bool high_half) +{ + target_ulong *ctr_prev = high_half ? &counter->mhpmcounterh_prev : + &counter->mhpmcounter_prev; + uint32_t event_class_field = FIELD_EX32(event_idx, + SIFIVE_U_PMU_MHPMEVENT, + EVENT_CLASS); + uint32_t event_mask_field = FIELD_EX32(event_idx, + SIFIVE_U_PMU_MHPMEVENT, + EVENT_MASK); + + if (event_class_field >= RISCV_SIFIVE_U_CLASS_MAX || + (32 - clz32(event_mask_field)) >= RISCV_SIFIVE_U_MASK_MAX) { + *ctr_prev = val; + return; + } + + switch (event_class_field) { + /* If we want to handle some events separately */ + + /* fall through */ + default: + /* In case we do not want handle it separately */ + if (riscv_sifive_u_supported_events(event_idx)) { + *ctr_prev = get_ticks(false, high_half); + return; + } + /* Did not find event in supported */ + *ctr_prev = val; + return; + } + + g_assert_not_reached(); /* unreachable */ + return; +} diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c index af5f923f54..2771102fbe 100644 --- a/hw/riscv/sifive_u.c +++ b/hw/riscv/sifive_u.c @@ -60,6 +60,7 @@ #include "sysemu/device_tree.h" #include "sysemu/runstate.h" #include "sysemu/sysemu.h" +#include "include/hw/misc/sifive_u_pmu.h" #include <libfdt.h> @@ -93,6 +94,17 @@ static const MemMapEntry sifive_u_memmap[] = { #define OTP_SERIAL 1 #define GEM_REVISION 0x10070109 +static void create_fdt_pmu(MachineState *s) +{ + g_autofree char *pmu_name = g_strdup_printf("/pmu"); + MachineState *ms = MACHINE(s); + RISCVCPU *hart = RISCV_CPU(qemu_get_cpu(0)); + + qemu_fdt_add_subnode(ms->fdt, pmu_name); + qemu_fdt_setprop_string(ms->fdt, pmu_name, "compatible", "riscv,pmu"); + sifive_u_pmu_generate_fdt_node(ms->fdt, hart->pmu_avail_ctrs, pmu_name); +} + static void create_fdt(SiFiveUState *s, const MemMapEntry *memmap, bool is_32_bit) { @@ -499,6 +511,8 @@ static void create_fdt(SiFiveUState *s, const MemMapEntry *memmap, qemu_fdt_setprop_string(fdt, "/aliases", "serial0", nodename); g_free(nodename); + + create_fdt_pmu(ms); } static void sifive_u_machine_reset(void *opaque, int n, int level) diff --git a/include/hw/misc/sifive_u_pmu.h b/include/hw/misc/sifive_u_pmu.h new file mode 100644 index 0000000000..8877c2ba46 --- /dev/null +++ b/include/hw/misc/sifive_u_pmu.h @@ -0,0 +1,24 @@ +#ifndef RISCV_SIFIVE_U_PMU_H +#define RISCV_SIFIVE_U_PMU_H + +#include "target/riscv/cpu.h" +#include "qapi/error.h" + +/* Maximum events per class */ +#define RISCV_SIFIVE_U_MASK_MAX 18 + +enum riscv_sifive_u_pmu_classes { + RISCV_SIFIVE_U_CLASS_INST = 0x0, + RISCV_SIFIVE_U_CLASS_MICROARCH, + RISCV_SIFIVE_U_CLASS_MEM, + RISCV_SIFIVE_U_CLASS_MAX = 0x3 +}; + +bool riscv_sifive_u_supported_events(uint32_t event_idx); +void riscv_sifive_u_pmu_ctr_write(PMUCTRState *counter, uint32_t event_idx, + target_ulong val, bool high_half); +target_ulong riscv_sifive_u_pmu_ctr_read(PMUCTRState *counter, + uint32_t event_idx, bool high_half); +void sifive_u_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name); + +#endif /* RISCV_SCR_PMU_H */ diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index a90808a3ba..2ae43c7658 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -38,6 +38,7 @@ #include "kvm/kvm_riscv.h" #include "tcg/tcg-cpu.h" #include "tcg/tcg.h" +#include "include/hw/misc/sifive_u_pmu.h" /* RISC-V CPU definitions */ static const char riscv_single_letter_exts[] = "IEMAFDQCBPVH"; @@ -477,6 +478,15 @@ static void riscv_max_cpu_init(Object *obj) #endif } +#ifndef CONFIG_USER_ONLY +static void riscv_sifive_u_hart_reg_pmu_cb(CPURISCVState *env) +{ + env->pmu_vendor_support = riscv_sifive_u_supported_events; + env->pmu_ctr_write = riscv_sifive_u_pmu_ctr_write; + env->pmu_ctr_read = riscv_sifive_u_pmu_ctr_read; +} +#endif + #if defined(TARGET_RISCV64) static void rv64_base_cpu_init(Object *obj) { @@ -498,9 +508,12 @@ static void rv64_sifive_u_cpu_init(Object *obj) RISCVCPU *cpu = RISCV_CPU(obj); CPURISCVState *env = &cpu->env; riscv_cpu_set_misa_ext(env, RVI | RVM | RVA | RVF | RVD | RVC | RVS | RVU); - env->priv_ver = PRIV_VERSION_1_10_0; + env->priv_ver = PRIV_VERSION_1_12_0; #ifndef CONFIG_USER_ONLY set_satp_mode_max_supported(RISCV_CPU(obj), VM_1_10_SV39); + if (!kvm_enabled()) { + riscv_sifive_u_hart_reg_pmu_cb(env); + } #endif /* inherited from parent obj via riscv_cpu_init() */ @@ -508,6 +521,8 @@ static void rv64_sifive_u_cpu_init(Object *obj) cpu->cfg.ext_zicsr = true; cpu->cfg.mmu = true; cpu->cfg.pmp = true; + cpu->cfg.ext_sscofpmf = true; + cpu->cfg.pmu_mask = MAKE_64BIT_MASK(3, 2); } static void rv64_sifive_e_cpu_init(Object *obj) @@ -660,6 +675,9 @@ static void rv32_sifive_u_cpu_init(Object *obj) env->priv_ver = PRIV_VERSION_1_10_0; #ifndef CONFIG_USER_ONLY set_satp_mode_max_supported(RISCV_CPU(obj), VM_1_10_SV32); + if (!kvm_enabled()) { + riscv_sifive_u_hart_reg_pmu_cb(env); + } #endif /* inherited from parent obj via riscv_cpu_init() */
Add support of hardware performance monitor into sifive_u machine. HPM support was made along to [SiFive FU740-C000 manual v1p6] sec. 3.8. All described events for now counting by the same algorithm. Each event counting could be implemented separately in read/write call backs, also events combination are working, but right counting for them should be implemented separatly. Signed-off-by: Alexei Filippov <alexei.filippov@syntacore.com> --- hw/misc/meson.build | 1 + hw/misc/sifive_u_pmu.c | 384 +++++++++++++++++++++++++++++++++ hw/riscv/sifive_u.c | 14 ++ include/hw/misc/sifive_u_pmu.h | 24 +++ target/riscv/cpu.c | 20 +- 5 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 hw/misc/sifive_u_pmu.c create mode 100644 include/hw/misc/sifive_u_pmu.h