[PATCH RFC v3 10/11] ACPI: RISC-V: Parse RISC-V Quality of Service Controller (RQSC) table

Drew Fustini
2026-04-15T01:56:58Z

Add parser for the ACPI RQSC table which describes the capacity and
bandwidth QoS controllers in a system. For each table entry, allocate a
cbqri_controller struct and populate it with the controller type, MMIO
base address, RCID/MCID counts, and resource identifiers (cache ID for
capacity controllers, proximity domain for bandwidth controllers).

Cache controller cpumasks are resolved via
acpi_pptt_get_cpumask_from_cache_id(). Bandwidth controller cpumasks
are derived from the proximity domain's NUMA node. Controllers with
invalid addresses or failed cpumask lookups are skipped with a warning.

The populated controller list is consumed by qos_resctrl_setup() in
arch/riscv/kernel/qos/ to probe the hardware and register resctrl
domains.

Link: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
Link: https://github.com/riscv-non-isa/riscv-rqsc/blob/main/src/
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
MAINTAINERS | 1 +
arch/riscv/include/asm/acpi.h | 10 ++++
drivers/acpi/riscv/Makefile | 1 +
drivers/acpi/riscv/rqsc.c | 136 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 148 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index af9698a16439..d5ec7d29bf11 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22786,6 +22786,7 @@ S: Supported
F: arch/riscv/include/asm/qos.h
F: arch/riscv/include/asm/resctrl.h
F: arch/riscv/kernel/qos/
+F: drivers/acpi/riscv/rqsc.c
F: include/linux/riscv_qos.h

RISC-V RPMI AND MPXY DRIVERS
diff --git a/arch/riscv/include/asm/acpi.h b/arch/riscv/include/asm/acpi.h
index 6e13695120bc..62296a2a519b 100644
--- a/arch/riscv/include/asm/acpi.h
+++ b/arch/riscv/include/asm/acpi.h
@@ -71,6 +71,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_ISA_SSQOSID
+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_ISA_SSQOSID */
+
#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..d7ae8729987a 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_ISA_SSQOSID) += 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..f647051be0bf
--- /dev/null
+++ b/drivers/acpi/riscv/rqsc.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Tenstorrent
+ * Author: Drew Fustini <fustini@kernel.org>
+ */
+
+#define pr_fmt(fmt) "ACPI: RQSC: " fmt
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/riscv_qos.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_fields *end;
+ struct acpi_table_rqsc_fields *node;
+ int err;
+ int num_controllers = 0;
+
+ rqsc = (struct acpi_table_rqsc *)table;
+
+ end = ACPI_ADD_PTR(struct acpi_table_rqsc_fields, rqsc, rqsc->header.length);
+
+ for (node = ACPI_ADD_PTR(struct acpi_table_rqsc_fields, rqsc,
+ sizeof(struct acpi_table_rqsc));
+ node < end;
+ node = ACPI_ADD_PTR(struct acpi_table_rqsc_fields, node, node->length)
+ ) {
+ struct cbqri_controller *ctrl;
+
+ if (node->length < sizeof(*node)) {
+ pr_err("malformed RQSC entry: length %u < %zu, aborting\n",
+ node->length, sizeof(*node));
+ err = -EINVAL;
+ goto err_free_controllers;
+ }
+
+ ctrl = kzalloc_obj(*ctrl, GFP_KERNEL);
+ if (!ctrl) {
+ err = -ENOMEM;
+ goto err_free_controllers;
+ }
+
+ ctrl->type = node->type;
+ /* reg[1] is the MMIO base address per the RQSC table layout */
+ ctrl->addr = node->reg[1];
+ ctrl->size = CBQRI_CTRL_SIZE;
+ ctrl->rcid_count = node->rcid;
+ ctrl->mcid_count = node->mcid;
+
+ if (!ctrl->addr) {
+ pr_warn("skipping controller with invalid addr=0x0\n");
+ kfree(ctrl);
+ continue;
+ }
+
+ if (node->nres == 0) {
+ pr_warn("controller at %pa has no resource descriptors, skipping\n",
+ &ctrl->addr);
+ kfree(ctrl);
+ continue;
+ }
+
+ if (node->length < sizeof(*node) + sizeof(node->res[0])) {
+ pr_warn("controller at %pa: node too short for resource descriptor, skipping\n",
+ &ctrl->addr);
+ kfree(ctrl);
+ continue;
+ }
+
+ if (node->nres > 1)
+ pr_warn("controller at %pa has %u resource descriptors, using first\n",
+ &ctrl->addr, node->nres);
+
+ pr_debug("Found controller with type %u addr %pa size %pa rcid %u mcid %u\n",
+ ctrl->type, &ctrl->addr, &ctrl->size,
+ ctrl->rcid_count, ctrl->mcid_count);
+ if (ctrl->type == CBQRI_CONTROLLER_TYPE_CAPACITY) {
+ ctrl->cache.cache_id = (u32)node->res[0].id1;
+ ctrl->cache.cache_level =
+ find_acpi_cache_level_from_id(ctrl->cache.cache_id);
+
+ if (acpi_pptt_get_cache_size_from_id(ctrl->cache.cache_id,
+ &ctrl->cache.cache_size)) {
+ pr_warn("failed to determine size for cache id 0x%x\n",
+ ctrl->cache.cache_id);
+ ctrl->cache.cache_size = 0;
+ }
+
+ pr_debug("Cache controller has ID 0x%x level %u size %u\n",
+ ctrl->cache.cache_id, ctrl->cache.cache_level,
+ ctrl->cache.cache_size);
+
+ /*
+ * For CBQRI, any cpu (technically a hart in RISC-V terms)
+ * can access the memory-mapped registers of any CBQRI
+ * controller in the system.
+ */
+ err = acpi_pptt_get_cpumask_from_cache_id(ctrl->cache.cache_id,
+ &ctrl->cache.cpu_mask);
+ if (err) {
+ pr_warn("Failed to get cpumask for cache id 0x%x (%d), skipping\n",
+ ctrl->cache.cache_id, err);
+ kfree(ctrl);
+ continue;
+ }
+
+ } else if (ctrl->type == CBQRI_CONTROLLER_TYPE_BANDWIDTH) {
+ ctrl->mem.prox_dom = (u32)node->res[0].id1;
+ cpumask_copy(&ctrl->mem.cpu_mask,
+ cpumask_of_node(pxm_to_node(ctrl->mem.prox_dom)));
+ pr_debug("Memory controller with proximity domain %u\n",
+ ctrl->mem.prox_dom);
+ }
+
+ /* List shared with RISC-V QoS resctrl implementation */
+ list_add_tail(&ctrl->list, &cbqri_controllers);
+ num_controllers++;
+ }
+
+ pr_info("found %d CBQRI controllers\n", num_controllers);
+ return 0;
+
+err_free_controllers:
+ while (!list_empty(&cbqri_controllers)) {
+ struct cbqri_controller *ctrl;
+
+ ctrl = list_first_entry(&cbqri_controllers, struct cbqri_controller, list);
+ list_del(&ctrl->list);
+ kfree(ctrl);
+ }
+ return err;
+}

--
2.43.0