[PATCH RFC v6 03/18] riscv: add support for srmcfg CSR from Ssqosid extension
Drew Fustini <[email protected]> Mon, 01 Jun 2026 13:35:57 -0700
| Newsgroups | dev.linux.lists.acpica-devel,dev.linux.lists.linux-rt-devel,org.infradead.lists.linux-riscv,org.kernel.vger.linux-acpi,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Add support for the srmcfg CSR defined in the Ssqosid ISA extension. The CSR contains two fields: - Resource Control ID (RCID) for resource allocation - Monitoring Counter ID (MCID) for tracking resource usage Requests from a hart to shared resources are tagged with these IDs, allowing resource usage to be associated with the running task. Add a srmcfg field to thread_struct with the same format as the CSR so the scheduler can set the RCID and MCID for each task on context switch. A per-cpu cpu_srmcfg variable mirrors the CSR state to avoid redundant writes. L1D-hot memory access is faster than a CSR read and avoids traps under virtualization. A per-cpu cpu_srmcfg_default holds the default srmcfg for each CPU as set by resctrl CPU group assignment. On context switch, RCID and MCID inherit from the CPU default independently: a task whose thread RCID field is zero takes the CPU default's RCID, and likewise for MCID. Link: https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0 Assisted-by: Claude:claude-opus-4-7 Co-developed-by: Kornel Dulęba <[email protected]> Signed-off-by: Kornel Dulęba <[email protected]> Signed-off-by: Drew Fustini <[email protected]> --- MAINTAINERS | 8 ++++ arch/riscv/Kconfig | 18 +++++++ arch/riscv/include/asm/csr.h | 5 ++ arch/riscv/include/asm/processor.h | 3 ++ arch/riscv/include/asm/qos.h | 87 +++++++++++++++++++++++++++++++++ arch/riscv/include/asm/switch_to.h | 3 ++ arch/riscv/kernel/Makefile | 2 + arch/riscv/kernel/qos.c | 98 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 224 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index c2c6d79275c6..e694fb2a22d2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23017,6 +23017,14 @@ F: drivers/perf/riscv_pmu.c F: drivers/perf/riscv_pmu_legacy.c F: drivers/perf/riscv_pmu_sbi.c +RISC-V QOS RESCTRL SUPPORT +M: Drew Fustini <[email protected]> +R: yunhui cui <[email protected]> +L: [email protected] +S: Supported +F: arch/riscv/include/asm/qos.h +F: arch/riscv/kernel/qos.c + RISC-V RPMI AND MPXY DRIVERS M: Rahul Pathak <[email protected]> M: Anup Patel <[email protected]> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index c5754942cf85..6abbb21f3a0d 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -591,6 +591,24 @@ config RISCV_ISA_SVNAPOT If you don't know what to do here, say Y. +config RISCV_ISA_SSQOSID + bool "Ssqosid extension support for supervisor mode Quality of Service ID" + depends on 64BIT + default n + help + Adds support for the Ssqosid ISA extension (Supervisor-mode + Quality of Service ID). + + Ssqosid defines the srmcfg CSR which allows the system to tag the + running process with an RCID (Resource Control ID) and MCID + (Monitoring Counter ID). The RCID is used to determine resource + allocation. The MCID is used to track resource usage in event + counters. + + For example, a cache controller may use the RCID to apply a + cache partitioning scheme and use the MCID to track how much + cache a process, or a group of processes, is using. + config RISCV_ISA_SVPBMT bool "Svpbmt extension support for supervisor mode page-based memory types" depends on 64BIT && MMU diff --git a/arch/riscv/include/asm/csr.h b/arch/riscv/include/asm/csr.h index 31b8988f4488..7bce928e5daa 100644 --- a/arch/riscv/include/asm/csr.h +++ b/arch/riscv/include/asm/csr.h @@ -84,6 +84,10 @@ #define SATP_ASID_MASK _AC(0xFFFF, UL) #endif +/* SRMCFG fields */ +#define SRMCFG_RCID_MASK GENMASK(11, 0) +#define SRMCFG_MCID_MASK GENMASK(27, 16) + /* Exception cause high bit - is an interrupt if set */ #define CAUSE_IRQ_FLAG (_AC(1, UL) << (__riscv_xlen - 1)) @@ -328,6 +332,7 @@ #define CSR_STVAL 0x143 #define CSR_SIP 0x144 #define CSR_SATP 0x180 +#define CSR_SRMCFG 0x181 #define CSR_STIMECMP 0x14D #define CSR_STIMECMPH 0x15D diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h index 812517b2cec1..49a386d74cd3 100644 --- a/arch/riscv/include/asm/processor.h +++ b/arch/riscv/include/asm/processor.h @@ -123,6 +123,9 @@ struct thread_struct { /* A forced icache flush is not needed if migrating to the previous cpu. */ unsigned int prev_cpu; #endif +#ifdef CONFIG_RISCV_ISA_SSQOSID + u32 srmcfg; +#endif }; /* Whitelist the fstate from the task_struct for hardened usercopy */ diff --git a/arch/riscv/include/asm/qos.h b/arch/riscv/include/asm/qos.h new file mode 100644 index 000000000000..727d438454f3 --- /dev/null +++ b/arch/riscv/include/asm/qos.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_RISCV_QOS_H +#define _ASM_RISCV_QOS_H + +#include <linux/percpu-defs.h> + +#ifdef CONFIG_RISCV_ISA_SSQOSID + +#include <linux/bitfield.h> +#include <linux/cpufeature.h> +#include <linux/sched.h> + +#include <asm/csr.h> +#include <asm/fence.h> +#include <asm/hwcap.h> + +/* cached value of srmcfg csr for each cpu */ +DECLARE_PER_CPU(u32, cpu_srmcfg); + +/* default srmcfg value for each cpu, set via resctrl cpu assignment */ +DECLARE_PER_CPU(u32, cpu_srmcfg_default); + +static inline void __switch_to_srmcfg(struct task_struct *next) +{ + u32 thread_srmcfg, default_srmcfg; + + thread_srmcfg = READ_ONCE(next->thread.srmcfg); + default_srmcfg = __this_cpu_read(cpu_srmcfg_default); + + /* + * RCID and MCID inherit from cpu_srmcfg_default independently. + * RESCTRL_RESERVED_CLOSID and RESCTRL_RESERVED_RMID are both 0, + * so a per-field zero means "no task assignment for this + * dimension" and the CPU default supplies that field. Matches + * x86 RDT's __resctrl_sched_in() per-field logic. The fully + * unassigned (thread.srmcfg == 0) and fully assigned (both + * fields non-zero) cases short-circuit the field math. + */ + if (thread_srmcfg == 0) { + thread_srmcfg = default_srmcfg; + } else { + u32 rcid = FIELD_GET(SRMCFG_RCID_MASK, thread_srmcfg); + u32 mcid = FIELD_GET(SRMCFG_MCID_MASK, thread_srmcfg); + + if (rcid == 0 || mcid == 0) { + if (rcid == 0) + rcid = FIELD_GET(SRMCFG_RCID_MASK, default_srmcfg); + if (mcid == 0) + mcid = FIELD_GET(SRMCFG_MCID_MASK, default_srmcfg); + thread_srmcfg = FIELD_PREP(SRMCFG_RCID_MASK, rcid) | + FIELD_PREP(SRMCFG_MCID_MASK, mcid); + } + } + + if (thread_srmcfg != __this_cpu_read(cpu_srmcfg)) { + /* + * Drain stores from the outgoing task before the CSR write + * so they retain the previous RCID/MCID tag at the cache + * interconnect. + */ + RISCV_FENCE(rw, o); + + __this_cpu_write(cpu_srmcfg, thread_srmcfg); + csr_write(CSR_SRMCFG, thread_srmcfg); + /* + * Order the csrw before the new task's loads/stores so they + * pick up the new tag. Zicsr 6.1.1 makes CSR writes weakly + * ordered (device-output) vs memory ops. Ssqosid v1.0 is + * silent so honor the general CSR rule. + */ + RISCV_FENCE(o, rw); + } +} + +static __always_inline bool has_srmcfg(void) +{ + return riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID); +} + +#else /* ! CONFIG_RISCV_ISA_SSQOSID */ + +struct task_struct; +static __always_inline bool has_srmcfg(void) { return false; } +static inline void __switch_to_srmcfg(struct task_struct *next) { } + +#endif /* CONFIG_RISCV_ISA_SSQOSID */ +#endif /* _ASM_RISCV_QOS_H */ diff --git a/arch/riscv/include/asm/switch_to.h b/arch/riscv/include/asm/switch_to.h index 0e71eb82f920..1c7ea53ec012 100644 --- a/arch/riscv/include/asm/switch_to.h +++ b/arch/riscv/include/asm/switch_to.h @@ -14,6 +14,7 @@ #include <asm/processor.h> #include <asm/ptrace.h> #include <asm/csr.h> +#include <asm/qos.h> #ifdef CONFIG_FPU extern void __fstate_save(struct task_struct *save_to); @@ -119,6 +120,8 @@ do { \ __switch_to_fpu(__prev, __next); \ if (has_vector() || has_xtheadvector()) \ __switch_to_vector(__prev, __next); \ + if (has_srmcfg()) \ + __switch_to_srmcfg(__next); \ if (switch_to_should_flush_icache(__next)) \ local_flush_icache_all(); \ __switch_to_envcfg(__next); \ diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile index cabb99cadfb6..ebe1c3588177 100644 --- a/arch/riscv/kernel/Makefile +++ b/arch/riscv/kernel/Makefile @@ -128,3 +128,5 @@ obj-$(CONFIG_ACPI_NUMA) += acpi_numa.o obj-$(CONFIG_GENERIC_CPU_VULNERABILITIES) += bugs.o obj-$(CONFIG_RISCV_USER_CFI) += usercfi.o + +obj-$(CONFIG_RISCV_ISA_SSQOSID) += qos.o diff --git a/arch/riscv/kernel/qos.c b/arch/riscv/kernel/qos.c new file mode 100644 index 000000000000..d18b99b195e7 --- /dev/null +++ b/arch/riscv/kernel/qos.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/cpu.h> +#include <linux/cpu_pm.h> +#include <linux/cpuhotplug.h> +#include <linux/notifier.h> +#include <linux/percpu-defs.h> +#include <linux/types.h> + +#include <asm/cpufeature-macros.h> +#include <asm/hwcap.h> +#include <asm/qos.h> + +/* + * Cached value of srmcfg csr for each cpu. Seeded to U32_MAX so the next + * __switch_to_srmcfg() unconditionally writes the CSR; the encoding + * MCID << 16 | RCID with both fields well under 16 bits can never + * produce this sentinel. This covers early-boot context switches that + * happen before riscv_srmcfg_init() runs as an arch_initcall. + */ +DEFINE_PER_CPU(u32, cpu_srmcfg) = U32_MAX; + +/* default srmcfg value for each cpu, set via resctrl cpu assignment */ +DEFINE_PER_CPU(u32, cpu_srmcfg_default); + +/* + * Seed the per-CPU srmcfg cache to a sentinel that no real srmcfg encoding + * can produce (MCID << 16 | RCID, both fields well under 16 bits) so the + * next __switch_to_srmcfg() unconditionally writes the CSR. Ssqosid v1.0 + * leaves CSR state across hart stop/start implementation-defined, so the + * cached value cannot be trusted after online. + */ +static int riscv_srmcfg_online(unsigned int cpu) +{ + per_cpu(cpu_srmcfg, cpu) = U32_MAX; + return 0; +} + +/* + * Invalidate the cache on offline too. The sentinel persists across the + * offline period, so a CPU brought back online finds the cache already + * invalidated before it is schedulable. This closes the window where a + * task scheduled before riscv_srmcfg_online() runs could match a stale + * cache and skip the CSR write while the hardware CSR was reset across + * hart stop/start. + */ +static int riscv_srmcfg_offline(unsigned int cpu) +{ + per_cpu(cpu_srmcfg, cpu) = U32_MAX; + return 0; +} + +/* + * CPU PM notifier: invalidate the cached srmcfg on resume from a deep + * idle / suspend. Ssqosid v1.0 leaves CSR_SRMCFG state across low-power + * transitions implementation-defined, and the boot CPU never goes + * through the cpuhp online callback during system suspend, so without + * this hook __switch_to_srmcfg() would skip the CSR write when the + * outgoing task happens to share its srmcfg with the pre-suspend cache. + */ +static int riscv_srmcfg_pm_notify(struct notifier_block *nb, + unsigned long action, void *unused) +{ + switch (action) { + case CPU_PM_EXIT: + case CPU_PM_ENTER_FAILED: + __this_cpu_write(cpu_srmcfg, U32_MAX); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block riscv_srmcfg_pm_nb = { + .notifier_call = riscv_srmcfg_pm_notify, +}; + +static int __init riscv_srmcfg_init(void) +{ + int err; + + if (!riscv_has_extension_unlikely(RISCV_ISA_EXT_SSQOSID)) + return 0; + + /* + * cpuhp_setup_state() invokes the startup callback locally on every + * already-online CPU, so no separate seed loop is needed here. + */ + err = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "riscv/srmcfg:online", + riscv_srmcfg_online, riscv_srmcfg_offline); + if (err < 0) { + pr_warn("srmcfg cpuhp registration failed (%d), cpus brought online after boot will not invalidate the CSR_SRMCFG cache\n", + err); + return err; + } + + cpu_pm_register_notifier(&riscv_srmcfg_pm_nb); + return 0; +} +arch_initcall(riscv_srmcfg_init); -- 2.43.0