From c5dbdcc371e11d6a00d4128ec44942aa2b4adc7c Mon Sep 17 00:00:00 2001 From: Chen Pei Date: Thu, 28 May 2026 09:43:44 +0800 Subject: [PATCH 1/2] Revert "xuantie: pmu: Support cycles&instructions overflow interrupt" This reverts commit bbd2d8a96920601429ce0ea5818adc57da5747cb. Signed-off-by: Chen Pei --- include/sbi/riscv_encoding.h | 2 -- lib/sbi/riscv_asm.c | 4 ---- lib/sbi/sbi_pmu.c | 27 ++++++--------------------- 3 files changed, 6 insertions(+), 27 deletions(-) diff --git a/include/sbi/riscv_encoding.h b/include/sbi/riscv_encoding.h index 1786fcb2..19fae2a6 100644 --- a/include/sbi/riscv_encoding.h +++ b/include/sbi/riscv_encoding.h @@ -656,8 +656,6 @@ #define CSR_MCOUNTINHIBIT 0x320 #define CSR_MCYCLECFG 0x321 #define CSR_MINSTRETCFG 0x322 -#define CSR_MHPMEVENT0 0x7E0 -#define CSR_MHPMEVENT2 0x7E1 #define CSR_MHPMEVENT3 0x323 #define CSR_MHPMEVENT4 0x324 #define CSR_MHPMEVENT5 0x325 diff --git a/lib/sbi/riscv_asm.c b/lib/sbi/riscv_asm.c index 20f10ed0..c7d75ac0 100644 --- a/lib/sbi/riscv_asm.c +++ b/lib/sbi/riscv_asm.c @@ -130,8 +130,6 @@ unsigned long csr_read_num(int csr_num) switchcase_csr_read(CSR_MCOUNTINHIBIT, ret) switchcase_csr_read(CSR_MCYCLECFG, ret) switchcase_csr_read(CSR_MINSTRETCFG, ret) - switchcase_csr_read(CSR_MHPMEVENT0, ret) - switchcase_csr_read(CSR_MHPMEVENT2, ret) switchcase_csr_read(CSR_MHPMEVENT3, ret) switchcase_csr_read_4(CSR_MHPMEVENT4, ret) switchcase_csr_read_8(CSR_MHPMEVENT8, ret) @@ -226,8 +224,6 @@ void csr_write_num(int csr_num, unsigned long val) switchcase_csr_write(CSR_MCOUNTINHIBIT, val) switchcase_csr_write(CSR_MCYCLECFG, val) switchcase_csr_write(CSR_MINSTRETCFG, val) - switchcase_csr_write(CSR_MHPMEVENT0, val) - switchcase_csr_write(CSR_MHPMEVENT2, val) switchcase_csr_write(CSR_MHPMEVENT3, val) switchcase_csr_write_4(CSR_MHPMEVENT4, val) switchcase_csr_write_8(CSR_MHPMEVENT8, val) diff --git a/lib/sbi/sbi_pmu.c b/lib/sbi/sbi_pmu.c index 55cb777b..07a5c544 100644 --- a/lib/sbi/sbi_pmu.c +++ b/lib/sbi/sbi_pmu.c @@ -329,19 +329,14 @@ static int pmu_ctr_enable_irq_hw(int ctr_idx) unsigned long mip_val; unsigned long of_mask; - if (ctr_idx >= SBI_PMU_HW_CTR_MAX) + if (ctr_idx < 3 || ctr_idx >= SBI_PMU_HW_CTR_MAX) return SBI_EFAIL; #if __riscv_xlen == 32 mhpmevent_csr = CSR_MHPMEVENT3H + ctr_idx - 3; of_mask = (uint32_t)~MHPMEVENTH_OF; #else - if (ctr_idx == 0) - mhpmevent_csr = CSR_MHPMEVENT0; - else if (ctr_idx == 2) - mhpmevent_csr = CSR_MHPMEVENT2; - else - mhpmevent_csr = CSR_MHPMEVENT3 + ctr_idx - 3; + mhpmevent_csr = CSR_MHPMEVENT3 + ctr_idx - 3; of_mask = ~MHPMEVENT_OF; #endif @@ -561,7 +556,7 @@ static int pmu_ctr_stop_fw(struct sbi_pmu_hart_state *phs, static int pmu_reset_hw_mhpmevent(int ctr_idx) { - if (ctr_idx >= SBI_PMU_HW_CTR_MAX) + if (ctr_idx < 3 || ctr_idx >= SBI_PMU_HW_CTR_MAX) return SBI_EFAIL; #if __riscv_xlen == 32 csr_write_num(CSR_MHPMEVENT3 + ctr_idx - 3, 0); @@ -569,12 +564,7 @@ static int pmu_reset_hw_mhpmevent(int ctr_idx) SBI_HART_EXT_SSCOFPMF)) csr_write_num(CSR_MHPMEVENT3H + ctr_idx - 3, 0); #else - if (ctr_idx == 0) - csr_write_num(CSR_MHPMEVENT0, 0); - else if (ctr_idx == 2) - csr_write_num(CSR_MHPMEVENT2, 0); - else - csr_write_num(CSR_MHPMEVENT3 + ctr_idx - 3, 0); + csr_write_num(CSR_MHPMEVENT3 + ctr_idx - 3, 0); #endif return 0; @@ -647,7 +637,7 @@ static int pmu_update_hw_mhpmevent(struct sbi_pmu_hw_event *hw_evt, int ctr_idx, /* Get the final mhpmevent value to be written from platform */ mhpmevent_val = sbi_platform_pmu_xlate_to_mhpmevent(plat, eindex, data); - if (!mhpmevent_val || ctr_idx >= SBI_PMU_HW_CTR_MAX) + if (!mhpmevent_val || ctr_idx < 3 || ctr_idx >= SBI_PMU_HW_CTR_MAX) return SBI_EFAIL; /** @@ -673,12 +663,7 @@ static int pmu_update_hw_mhpmevent(struct sbi_pmu_hw_event *hw_evt, int ctr_idx, csr_write_num(CSR_MHPMEVENT3H + ctr_idx - 3, mhpmevent_val >> BITS_PER_LONG); #else - if (ctr_idx == 0) - csr_write_num(CSR_MHPMEVENT0, mhpmevent_val); - else if (ctr_idx == 2) - csr_write_num(CSR_MHPMEVENT2, mhpmevent_val); - else - csr_write_num(CSR_MHPMEVENT3 + ctr_idx - 3, mhpmevent_val); + csr_write_num(CSR_MHPMEVENT3 + ctr_idx - 3, mhpmevent_val); #endif return 0; From cc97190b36c22d516f8a2ca20c3c221f1c954c8a Mon Sep 17 00:00:00 2001 From: Chen Pei Date: Thu, 28 May 2026 10:26:05 +0800 Subject: [PATCH 2/2] platform: xuantie: Add custom PMU device for cycle/instret OF Sscofpmf does not define overflow interrupts for the fixed cycle and instret counters. XuanTie cores expose this via vendor mhpmevent0 (0x7E0) and mhpmevent2 (0x7E1), with the OF/INH bits split into mhpmevent0H (0x7E2) / mhpmevent2H (0x7E3) on RV32 -- mirroring the standard mhpmevent3/3H layout. mxstatus[8] (OFINT) is the per-hart master gate that lets the vendor OF bit raise lcofip. Instead of patching the generic sbi_pmu code path, register a sbi_pmu_device (mirroring the thead c9xx pattern) and layer the vendor OF handling on top of Sscofpmf via hw_counter_enable_irq / hw_counter_disable_irq. The hooks only act on ctr_idx 0 and 2; the programmable counters (ctr_idx >= 3) continue to use the standard Sscofpmf code path unchanged. On RV32 the OF bit is reached via mhpmevent0H/mhpmevent2H, matching what pmu_ctr_enable_irq_hw() does for mhpmevent3H+. Set mxstatus[8] (OFINT) in xuantie_early_init() outside the cold_boot guard so it is enabled per hart on every boot path. Wire it up via QUIRK_XUANTIE_PMU. The xuantie,dummy match enables it alongside the existing quirks, and a new xuantie,pmu compatible is provided for boards that need just this driver. Signed-off-by: Chen Pei --- include/sbi/riscv_encoding.h | 6 ++ .../generic/include/xuantie/xuantie_pmu.h | 23 +++++ .../generic/include/xuantie/xuantie_quirk.h | 1 + platform/generic/xuantie/objects.mk | 2 +- platform/generic/xuantie/xuantie_dummy.c | 15 ++- platform/generic/xuantie/xuantie_pmu.c | 91 +++++++++++++++++++ 6 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 platform/generic/include/xuantie/xuantie_pmu.h create mode 100644 platform/generic/xuantie/xuantie_pmu.c diff --git a/include/sbi/riscv_encoding.h b/include/sbi/riscv_encoding.h index 19fae2a6..229c9a6f 100644 --- a/include/sbi/riscv_encoding.h +++ b/include/sbi/riscv_encoding.h @@ -656,6 +656,9 @@ #define CSR_MCOUNTINHIBIT 0x320 #define CSR_MCYCLECFG 0x321 #define CSR_MINSTRETCFG 0x322 +/* XuanTie vendor: per-counter event regs for fixed mcycle/minstret. */ +#define CSR_MHPMEVENT0 0x7E0 +#define CSR_MHPMEVENT2 0x7E1 #define CSR_MHPMEVENT3 0x323 #define CSR_MHPMEVENT4 0x324 #define CSR_MHPMEVENT5 0x325 @@ -689,6 +692,9 @@ /* For RV32 */ #define CSR_MCYCLECFGH 0x721 #define CSR_MINSTRETCFGH 0x722 +/* XuanTie vendor: high half of mhpmevent0/2 (for OF/INH bits on RV32). */ +#define CSR_MHPMEVENT0H 0x7E2 +#define CSR_MHPMEVENT2H 0x7E3 #define CSR_MHPMEVENT3H 0x723 #define CSR_MHPMEVENT4H 0x724 #define CSR_MHPMEVENT5H 0x725 diff --git a/platform/generic/include/xuantie/xuantie_pmu.h b/platform/generic/include/xuantie/xuantie_pmu.h new file mode 100644 index 00000000..99d0f996 --- /dev/null +++ b/platform/generic/include/xuantie/xuantie_pmu.h @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef __RISCV_XUANTIE_PMU_H__ +#define __RISCV_XUANTIE_PMU_H__ + +/* + * XuanTie cores expose Sscofpmf-style OF semantics on the fixed cycle and + * instret counters via vendor per-counter event registers (mhpmevent0/2, + * and the matching high halves on RV32). The CSR addresses live in + * next to the standard mhpmevent CSRs. + * + * mxstatus[8] (OFINT) is the per-hart master gate that lets the vendor + * OF bit raise lcofip; xuantie_pmu_enable_ofint() sets it. + */ + +#define MXSTATUS_OFINT BIT(8) + +void xuantie_pmu_register_device(void); +void xuantie_pmu_enable_ofint(void); + +#endif /* __RISCV_XUANTIE_PMU_H__ */ diff --git a/platform/generic/include/xuantie/xuantie_quirk.h b/platform/generic/include/xuantie/xuantie_quirk.h index 0eaf1fe9..e6466d26 100644 --- a/platform/generic/include/xuantie/xuantie_quirk.h +++ b/platform/generic/include/xuantie/xuantie_quirk.h @@ -8,6 +8,7 @@ #define QUIRK_XUANTIE_PMC BIT(0) #define QUIRK_XUANTIE_LINK BIT(1) #define QUIRK_XUANTIE_PMP_EXT BIT(2) +#define QUIRK_XUANTIE_PMU BIT(3) struct xuantie_generic_quirks { u32 quirk; diff --git a/platform/generic/xuantie/objects.mk b/platform/generic/xuantie/objects.mk index 6132dddf..f512ed24 100644 --- a/platform/generic/xuantie/objects.mk +++ b/platform/generic/xuantie/objects.mk @@ -3,4 +3,4 @@ # carray-platform_override_modules-$(CONFIG_PLATFORM_XUANTIE) += xuantie_dummy -platform-objs-$(CONFIG_PLATFORM_XUANTIE) += xuantie/xuantie_dummy.o xuantie/xuantie_pmc.o xuantie/xuantie_link.o xuantie/xuantie_pmp_ext.o +platform-objs-$(CONFIG_PLATFORM_XUANTIE) += xuantie/xuantie_dummy.o xuantie/xuantie_pmc.o xuantie/xuantie_link.o xuantie/xuantie_pmp_ext.o xuantie/xuantie_pmu.o diff --git a/platform/generic/xuantie/xuantie_dummy.c b/platform/generic/xuantie/xuantie_dummy.c index 00d536c0..528ffb3b 100644 --- a/platform/generic/xuantie/xuantie_dummy.c +++ b/platform/generic/xuantie/xuantie_dummy.c @@ -14,6 +14,7 @@ #include #include #include +#include static u32 gquirk = 0; @@ -24,6 +25,10 @@ int xuantie_early_init(bool cold_boot) xuantie_pmp_ext_cfg(); } + /* mxstatus[8] OFINT is per-hart: enable on every boot path. */ + if (gquirk & QUIRK_XUANTIE_PMU) + xuantie_pmu_enable_ofint(); + return generic_early_init(cold_boot); } @@ -34,6 +39,8 @@ int xuantie_final_init(bool cold_boot) xuantie_pmc_device_init(); if (gquirk & QUIRK_XUANTIE_LINK) xuantie_link_pmu_device_init(); + if (gquirk & QUIRK_XUANTIE_PMU) + xuantie_pmu_register_device(); } return generic_final_init(cold_boot); @@ -52,7 +59,8 @@ static int xuantie_dummy_platform_init(const void *fdt, int nodeoff, } static const struct xuantie_generic_quirks xuantie_quirks = { - .quirk = QUIRK_XUANTIE_PMC | QUIRK_XUANTIE_LINK | QUIRK_XUANTIE_PMP_EXT, + .quirk = QUIRK_XUANTIE_PMC | QUIRK_XUANTIE_LINK | QUIRK_XUANTIE_PMP_EXT | + QUIRK_XUANTIE_PMU, }; static const struct xuantie_generic_quirks xuantie_pmc_quirks = { @@ -67,10 +75,15 @@ static const struct xuantie_generic_quirks xuantie_pmp_ext_quirks = { .quirk = QUIRK_XUANTIE_PMP_EXT, }; +static const struct xuantie_generic_quirks xuantie_pmu_quirks = { + .quirk = QUIRK_XUANTIE_PMU, +}; + static const struct fdt_match xuantie_dummy_match[] = { { .compatible = "xuantie,dummy", .data = &xuantie_quirks }, { .compatible = "xuantie,pmc", .data = &xuantie_pmc_quirks }, { .compatible = "xuantie,link", .data = &xuantie_link_quirks }, + { .compatible = "xuantie,pmu", .data = &xuantie_pmu_quirks }, { .compatible = "riscv-virtio", .data = &xuantie_pmp_ext_quirks }, // qemu debug { }, }; diff --git a/platform/generic/xuantie/xuantie_pmu.c b/platform/generic/xuantie/xuantie_pmu.c new file mode 100644 index 00000000..91596f54 --- /dev/null +++ b/platform/generic/xuantie/xuantie_pmu.c @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * Sscofpmf does not provide OF interrupts for the fixed cycle/instret + * counters. XuanTie cores back those counters with vendor mhpmevent0/2 + * CSRs whose top bits follow the same OF/MINH/...INH layout as the + * standard mhpmevent3+. The generic sbi_pmu_device hooks let us layer + * the vendor OF handling on top of the regular Sscofpmf path: the core + * already calls hw_counter_enable_irq/disable_irq with ctr_idx 0 and 2 + * via the fixed-counter fallback in pmu_fixed_ctr_update_inhibit_bits(). + * + * Only ctr_idx == 0 (cycle) and ctr_idx == 2 (instret) need vendor + * handling. Other counters are programmable mhpmevent3+ and follow the + * standard Sscofpmf code path unchanged. + */ + +static void xuantie_pmu_ctr_enable_irq(uint32_t ctr_idx) +{ + if (ctr_idx != 0 && ctr_idx != 2) + return; + + /* + * Mirror pmu_ctr_enable_irq_hw(): only clear OF when no lcofip is + * still pending, so we don't race with software that hasn't yet + * handled the previous overflow. + */ + if (csr_read(CSR_MIP) & MIP_LCOFIP) + return; + +#if __riscv_xlen == 32 + /* OF lives in the H half on RV32; clear via mhpmevent0H/mhpmevent2H. */ + if (ctr_idx == 0) + csr_clear(CSR_MHPMEVENT0H, MHPMEVENTH_OF); + else + csr_clear(CSR_MHPMEVENT2H, MHPMEVENTH_OF); +#else + if (ctr_idx == 0) + csr_clear(CSR_MHPMEVENT0, MHPMEVENT_OF); + else + csr_clear(CSR_MHPMEVENT2, MHPMEVENT_OF); +#endif +} + +static void xuantie_pmu_ctr_disable_irq(uint32_t ctr_idx) +{ + if (ctr_idx != 0 && ctr_idx != 2) + return; + + /* + * Setting OF latches the counter so a subsequent overflow cannot + * raise lcofip until enable_irq clears it again. This matches the + * "OF set = disabled" convention used by pmu_update_hw_mhpmevent(). + */ +#if __riscv_xlen == 32 + if (ctr_idx == 0) + csr_set(CSR_MHPMEVENT0H, MHPMEVENTH_OF); + else + csr_set(CSR_MHPMEVENT2H, MHPMEVENTH_OF); +#else + if (ctr_idx == 0) + csr_set(CSR_MHPMEVENT0, MHPMEVENT_OF); + else + csr_set(CSR_MHPMEVENT2, MHPMEVENT_OF); +#endif +} + +static const struct sbi_pmu_device xuantie_pmu_device = { + .name = "xuantie,pmu", + .hw_counter_enable_irq = xuantie_pmu_ctr_enable_irq, + .hw_counter_disable_irq = xuantie_pmu_ctr_disable_irq, +}; + +void xuantie_pmu_register_device(void) +{ + sbi_pmu_set_device(&xuantie_pmu_device); +} + +void xuantie_pmu_enable_ofint(void) +{ + csr_set(THEAD_C9XX_CSR_MXSTATUS, MXSTATUS_OFINT); +}