[PATCH RFC v6 16/18] ACPI: RISC-V: Parse RISC-V Quality of Service Controller (RQSC) table
Drew Fustini <[email protected]> Mon, 01 Jun 2026 13:36:10 -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 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 <[email protected]> --- MAINTAINERS | 2 + arch/riscv/include/asm/acpi.h | 10 +++ drivers/acpi/riscv/Makefile | 1 + drivers/acpi/riscv/rqsc.c | 202 ++++++++++++++++++++++++++++++++++++++++++ drivers/acpi/riscv/rqsc.h | 66 ++++++++++++++ 5 files changed, 281 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..1b1ae2e353a5 --- /dev/null +++ b/drivers/acpi/riscv/rqsc.c @@ -0,0 +1,202 @@ +// 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; + } + + if (!node->reg.address) { + pr_warn("controller has zero address, skipping\n"); + continue; + } + + info.type = node->type; + /* RQSC 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; + } + + /* + * RQSC Table 2: at least one of RCID Count or MCID Count must be non-zero. + */ + if (!info.rcid_count && !info.mcid_count) { + pr_warn("controller at %pa: both rcid_count and mcid_count are zero, skipping\n", + &info.addr); + 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 but it is used for cache_id and prox_dom + * which are only u32. 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 Table 4 defines the mapping: + * 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..fa0d96e267e1 --- /dev/null +++ b/drivers/acpi/riscv/rqsc.h @@ -0,0 +1,66 @@ +/* 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 Table 1: current revision number. */ +#define ACPI_RQSC_REVISION 1 + +/* RQSC 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 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 +#define ACPI_RQSC_RESOURCE_ID_TYPE_MEMORY_SIDE_CACHE 2 +#define ACPI_RQSC_RESOURCE_ID_TYPE_ACPI_DEVICE 3 +#define ACPI_RQSC_RESOURCE_ID_TYPE_PCI_DEVICE 4 + +/* + * 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 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