[PATCH 2/2] platform: generic: mips: p8700: Emulate S-mode access to CM PMU registers
Vladimir Kondratiev
vladimir.kondratiev at mobileye.com
Tue Oct 27 03:44:36 PDT 2026
The CM3 performance counter registers (GCR.Debug block at offset 0x900)
are used by the Linux mips_p8700_cm_pmu driver, but they share a PMP
granule with GCR registers that must stay M-mode only, so the whole GCR
block is closed to S-mode. S-mode loads and stores to it therefore trap
to M-mode as access faults.
Implement the emulate_load/emulate_store platform hooks in a new
p8700_access.c. The faulting S-mode virtual address is translated with
sbi_va_to_pa(), checked to be a naturally aligned 32 or 64-bit
access fully inside the PMU register range of one of the CM instances
described by p8700_cm_info, and then performed on behalf of S-mode.
A cheap page-offset check rejects anything outside the PMU window
before the page table walk. All other accesses are refused and
redirected to S-mode as a genuine access fault.
Add the PMU register offsets to p8700.h, mirroring the Linux driver,
and hook the emulators up for eyeq7h.
Signed-off-by: Vladimir Kondratiev <vladimir.kondratiev at mobileye.com>
---
platform/generic/include/mips/p8700.h | 34 +++++++++
platform/generic/mips/eyeq7h.c | 2 +
platform/generic/mips/objects.mk | 2 +-
platform/generic/mips/p8700_access.c | 126 ++++++++++++++++++++++++++++++++++
4 files changed, 163 insertions(+), 1 deletion(-)
diff --git a/platform/generic/include/mips/p8700.h b/platform/generic/include/mips/p8700.h
index e64c62e17037..95144cf81152 100644
--- a/platform/generic/include/mips/p8700.h
+++ b/platform/generic/include/mips/p8700.h
@@ -179,6 +179,22 @@ extern const struct p8700_cm_info *p8700_cm_info;
#define GCR_MMIO_BOTTOM_DIS_RQ_LIM BIT(1)
#define GCR_MMIO_BOTTOM_EN BIT(0)
+/*
+ * GCR.Debug performance counter (CM PMU) registers, offsets from GCR base.
+ * Keep in sync with linux/drivers/perf/mips_p8700_cm_pmu.c.
+ */
+#define GCR_PC_CTL 0x0900
+#define GCR_PC_OV 0x0920
+#define GCR_PC_EVENT 0x0930
+#define GCR_PC_CYCL 0x0980
+#define GCR_PC_QUAL0 0x0990
+#define GCR_PC_CNT0 0x0998
+#define GCR_PC_COUNTER_STRIDE 0x10
+#define GCR_PC_MAX_PROG_COUNTERS 2
+/* Range of the PMU block that S-mode is allowed to access via emulation */
+#define GCR_PMU_OFFSET GCR_PC_CTL
+#define GCR_PMU_SIZE 0x100
+
/* CPC Block offsets */
#define CPC_PWRUP_CTL 0x0030
#define CPC_TIMECTL 0x0058
@@ -224,4 +240,22 @@ int mips_p8700_add_memranges(void);
struct fdt_match;
int mips_p8700_platform_init(const void *fdt, int nodeoff, const struct fdt_match *match);
+/*
+ * S-mode access to CM PMU registers (p8700_access.c). The GCR block is
+ * PMP-protected from S-mode; S-mode accesses trap to M-mode and are
+ * emulated by these platform hooks for the PMU register range only.
+ */
+union sbi_ldst_data;
+struct sbi_trap_context;
+
+/** platform specific handler to fixup load fault
+ * Refer to comments at sbi_platform_emulate_load */
+int mips_p8700_emulate_load(ulong insn, int rlen, ulong addr, union sbi_ldst_data *out_val,
+ struct sbi_trap_context *tcntx);
+
+/** platform specific handler to fixup store fault
+ * Refer to comments at sbi_platform_emulate_store */
+int mips_p8700_emulate_store(ulong insn, int wlen, ulong addr, union sbi_ldst_data in_val,
+ struct sbi_trap_context *tcntx);
+
#endif
diff --git a/platform/generic/mips/eyeq7h.c b/platform/generic/mips/eyeq7h.c
index f08b51972236..5cc05542ff7f 100644
--- a/platform/generic/mips/eyeq7h.c
+++ b/platform/generic/mips/eyeq7h.c
@@ -458,6 +458,8 @@ static int eyeq7h_platform_init(const void *fdt, int nodeoff, const struct fdt_m
generic_platform_ops.final_init = eyeq7h_final_init;
generic_platform_ops.nascent_init = eyeq7h_nascent_init;
generic_platform_ops.pmp_set = mips_p8700_pmp_set;
+ generic_platform_ops.emulate_load = mips_p8700_emulate_load;
+ generic_platform_ops.emulate_store = mips_p8700_emulate_store;
return 0;
}
diff --git a/platform/generic/mips/objects.mk b/platform/generic/mips/objects.mk
index 14524aadc12f..77278d434720 100644
--- a/platform/generic/mips/objects.mk
+++ b/platform/generic/mips/objects.mk
@@ -5,7 +5,7 @@
ifeq ($(PLATFORM_RISCV_XLEN), 64)
carray-platform_override_modules-$(CONFIG_PLATFORM_MIPS_P8700_EYEQ7H) += mips_p8700_eyeq7h
carray-platform_override_modules-$(CONFIG_PLATFORM_MIPS_P8700_BOSTON) += mips_p8700_boston
-platform-objs-$(CONFIG_CPU_MIPS_P8700) += mips/p8700.o mips/mips_warm_boot.o
+platform-objs-$(CONFIG_CPU_MIPS_P8700) += mips/p8700.o mips/p8700_access.o mips/mips_warm_boot.o
platform-objs-$(CONFIG_PLATFORM_MIPS_P8700_EYEQ7H) += mips/eyeq7h.o
platform-objs-$(CONFIG_PLATFORM_MIPS_P8700_BOSTON) += mips/boston.o
endif
diff --git a/platform/generic/mips/p8700_access.c b/platform/generic/mips/p8700_access.c
new file mode 100644
index 000000000000..004d21f45552
--- /dev/null
+++ b/platform/generic/mips/p8700_access.c
@@ -0,0 +1,126 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Mobileye
+ *
+ * S-mode access to selected CM (GCR) registers.
+ *
+ * The CM PMU registers share a PMP granule with M-mode only GCR registers,
+ * so the whole GCR block is closed for S-mode by PMP. S-mode loads/stores
+ * to it trap into M-mode as access faults, and sbi_trap_ldst.c forwards
+ * them here. We translate the faulting S-mode virtual address to a
+ * physical address, check that it falls in the PMU register range of one
+ * of the CM instances and, if so, perform the access on behalf of S-mode.
+ * Anything else is rejected and redirected to S-mode as a real fault.
+ */
+
+#include <sbi/riscv_io.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_trap_ldst.h>
+#include <sbi/sbi_unpriv.h>
+#include <mips/p8700.h>
+
+/**
+ * Check whether an access of @len bytes at physical address @pa is an
+ * access to the CM PMU registers of one of the CM instances.
+ */
+static bool p8700_smode_reg_allowed(ulong pa, int len)
+{
+ unsigned int i;
+ ulong off;
+
+ if (!p8700_cm_info)
+ return false;
+
+ /* Only naturally aligned 32/64-bit accesses, as the PMU driver does */
+ if ((len != 4 && len != 8) || (pa & (len - 1)))
+ return false;
+
+ for (i = 0; i < p8700_cm_info->num_cm; i++) {
+ if (pa < p8700_cm_info->gcr_base[i])
+ continue;
+ off = pa - p8700_cm_info->gcr_base[i];
+ if (off >= GCR_PMU_OFFSET && off + len <= GCR_PMU_OFFSET + GCR_PMU_SIZE)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Fast reject: the page offset is translation invariant, so if it cannot
+ * be a PMU register there is no point in walking the page tables.
+ */
+static bool p8700_page_off_may_be_pmu(ulong va)
+{
+ ulong off = va & ~PAGE_MASK;
+
+ return off >= GCR_PMU_OFFSET && off < GCR_PMU_OFFSET + GCR_PMU_SIZE;
+}
+
+int mips_p8700_emulate_load(ulong insn, int rlen, ulong addr,
+ union sbi_ldst_data *out_val,
+ struct sbi_trap_context *tcntx)
+{
+ ulong pa;
+ int rc;
+
+ /* rlen == 0 is a vector/custom instruction; not supported */
+ if (!rlen)
+ return SBI_ENOTSUPP;
+ if (!p8700_page_off_may_be_pmu(addr))
+ return SBI_EDENIED;
+
+ rc = sbi_va_to_pa(&tcntx->regs, addr, &pa);
+ if (rc)
+ return rc;
+ if (!p8700_smode_reg_allowed(pa, rlen))
+ return SBI_EDENIED;
+
+ switch (rlen) {
+ case 4:
+ out_val->data_u32 = readl_relaxed((void *)pa);
+ break;
+ case 8:
+ out_val->data_u64 = readq_relaxed((void *)pa);
+ break;
+ default:
+ return SBI_ENOTSUPP;
+ }
+
+ /* >0: caller writes rd from out_val and advances mepc */
+ return 1;
+}
+
+int mips_p8700_emulate_store(ulong insn, int wlen, ulong addr,
+ union sbi_ldst_data in_val,
+ struct sbi_trap_context *tcntx)
+{
+ ulong pa;
+ int rc;
+
+ if (!wlen)
+ return SBI_ENOTSUPP;
+ if (!p8700_page_off_may_be_pmu(addr))
+ return SBI_EDENIED;
+
+ rc = sbi_va_to_pa(&tcntx->regs, addr, &pa);
+ if (rc)
+ return rc;
+ if (!p8700_smode_reg_allowed(pa, wlen))
+ return SBI_EDENIED;
+
+ switch (wlen) {
+ case 4:
+ writel_relaxed(in_val.data_u32, (void *)pa);
+ break;
+ case 8:
+ writeq_relaxed(in_val.data_u64, (void *)pa);
+ break;
+ default:
+ return SBI_ENOTSUPP;
+ }
+
+ /* >0: caller advances mepc */
+ return 1;
+}
--
2.43.0
More information about the opensbi
mailing list