Re: [External] [PATCH v2 3/8] riscv: Add support for srmcfg CSR from Ssqosid extension
From: yunhui cui
Date: Sat Jun 27 2026 - 05:11:39 EST
Hi Drew,
On Thu, Jun 25, 2026 at 9:40 AM Drew Fustini <fustini@xxxxxxxxxx> wrote:
>
> 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 <mindal@xxxxxxxxxxxx>
> Signed-off-by: Kornel Dulęba <mindal@xxxxxxxxxxxx>
> Signed-off-by: Drew Fustini <fustini@xxxxxxxxxx>
> ---
> 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 | 83 ++++++++++++++++++++++++++++++++
> arch/riscv/include/asm/switch_to.h | 3 ++
> arch/riscv/kernel/Makefile | 2 +
> arch/riscv/kernel/qos.c | 98 ++++++++++++++++++++++++++++++++++++++
> 8 files changed, 220 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0b9d7c8276acbafdb28a0ea5e81aa853ebee50b9..07109e1a8f8470377916c98074ab68fec51dfdc6 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23293,6 +23293,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 <fustini@xxxxxxxxxx>
> +R: yunhui cui <cuiyunhui@xxxxxxxxxxxxx>
> +L: linux-riscv@xxxxxxxxxxxxxxxxxxx
> +S: Supported
> +F: arch/riscv/include/asm/qos.h
> +F: arch/riscv/kernel/qos.c
> +
> RISC-V RPMI AND MPXY DRIVERS
> M: Rahul Pathak <rahul@xxxxxxxxxxxxxx>
> M: Anup Patel <anup@xxxxxxxxxxxxxx>
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index 3f0a647218e407f72890e83722ba8472858c1a59..ee586925f97227668c228b5481c05a2f914d928c 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -590,6 +590,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 31b8988f4488daa89b854ccc97c4efe1c82bcc3e..7bce928e5daa09bd62f0917279b04cfad30f46f5 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 812517b2cec1350f741849c1c56a35027321ef50..49a386d74cd3f0603a3ff919059d077a7e4d513c 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 0000000000000000000000000000000000000000..e9e1d69f3797be5f89785a9b3aa7d9d51c476a8a
> --- /dev/null
> +++ b/arch/riscv/include/asm/qos.h
> @@ -0,0 +1,83 @@
> +/* 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
> + * zero field means "unassigned" and takes the CPU default.
> + */
> + 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);
This is in the context-switch path and may be expensive in practice. Even if
the target workload is pinned and grouped, unpinned/default-group tasks or
kworkers may still run on those CPUs, causing frequent SRMCFG transitions and
paying two fences each time.
Is this strict ordering required by the Ssqosid spec or known hardware? If
not, can we make this a trade-off and avoid the fences by default, accepting a
small QoS-tagging inaccuracy around the context-switch boundary?
> + }
> +}
> +
> +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 0e71eb82f920cac2f14bb626879bb219a2f247cc..1c7ea53ec012adeaf03bf7c5d549ab21849768b5 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 cabb99cadfb6d1e1284d6b4e9ae76044d36949f5..ebe1c3588177b4b825a52af9ca17e17b5561427c 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 0000000000000000000000000000000000000000..ea33201a43f61534bf28b9c02b62801f30f62154
> --- /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);
> +
> +/*
> + * Invalidate the per-CPU srmcfg cache, used as both the cpuhp startup and
> + * teardown callback. The sentinel is a value 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. Invalidating
> + * on offline as well means the sentinel persists across the offline period:
> + * a CPU brought back online finds the cache already invalidated before it is
> + * schedulable, closing the window where a task scheduled before the startup
> + * callback 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_reset_cache(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:
> + /*
> + * The CSR is implementation-defined across the low-power
> + * transition. Invalidate the cache and eagerly rewrite the
> + * CSR for the current task so it does not run mis-tagged
> + * until the next context switch.
> + */
> + __this_cpu_write(cpu_srmcfg, U32_MAX);
> + __switch_to_srmcfg(current);
> + 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_reset_cache, riscv_srmcfg_reset_cache);
> + 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.34.1
>
Thanks,
Yunhui