Add PMU events implementation for `virt` machine
according to SBI spec.
Signed-off-by: Aleksandr Sergeev <[email protected]>
Reviewed-by: Alexei Filippov <[email protected]>
---
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/virt_pmu.c | 142 +++++++++++++++++++++++++++++++++++++
hw/riscv/Kconfig | 1 +
hw/riscv/virt.c | 4 +-
include/hw/misc/virt_pmu.h | 91 ++++++++++++++++++++++++
target/riscv/cpu.c | 8 +++
target/riscv/pmu.c | 53 --------------
target/riscv/pmu.h | 1 -
9 files changed, 248 insertions(+), 56 deletions(-)
create mode 100644 hw/misc/virt_pmu.c
create mode 100644 include/hw/misc/virt_pmu.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index fccd735c24..099200f711 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -150,6 +150,9 @@ config IOTKIT_SYSINFO
config PVPANIC_COMMON
bool
+config VIRT_PMU
+ bool
+
config PVPANIC_PCI
bool
default y if PCI_DEVICES
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index b1d8d8e5d2..977a3ac620 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_VIRT_PMU', if_true: files('virt_pmu.c'))
subdir('macio')
diff --git a/hw/misc/virt_pmu.c b/hw/misc/virt_pmu.c
new file mode 100644
index 0000000000..afba06de0e
--- /dev/null
+++ b/hw/misc/virt_pmu.c
@@ -0,0 +1,142 @@
+/*
+ * RISC-V Virt machine PMU emulation.
+ *
+ * Copyright (c) 2025 Syntacore.
+ *
+ * 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/virt_pmu.h"
+#include "system/device_tree.h"
+
+#define SBI_CACHE_EVENT_ID_CNT (SBI_PMU_HW_CACHE_NODE - SBI_PMU_HW_CACHE_L1D +
1)
+#define SBI_CACHE_OP_ID_CNT (SBI_PMU_HW_CACHE_OP_PREFETCH -
SBI_PMU_HW_CACHE_OP_READ + 1)
+#define SBI_EVT_CTR_SIZE (SBI_CACHE_EVENT_ID_CNT * SBI_CACHE_OP_ID_CNT + 3)
+
+void riscv_virt_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char
*pmu_name)
+{
+ uint32_t fdt_event_mhpmctr_map[SBI_EVT_CTR_SIZE][3] = {};
+ uint32_t event_mhpmctr_idx = 0;
+
+ uint32_t event_idx_low, event_idx_high;
+
+ /* SBI_PMU_HW_CPU_CYCLES */
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][0] =
cpu_to_be32(SBI_PMU_HW_CPU_CYCLES);
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][1] =
cpu_to_be32(SBI_PMU_HW_CPU_CYCLES);
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][2] = cpu_to_be32(cmask | 1 << 0);
+ event_mhpmctr_idx++;
+
+ /* SBI_PMU_HW_INSTRUCTIONS */
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][0] =
+ cpu_to_be32(SBI_PMU_HW_INSTRUCTIONS);
+
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][1] =
+ cpu_to_be32(SBI_PMU_HW_INSTRUCTIONS);
+
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][2] = cpu_to_be32(cmask | 1 << 2);
+ event_mhpmctr_idx++;
+
+ /* Other generic type events */
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][0] =
+ cpu_to_be32(SBI_PMU_HW_CACHE_REFERENCES);
+
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][1] =
+ cpu_to_be32(SBI_PMU_HW_REF_CPU_CYCLES);
+
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][2] = cpu_to_be32(cmask);
+ event_mhpmctr_idx++;
+
+ /* Cache type */
+ for (uint32_t ev_id = 0; ev_id < SBI_CACHE_EVENT_ID_CNT; ++ev_id) {
+ for (uint32_t op_id = 0; op_id < SBI_CACHE_OP_ID_CNT; ++op_id) {
+ event_idx_low = FIELD_DP32(0, SBI_MHPMEVENT_CACHE, OP, op_id);
+ event_idx_low = FIELD_DP32(event_idx_low, SBI_MHPMEVENT_CACHE,
EVENT, ev_id);
+ event_idx_low = FIELD_DP32(event_idx_low, SBI_MHPMEVENT, TYPE,
+ RISCV_SBI_EVENT_TYPE_CACHE);
+ event_idx_low = FIELD_DP32(event_idx_low, SBI_MHPMEVENT_CACHE,
RESULT,
+ SBI_PMU_HW_CACHE_RESULT_ACCESS);
+
+ event_idx_high = FIELD_DP32(event_idx_low, SBI_MHPMEVENT_CACHE,
RESULT,
+ SBI_PMU_HW_CACHE_RESULT_MISS);
+
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][0] =
cpu_to_be32(event_idx_low);
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][1] =
cpu_to_be32(event_idx_high);
+ fdt_event_mhpmctr_map[event_mhpmctr_idx][2] = cpu_to_be32(cmask);
+ event_mhpmctr_idx++;
+ }
+ }
+
+ g_assert(event_mhpmctr_idx <= SBI_EVT_CTR_SIZE);
+
+ /* This a OpenSBI specific DT property documented in OpenSBI docs */
+ qemu_fdt_setprop(fdt, pmu_name, "riscv,event-to-mhpmcounters",
+ fdt_event_mhpmctr_map,
+ event_mhpmctr_idx * sizeof(fdt_event_mhpmctr_map[0]));
+}
+
+bool riscv_virt_supported_events(CPURISCVState *env, uint32_t ctr_idx)
+{
+ uint32_t sbi_event_type = FIELD_EX32(env->mhpmevent_val[ctr_idx],
+ SBI_MHPMEVENT, TYPE);
+ uint32_t sbi_cache_event;
+ uint32_t sbi_cache_op;
+ uint32_t sbi_cache_result;
+
+ switch (sbi_event_type) {
+ case RISCV_SBI_EVENT_TYPE_GEN:
+ return sbi_event_type <= SBI_PMU_HW_REF_CPU_CYCLES;
+ case RISCV_SBI_EVENT_TYPE_CACHE:
+ sbi_cache_event = FIELD_EX32(sbi_event_type, SBI_MHPMEVENT_CACHE,
EVENT);
+ sbi_cache_op = FIELD_EX32(sbi_event_type, SBI_MHPMEVENT_CACHE, OP);
+ sbi_cache_result = FIELD_EX32(sbi_event_type, SBI_MHPMEVENT_CACHE,
RESULT);
+
+ return sbi_cache_event <= SBI_PMU_HW_CACHE_NODE &&
+ sbi_cache_op <= SBI_PMU_HW_CACHE_OP_PREFETCH &&
+ sbi_cache_result <= SBI_PMU_HW_CACHE_RESULT_MISS;
+ default:
+ return false;
+ }
+}
+
+RISCVException riscv_virt_pmu_ctr_read(CPURISCVState *env, uint32_t ctr_idx,
+ uint64_t *value)
+{
+ uint32_t sbi_event_type = FIELD_EX32(env->mhpmevent_val[ctr_idx],
+ SBI_MHPMEVENT, TYPE);
+
+ switch (sbi_event_type) {
+ /* If we want to handle some events separately */
+ default:
+ /* In case we do not want handle it separately */
+ return riscv_pmu_ctr_read_general(env, ctr_idx, value);
+ }
+}
+
+RISCVException riscv_virt_pmu_ctr_write(CPURISCVState *env, uint32_t ctr_idx,
+ uint64_t value)
+{
+ uint32_t sbi_event_type = FIELD_EX32(env->mhpmevent_val[ctr_idx],
+ SBI_MHPMEVENT, TYPE);
+
+ switch (sbi_event_type) {
+ /* If we want to handle some events separately */
+ default:
+ /* In case we do not want handle it separately */
+ return riscv_pmu_ctr_write_general(env, ctr_idx, value);
+ }
+}
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index fc9c35bd98..920b65d733 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -49,6 +49,7 @@ config RISCV_VIRT
imply VIRTIO_VGA
imply TEST_DEVICES
imply TPM_TIS_SYSBUS
+ select VIRT_PMU
select DEVICE_TREE
select RISCV_NUMA
select GOLDFISH_RTC
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 17909206c7..7b0e6869db 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -30,7 +30,6 @@
#include "hw/char/serial-mm.h"
#include "target/riscv/cpu.h"
#include "hw/core/sysbus-fdt.h"
-#include "target/riscv/pmu.h"
#include "hw/riscv/riscv_hart.h"
#include "hw/riscv/iommu.h"
#include "hw/riscv/riscv-iommu-bits.h"
@@ -58,6 +57,7 @@
#include "qapi/qapi-visit-common.h"
#include "hw/virtio/virtio-iommu.h"
#include "hw/uefi/var-service-api.h"
+#include "hw/misc/virt_pmu.h"
/* KVM AIA only supports APLIC MSI. APLIC Wired is always emulated by QEMU. */
static bool virt_use_kvm_aia_aplic_imsic(RISCVVirtAIAType aia_type)
@@ -735,7 +735,7 @@ static void create_fdt_pmu(RISCVVirtState *s)
qemu_fdt_add_subnode(ms->fdt, pmu_name);
qemu_fdt_setprop_string(ms->fdt, pmu_name, "compatible", "riscv,pmu");
- riscv_pmu_generate_fdt_node(ms->fdt, hart.pmu_avail_ctrs, pmu_name);
+ riscv_virt_pmu_generate_fdt_node(ms->fdt, hart.pmu_avail_ctrs, pmu_name);
}
static void create_fdt_sockets(RISCVVirtState *s,
diff --git a/include/hw/misc/virt_pmu.h b/include/hw/misc/virt_pmu.h
new file mode 100644
index 0000000000..ea9ace5f6a
--- /dev/null
+++ b/include/hw/misc/virt_pmu.h
@@ -0,0 +1,91 @@
+/*
+ * RISC-V Virt machine PMU header file.
+ * Copyright (C) 2025, Syntacore Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
USA.
+ * Also add information on how to contact you by electronic and paper mail.
+ */
+
+#ifndef RISCV_VIRT_PMU_H
+#define RISCV_VIRT_PMU_H
+
+#include "target/riscv/cpu.h"
+#include "qapi/error.h"
+
+typedef enum riscv_sbi_pmu_types {
+ RISCV_SBI_EVENT_TYPE_GEN = 0x0,
+ RISCV_SBI_EVENT_TYPE_CACHE = 0x1,
+ RISCV_SBI_EVENT_TYPE_RAW = 0x2,
+ RISCV_SBI_EVENT_TYPE_RAW_V2 = 0x3,
+ RISCV_SBI_EVENT_TYPE_FIRMWARE = 0xf,
+} riscv_sbi_pmu_types;
+
+REG32(SBI_MHPMEVENT, 0x323)
+ FIELD(SBI_MHPMEVENT, CODE, 0, 16)
+ FIELD(SBI_MHPMEVENT, TYPE, 16, 4)
+
+/* Generic type events */
+
+typedef enum riscv_sbi_pmu_hw {
+ SBI_PMU_HW_NO_EVENT = 0x00000,
+ SBI_PMU_HW_CPU_CYCLES = 0x00001,
+ SBI_PMU_HW_INSTRUCTIONS = 0x00002,
+ SBI_PMU_HW_CACHE_REFERENCES = 0x00003,
+ SBI_PMU_HW_CACHE_MISSES = 0x00004,
+ SBI_PMU_HW_BRANCH_INSTRUCTIONS = 0x00005,
+ SBI_PMU_HW_BRANCH_MISSES = 0x00006,
+ SBI_PMU_HW_BUS_CYCLES = 0x00007,
+ SBI_PMU_HW_STALLED_CYCLES_FRONTEND = 0x00008,
+ SBI_PMU_HW_STALLED_CYCLES_BACKEND = 0x00009,
+ SBI_PMU_HW_REF_CPU_CYCLES = 0x0000A,
+} riscv_sbi_pmu_hw;
+
+/* Cache type events */
+
+typedef enum riscv_sbi_pmu_cache_event {
+ SBI_PMU_HW_CACHE_L1D = 0x0,
+ SBI_PMU_HW_CACHE_L1I = 0x1,
+ SBI_PMU_HW_CACHE_LL = 0x2,
+ SBI_PMU_HW_CACHE_DTLB = 0x3,
+ SBI_PMU_HW_CACHE_ITLB = 0x4,
+ SBI_PMU_HW_CACHE_BPU = 0x5,
+ SBI_PMU_HW_CACHE_NODE = 0x6,
+} riscv_sbi_pmu_cache_event;
+
+typedef enum riscv_sbi_pmu_cache_op {
+ SBI_PMU_HW_CACHE_OP_READ = 0,
+ SBI_PMU_HW_CACHE_OP_WRITE = 1,
+ SBI_PMU_HW_CACHE_OP_PREFETCH = 2,
+} riscv_sbi_pmu_cache_op;
+
+typedef enum riscv_sbi_pmu_cache_result {
+ SBI_PMU_HW_CACHE_RESULT_ACCESS = 0,
+ SBI_PMU_HW_CACHE_RESULT_MISS = 1,
+} riscv_sbi_pmu_cache_result;
+
+REG32(SBI_MHPMEVENT_CACHE, 0x323)
+ FIELD(SBI_MHPMEVENT_CACHE, RESULT, 0, 1)
+ FIELD(SBI_MHPMEVENT_CACHE, OP, 1, 2)
+ FIELD(SBI_MHPMEVENT_CACHE, EVENT, 3, 13)
+
+
+void riscv_virt_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char
*pmu_name);
+bool riscv_virt_supported_events(CPURISCVState *env, uint32_t ctr_idx);
+RISCVException riscv_virt_pmu_ctr_read(CPURISCVState *env, uint32_t ctr_idx,
+ uint64_t *value);
+RISCVException riscv_virt_pmu_ctr_write(CPURISCVState *env, uint32_t ctr_idx,
+ uint64_t value);
+
+#endif /* RISCV_VIRT_PMU_H */
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index 73d4280d7c..40853d7214 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -37,6 +37,7 @@
#include "kvm/kvm_riscv.h"
#include "tcg/tcg-cpu.h"
#include "tcg/tcg.h"
+#include "hw/misc/virt_pmu.h"
/* RISC-V CPU definitions */
static const char riscv_single_letter_exts[] = "IEMAFDQCBPVH";
@@ -1125,6 +1126,13 @@ static void riscv_cpu_init(Object *obj)
cpu->env.vext_ver = VEXT_VERSION_1_00_0;
cpu->cfg.max_satp_mode = -1;
+#ifndef CONFIG_USER_ONLY
+ /* Default PMU implementation */
+ env->pmu_ctr_write = riscv_virt_pmu_ctr_write;
+ env->pmu_ctr_read = riscv_virt_pmu_ctr_read;
+ env->pmu_vendor_support = riscv_virt_supported_events;
+#endif /* CONFIG_USER_ONLY */
+
if (mcc->def->profile) {
mcc->def->profile->enabled = true;
}
diff --git a/target/riscv/pmu.c b/target/riscv/pmu.c
index 5109fd14bf..899d5941bb 100644
--- a/target/riscv/pmu.c
+++ b/target/riscv/pmu.c
@@ -23,62 +23,9 @@
#include "cpu.h"
#include "pmu.h"
#include "exec/icount.h"
-#include "system/device_tree.h"
#define RISCV_TIMEBASE_FREQ 1000000000 /* 1Ghz */
-/*
- * To keep it simple, any event can be mapped to any programmable counters in
- * QEMU. The generic cycle & instruction count events can also be monitored
- * using programmable counters. In that case, mcycle & minstret must continue
- * to provide the correct value as well. Heterogeneous PMU per hart is not
- * supported yet. Thus, number of counters are same across all harts.
- */
-void riscv_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name)
-{
- uint32_t fdt_event_ctr_map[15] = {};
-
- /*
- * The event encoding is specified in the SBI specification
- * Event idx is a 20bits wide number encoded as follows:
- * event_idx[19:16] = type
- * event_idx[15:0] = code
- * The code field in cache events are encoded as follows:
- * event_idx.code[15:3] = cache_id
- * event_idx.code[2:1] = op_id
- * event_idx.code[0:0] = result_id
- */
-
- /* SBI_PMU_HW_CPU_CYCLES: 0x01 : type(0x00) */
- fdt_event_ctr_map[0] = cpu_to_be32(0x00000001);
- fdt_event_ctr_map[1] = cpu_to_be32(0x00000001);
- fdt_event_ctr_map[2] = cpu_to_be32(cmask | 1 << 0);
-
- /* SBI_PMU_HW_INSTRUCTIONS: 0x02 : type(0x00) */
- fdt_event_ctr_map[3] = cpu_to_be32(0x00000002);
- fdt_event_ctr_map[4] = cpu_to_be32(0x00000002);
- fdt_event_ctr_map[5] = cpu_to_be32(cmask | 1 << 2);
-
- /* SBI_PMU_HW_CACHE_DTLB : 0x03 READ : 0x00 MISS : 0x00 type(0x01) */
- fdt_event_ctr_map[6] = cpu_to_be32(0x00010019);
- fdt_event_ctr_map[7] = cpu_to_be32(0x00010019);
- fdt_event_ctr_map[8] = cpu_to_be32(cmask);
-
- /* SBI_PMU_HW_CACHE_DTLB : 0x03 WRITE : 0x01 MISS : 0x00 type(0x01) */
- fdt_event_ctr_map[9] = cpu_to_be32(0x0001001B);
- fdt_event_ctr_map[10] = cpu_to_be32(0x0001001B);
- fdt_event_ctr_map[11] = cpu_to_be32(cmask);
-
- /* SBI_PMU_HW_CACHE_ITLB : 0x04 READ : 0x00 MISS : 0x00 type(0x01) */
- fdt_event_ctr_map[12] = cpu_to_be32(0x00010021);
- fdt_event_ctr_map[13] = cpu_to_be32(0x00010021);
- fdt_event_ctr_map[14] = cpu_to_be32(cmask);
-
- /* This a OpenSBI specific DT property documented in OpenSBI docs */
- qemu_fdt_setprop(fdt, pmu_name, "riscv,event-to-mhpmcounters",
- fdt_event_ctr_map, sizeof(fdt_event_ctr_map));
-}
-
static bool riscv_pmu_counter_valid(RISCVCPU *cpu, uint32_t ctr_idx)
{
CPURISCVState *env = &cpu->env;
diff --git a/target/riscv/pmu.h b/target/riscv/pmu.h
index 283e311b04..e0603b7e33 100644
--- a/target/riscv/pmu.h
+++ b/target/riscv/pmu.h
@@ -26,7 +26,6 @@
void riscv_pmu_timer_cb(void *priv);
void riscv_pmu_init(RISCVCPU *cpu, Error **errp);
-void riscv_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name);
int riscv_pmu_setup_timer(CPURISCVState *env, uint64_t value,
uint32_t ctr_idx);
uint32_t riscv_pmu_csrno_to_ctr_idx(int csrno);
--
2.51.0