[PATCH RFC v5 16/18] ACPI: RISC-V: Parse RISC-V Quality of Service Controller (RQSC) table

From: Drew Fustini

Date: Sun May 24 2026 - 19:58:19 EST


Add a parser for the ACPI RQSC table, which describes the CBQRI
controllers in a system. For each table entry, populate a
cbqri_controller_info descriptor and hand it to the CBQRI driver via
riscv_cbqri_register_controller(). The driver owns all subsequent state,
including cpumask resolution at cbqri_resctrl_setup() time.

Link: https://github.com/riscv-non-isa/riscv-rqsc/blob/main/src/
Link: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Drew Fustini <fustini@xxxxxxxxxx>
---
MAINTAINERS | 2 +
arch/riscv/include/asm/acpi.h | 10 +++
drivers/acpi/riscv/Makefile | 1 +
drivers/acpi/riscv/rqsc.c | 194 ++++++++++++++++++++++++++++++++++++++++++
drivers/acpi/riscv/rqsc.h | 63 ++++++++++++++
5 files changed, 270 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 7821dd5159cb..eab31c7b5e91 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23025,6 +23025,8 @@ S: Supported
F: arch/riscv/include/asm/qos.h
F: arch/riscv/include/asm/resctrl.h
F: arch/riscv/kernel/qos.c
+F: drivers/acpi/riscv/rqsc.c
+F: drivers/acpi/riscv/rqsc.h
F: drivers/resctrl/cbqri_devices.c
F: drivers/resctrl/cbqri_internal.h
F: drivers/resctrl/cbqri_resctrl.c
diff --git a/arch/riscv/include/asm/acpi.h b/arch/riscv/include/asm/acpi.h
index 26ab37c171bc..3cfd0102085e 100644
--- a/arch/riscv/include/asm/acpi.h
+++ b/arch/riscv/include/asm/acpi.h
@@ -67,6 +67,16 @@ int acpi_get_riscv_isa(struct acpi_table_header *table,

void acpi_get_cbo_block_size(struct acpi_table_header *table, u32 *cbom_size,
u32 *cboz_size, u32 *cbop_size);
+
+#ifdef CONFIG_RISCV_CBQRI_DRIVER
+int __init acpi_parse_rqsc(struct acpi_table_header *table);
+#else
+static inline int acpi_parse_rqsc(struct acpi_table_header *table)
+{
+ return -EINVAL;
+}
+#endif /* CONFIG_RISCV_CBQRI_DRIVER */
+
#else
static inline void acpi_init_rintc_map(void) { }
static inline struct acpi_madt_rintc *acpi_cpu_get_madt_rintc(int cpu)
diff --git a/drivers/acpi/riscv/Makefile b/drivers/acpi/riscv/Makefile
index 1284a076fa88..77f8f0101b7e 100644
--- a/drivers/acpi/riscv/Makefile
+++ b/drivers/acpi/riscv/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += rhct.o init.o irq.o
+obj-$(CONFIG_RISCV_CBQRI_DRIVER) += rqsc.o
obj-$(CONFIG_ACPI_PROCESSOR_IDLE) += cpuidle.o
obj-$(CONFIG_ACPI_CPPC_LIB) += cppc.o
obj-$(CONFIG_ACPI_RIMT) += rimt.o
diff --git a/drivers/acpi/riscv/rqsc.c b/drivers/acpi/riscv/rqsc.c
new file mode 100644
index 000000000000..1cbc5c07e191
--- /dev/null
+++ b/drivers/acpi/riscv/rqsc.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) "ACPI: RQSC: " fmt
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/riscv_cbqri.h>
+
+#include "rqsc.h"
+
+#define CBQRI_CTRL_SIZE 0x1000
+
+int __init acpi_parse_rqsc(struct acpi_table_header *table)
+{
+ struct acpi_table_rqsc *rqsc = (struct acpi_table_rqsc *)table;
+ struct acpi_rqsc_node *end, *node;
+ int num_controllers = 0;
+
+ /*
+ * Reject revisions newer than this parser was written against. A
+ * future revision could extend the fixed RQSC header before the
+ * first node, which would shift the resource subtables and cause the
+ * sizeof(*node)-based offset below to point into the wrong place.
+ */
+ if (rqsc->header.revision != ACPI_RQSC_REVISION) {
+ pr_err("RQSC table revision %u, expected %u, aborting\n",
+ rqsc->header.revision, ACPI_RQSC_REVISION);
+ return -EINVAL;
+ }
+
+ /* Reject tables shorter than the fixed RQSC header. */
+ if (rqsc->header.length < sizeof(struct acpi_table_rqsc)) {
+ pr_err("RQSC table truncated: length %u < %zu, aborting\n",
+ rqsc->header.length, sizeof(struct acpi_table_rqsc));
+ return -EINVAL;
+ }
+
+ end = ACPI_ADD_PTR(struct acpi_rqsc_node, rqsc, rqsc->header.length);
+
+ for (node = ACPI_ADD_PTR(struct acpi_rqsc_node, rqsc,
+ sizeof(struct acpi_table_rqsc));
+ node < end;
+ node = ACPI_ADD_PTR(struct acpi_rqsc_node, node, node->length)
+ ) {
+ const struct acpi_rqsc_resource *res0;
+ struct cbqri_controller_info info = {};
+ int ret;
+
+ if ((void *)node + sizeof(*node) > (void *)end) {
+ pr_err("truncated entry at end of table, aborting\n");
+ riscv_cbqri_unregister_last(num_controllers);
+ return -EINVAL;
+ }
+
+ if (node->length < sizeof(*node)) {
+ pr_err("malformed RQSC entry: length %u < %zu, aborting\n",
+ node->length, sizeof(*node));
+ riscv_cbqri_unregister_last(num_controllers);
+ return -EINVAL;
+ }
+
+ /*
+ * Without this check, a node whose length claims to extend
+ * past the end of the table would advance the loop cursor
+ * past `end` and silently terminate. Flag the corruption
+ * explicitly so a malformed firmware table cannot truncate
+ * the controller list without noise.
+ */
+ if ((void *)node + node->length > (void *)end) {
+ pr_err("RQSC entry length %u overruns table end, aborting\n",
+ node->length);
+ riscv_cbqri_unregister_last(num_controllers);
+ return -EINVAL;
+ }
+
+ /* GAS must describe system memory. ioremap() consumes it later. */
+ if (node->reg.space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) {
+ pr_warn("controller has unsupported address space_id=%u, skipping\n",
+ node->reg.space_id);
+ continue;
+ }
+
+ /* Address 0 would map page 0 (reset vectors, SBI, boot ROM). */
+ if (!node->reg.address) {
+ pr_warn("controller has zero address, skipping\n");
+ continue;
+ }
+
+ info.type = node->type;
+ /* RQSC v0.9.2 section 2 Table 2: 12-byte GAS-format register interface address */
+ info.addr = node->reg.address;
+ info.size = CBQRI_CTRL_SIZE;
+ info.rcid_count = node->rcid;
+ info.mcid_count = node->mcid;
+
+ /* See CBQRI_MAX_RCID/MCID in <linux/riscv_cbqri.h> for the rationale. */
+ if (info.rcid_count > CBQRI_MAX_RCID) {
+ pr_warn("controller at %pa: rcid_count %u exceeds CBQRI_MAX_RCID %u, skipping\n",
+ &info.addr, info.rcid_count, CBQRI_MAX_RCID);
+ continue;
+ }
+
+ if (info.mcid_count > CBQRI_MAX_MCID) {
+ pr_warn("controller at %pa: mcid_count %u exceeds CBQRI_MAX_MCID %u, skipping\n",
+ &info.addr, info.mcid_count, CBQRI_MAX_MCID);
+ continue;
+ }
+
+ if (node->nres == 0) {
+ pr_warn("controller at %pa has no resource descriptors, skipping\n",
+ &info.addr);
+ continue;
+ }
+
+ /*
+ * Resources follow the node header in-line. Only res[0] is
+ * consumed. Bound it against end before reading its prefix so
+ * a table that ends partway through a resource subtable is
+ * rejected rather than read past the mapping.
+ */
+ res0 = (const struct acpi_rqsc_resource *)
+ ((const u8 *)node + sizeof(*node));
+ if ((void *)res0 + sizeof(*res0) > (void *)end ||
+ node->length < sizeof(*node) + sizeof(*res0) ||
+ res0->length < sizeof(*res0)) {
+ pr_warn("controller at %pa: node too short for resource descriptor, skipping\n",
+ &info.addr);
+ continue;
+ }
+
+ if (node->nres > 1)
+ pr_warn("controller at %pa has %u resource descriptors, using first\n",
+ &info.addr, node->nres);
+
+ /*
+ * id1 is u64 on the wire but cache_id and prox_dom are u32
+ * downstream (PPTT cache_id, ACPI proximity domain). Reject
+ * rather than truncate, so a too-large id is not silently
+ * mapped to the wrong PPTT entry or NUMA node.
+ */
+ if (res0->id1 > U32_MAX) {
+ pr_warn("controller at %pa: id1 0x%llx exceeds u32, skipping\n",
+ &info.addr, res0->id1);
+ continue;
+ }
+
+ /*
+ * Pair the QoS controller type with the resource descriptor
+ * fields that index id1. RQSC v0.9.2 Table 4 defines the
+ * mapping: a Capacity controller indexes a Processor Cache
+ * via PPTT cache_id, a Bandwidth controller indexes a Memory
+ * Range via SRAT proximity domain. Mismatched pairings
+ * (e.g. a CC whose first resource is Memory) would otherwise
+ * route id1 into the wrong downstream lookup.
+ */
+ switch (info.type) {
+ case CBQRI_CONTROLLER_TYPE_CAPACITY:
+ if (res0->type != ACPI_RQSC_RESOURCE_TYPE_CACHE ||
+ res0->id_type != ACPI_RQSC_RESOURCE_ID_TYPE_PROCESSOR_CACHE) {
+ pr_warn("CC at %pa: resource type=%u id_type=%u not (cache, processor cache), skipping\n",
+ &info.addr, res0->type, res0->id_type);
+ continue;
+ }
+ info.cache_id = (u32)res0->id1;
+ break;
+ case CBQRI_CONTROLLER_TYPE_BANDWIDTH:
+ if (res0->type != ACPI_RQSC_RESOURCE_TYPE_MEMORY ||
+ res0->id_type != ACPI_RQSC_RESOURCE_ID_TYPE_MEMORY_RANGE) {
+ pr_warn("BC at %pa: resource type=%u id_type=%u not (memory, memory range), skipping\n",
+ &info.addr, res0->type, res0->id_type);
+ continue;
+ }
+ info.prox_dom = (u32)res0->id1;
+ break;
+ default:
+ pr_warn("controller at %pa: unknown type %u, skipping\n",
+ &info.addr, info.type);
+ continue;
+ }
+
+ pr_debug("registering controller type=%u addr=%pa rcid=%u mcid=%u\n",
+ info.type, &info.addr, info.rcid_count, info.mcid_count);
+
+ ret = riscv_cbqri_register_controller(&info);
+ if (ret == 0)
+ num_controllers++;
+ else
+ pr_warn("controller at %pa: registration failed (%d), skipping\n",
+ &info.addr, ret);
+ }
+
+ pr_info("found %d CBQRI controllers\n", num_controllers);
+ return 0;
+}
diff --git a/drivers/acpi/riscv/rqsc.h b/drivers/acpi/riscv/rqsc.h
new file mode 100644
index 000000000000..f7b556f29e16
--- /dev/null
+++ b/drivers/acpi/riscv/rqsc.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Local definitions for the RISC-V Quality of Service Controller (RQSC)
+ * ACPI table. Will move to ACPICA's include/acpi/actbl2.h once the spec
+ * is ratified.
+ */
+#ifndef _DRIVERS_ACPI_RISCV_RQSC_H
+#define _DRIVERS_ACPI_RISCV_RQSC_H
+
+#include <linux/types.h>
+#include <acpi/actbl.h>
+
+#define ACPI_SIG_RQSC "RQSC" /* RISC-V Quality of Service Controller */
+
+/* RQSC v0.9.2 Table 1: current revision number. */
+#define ACPI_RQSC_REVISION 1
+
+/* RQSC v0.9.2 Table 4: Resource Type values for acpi_rqsc_resource.type. */
+#define ACPI_RQSC_RESOURCE_TYPE_CACHE 0
+#define ACPI_RQSC_RESOURCE_TYPE_MEMORY 1
+
+/* RQSC v0.9.2 Table 4: Resource ID Type values for .id_type. */
+#define ACPI_RQSC_RESOURCE_ID_TYPE_PROCESSOR_CACHE 0
+#define ACPI_RQSC_RESOURCE_ID_TYPE_MEMORY_RANGE 1
+
+/*
+ * Byte-packed: u64 id1 would otherwise pad to 8-byte alignment and inflate
+ * sizeof(*res) from the spec's 20 bytes to 24, mis-sizing resource subtables.
+ */
+struct acpi_rqsc_resource {
+ u8 type;
+ u8 resv;
+ u16 length;
+ u16 flags;
+ u8 resv2;
+ u8 id_type;
+ u64 id1;
+ u32 id2;
+} __packed;
+
+struct acpi_rqsc_node {
+ u8 type;
+ u8 resv;
+ u16 length;
+ /* RQSC v0.9.2 section 2 Table 2: 12-byte GAS-format register interface address */
+ struct acpi_generic_address reg;
+ u16 rcid;
+ u16 mcid;
+ u16 flags;
+ u16 nres;
+ /*
+ * Followed by nres acpi_rqsc_resource subtables. Walk them via
+ * each resource's own length field so a future RQSC revision that
+ * extends the resource layout cannot misalign older parsers.
+ */
+} __packed;
+
+struct acpi_table_rqsc {
+ struct acpi_table_header header; /* Common ACPI table header */
+ u32 num;
+} __packed;
+
+#endif /* _DRIVERS_ACPI_RISCV_RQSC_H */

--
2.43.0