* [PATCH v7 0/6] riscv: implement Ssqosid extension and CBQRI controllers
@ 2026-11-01 20:25 Drew Fustini
2026-11-01 20:25 ` [PATCH v7 1/6] riscv: implement Ssqosid extension and srmcfg CSR Drew Fustini
` (5 more replies)
0 siblings, 6 replies; 10+ messages in thread
From: Drew Fustini @ 2026-11-01 20:25 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, Palmer Dabbelt, Alistair Francis, Sunil V L,
Anup Patel, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
Chao Liu, Paolo Bonzini, Nicolas Pitre, Kornel Dulęba,
Atish Patra, Radim Krčmář, yunhui cui, Chen Pei,
guo.wenjia23, liu.qingtao2, Drew Fustini, Daniel Henrique Barboza
This series implements the RISC-V Quality-of-Service Identifiers
(Ssqosid) extension [1] which adds the srmcfg register. It also
implements the RISC-V Capacity and Bandwidth QoS Register Interface
(CBQRI) specification [2]. Quality of Service (QoS) in this context is
concerned with shared resources on an SoC such as cache capacity and
memory bandwidth.
Ssqosid srmcfg CSR
------------------
The srmcfg CSR configures a hart with two identifiers:
- Resource Control ID (RCID)
- Monitoring Counter ID (MCID)
These identifiers accompany each request issued by the hart to shared
resource controllers. This allows the capacity and bandwidth resources
used by a software workload (e.g. a process or a set of processes) to be
controlled and monitored.
CBQRI controllers
-----------------
CBQRI defines operations to configure resource usage limits, in the form
of capacity or bandwidth, for an RCID. CBQRI also defines operations to
configure counters to track the resource utilization per MCID.
This series implements a CBQRI capacity controller and a CBQRI
bandwidth controller which can be configured from the command line:
$ qemu-system-riscv64 -M virt ... \
-device riscv.cbqri.capacity,mmio_base=0x04820000[,...] \
-device riscv.cbqri.bandwidth,mmio_base=0x04829000[,...]
The mmio_base option is mandatory. The others are optional.
As many -device arguments as wanted can be provided as long as their
mmio regions don't conflict.
To see all possible options:
$ qemu-system-riscv64 -device riscv.cbqri.capacity,help
riscv.cbqri.capacity options:
alloc_op_config_limit=<bool> - on/off (default: on)
alloc_op_flush_rcid=<bool> - on/off (default: on)
alloc_op_read_limit=<bool> - on/off (default: on)
at_code=<bool> - on/off (default: on)
max_mcids=<uint16> - (default: 256)
max_rcids=<uint16> - (default: 64)
mmio_base=<uint64> - (default: 0)
mon_evt_id_none=<bool> - on/off (default: on)
mon_evt_id_occupancy=<bool> - on/off (default: on)
mon_op_config_event=<bool> - on/off (default: on)
mon_op_read_counter=<bool> - on/off (default: on)
ncblks=<uint16> - (default: 16)
$ qemu-system-riscv64 -device riscv.cbqri.bandwidth,help
riscv.cbqri.bandwidth options:
alloc_op_config_limit=<bool> - on/off (default: on)
alloc_op_read_limit=<bool> - on/off (default: on)
at_code=<bool> - on/off (default: on)
max_mcids=<uint16> - (default: 256)
max_rcids=<uint16> - (default: 64)
mmio_base=<uint64> - (default: 0)
mon_evt_id_none=<bool> - on/off (default: on)
mon_evt_id_rdonly_count=<bool> - on/off (default: on)
mon_evt_id_rdwr_count=<bool> - on/off (default: on)
mon_evt_id_wronly_count=<bool> - on/off (default: on)
mon_op_config_event=<bool> - on/off (default: on)
mon_op_read_counter=<bool> - on/off (default: on)
mrbwb=<uint16> - (default: 819)
nbwblks=<uint16> - (default: 1024)
Boolean options correspond to hardware capabilities that can be
disabled.
Example SoC for CBQRI
---------------------
An example SoC with the following CBQRI controller configuration can be
used to test the implementation:
- L2 cache controllers
- Resource type: Capacity
- Number of capacity blocks (NCBLKS): 12
- In the context of a set-associative cache, the number of
capacity blocks can be thought of as the number of ways
- Number of access types: 2 (code and data)
- Usage monitoring not supported
- Capacity allocation operations: CONFIG_LIMIT, READ_LIMIT
- Last-level cache (LLC) controller
- Resource type: Capacity
- Number of capacity blocks (NCBLKS): 16
- Number of access types: 2 (code and data)
- Usage monitoring operations: CONFIG_EVENT, READ_COUNTER
- Event IDs supported: None, Occupancy
- Capacity allocation ops: CONFIG_LIMIT, READ_LIMIT, FLUSH_RCID
- Memory controllers
- Resource type: Bandwidth
- Number of bandwidth blocks (NBWBLKS): 1024
- Bandwidth blocks do not have a unit but instead represent a
portion of the total bandwidth resource. For NBWBLKS of 1024,
each block represents about 0.1% of the bandwidth resource.
- Maximum reserved bandwidth blocks (MRBWB): 819 [80% of NBWBLKS]
- Number of access types: 2 (code and data)
- Usage monitoring operations: CONFIG_EVENT, READ_COUNTER
- Event IDs supported: None, Total read/write byte count, Total
read byte count, Total write byte count
- Bandwidth allocation operations: CONFIG_LIMIT, READ_LIMIT
The memory map used for the example SoC:
Base addr Size
0x4820000 4KB Cluster 0 L2 cache controller
0x4821000 4KB Cluster 1 L2 cache controller
0x4828000 4KB Memory controller 0
0x4829000 4KB Memory controller 1
0x482a000 4KB Memory controller 2
0x482b000 4KB Shared LLC cache controller
This configuration is meant to provide a "concrete" example for software
(like Linux) to test against. It represents just one of many possible
ways for hardware to implement the CBQRI spec.
The example SoC configuration is created with the following:
qemu-system-riscv64 \
-M virt \
-nographic \
-smp 8 \
-device riscv.cbqri.capacity,max_mcids=256,max_rcids=64,ncblks=12,alloc_op_flush_rcid=false,mon_op_config_event=false,mon_op_read_counter=false,mon_evt_id_none=false,mon_evt_id_occupancy=false,mmio_base=0x04820000 \
-device riscv.cbqri.capacity,max_mcids=256,max_rcids=64,ncblks=12,alloc_op_flush_rcid=false,mon_op_config_event=false,mon_op_read_counter=false,mon_evt_id_none=false,mon_evt_id_occupancy=false,mmio_base=0x04821000 \
-device riscv.cbqri.capacity,max_mcids=256,max_rcids=64,ncblks=16,mmio_base=0x0482b000 \
-device riscv.cbqri.bandwidth,max_mcids=256,max_rcids=64,nbwblks=1024,mrbwb=819,mmio_base=0x04828000 \
-device riscv.cbqri.bandwidth,max_mcids=256,max_rcids=64,nbwblks=1024,mrbwb=819,mmio_base=0x04829000 \
-device riscv.cbqri.bandwidth,max_mcids=256,max_rcids=64,nbwblks=1024,mrbwb=819,mmio_base=0x0482a000
The virt machine does not describe the controllers to the guest. No
device tree node or ACPI table is generated for them, so a dtb with the
controller nodes must be passed with -dtb. I plan to add device tree
generation and ACPI RQSC support [7] as follow-up series.
In addition, please note that this series only implements the register
interface that CBQRI specifies. It does not attempt to emulate the
performance impact of configuring limits on shared resources like cache
and memory bandwidth. Similarly, the code does not attempt to emulate
cache and memory bandwidth utilization, like what would be observed on a
real hardware system implementing CBQRI.
Linux support for the srmcfg CSR from the Ssqosid extension has been
upstream since v7.3. There is a Linux kernel patch series [3] which adds
CBQRI cache allocation with resctrl integration. It is also available as
a branch named cbqri-cache-alloc-dt [4]. That series only supports
capacity controllers. Linux support for bandwidth controllers was last
posted as an RFC [5], and I plan to rebase it on the resctrl generic
schema v2 proof-of-concept [6].
[1] https://github.com/riscv/riscv-ssqosid/releases/tag/v1.0
[2] https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
[3] https://lore.kernel.org/all/20261003-dfustini-atl-sc-cbqri-dt-v9-0-52fd8be98dcf@kernel.org/
[4] https://git.kernel.org/pub/scm/linux/kernel/git/fustini/linux.git/log/?h=cbqri-cache-alloc-dt
[5] https://lore.kernel.org/all/20260601-ssqosid-cbqri-rqsc-v7-0-v6-0-baf00f50028a@kernel.org/
[6] https://lore.kernel.org/all/d258a32f-12d5-464d-abe9-4720fb3e44b3@intel.com/
[7] https://lore.kernel.org/all/20260202-riscv-rqsc-v1-0-dcf448a3ed73@kernel.org/
Changes since v6:
- Rebase on riscv-to-apply.next
- Add SMSTATEEN0_SRMCFG to the mstateen0 and mstateen0h write masks so
that M-mode can enable S-mode access to srmcfg.
- Remove the at_data property because the spec says that unsupported AT
values behave as AT 0.
- Add vmstate for srmcfg
- Mark both controllers unmigratable
- Remove the unused target property and unused bc_bw_alloc field
- Use an integer helper to size the bandwidth allocation table instead
of pointer arithmetic on NULL.
- Move MonitorCounter to cbqri.h
- Add note to the cover letter about plans for device tree generation
- Link to v6: https://lore.kernel.org/qemu-devel/20261026-riscv-ssqosid-cbqri-v6-0-1a367a35cd0e@kernel.org
Changes since v5:
- Rebase on riscv-to-apply.next
- Report version 1.0 in cc_capabilities and bc_capabilities
- Use MAKE_64BIT_MASK() and QEMU_ALIGN_DOWN() instead of UL constants
- Check that the sum of Rbwb across all RCIDs doesn't exceed MRBWB
- Set the mrbwb property in riscv_cbqri_bc_create()
- Add comments that the counter is marked INVALID
- Link to v5: https://lore.kernel.org/qemu-devel/20260201-riscv-ssqosid-cbqri-v5-0-273ea4a21703@kernel.org
Changes since v4:
- Add read and write wrappers to support 32-bit access size as the spec
states that "CBQRI registers are defined so that software can perform
two individual 4 byte accesses". Thanks to Radim for suggesting this
solution.
- Link to v4: https://lore.kernel.org/qemu-devel/20260105-riscv-ssqosid-cbqri-v4-0-9ad7671dde78@kernel.org
Changes since v3:
- NOTE: I should have used version v3 for the previous version posted
on Nov 19, 2025 given there was already v1/v2 back in 2023. Therefore
I am using v4 in this new revision.
- Rebase on current master as of Dec 29 (942b0d378a1d) and update
include paths for qdev-properties.h and sysbus.h
- Add Rb tags from Daniel
- Squash the Kconfig and meson.build patches together per Daniel
- Use riscv_cpu_cfg() instead of env_archcpu() in check_srmcfg()
- Add check for mstateen0.SRMCFG in check_srmcfg()
- Increase ISA_EXT_DATA_ENTRY() for ssqosid from PRIV_VERSION_1_12_0
to PRIV_VERSION_1_13_0 as it was added in Privileged Arch 1.13
- Calculate the size of the capacity controller MMIO space based on
on cc->ncblks and roundup to be aligned to the 4KB page boundary.
- Remove rpfx, p and cunits properties that were added in the previous
revision as I have decided to not support those optional features in
this capacity controller implementation.
- Update riscv_cbqri_cc_read to set 0 for RPFX, P and CUNITS in
cc_capabilities. This indicates that this capacity controller
implementation does not support RCID-prefixed mode and nor does it
support capacity unit limits.
- Add cc_cunits register after cc_block_mask register in the
alloc_blockmasks array and adjust logic in get_blockmask_offset,
riscv_cbqri_cc_read, riscv_cbqri_cc_write. Since capacity units is
not supported, the cc_cunits register will always be 0.
- Only assign capacity for rcid 0 in riscv_cbqri_cc_reset and do not
touch allocation for other rcid values as they are unspecified
following a reset.
- Change CC_ALLOC_OP_FLUSH_RCID to be a no-op instead of calling
alloc_blockmask_init() as the spec states that the configured
capacity block allocation or the capacity unit limit is not
changed by the flush operation.
- Add comment to explain that, while the spec only requires the busy
field to be reset to 0, the entire contents of the cc->cc_mon_ctl
and cc->cc_alloc_ctl registers are set to 0 simplify the code in
riscv_cbqri_cc_reset().
- Simplify the access type logic for blockmask initialization in
riscv_cbqri_cc_reset().
- Add static inline helpers get_bmw() and get_slots().
- Add check to bandwidth_config() that rbwb is not greater than mrbwb.
- Link to v3: https://lore.kernel.org/qemu-devel/20251119-riscv-ssqosid-cbqri-v1-0-3392fc760e48@kernel.org
Changes since v2:
- NOTE: I should have used version v3 for this version posted on Nov 19
as there were already v1 and v2 back in 2023.
- Rebase on master which is currently at version v10.1.50
- Add fields that were not in the draft used for the proof of concept
and introduced in final CBQRI 1.0 spec: capacity units (cunits) and
RCID-prefixed mode (RPFX) along with parameter P (prefixed bits)
- Fix check_srmcfg() to check if virtualization is enabled, and if so,
return virt instruction fault, otherwise return smode()
- Fix indentation in read_srmcfg()
- Add SPDX headers
- Add MAINTAINERS entries
- Link to v2: https://lore.kernel.org/qemu-devel/20230425203834.1135306-1-dfustini@baylibre.com/
Changes since v1:
- Rebase on current master (8.0.50) instead of 8.0.0-rc4
- Configure CBQRI controllers based on device properties in command
line arguments instead of a fixed example SoC configuration.
- Move TYPE_RISCV_CBQRI_BC and TYPE_RISCV_CBQRI_CC into header so that
machines may use it (suggested by Alistair).
- Change 'select RISC_CBQRI' to 'imply RISCV_CBQRI' for RISCV_VIRT.
- Patches 8/9 could be dropped as they are not needed for the CBQRI
proof-of-concept to work. They are only meant to serve as an example
for those implementing new machines.
- Link to v1: https://lore.kernel.org/qemu-devel/20230416232050.4094820-1-dfustini@baylibre.com/
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
Kornel Dulęba (1):
riscv: implement Ssqosid extension and srmcfg CSR
Nicolas Pitre (5):
hw/riscv: define capabilities of CBQRI controllers
hw/riscv: implement CBQRI capacity controller
hw/riscv: implement CBQRI bandwidth controller
hw/riscv: add CBQRI to Kconfig and build if enabled
hw/riscv: add CBQRI controllers to virt machine
MAINTAINERS | 9 +
disas/riscv.c | 1 +
hw/core/sysbus-fdt.c | 4 +
hw/riscv/Kconfig | 4 +
hw/riscv/cbqri_bandwidth.c | 650 ++++++++++++++++++++++++++++++++++
hw/riscv/cbqri_capacity.c | 715 ++++++++++++++++++++++++++++++++++++++
hw/riscv/meson.build | 2 +
hw/riscv/virt.c | 3 +
include/hw/riscv/cbqri.h | 74 ++++
target/riscv/cpu.c | 3 +
target/riscv/cpu.h | 3 +
target/riscv/cpu_bits.h | 9 +
target/riscv/cpu_cfg_fields.h.inc | 1 +
target/riscv/machine.c | 19 +
target/riscv/tcg/csr.c | 45 +++
15 files changed, 1542 insertions(+)
---
base-commit: bf67d56f282c1ee25b67dfda3f8ca54d2bd4648e
change-id: 20251229-riscv-ssqosid-cbqri-aee2271532ff
Best regards,
--
Drew Fustini <fustini@kernel.org>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v7 1/6] riscv: implement Ssqosid extension and srmcfg CSR
2026-11-01 20:25 [PATCH v7 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
@ 2026-11-01 20:25 ` Drew Fustini
2026-11-01 20:25 ` [PATCH v7 2/6] hw/riscv: define capabilities of CBQRI controllers Drew Fustini
` (4 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Drew Fustini @ 2026-11-01 20:25 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, Palmer Dabbelt, Alistair Francis, Sunil V L,
Anup Patel, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
Chao Liu, Paolo Bonzini, Nicolas Pitre, Kornel Dulęba,
Atish Patra, Radim Krčmář, yunhui cui, Chen Pei,
guo.wenjia23, liu.qingtao2, Drew Fustini
From: Kornel Dulęba <mindal@semihalf.com>
Implement the srmcfg CSR defined by the Ssqosid ISA extension
(Supervisor-mode Quality of Service ID). The CSR contains two fields:
- Resource Control ID (RCID) used to determine resource allocation
- Monitoring Counter ID (MCID) used to track resource usage
The CSR is defined for S-mode, so check mstateen0.srmcfg to determine
if S-mode is allowed to access it. In addition, accessing it when V=1
shall cause a virtual instruction exception.
Link: https://github.com/riscv-non-isa/riscv-cbqri/releases/download/v1.0/riscv-cbqri.pdf
Link: https://docs.riscv.org/reference/isa/priv/smstateen.html
Signed-off-by: Kornel Dulęba <mindal@semihalf.com>
[fustini: rebase, fix check_srmcfg]
Reviewed-by: Radim Krčmář <radim.krcmar@oss.qualcomm.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
disas/riscv.c | 1 +
target/riscv/cpu.c | 3 +++
target/riscv/cpu.h | 3 +++
target/riscv/cpu_bits.h | 9 ++++++++
target/riscv/cpu_cfg_fields.h.inc | 1 +
target/riscv/machine.c | 19 +++++++++++++++++
target/riscv/tcg/csr.c | 45 +++++++++++++++++++++++++++++++++++++++
7 files changed, 81 insertions(+)
diff --git a/disas/riscv.c b/disas/riscv.c
index ca0d6e90b373..bce5f58305dd 100644
--- a/disas/riscv.c
+++ b/disas/riscv.c
@@ -703,6 +703,7 @@ static const char *csr_name(int csrno)
case 0x0143: return "stval";
case 0x0144: return "sip";
case 0x0180: return "satp";
+ case 0x0181: return "srmcfg";
case 0x0200: return "hstatus";
case 0x0202: return "hedeleg";
case 0x0203: return "hideleg";
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index 5a0529fbdade..3432fea38ba0 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -284,6 +284,7 @@ const RISCVIsaExtData isa_edata_arr[] = {
ISA_EXT_DATA_ENTRY(ssdbltrp, PRIV_VERSION_1_13_0, ext_ssdbltrp),
ISA_EXT_DATA_ENTRY(ssnpm, PRIV_VERSION_1_13_0, ext_ssnpm),
ISA_EXT_DATA_ENTRY(sspm, PRIV_VERSION_1_13_0, ext_sspm),
+ ISA_EXT_DATA_ENTRY(ssqosid, PRIV_VERSION_1_13_0, ext_ssqosid),
ISA_INTERNAL_EXT_DATA_ENTRY(ssstateen, PRIV_VERSION_1_12_0, ext_ssstateen),
ISA_INTERNAL_EXT_DATA_ENTRY(ssstrict, PRIV_VERSION_1_12_0, has_priv_1_12),
ISA_EXT_DATA_ENTRY(sstc, PRIV_VERSION_1_12_0, ext_sstc),
@@ -3504,6 +3505,7 @@ static const TypeInfo riscv_cpu_type_infos[] = {
.cfg.ext_zbc = true,
.cfg.ext_zbs = true,
.cfg.ext_sstc = true,
+ .cfg.ext_ssqosid = true,
.cfg.ext_svadu = true,
.cfg.ext_svvptc = true,
),
@@ -3577,6 +3579,7 @@ static const TypeInfo riscv_cpu_type_infos[] = {
.cfg.ext_zbc = true,
.cfg.ext_zbs = true,
.cfg.ext_sstc = true,
+ .cfg.ext_ssqosid = true,
.cfg.ext_svadu = true,
.cfg.ext_svvptc = true,
),
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 49a1186bdbb2..0dfb280d1f33 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -369,6 +369,9 @@ struct CPUArchState {
uint64_t ctr_dst[16 << SCTRDEPTH_MAX];
uint64_t ctr_data[16 << SCTRDEPTH_MAX];
+ /* Ssqosid extension */
+ target_ulong srmcfg;
+
/* Machine and Supervisor interrupt priorities */
uint8_t miprio[64];
uint8_t siprio[64];
diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h
index 3579789a1f56..33af64f3667e 100644
--- a/target/riscv/cpu_bits.h
+++ b/target/riscv/cpu_bits.h
@@ -228,6 +228,9 @@
#define CSR_SPTBR 0x180
#define CSR_SATP 0x180
+/* Ssqosid extension */
+#define CSR_SRMCFG 0x181
+
/* Supervisor-Level Window to Indirectly Accessed Registers (AIA) */
#define CSR_SISELECT 0x150
#define CSR_SIREG 0x151
@@ -356,6 +359,7 @@
#define SMSTATEEN0_FCSR (1ULL << 1)
#define SMSTATEEN0_JVT (1ULL << 2)
#define SMSTATEEN0_CTR (1ULL << 54)
+#define SMSTATEEN0_SRMCFG (1ULL << 55)
#define SMSTATEEN0_P1P13 (1ULL << 56)
#define SMSTATEEN0_HSCONTXT (1ULL << 57)
#define SMSTATEEN0_IMSIC (1ULL << 58)
@@ -1199,4 +1203,9 @@ typedef enum CTRType {
#define MCONTEXT64 0x0000000000001FFFULL
#define MCONTEXT32_HCONTEXT 0x0000007F
#define MCONTEXT64_HCONTEXT 0x0000000000003FFFULL
+
+/* SRMCFG CSR field masks (Ssqosid extension) */
+#define SRMCFG_RCID 0x00000FFF
+#define SRMCFG_MCID 0x0FFF0000
+
#endif
diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc
index 85fc3ad8bb8c..1ecdf9f393b4 100644
--- a/target/riscv/cpu_cfg_fields.h.inc
+++ b/target/riscv/cpu_cfg_fields.h.inc
@@ -113,6 +113,7 @@ BOOL_FIELD(ext_ssaia)
BOOL_FIELD(ext_smctr)
BOOL_FIELD(ext_ssctr)
BOOL_FIELD(ext_sscofpmf)
+BOOL_FIELD(ext_ssqosid)
BOOL_FIELD(ext_smepmp)
BOOL_FIELD(ext_smrnmi)
BOOL_FIELD(ext_ssnpm)
diff --git a/target/riscv/machine.c b/target/riscv/machine.c
index 0de06e958809..fa99c68fc852 100644
--- a/target/riscv/machine.c
+++ b/target/riscv/machine.c
@@ -382,6 +382,24 @@ static const VMStateDescription vmstate_sdext = {
}
};
+static bool ssqosid_needed(void *opaque)
+{
+ RISCVCPU *cpu = opaque;
+
+ return cpu->cfg.ext_ssqosid;
+}
+
+static const VMStateDescription vmstate_ssqosid = {
+ .name = "cpu/ssqosid",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ssqosid_needed,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINTTL(env.srmcfg, RISCVCPU),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static int riscv_cpu_post_load(void *opaque, int version_id)
{
RISCVCPU *cpu = opaque;
@@ -656,6 +674,7 @@ const VMStateDescription vmstate_riscv_cpu = {
&vmstate_sstc,
&vmstate_mseccfg,
&vmstate_sdext,
+ &vmstate_ssqosid,
NULL
}
};
diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
index af444e3e6f32..cf8c8d924835 100644
--- a/target/riscv/tcg/csr.c
+++ b/target/riscv/tcg/csr.c
@@ -1805,6 +1805,40 @@ static RISCVException write_stimecmph(CPURISCVState *env, int csrno,
return RISCV_EXCP_NONE;
}
+static RISCVException check_srmcfg(CPURISCVState *env, int csrno)
+{
+ if (!riscv_cpu_cfg(env)->ext_ssqosid) {
+ return RISCV_EXCP_ILLEGAL_INST;
+ }
+
+ RISCVException ret = smstateen_acc_ok(env, 0, SMSTATEEN0_SRMCFG);
+ if (ret != RISCV_EXCP_NONE) {
+ return ret;
+ }
+
+ /*
+ * Even though this is an S-mode CSR the spec says that we need to throw
+ * a virtual instruction fault if a guest tries to access it.
+ */
+ return env->virt_enabled ?
+ RISCV_EXCP_VIRT_INSTRUCTION_FAULT : smode(env, csrno);
+}
+
+static RISCVException read_srmcfg(CPURISCVState *env, int csrno,
+ target_ulong *val)
+{
+ *val = env->srmcfg;
+ return RISCV_EXCP_NONE;
+}
+
+static RISCVException write_srmcfg(CPURISCVState *env, int csrno,
+ target_ulong val, uintptr_t ra)
+{
+ env->srmcfg = val & (SRMCFG_RCID | SRMCFG_MCID);
+ return RISCV_EXCP_NONE;
+}
+
+
#define VSTOPI_NUM_SRCS 5
/*
@@ -3738,6 +3772,10 @@ static RISCVException write_mstateen0(CPURISCVState *env, int csrno,
if (riscv_cpu_cfg(env)->ext_ssctr) {
wr_mask |= SMSTATEEN0_CTR;
}
+
+ if (riscv_cpu_cfg(env)->ext_ssqosid) {
+ wr_mask |= SMSTATEEN0_SRMCFG;
+ }
}
return write_mstateen(env, csrno, wr_mask, new_val);
@@ -3797,6 +3835,10 @@ static RISCVException write_mstateen0h(CPURISCVState *env, int csrno,
wr_mask |= SMSTATEEN0_CTR;
}
+ if (riscv_cpu_cfg(env)->ext_ssqosid) {
+ wr_mask |= SMSTATEEN0_SRMCFG;
+ }
+
return write_mstateenh(env, csrno, wr_mask, new_val);
}
@@ -6377,6 +6419,9 @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = {
/* Supervisor Protection and Translation */
[CSR_SATP] = { "satp", satp, read_satp, write_satp },
+ /* Supervisor-Level Quality-of-Service Identifiers (Ssqosid) */
+ [CSR_SRMCFG] = { "srmcfg", check_srmcfg, read_srmcfg, write_srmcfg },
+
/* Supervisor-Level Window to Indirectly Accessed Registers (AIA) */
[CSR_SISELECT] = { "siselect", csrind_or_aia_smode, NULL, NULL,
rmw_xiselect },
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 2/6] hw/riscv: define capabilities of CBQRI controllers
2026-11-01 20:25 [PATCH v7 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
2026-11-01 20:25 ` [PATCH v7 1/6] riscv: implement Ssqosid extension and srmcfg CSR Drew Fustini
@ 2026-11-01 20:25 ` Drew Fustini
2026-11-01 20:25 ` [PATCH v7 3/6] hw/riscv: implement CBQRI capacity controller Drew Fustini
` (3 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Drew Fustini @ 2026-11-01 20:25 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, Palmer Dabbelt, Alistair Francis, Sunil V L,
Anup Patel, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
Chao Liu, Paolo Bonzini, Nicolas Pitre, Kornel Dulęba,
Atish Patra, Radim Krčmář, yunhui cui, Chen Pei,
guo.wenjia23, liu.qingtao2, Drew Fustini, Daniel Henrique Barboza
From: Nicolas Pitre <npitre@baylibre.com>
Define structs to represent the hardware capabilities of capacity and
bandwidth controllers according to the RISC-V Capacity and Bandwidth QoS
Register Interface (CBQRI).
Link: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
Reviewed-by: Radim Krčmář <radim.krcmar@oss.qualcomm.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
MAINTAINERS | 7 +++++
include/hw/riscv/cbqri.h | 66 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 73 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 4ca1751e30f5..2984779ce43a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -403,6 +403,13 @@ F: target/riscv/XVentanaCondOps.decode
F: target/riscv/insn_trans/trans_xventanacondops.c.inc
F: disas/riscv-xventana*
+RISC-V QoS (Ssqosid ext and CBQRI spec)
+M: Nicolas Pitre <npitre@baylibre.com>
+M: Drew Fustini <fustini@kernel.org>
+L: qemu-riscv@nongnu.org
+S: Supported
+F: include/hw/riscv/cbqri.h
+
RISC-V SiFive (Xsf*) extensions
M: Max Chou <max.chou@sifive.com>
L: qemu-riscv@nongnu.org
diff --git a/include/hw/riscv/cbqri.h b/include/hw/riscv/cbqri.h
new file mode 100644
index 000000000000..1b3d85973717
--- /dev/null
+++ b/include/hw/riscv/cbqri.h
@@ -0,0 +1,66 @@
+/*
+ * RISC-V Capacity and Bandwidth QoS Register Interface
+ * URL: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
+ *
+ * Copyright (c) 2023 BayLibre SAS
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_RISCV_CBQRI_H
+#define HW_RISCV_CBQRI_H
+
+#include "qemu/typedefs.h"
+
+#define RISCV_CBQRI_VERSION_MAJOR 1
+#define RISCV_CBQRI_VERSION_MINOR 0
+
+#define TYPE_RISCV_CBQRI_CC "riscv.cbqri.capacity"
+#define TYPE_RISCV_CBQRI_BC "riscv.cbqri.bandwidth"
+
+/* Capacity Controller hardware capabilities */
+typedef struct RiscvCbqriCapacityCaps {
+ uint16_t nb_mcids;
+ uint16_t nb_rcids;
+
+ uint16_t ncblks;
+
+ bool supports_at_code:1;
+
+ bool supports_alloc_op_config_limit:1;
+ bool supports_alloc_op_read_limit:1;
+ bool supports_alloc_op_flush_rcid:1;
+
+ bool supports_mon_op_config_event:1;
+ bool supports_mon_op_read_counter:1;
+
+ bool supports_mon_evt_id_none:1;
+ bool supports_mon_evt_id_occupancy:1;
+} RiscvCbqriCapacityCaps;
+
+/* Bandwidth Controller hardware capabilities */
+typedef struct RiscvCbqriBandwidthCaps {
+ uint16_t nb_mcids;
+ uint16_t nb_rcids;
+
+ uint16_t nbwblks;
+ uint16_t mrbwb;
+
+ bool supports_at_code:1;
+
+ bool supports_alloc_op_config_limit:1;
+ bool supports_alloc_op_read_limit:1;
+
+ bool supports_mon_op_config_event:1;
+ bool supports_mon_op_read_counter:1;
+
+ bool supports_mon_evt_id_none:1;
+ bool supports_mon_evt_id_rdwr_count:1;
+ bool supports_mon_evt_id_rdonly_count:1;
+ bool supports_mon_evt_id_wronly_count:1;
+} RiscvCbqriBandwidthCaps;
+
+DeviceState *riscv_cbqri_cc_create(hwaddr addr,
+ const RiscvCbqriCapacityCaps *caps);
+DeviceState *riscv_cbqri_bc_create(hwaddr addr,
+ const RiscvCbqriBandwidthCaps *caps);
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 3/6] hw/riscv: implement CBQRI capacity controller
2026-11-01 20:25 [PATCH v7 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
2026-11-01 20:25 ` [PATCH v7 1/6] riscv: implement Ssqosid extension and srmcfg CSR Drew Fustini
2026-11-01 20:25 ` [PATCH v7 2/6] hw/riscv: define capabilities of CBQRI controllers Drew Fustini
@ 2026-11-01 20:25 ` Drew Fustini
2026-11-01 20:25 ` [PATCH v7 4/6] hw/riscv: implement CBQRI bandwidth controller Drew Fustini
` (2 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Drew Fustini @ 2026-11-01 20:25 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, Palmer Dabbelt, Alistair Francis, Sunil V L,
Anup Patel, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
Chao Liu, Paolo Bonzini, Nicolas Pitre, Kornel Dulęba,
Atish Patra, Radim Krčmář, yunhui cui, Chen Pei,
guo.wenjia23, liu.qingtao2, Drew Fustini
From: Nicolas Pitre <npitre@baylibre.com>
Implement a capacity controller according to the Capacity and Bandwidth
QoS Register Interface (CBQRI) which supports these capabilities:
- Number of access types: 2 (code and data)
- Usage monitoring operations: CONFIG_EVENT, READ_COUNTER
- Event IDs supported: None, Occupancy
- Capacity allocation ops: CONFIG_LIMIT, READ_LIMIT, FLUSH_RCID
Link: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
[fustini: add fields introduced in the ratified spec: cunits, rpfx, p]
Reviewed-by: Radim Krčmář <radim.krcmar@oss.qualcomm.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
MAINTAINERS | 1 +
hw/riscv/cbqri_capacity.c | 715 ++++++++++++++++++++++++++++++++++++++++++++++
include/hw/riscv/cbqri.h | 8 +
3 files changed, 724 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 2984779ce43a..f230cdbbe1b8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -408,6 +408,7 @@ M: Nicolas Pitre <npitre@baylibre.com>
M: Drew Fustini <fustini@kernel.org>
L: qemu-riscv@nongnu.org
S: Supported
+F: hw/riscv/cbqri_capacity.c
F: include/hw/riscv/cbqri.h
RISC-V SiFive (Xsf*) extensions
diff --git a/hw/riscv/cbqri_capacity.c b/hw/riscv/cbqri_capacity.c
new file mode 100644
index 000000000000..757cd5e66f13
--- /dev/null
+++ b/hw/riscv/cbqri_capacity.c
@@ -0,0 +1,715 @@
+/*
+ * RISC-V Capacity and Bandwidth QoS Register Interface
+ * URL: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
+ *
+ * Copyright (c) 2023 BayLibre SAS
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This file contains the Capacity-controller QoS Register Interface.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/bitmap.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/sysbus.h"
+#include "migration/vmstate.h"
+#include "target/riscv/cpu.h"
+#include "hw/riscv/cbqri.h"
+
+/* Encodings of `AT` field */
+enum {
+ CC_AT_DATA = 0,
+ CC_AT_CODE = 1,
+ CC_NB_ATS,
+};
+
+/* Capabilities */
+REG64(CC_CAPABILITIES, 0);
+FIELD(CC_CAPABILITIES, VER, 0, 8);
+FIELD(CC_CAPABILITIES, VER_MINOR, 0, 4);
+FIELD(CC_CAPABILITIES, VER_MAJOR, 4, 4);
+FIELD(CC_CAPABILITIES, NCBLKS, 8, 16);
+FIELD(CC_CAPABILITIES, FRCID, 24, 1);
+FIELD(CC_CAPABILITIES, CUNITS, 25, 1);
+FIELD(CC_CAPABILITIES, RPFX, 26, 1);
+FIELD(CC_CAPABILITIES, P, 27, 4);
+
+/* Usage monitoring control */
+REG64(CC_MON_CTL, 8);
+FIELD(CC_MON_CTL, OP, 0, 5);
+FIELD(CC_MON_CTL, AT, 5, 3);
+FIELD(CC_MON_CTL, MCID, 8, 12);
+FIELD(CC_MON_CTL, EVT_ID, 20, 8);
+FIELD(CC_MON_CTL, ATV, 28, 1);
+FIELD(CC_MON_CTL, STATUS, 32, 7);
+FIELD(CC_MON_CTL, BUSY, 39, 1);
+
+/* Usage monitoring operations */
+enum {
+ CC_MON_OP_CONFIG_EVENT = 1,
+ CC_MON_OP_READ_COUNTER = 2,
+};
+
+/* Usage monitoring event ID */
+enum {
+ CC_EVT_ID_None = 0,
+ CC_EVT_ID_Occupancy = 1,
+};
+
+/* CC_MON_CTL.STATUS field encodings */
+enum {
+ CC_MON_CTL_STATUS_SUCCESS = 1,
+ CC_MON_CTL_STATUS_INVAL_OP = 2,
+ CC_MON_CTL_STATUS_INVAL_MCID = 3,
+ CC_MON_CTL_STATUS_INVAL_EVT_ID = 4,
+ CC_MON_CTL_STATUS_INVAL_AT = 5,
+};
+
+/* Monitoring counter value */
+REG64(CC_MON_CTR_VAL, 16);
+FIELD(CC_MON_CTR_VAL, CTR, 0, 63);
+FIELD(CC_MON_CTR_VAL, INVALID, 63, 1);
+
+/* Capacity allocation control */
+REG64(CC_ALLOC_CTL, 24);
+FIELD(CC_ALLOC_CTL, OP, 0, 5);
+FIELD(CC_ALLOC_CTL, AT, 5, 3);
+FIELD(CC_ALLOC_CTL, RCID, 8, 12);
+FIELD(CC_ALLOC_CTL, STATUS, 32, 7);
+FIELD(CC_ALLOC_CTL, BUSY, 39, 1);
+
+/* Capacity allocation operations */
+enum {
+ CC_ALLOC_OP_CONFIG_LIMIT = 1,
+ CC_ALLOC_OP_READ_LIMIT = 2,
+ CC_ALLOC_OP_FLUSH_RCID = 3,
+};
+
+/* CC_ALLOC_CTL.STATUS field encodings */
+enum {
+ CC_ALLOC_STATUS_SUCCESS = 1,
+ CC_ALLOC_STATUS_INVAL_OP = 2,
+ CC_ALLOC_STATUS_INVAL_RCID = 3,
+ CC_ALLOC_STATUS_INVAL_AT = 4,
+ CC_ALLOC_STATUS_INVAL_BLKMASK = 5,
+};
+
+REG64(CC_BLOCK_MASK, 32);
+
+
+typedef struct RiscvCbqriCapacityState {
+ SysBusDevice parent_obj;
+ MemoryRegion mmio;
+
+ /* cached value of some registers */
+ uint64_t cc_mon_ctl;
+ uint64_t cc_mon_ctr_val;
+ uint64_t cc_alloc_ctl;
+
+ /* monitoring counters */
+ MonitorCounter *mon_counters;
+
+ /* allocation blockmasks (1st one is the CC_BLOCK_MASK register) */
+ uint64_t *alloc_blockmasks;
+
+ /* properties */
+ uint64_t mmio_base;
+ uint16_t nb_mcids;
+ uint16_t nb_rcids;
+
+ uint16_t ncblks;
+
+ bool supports_at_code;
+
+ bool supports_alloc_op_config_limit;
+ bool supports_alloc_op_read_limit;
+ bool supports_alloc_op_flush_rcid;
+
+ bool supports_mon_op_config_event;
+ bool supports_mon_op_read_counter;
+
+ bool supports_mon_evt_id_none;
+ bool supports_mon_evt_id_occupancy;
+} RiscvCbqriCapacityState;
+
+#define RISCV_CBQRI_CC(obj) \
+ OBJECT_CHECK(RiscvCbqriCapacityState, (obj), TYPE_RISCV_CBQRI_CC)
+
+static inline unsigned int get_bmw(RiscvCbqriCapacityState *cc)
+{
+ unsigned int bmw = ((cc->ncblks + 63) / 64) * 64;
+
+ return bmw;
+}
+
+static inline unsigned int get_slots(RiscvCbqriCapacityState *cc)
+{
+ unsigned int slots = (cc->ncblks + 63) / 64;
+
+ return slots;
+}
+
+static uint64_t get_blockmask_offset(RiscvCbqriCapacityState *cc,
+ uint32_t rcid, uint32_t at)
+{
+ /*
+ * Each blockmask is made of one or more uint64_t "slots".
+ *
+ * The first slot (or set of slots when BMW is greater than 64)
+ * holds the CC_BLOCK_MASK register content.
+ *
+ * The following slot holds the CC_CUNITS register content
+ * which has a fixed size of 8 bytes.
+ *
+ * The remaining slots contain the blockmask for each AT per RCID.
+ *
+ * For example, this would be the layout of the slots for a
+ * controller with NCBLKS of 16. This results in a BMW of 64 so each
+ * blockmask can fit in 1 slot. The first 6 slots would be:
+ *
+ * Slot
+ * [ 0] register: CC_BLOCK_MASK
+ * [ 1] register: CC_CUNITS
+ * [ 2] RCID= 0 AT=0: cc_block_mask
+ * [ 3] RCID= 0 AT=1: cc_block_mask
+ * [ 4] RCID= 1 AT=0: cc_block_mask
+ * [ 5] RCID= 1 AT=1: cc_block_mask
+ *
+ * This would be the layout for NCBLKS of 100. BMW would be 128 so
+ * each blockmask takes 2 slots. The first 11 slots would be:
+ *
+ * Slot
+ * [ 0] register: CC_BLOCK_MASK
+ * [ 1] register: CC_BLOCK_MASK
+ * [ 2] register: CC_CUNITS
+ * [ 3] RCID= 0 AT=0: cc_block_mask
+ * [ 4] RCID= 0 AT=0: cc_block_mask
+ * [ 5] RCID= 0 AT=1: cc_block_mask
+ * [ 6] RCID= 0 AT=1: cc_block_mask
+ * [ 7] RCID= 1 AT=0: cc_block_mask
+ * [ 8] RCID= 1 AT=0: cc_block_mask
+ * [ 9] RCID= 1 AT=1: cc_block_mask
+ * [10] RCID= 1 AT=1: cc_block_mask
+ *
+ * Blockmasks are indexed by the AT encoding, so each RCID has one
+ * blockmask per AT value whether or not the controller supports the
+ * code access type. is_valid_at() rejects an unsupported AT before a
+ * blockmask is selected.
+ */
+ assert(at < CC_NB_ATS);
+
+ unsigned int blockmask_offset;
+ unsigned int blockmask_slots = get_slots(cc);
+ blockmask_offset = blockmask_slots * ((rcid * CC_NB_ATS) + at);
+
+ /*
+ * Add offset for cc_blockmask register in slot 0 to (blockmask_slots-1)
+ */
+ blockmask_offset += blockmask_slots;
+
+ /*
+ * Add offset to account for cc_cunits register which is always 1 slot
+ */
+ blockmask_offset += 1;
+
+ return blockmask_offset;
+}
+
+static uint64_t *get_blockmask_location(RiscvCbqriCapacityState *cc,
+ uint32_t rcid, uint32_t at)
+{
+ assert(cc->alloc_blockmasks != NULL);
+ unsigned int blockmask_offset = get_blockmask_offset(cc, rcid, at);
+ return cc->alloc_blockmasks + blockmask_offset;
+}
+
+static uint32_t alloc_blockmask_config(RiscvCbqriCapacityState *cc,
+ uint32_t rcid, uint32_t at,
+ bool *busy)
+{
+ unsigned int blockmask_slots = get_slots(cc);
+
+ if ((cc->ncblks % 64) != 0) {
+ /* make sure provided mask isn't too large */
+ uint64_t tail = cc->alloc_blockmasks[blockmask_slots - 1];
+ if ((tail >> (cc->ncblks % 64)) != 0) {
+ return CC_ALLOC_STATUS_INVAL_BLKMASK;
+ }
+ }
+
+ /* for now we only preserve the current CC_BLOCK_MASK register content */
+ memcpy(get_blockmask_location(cc, rcid, at),
+ cc->alloc_blockmasks, blockmask_slots * 8);
+ return CC_ALLOC_STATUS_SUCCESS;
+}
+
+static uint32_t alloc_blockmask_read(RiscvCbqriCapacityState *cc,
+ uint32_t rcid, uint32_t at,
+ bool *busy)
+{
+ unsigned int blockmask_slots = get_slots(cc);
+
+ memcpy(cc->alloc_blockmasks,
+ get_blockmask_location(cc, rcid, at),
+ blockmask_slots * 8);
+ return CC_ALLOC_STATUS_SUCCESS;
+}
+
+static uint32_t alloc_blockmask_init(RiscvCbqriCapacityState *cc,
+ uint32_t rcid, uint32_t at, bool set,
+ bool *busy)
+{
+ void *blockmask = get_blockmask_location(cc, rcid, at);
+
+ if (set) {
+ bitmap_fill(blockmask, cc->ncblks);
+ } else {
+ bitmap_zero(blockmask, cc->ncblks);
+ }
+ return CC_ALLOC_STATUS_SUCCESS;
+}
+
+static bool is_valid_at(RiscvCbqriCapacityState *cc, uint32_t at)
+{
+ switch (at) {
+ case CC_AT_DATA:
+ return true;
+ case CC_AT_CODE:
+ return cc->supports_at_code;
+ default:
+ return false;
+ }
+}
+
+static void riscv_cbqri_cc_write_mon_ctl(RiscvCbqriCapacityState *cc,
+ uint64_t value)
+{
+ if (!cc->supports_mon_op_config_event &&
+ !cc->supports_mon_op_read_counter) {
+ /* monitoring not supported: leave mon_ctl set to 0 */
+ return;
+ }
+
+ /* extract writable fields */
+ uint32_t op = FIELD_EX64(value, CC_MON_CTL, OP);
+ uint32_t at = FIELD_EX64(value, CC_MON_CTL, AT);
+ uint32_t mcid = FIELD_EX64(value, CC_MON_CTL, MCID);
+ uint32_t evt_id = FIELD_EX64(value, CC_MON_CTL, EVT_ID);
+ bool atv = FIELD_EX64(value, CC_MON_CTL, ATV);
+
+ /* extract read-only fields */
+ uint32_t status = FIELD_EX64(cc->cc_mon_ctl, CC_MON_CTL, STATUS);
+ bool busy = FIELD_EX64(cc->cc_mon_ctl, CC_MON_CTL, BUSY);
+
+ if (busy) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: busy flag still set, ignored",
+ __func__);
+ return;
+ }
+
+ if (!cc->supports_at_code) {
+ /* AT not supported: hardwire to 0 */
+ at = 0;
+ atv = false;
+ }
+
+ if (mcid >= cc->nb_mcids) {
+ status = CC_MON_CTL_STATUS_INVAL_MCID;
+ } else if (op == CC_MON_OP_CONFIG_EVENT &&
+ cc->supports_mon_op_config_event) {
+ if (evt_id == CC_EVT_ID_None &&
+ cc->supports_mon_evt_id_none) {
+ cc->mon_counters[mcid].active = false;
+ status = CC_MON_CTL_STATUS_SUCCESS;
+ } else if (evt_id == CC_EVT_ID_Occupancy &&
+ cc->supports_mon_evt_id_occupancy) {
+ if (atv && !is_valid_at(cc, at)) {
+ status = CC_MON_CTL_STATUS_INVAL_AT;
+ } else {
+ /*
+ * The model does not account for cache occupancy, so the
+ * counter is marked INVALID and stays that way.
+ */
+ cc->mon_counters[mcid].ctr_val =
+ FIELD_DP64(0, CC_MON_CTR_VAL, INVALID, 1);
+ cc->mon_counters[mcid].evt_id = evt_id;
+ cc->mon_counters[mcid].at = atv ? at : -1;
+ cc->mon_counters[mcid].active = true;
+ status = CC_MON_CTL_STATUS_SUCCESS;
+ }
+ } else {
+ status = CC_MON_CTL_STATUS_INVAL_EVT_ID;
+ }
+ } else if (op == CC_MON_OP_READ_COUNTER &&
+ cc->supports_mon_op_read_counter) {
+ cc->cc_mon_ctr_val = cc->mon_counters[mcid].ctr_val;
+ status = CC_MON_CTL_STATUS_SUCCESS;
+ } else {
+ status = CC_MON_CTL_STATUS_INVAL_OP;
+ }
+
+ /* reconstruct updated register value */
+ value = 0;
+ value = FIELD_DP64(value, CC_MON_CTL, OP, op);
+ value = FIELD_DP64(value, CC_MON_CTL, AT, at);
+ value = FIELD_DP64(value, CC_MON_CTL, MCID, mcid);
+ value = FIELD_DP64(value, CC_MON_CTL, EVT_ID, evt_id);
+ value = FIELD_DP64(value, CC_MON_CTL, ATV, atv);
+ value = FIELD_DP64(value, CC_MON_CTL, STATUS, status);
+ value = FIELD_DP64(value, CC_MON_CTL, BUSY, busy);
+ cc->cc_mon_ctl = value;
+}
+
+static void riscv_cbqri_cc_write_alloc_ctl(RiscvCbqriCapacityState *cc,
+ uint64_t value)
+{
+ if (cc->ncblks == 0 ||
+ (!cc->supports_alloc_op_config_limit &&
+ !cc->supports_alloc_op_read_limit &&
+ !cc->supports_alloc_op_flush_rcid)) {
+ /* capacity allocation not supported: leave alloc_ctl set to 0 */
+ return;
+ }
+
+ /* extract writable fields */
+ uint32_t op = FIELD_EX64(value, CC_ALLOC_CTL, OP);
+ uint32_t at = FIELD_EX64(value, CC_ALLOC_CTL, AT);
+ uint32_t rcid = FIELD_EX64(value, CC_ALLOC_CTL, RCID);
+
+ /* extract read-only fields */
+ uint32_t status = FIELD_EX64(cc->cc_alloc_ctl, CC_ALLOC_CTL, STATUS);
+ bool busy = FIELD_EX64(cc->cc_alloc_ctl, CC_ALLOC_CTL, BUSY);
+
+ if (busy) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: busy flag still set, ignored",
+ __func__);
+ return;
+ }
+
+ bool atv = true;
+ if (!cc->supports_at_code) {
+ /* AT not supported: hardwire to 0 */
+ at = 0;
+ atv = false;
+ }
+
+ if (rcid >= cc->nb_rcids) {
+ status = CC_ALLOC_STATUS_INVAL_RCID;
+ } else if (atv && !is_valid_at(cc, at)) {
+ status = CC_ALLOC_STATUS_INVAL_AT;
+ } else if (op == CC_ALLOC_OP_CONFIG_LIMIT &&
+ cc->supports_alloc_op_config_limit) {
+ status = alloc_blockmask_config(cc, rcid, at, &busy);
+ } else if (op == CC_ALLOC_OP_READ_LIMIT &&
+ cc->supports_alloc_op_read_limit) {
+ status = alloc_blockmask_read(cc, rcid, at, &busy);
+ } else if (op == CC_ALLOC_OP_FLUSH_RCID &&
+ cc->supports_alloc_op_flush_rcid) {
+ /*
+ * The flush operation is not allowed to change the configured
+ * capacity block allocation or the capacity unit limit.
+ */
+ status = CC_ALLOC_STATUS_SUCCESS;
+ } else {
+ status = CC_ALLOC_STATUS_INVAL_OP;
+ }
+
+ /* reconstruct updated register value */
+ value = 0;
+ value = FIELD_DP64(value, CC_ALLOC_CTL, OP, op);
+ value = FIELD_DP64(value, CC_ALLOC_CTL, AT, at);
+ value = FIELD_DP64(value, CC_ALLOC_CTL, RCID, rcid);
+ value = FIELD_DP64(value, CC_ALLOC_CTL, STATUS, status);
+ value = FIELD_DP64(value, CC_ALLOC_CTL, BUSY, busy);
+ cc->cc_alloc_ctl = value;
+}
+
+static void riscv_cbqri_cc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ RiscvCbqriCapacityState *cc = opaque;
+
+ assert((addr % 8) == 0);
+
+ switch (addr) {
+ case A_CC_CAPABILITIES:
+ /* read-only register */
+ break;
+ case A_CC_MON_CTL:
+ riscv_cbqri_cc_write_mon_ctl(cc, value);
+ break;
+ case A_CC_ALLOC_CTL:
+ riscv_cbqri_cc_write_alloc_ctl(cc, value);
+ break;
+ case A_CC_MON_CTR_VAL:
+ /* read-only register */
+ break;
+ case A_CC_BLOCK_MASK:
+ if (cc->ncblks == 0) {
+ break;
+ }
+ /* fallthrough */
+ default:
+ uint32_t blkmask_slot = (addr - A_CC_BLOCK_MASK) / 8;
+ if (blkmask_slot == get_slots(cc)) {
+ /* this is cc_cunits register */
+ break;
+ } else if (blkmask_slot > get_slots(cc)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: out of bounds (addr=0x%x)",
+ __func__, (uint32_t)addr);
+ break;
+ }
+ cc->alloc_blockmasks[blkmask_slot] = value;
+ }
+}
+
+static uint64_t riscv_cbqri_cc_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RiscvCbqriCapacityState *cc = opaque;
+ uint64_t value = 0;
+
+ assert((addr % 8) == 0);
+
+ switch (addr) {
+ case A_CC_CAPABILITIES:
+ value = FIELD_DP64(value, CC_CAPABILITIES, VER_MAJOR,
+ RISCV_CBQRI_VERSION_MAJOR);
+ value = FIELD_DP64(value, CC_CAPABILITIES, VER_MINOR,
+ RISCV_CBQRI_VERSION_MINOR);
+ value = FIELD_DP64(value, CC_CAPABILITIES, NCBLKS,
+ cc->ncblks);
+ value = FIELD_DP64(value, CC_CAPABILITIES, FRCID,
+ cc->supports_alloc_op_flush_rcid);
+ value = FIELD_DP64(value, CC_CAPABILITIES, CUNITS, 0);
+ value = FIELD_DP64(value, CC_CAPABILITIES, RPFX, 0);
+ value = FIELD_DP64(value, CC_CAPABILITIES, P, 0);
+ break;
+ case A_CC_MON_CTL:
+ value = cc->cc_mon_ctl;
+ break;
+ case A_CC_ALLOC_CTL:
+ value = cc->cc_alloc_ctl;
+ break;
+ case A_CC_MON_CTR_VAL:
+ value = cc->cc_mon_ctr_val;
+ break;
+ case A_CC_BLOCK_MASK:
+ if (cc->ncblks == 0) {
+ break;
+ }
+ /* fallthrough */
+ default:
+ unsigned int blkmask_slot = (addr - A_CC_BLOCK_MASK) / 8;
+ if (blkmask_slot == get_slots(cc)) {
+ /*
+ * The cc_cunits register is always the slot following the
+ * last slot of cc_blockmask register. Capacity units are
+ * not supported by this implementation so must return 0.
+ */
+ value = 0;
+ break;
+ } else if (blkmask_slot > get_slots(cc)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: out of bounds (addr=0x%x)",
+ __func__, (uint32_t)addr);
+ break;
+ }
+ value = cc->alloc_blockmasks[blkmask_slot];
+ }
+
+ return value;
+}
+
+static uint64_t riscv_cbqri_cc_read_wrapper(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint64_t value = riscv_cbqri_cc_read(opaque, QEMU_ALIGN_DOWN(addr, 8), 8);
+ if (size == 4) {
+ if (addr & 0x7) {
+ return value >> 32;
+ } else {
+ return value & MAKE_64BIT_MASK(0, 32);
+ }
+ }
+ return value;
+}
+
+static void riscv_cbqri_cc_write_wrapper(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ hwaddr reg_addr = QEMU_ALIGN_DOWN(addr, 8);
+
+ if (size == 4) {
+ uint64_t reg = riscv_cbqri_cc_read(opaque, reg_addr, 8);
+ if (addr & 0x7) {
+ value = value << 32 | (reg & MAKE_64BIT_MASK(0, 32));
+ } else {
+ value = value | (reg & MAKE_64BIT_MASK(32, 32));
+ }
+ }
+ riscv_cbqri_cc_write(opaque, reg_addr, value, 8);
+}
+
+static const MemoryRegionOps riscv_cbqri_cc_ops = {
+ .read = riscv_cbqri_cc_read_wrapper,
+ .write = riscv_cbqri_cc_write_wrapper,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 8,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 8,
+};
+
+static void riscv_cbqri_cc_realize(DeviceState *dev, Error **errp)
+{
+ RiscvCbqriCapacityState *cc = RISCV_CBQRI_CC(dev);
+ unsigned int bmw = get_bmw(cc);
+ /* The size of the CC registers other than bmw is 40 bytes */
+ unsigned int mmio_size = (bmw / 8) + 40;
+
+ if (!cc->mmio_base) {
+ error_setg(errp, "mmio_base property not set");
+ return;
+ }
+
+ assert(cc->mon_counters == NULL);
+ cc->mon_counters = g_new0(MonitorCounter, cc->nb_mcids);
+
+ assert(cc->alloc_blockmasks == NULL);
+ unsigned int blockmasks_size = get_blockmask_offset(cc, cc->nb_rcids, 0);
+ cc->alloc_blockmasks = g_new0(uint64_t, blockmasks_size);
+
+ memory_region_init_io(&cc->mmio, OBJECT(dev), &riscv_cbqri_cc_ops,
+ cc, TYPE_RISCV_CBQRI_CC".mmio", mmio_size);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &cc->mmio);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, cc->mmio_base);
+}
+
+static void riscv_cbqri_cc_reset(DeviceState *dev)
+{
+ RiscvCbqriCapacityState *cc = RISCV_CBQRI_CC(dev);
+
+ /*
+ * The spec requires that the reset value is 0 for the cc_mon_ctl.BUSY
+ * and cc_alloc_ctl.BUSY fields, and that the reset value is UNSPECIFIED
+ * for all other register fields.
+ *
+ * Therefore, it is legal to set the entire contents of cc_mon_ctl and
+ * cc_alloc_ctl to 0.
+ */
+ cc->cc_mon_ctl = 0;
+ cc->cc_alloc_ctl = 0;
+
+ /*
+ * The capacity controllers at reset must allocate all available
+ * capacity to RCID value of 0.
+ *
+ * When the capacity controller supports capacity allocation per
+ * access type, then all available capacity is shared by all the
+ * access types for RCID=0.
+ *
+ * For unsupported AT values the resource controller behaves as
+ * if AT was 0 (CC_AT_DATA).
+ */
+ alloc_blockmask_init(cc, 0, CC_AT_DATA, 1, NULL);
+ if (cc->supports_at_code) {
+ alloc_blockmask_init(cc, 0, CC_AT_CODE, 1, NULL);
+ }
+}
+
+static const Property riscv_cbqri_cc_properties[] = {
+ DEFINE_PROP_UINT64("mmio_base", RiscvCbqriCapacityState, mmio_base, 0),
+
+ DEFINE_PROP_UINT16("max_mcids", RiscvCbqriCapacityState, nb_mcids, 256),
+ DEFINE_PROP_UINT16("max_rcids", RiscvCbqriCapacityState, nb_rcids, 64),
+ DEFINE_PROP_UINT16("ncblks", RiscvCbqriCapacityState, ncblks, 16),
+
+ DEFINE_PROP_BOOL("at_code", RiscvCbqriCapacityState,
+ supports_at_code, true),
+
+ DEFINE_PROP_BOOL("alloc_op_config_limit", RiscvCbqriCapacityState,
+ supports_alloc_op_config_limit, true),
+ DEFINE_PROP_BOOL("alloc_op_read_limit", RiscvCbqriCapacityState,
+ supports_alloc_op_read_limit, true),
+ DEFINE_PROP_BOOL("alloc_op_flush_rcid", RiscvCbqriCapacityState,
+ supports_alloc_op_flush_rcid, true),
+
+ DEFINE_PROP_BOOL("mon_op_config_event", RiscvCbqriCapacityState,
+ supports_mon_op_config_event, true),
+ DEFINE_PROP_BOOL("mon_op_read_counter", RiscvCbqriCapacityState,
+ supports_mon_op_read_counter, true),
+
+ DEFINE_PROP_BOOL("mon_evt_id_none", RiscvCbqriCapacityState,
+ supports_mon_evt_id_none, true),
+ DEFINE_PROP_BOOL("mon_evt_id_occupancy", RiscvCbqriCapacityState,
+ supports_mon_evt_id_occupancy, true),
+};
+
+static const VMStateDescription vmstate_riscv_cbqri_cc = {
+ .name = "riscv-cbqri-capacity",
+ .unmigratable = 1,
+};
+
+static void riscv_cbqri_cc_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->realize = riscv_cbqri_cc_realize;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->desc = "RISC-V CBQRI Capacity Controller";
+ device_class_set_props(dc, riscv_cbqri_cc_properties);
+ dc->legacy_reset = riscv_cbqri_cc_reset;
+ dc->vmsd = &vmstate_riscv_cbqri_cc;
+ dc->user_creatable = true;
+}
+
+static const TypeInfo riscv_cbqri_cc_info = {
+ .name = TYPE_RISCV_CBQRI_CC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RiscvCbqriCapacityState),
+ .class_init = riscv_cbqri_cc_class_init,
+};
+
+static void riscv_cbqri_cc_register_types(void)
+{
+ type_register_static(&riscv_cbqri_cc_info);
+}
+
+DeviceState *riscv_cbqri_cc_create(hwaddr addr,
+ const RiscvCbqriCapacityCaps *caps)
+{
+ DeviceState *dev = qdev_new(TYPE_RISCV_CBQRI_CC);
+
+ qdev_prop_set_uint64(dev, "mmio_base", addr);
+ qdev_prop_set_uint16(dev, "max_mcids", caps->nb_mcids);
+ qdev_prop_set_uint16(dev, "max_rcids", caps->nb_rcids);
+ qdev_prop_set_uint16(dev, "ncblks", caps->ncblks);
+
+ qdev_prop_set_bit(dev, "at_code",
+ caps->supports_at_code);
+ qdev_prop_set_bit(dev, "alloc_op_config_limit",
+ caps->supports_alloc_op_config_limit);
+ qdev_prop_set_bit(dev, "alloc_op_read_limit",
+ caps->supports_alloc_op_read_limit);
+ qdev_prop_set_bit(dev, "alloc_op_flush_rcid",
+ caps->supports_alloc_op_flush_rcid);
+ qdev_prop_set_bit(dev, "mon_op_config_event",
+ caps->supports_mon_op_config_event);
+ qdev_prop_set_bit(dev, "mon_op_read_counter",
+ caps->supports_mon_op_read_counter);
+ qdev_prop_set_bit(dev, "mon_evt_id_none",
+ caps->supports_mon_evt_id_none);
+ qdev_prop_set_bit(dev, "mon_evt_id_occupancy",
+ caps->supports_mon_evt_id_occupancy);
+
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+ return dev;
+}
+
+type_init(riscv_cbqri_cc_register_types)
diff --git a/include/hw/riscv/cbqri.h b/include/hw/riscv/cbqri.h
index 1b3d85973717..d5baba359b1e 100644
--- a/include/hw/riscv/cbqri.h
+++ b/include/hw/riscv/cbqri.h
@@ -17,6 +17,14 @@
#define TYPE_RISCV_CBQRI_CC "riscv.cbqri.capacity"
#define TYPE_RISCV_CBQRI_BC "riscv.cbqri.bandwidth"
+/* Monitoring counter, one per MCID in both controllers */
+typedef struct MonitorCounter {
+ uint64_t ctr_val;
+ int at;
+ int evt_id;
+ bool active;
+} MonitorCounter;
+
/* Capacity Controller hardware capabilities */
typedef struct RiscvCbqriCapacityCaps {
uint16_t nb_mcids;
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 4/6] hw/riscv: implement CBQRI bandwidth controller
2026-11-01 20:25 [PATCH v7 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
` (2 preceding siblings ...)
2026-11-01 20:25 ` [PATCH v7 3/6] hw/riscv: implement CBQRI capacity controller Drew Fustini
@ 2026-11-01 20:25 ` Drew Fustini
2026-11-09 13:09 ` Daniel Henrique Barboza
2026-11-01 20:25 ` [PATCH v7 5/6] hw/riscv: add CBQRI to Kconfig and build if enabled Drew Fustini
2026-11-01 20:25 ` [PATCH v7 6/6] hw/riscv: add CBQRI controllers to virt machine Drew Fustini
5 siblings, 1 reply; 10+ messages in thread
From: Drew Fustini @ 2026-11-01 20:25 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, Palmer Dabbelt, Alistair Francis, Sunil V L,
Anup Patel, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
Chao Liu, Paolo Bonzini, Nicolas Pitre, Kornel Dulęba,
Atish Patra, Radim Krčmář, yunhui cui, Chen Pei,
guo.wenjia23, liu.qingtao2, Drew Fustini
From: Nicolas Pitre <npitre@baylibre.com>
Implement a bandwidth controller according to the Capacity and Bandwidth
QoS Register Interface (CBQRI) which supports these capabilities:
- Number of access types: 2 (code and data)
- Usage monitoring operations: CONFIG_EVENT, READ_COUNTER
- Event IDs supported: None, Total read/write byte count, Total
read byte count, Total write byte count
- Bandwidth allocation operations: CONFIG_LIMIT, READ_LIMIT
The controller gives the Linux resctrl support for CBQRI a device to
configure and read back bandwidth limits per RCID on the virt machine.
Only the register interface is modeled. The configured limits are not
enforced and the monitoring counters are always marked INVALID because
QEMU does not model memory bandwidth usage.
Link: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
[fustini: add fields introduced in the ratified spec: rpfx and p]
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
MAINTAINERS | 1 +
hw/riscv/cbqri_bandwidth.c | 650 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 651 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index f230cdbbe1b8..6d68b839febc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -408,6 +408,7 @@ M: Nicolas Pitre <npitre@baylibre.com>
M: Drew Fustini <fustini@kernel.org>
L: qemu-riscv@nongnu.org
S: Supported
+F: hw/riscv/cbqri_bandwidth.c
F: hw/riscv/cbqri_capacity.c
F: include/hw/riscv/cbqri.h
diff --git a/hw/riscv/cbqri_bandwidth.c b/hw/riscv/cbqri_bandwidth.c
new file mode 100644
index 000000000000..57f8897252bd
--- /dev/null
+++ b/hw/riscv/cbqri_bandwidth.c
@@ -0,0 +1,650 @@
+/*
+ * RISC-V Capacity and Bandwidth QoS Register Interface
+ * URL: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
+ *
+ * Copyright (c) 2023 BayLibre SAS
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This file contains the Bandwidth-controller QoS Register Interface.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/sysbus.h"
+#include "migration/vmstate.h"
+#include "target/riscv/cpu.h"
+#include "hw/riscv/cbqri.h"
+
+/* Encodings of `AT` field */
+enum {
+ BC_AT_DATA = 0,
+ BC_AT_CODE = 1,
+ BC_NB_ATS,
+};
+
+/* Capabilities */
+REG64(BC_CAPABILITIES, 0);
+FIELD(BC_CAPABILITIES, VER, 0, 8);
+FIELD(BC_CAPABILITIES, VER_MINOR, 0, 4);
+FIELD(BC_CAPABILITIES, VER_MAJOR, 4, 4);
+FIELD(BC_CAPABILITIES, NBWBLKS, 8, 16);
+FIELD(BC_CAPABILITIES, RPFX, 24, 1);
+FIELD(BC_CAPABILITIES, P, 25, 4);
+FIELD(BC_CAPABILITIES, MRBWB, 32, 16);
+
+/* Usage monitoring control */
+REG64(BC_MON_CTL, 8);
+FIELD(BC_MON_CTL, OP, 0, 5);
+FIELD(BC_MON_CTL, AT, 5, 3);
+FIELD(BC_MON_CTL, MCID, 8, 12);
+FIELD(BC_MON_CTL, EVT_ID, 20, 8);
+FIELD(BC_MON_CTL, ATV, 28, 1);
+FIELD(BC_MON_CTL, STATUS, 32, 7);
+FIELD(BC_MON_CTL, BUSY, 39, 1);
+
+/* Usage monitoring operations */
+enum {
+ BC_MON_OP_CONFIG_EVENT = 1,
+ BC_MON_OP_READ_COUNTER = 2,
+};
+
+/* Bandwidth monitoring event ID */
+enum {
+ BC_EVT_ID_None = 0,
+ BC_EVT_ID_RDWR_count = 1,
+ BC_EVT_ID_RDONLY_count = 2,
+ BC_EVT_ID_WRONLY_count = 3,
+};
+
+/* BC_MON_CTL.STATUS field encodings */
+enum {
+ BC_MON_CTL_STATUS_SUCCESS = 1,
+ BC_MON_CTL_STATUS_INVAL_OP = 2,
+ BC_MON_CTL_STATUS_INVAL_MCID = 3,
+ BC_MON_CTL_STATUS_INVAL_EVT_ID = 4,
+ BC_MON_CTL_STATUS_INVAL_AT = 5,
+};
+
+/* Monitoring counter value */
+REG64(BC_MON_CTR_VAL, 16);
+FIELD(BC_MON_CTR_VAL, CTR, 0, 62);
+FIELD(BC_MON_CTR_VAL, INVALID, 62, 1);
+FIELD(BC_MON_CTR_VAL, OVF, 63, 1);
+
+/* Bandwidth Allocation control */
+REG64(BC_ALLOC_CTL, 24);
+FIELD(BC_ALLOC_CTL, OP, 0, 5);
+FIELD(BC_ALLOC_CTL, AT, 5, 3);
+FIELD(BC_ALLOC_CTL, RCID, 8, 12);
+FIELD(BC_ALLOC_CTL, STATUS, 32, 7);
+FIELD(BC_ALLOC_CTL, BUSY, 39, 1);
+
+/* Bandwidth allocation operations */
+enum {
+ BC_ALLOC_OP_CONFIG_LIMIT = 1,
+ BC_ALLOC_OP_READ_LIMIT = 2,
+};
+
+/* BC_ALLOC_CTL.STATUS field encodings */
+enum {
+ BC_ALLOC_STATUS_SUCCESS = 1,
+ BC_ALLOC_STATUS_INVAL_OP = 2,
+ BC_ALLOC_STATUS_INVAL_RCID = 3,
+ BC_ALLOC_STATUS_INVAL_AT = 4,
+ BC_ALLOC_STATUS_INVAL_BLKS = 5,
+};
+
+/* Bandwidth allocation */
+REG64(BC_BW_ALLOC, 32);
+FIELD(BC_BW_ALLOC, Rbwb, 0, 16);
+FIELD(BC_BW_ALLOC, Mweight, 20, 8);
+FIELD(BC_BW_ALLOC, sharedAT, 28, 3);
+FIELD(BC_BW_ALLOC, useShared, 31, 1);
+
+
+typedef struct BandwidthAllocation {
+ uint32_t Rbwb:16;
+ uint32_t Mweight:8;
+ uint32_t sharedAT:3;
+ bool useShared:1;
+} BandwidthAllocation;
+
+typedef struct RiscvCbqriBandwidthState {
+ SysBusDevice parent_obj;
+ MemoryRegion mmio;
+
+ /* cached value of some registers */
+ uint64_t bc_mon_ctl;
+ uint64_t bc_mon_ctr_val;
+ uint64_t bc_alloc_ctl;
+
+ MonitorCounter *mon_counters;
+ BandwidthAllocation *bw_allocations;
+
+ /* properties */
+
+ uint64_t mmio_base;
+ uint16_t nb_mcids;
+ uint16_t nb_rcids;
+
+ uint16_t nbwblks;
+ uint16_t mrbwb;
+
+ bool supports_at_code;
+
+ bool supports_alloc_op_config_limit;
+ bool supports_alloc_op_read_limit;
+
+ bool supports_mon_op_config_event;
+ bool supports_mon_op_read_counter;
+
+ bool supports_mon_evt_id_none;
+ bool supports_mon_evt_id_rdwr_count;
+ bool supports_mon_evt_id_rdonly_count;
+ bool supports_mon_evt_id_wronly_count;
+} RiscvCbqriBandwidthState;
+
+#define RISCV_CBQRI_BC(obj) \
+ OBJECT_CHECK(RiscvCbqriBandwidthState, (obj), TYPE_RISCV_CBQRI_BC)
+
+/*
+ * All bandwidth allocation records are contiguous to simplify
+ * allocation. The first one is used to hold the BC_BW_ALLOC register
+ * content, followed by respective records for each AT per RCID.
+ *
+ * Records are indexed by the AT encoding, so each RCID has one record
+ * per AT value whether or not the controller supports the code access
+ * type. is_valid_at() rejects an unsupported AT before a record is
+ * selected.
+ */
+static unsigned int bw_alloc_index(uint32_t rcid, uint32_t at)
+{
+ assert(at < BC_NB_ATS);
+
+ return 1 + rcid * BC_NB_ATS + at;
+}
+
+static BandwidthAllocation *get_bw_alloc(RiscvCbqriBandwidthState *bc,
+ uint32_t rcid, uint32_t at)
+{
+ return &bc->bw_allocations[bw_alloc_index(rcid, at)];
+}
+
+static uint32_t bandwidth_config(RiscvCbqriBandwidthState *bc,
+ uint32_t rcid, uint32_t at,
+ bool *busy)
+{
+ BandwidthAllocation *bw_alloc = get_bw_alloc(bc, rcid, at);
+ BandwidthAllocation *end = get_bw_alloc(bc, bc->nb_rcids, 0);
+ BandwidthAllocation *cur;
+ uint32_t rbwb = bc->bw_allocations[0].Rbwb;
+ uint64_t sum = rbwb;
+
+ /*
+ * When useShared is set the allocation of sharedAT applies instead
+ * and the Rbwb and Mweight fields are ignored.
+ */
+ if (bc->bw_allocations[0].useShared) {
+ *bw_alloc = bc->bw_allocations[0];
+ return BC_ALLOC_STATUS_SUCCESS;
+ }
+
+ /*
+ * Bandwidth is allocated in multiples of bandwidth blocks, and the
+ * value in Rbwb must be at least 1 and must not exceed MRBWB value.
+ */
+ if (rbwb < 1 || rbwb > bc->mrbwb) {
+ return BC_ALLOC_STATUS_INVAL_BLKS;
+ }
+
+ /*
+ * The sum of Rbwb allocated across all RCIDs must not exceed MRBWB
+ * either. The allocation being replaced and allocations shared with
+ * another access type do not count.
+ */
+ for (cur = &bc->bw_allocations[1]; cur < end; cur++) {
+ if (cur != bw_alloc && !cur->useShared) {
+ sum += cur->Rbwb;
+ }
+ }
+ if (sum > bc->mrbwb) {
+ return BC_ALLOC_STATUS_INVAL_BLKS;
+ }
+
+ /* Save contents of BC_BW_ALLOC register for this rcid and at */
+ *bw_alloc = bc->bw_allocations[0];
+ return BC_ALLOC_STATUS_SUCCESS;
+}
+
+static uint32_t bandwidth_read(RiscvCbqriBandwidthState *bc,
+ uint32_t rcid, uint32_t at,
+ bool *busy)
+{
+ BandwidthAllocation *bw_alloc = get_bw_alloc(bc, rcid, at);
+
+ /* Populate BC_BW_ALLOC register with selected content */
+ bc->bw_allocations[0] = *bw_alloc;
+ return BC_ALLOC_STATUS_SUCCESS;
+}
+
+static bool is_valid_at(RiscvCbqriBandwidthState *bc, uint32_t at)
+{
+ switch (at) {
+ case BC_AT_DATA:
+ return true;
+ case BC_AT_CODE:
+ return bc->supports_at_code;
+ default:
+ return false;
+ }
+}
+
+static void riscv_cbqri_bc_write_mon_ctl(RiscvCbqriBandwidthState *bc,
+ uint64_t value)
+{
+ if (!bc->supports_mon_op_config_event &&
+ !bc->supports_mon_op_read_counter) {
+ /* monitoring not supported: leave mon_ctl set to 0 */
+ return;
+ }
+
+ /* extract writable fields */
+ uint32_t op = FIELD_EX64(value, BC_MON_CTL, OP);
+ uint32_t at = FIELD_EX64(value, BC_MON_CTL, AT);
+ uint32_t mcid = FIELD_EX64(value, BC_MON_CTL, MCID);
+ uint32_t evt_id = FIELD_EX64(value, BC_MON_CTL, EVT_ID);
+ bool atv = FIELD_EX64(value, BC_MON_CTL, ATV);
+
+ /* extract read-only fields */
+ uint32_t status = FIELD_EX64(bc->bc_mon_ctl, BC_MON_CTL, STATUS);
+ bool busy = FIELD_EX64(bc->bc_mon_ctl, BC_MON_CTL, BUSY);
+
+ if (busy) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: busy flag still set, ignored",
+ __func__);
+ return;
+ }
+
+ if (!bc->supports_at_code) {
+ /* AT not supported: hardwire to 0 */
+ at = 0;
+ atv = false;
+ }
+
+ if (mcid >= bc->nb_mcids) {
+ status = BC_MON_CTL_STATUS_INVAL_MCID;
+ } else if (op == BC_MON_OP_CONFIG_EVENT &&
+ bc->supports_mon_op_config_event) {
+ if (evt_id == BC_EVT_ID_None &&
+ bc->supports_mon_evt_id_none) {
+ bc->mon_counters[mcid].active = false;
+ status = BC_MON_CTL_STATUS_SUCCESS;
+ } else if ((evt_id == BC_EVT_ID_RDWR_count &&
+ bc->supports_mon_evt_id_rdwr_count) ||
+ (evt_id == BC_EVT_ID_RDONLY_count &&
+ bc->supports_mon_evt_id_rdonly_count) ||
+ (evt_id == BC_EVT_ID_WRONLY_count &&
+ bc->supports_mon_evt_id_wronly_count)) {
+ if (atv && !is_valid_at(bc, at)) {
+ status = BC_MON_CTL_STATUS_INVAL_AT;
+ } else {
+ /*
+ * The model does not account for bandwidth usage, so the
+ * counter is marked INVALID and stays that way.
+ */
+ bc->mon_counters[mcid].ctr_val =
+ FIELD_DP64(0, BC_MON_CTR_VAL, INVALID, 1);
+ bc->mon_counters[mcid].evt_id = evt_id;
+ bc->mon_counters[mcid].at = atv ? at : -1;
+ bc->mon_counters[mcid].active = true;
+ status = BC_MON_CTL_STATUS_SUCCESS;
+ }
+ } else {
+ status = BC_MON_CTL_STATUS_INVAL_EVT_ID;
+ }
+ } else if (op == BC_MON_OP_READ_COUNTER &&
+ bc->supports_mon_op_read_counter) {
+ bc->bc_mon_ctr_val = bc->mon_counters[mcid].ctr_val;
+ status = BC_MON_CTL_STATUS_SUCCESS;
+ } else {
+ status = BC_MON_CTL_STATUS_INVAL_OP;
+ }
+
+ /* reconstruct updated register value */
+ value = 0;
+ value = FIELD_DP64(value, BC_MON_CTL, OP, op);
+ value = FIELD_DP64(value, BC_MON_CTL, AT, at);
+ value = FIELD_DP64(value, BC_MON_CTL, MCID, mcid);
+ value = FIELD_DP64(value, BC_MON_CTL, EVT_ID, evt_id);
+ value = FIELD_DP64(value, BC_MON_CTL, ATV, atv);
+ value = FIELD_DP64(value, BC_MON_CTL, STATUS, status);
+ value = FIELD_DP64(value, BC_MON_CTL, BUSY, busy);
+ bc->bc_mon_ctl = value;
+}
+
+static void riscv_cbqri_bc_write_alloc_ctl(RiscvCbqriBandwidthState *bc,
+ uint64_t value)
+{
+ if (bc->nbwblks == 0 ||
+ (!bc->supports_alloc_op_config_limit &&
+ !bc->supports_alloc_op_read_limit)) {
+ /* bandwidth allocation not supported: leave bc_alloc_ctl set to 0 */
+ return;
+ }
+
+ /* extract writable fields */
+ uint32_t op = FIELD_EX64(value, BC_ALLOC_CTL, OP);
+ uint32_t at = FIELD_EX64(value, BC_ALLOC_CTL, AT);
+ uint32_t rcid = FIELD_EX64(value, BC_ALLOC_CTL, RCID);
+
+ /* extract read-only fields */
+ uint32_t status = FIELD_EX64(bc->bc_alloc_ctl, BC_ALLOC_CTL, STATUS);
+ bool busy = FIELD_EX64(bc->bc_alloc_ctl, BC_ALLOC_CTL, BUSY);
+
+ if (busy) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: busy flag still set, ignored",
+ __func__);
+ return;
+ }
+
+ bool atv = true;
+ if (!bc->supports_at_code) {
+ /* AT not supported: hardwire to 0 */
+ at = 0;
+ atv = false;
+ }
+
+ if (rcid >= bc->nb_rcids) {
+ status = BC_ALLOC_STATUS_INVAL_RCID;
+ } else if (atv && !is_valid_at(bc, at)) {
+ status = BC_ALLOC_STATUS_INVAL_AT;
+ } else if (op == BC_ALLOC_OP_CONFIG_LIMIT &&
+ bc->supports_alloc_op_config_limit) {
+ status = bandwidth_config(bc, rcid, at, &busy);
+ } else if (op == BC_ALLOC_OP_READ_LIMIT &&
+ bc->supports_alloc_op_read_limit) {
+ status = bandwidth_read(bc, rcid, at, &busy);
+ } else {
+ status = BC_ALLOC_STATUS_INVAL_OP;
+ }
+
+ /* reconstruct updated register value */
+ value = 0;
+ value = FIELD_DP64(value, BC_ALLOC_CTL, OP, op);
+ value = FIELD_DP64(value, BC_ALLOC_CTL, AT, at);
+ value = FIELD_DP64(value, BC_ALLOC_CTL, RCID, rcid);
+ value = FIELD_DP64(value, BC_ALLOC_CTL, STATUS, status);
+ value = FIELD_DP64(value, BC_ALLOC_CTL, BUSY, busy);
+ bc->bc_alloc_ctl = value;
+}
+
+static void riscv_cbqri_bc_write_bw_alloc(RiscvCbqriBandwidthState *bc,
+ uint64_t value)
+{
+ if (bc->nbwblks == 0) {
+ /* bandwidth allocation not supported: leave bw_alloc set to 0 */
+ return;
+ }
+
+ BandwidthAllocation *bc_bw_alloc = &bc->bw_allocations[0];
+
+ /* extract writable fields */
+ bc_bw_alloc->Rbwb = FIELD_EX64(value, BC_BW_ALLOC, Rbwb);
+ bc_bw_alloc->Mweight = FIELD_EX64(value, BC_BW_ALLOC, Mweight);
+ bc_bw_alloc->sharedAT = FIELD_EX64(value, BC_BW_ALLOC, sharedAT);
+ bc_bw_alloc->useShared = FIELD_EX64(value, BC_BW_ALLOC, useShared);
+
+ if (!bc->supports_at_code) {
+ /* AT not supported: hardwire to 0 */
+ bc_bw_alloc->sharedAT = 0;
+ bc_bw_alloc->useShared = false;
+ }
+}
+
+static void riscv_cbqri_bc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ RiscvCbqriBandwidthState *bc = opaque;
+
+ assert((addr % 8) == 0);
+
+ switch (addr) {
+ case A_BC_CAPABILITIES:
+ /* read-only register */
+ break;
+ case A_BC_MON_CTL:
+ riscv_cbqri_bc_write_mon_ctl(bc, value);
+ break;
+ case A_BC_MON_CTR_VAL:
+ /* read-only register */
+ break;
+ case A_BC_ALLOC_CTL:
+ riscv_cbqri_bc_write_alloc_ctl(bc, value);
+ break;
+ case A_BC_BW_ALLOC:
+ riscv_cbqri_bc_write_bw_alloc(bc, value);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: out of bounds (addr=0x%x)",
+ __func__, (uint32_t)addr);
+ }
+}
+
+static uint64_t riscv_cbqri_bc_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RiscvCbqriBandwidthState *bc = opaque;
+ uint64_t value = 0;
+
+ assert((addr % 8) == 0);
+
+ switch (addr) {
+ case A_BC_CAPABILITIES:
+ value = FIELD_DP64(value, BC_CAPABILITIES, VER_MAJOR,
+ RISCV_CBQRI_VERSION_MAJOR);
+ value = FIELD_DP64(value, BC_CAPABILITIES, VER_MINOR,
+ RISCV_CBQRI_VERSION_MINOR);
+ value = FIELD_DP64(value, BC_CAPABILITIES, RPFX, 0);
+ value = FIELD_DP64(value, BC_CAPABILITIES, P, 0);
+ value = FIELD_DP64(value, BC_CAPABILITIES, NBWBLKS, bc->nbwblks);
+ value = FIELD_DP64(value, BC_CAPABILITIES, MRBWB, bc->mrbwb);
+ break;
+ case A_BC_MON_CTL:
+ value = bc->bc_mon_ctl;
+ break;
+ case A_BC_MON_CTR_VAL:
+ value = bc->bc_mon_ctr_val;
+ break;
+ case A_BC_ALLOC_CTL:
+ value = bc->bc_alloc_ctl;
+ break;
+ case A_BC_BW_ALLOC:
+ BandwidthAllocation *bc_bw_alloc = &bc->bw_allocations[0];
+ value = FIELD_DP64(value, BC_BW_ALLOC, Rbwb, bc_bw_alloc->Rbwb);
+ value = FIELD_DP64(value, BC_BW_ALLOC, Mweight, bc_bw_alloc->Mweight);
+ value = FIELD_DP64(value, BC_BW_ALLOC, sharedAT, bc_bw_alloc->sharedAT);
+ value = FIELD_DP64(value, BC_BW_ALLOC, useShared,
+ bc_bw_alloc->useShared);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: out of bounds (addr=0x%x)",
+ __func__, (uint32_t)addr);
+ }
+
+ return value;
+}
+
+static uint64_t riscv_cbqri_bc_read_wrapper(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint64_t value = riscv_cbqri_bc_read(opaque, QEMU_ALIGN_DOWN(addr, 8), 8);
+ if (size == 4) {
+ if (addr & 0x7) {
+ return value >> 32;
+ } else {
+ return value & MAKE_64BIT_MASK(0, 32);
+ }
+ }
+ return value;
+}
+
+static void riscv_cbqri_bc_write_wrapper(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ hwaddr reg_addr = QEMU_ALIGN_DOWN(addr, 8);
+
+ if (size == 4) {
+ uint64_t reg = riscv_cbqri_bc_read(opaque, reg_addr, 8);
+ if (addr & 0x7) {
+ value = value << 32 | (reg & MAKE_64BIT_MASK(0, 32));
+ } else {
+ value = value | (reg & MAKE_64BIT_MASK(32, 32));
+ }
+ }
+ riscv_cbqri_bc_write(opaque, reg_addr, value, 8);
+}
+
+
+static const MemoryRegionOps riscv_cbqri_bc_ops = {
+ .read = riscv_cbqri_bc_read_wrapper,
+ .write = riscv_cbqri_bc_write_wrapper,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 8,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 8,
+};
+
+static void riscv_cbqri_bc_realize(DeviceState *dev, Error **errp)
+{
+ RiscvCbqriBandwidthState *bc = RISCV_CBQRI_BC(dev);
+
+ if (!bc->mmio_base) {
+ error_setg(errp, "mmio_base property not set");
+ return;
+ }
+
+ assert(bc->mon_counters == NULL);
+ bc->mon_counters = g_new0(MonitorCounter, bc->nb_mcids);
+
+ assert(bc->bw_allocations == NULL);
+ bc->bw_allocations = g_new0(BandwidthAllocation,
+ bw_alloc_index(bc->nb_rcids, 0));
+
+ memory_region_init_io(&bc->mmio, OBJECT(dev), &riscv_cbqri_bc_ops,
+ bc, TYPE_RISCV_CBQRI_BC".mmio", 4 * 1024);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &bc->mmio);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, bc->mmio_base);
+}
+
+static void riscv_cbqri_bc_reset(DeviceState *dev)
+{
+ RiscvCbqriBandwidthState *bc = RISCV_CBQRI_BC(dev);
+
+ bc->bc_mon_ctl = 0;
+ bc->bc_alloc_ctl = 0;
+}
+
+static const Property riscv_cbqri_bc_properties[] = {
+ DEFINE_PROP_UINT64("mmio_base", RiscvCbqriBandwidthState, mmio_base, 0),
+
+ DEFINE_PROP_UINT16("max_mcids", RiscvCbqriBandwidthState, nb_mcids, 256),
+ DEFINE_PROP_UINT16("max_rcids", RiscvCbqriBandwidthState, nb_rcids, 64),
+ DEFINE_PROP_UINT16("nbwblks", RiscvCbqriBandwidthState, nbwblks, 1024),
+ DEFINE_PROP_UINT16("mrbwb", RiscvCbqriBandwidthState, mrbwb, 819),
+
+ DEFINE_PROP_BOOL("at_code", RiscvCbqriBandwidthState,
+ supports_at_code, true),
+
+ DEFINE_PROP_BOOL("alloc_op_config_limit", RiscvCbqriBandwidthState,
+ supports_alloc_op_config_limit, true),
+ DEFINE_PROP_BOOL("alloc_op_read_limit", RiscvCbqriBandwidthState,
+ supports_alloc_op_read_limit, true),
+
+ DEFINE_PROP_BOOL("mon_op_config_event", RiscvCbqriBandwidthState,
+ supports_mon_op_config_event, true),
+ DEFINE_PROP_BOOL("mon_op_read_counter", RiscvCbqriBandwidthState,
+ supports_mon_op_read_counter, true),
+
+ DEFINE_PROP_BOOL("mon_evt_id_none", RiscvCbqriBandwidthState,
+ supports_mon_evt_id_none, true),
+ DEFINE_PROP_BOOL("mon_evt_id_rdwr_count", RiscvCbqriBandwidthState,
+ supports_mon_evt_id_rdwr_count, true),
+ DEFINE_PROP_BOOL("mon_evt_id_rdonly_count", RiscvCbqriBandwidthState,
+ supports_mon_evt_id_rdonly_count, true),
+ DEFINE_PROP_BOOL("mon_evt_id_wronly_count", RiscvCbqriBandwidthState,
+ supports_mon_evt_id_wronly_count, true),
+};
+
+static const VMStateDescription vmstate_riscv_cbqri_bc = {
+ .name = "riscv-cbqri-bandwidth",
+ .unmigratable = 1,
+};
+
+static void riscv_cbqri_bc_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->realize = riscv_cbqri_bc_realize;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+ dc->desc = "RISC-V CBQRI Bandwidth Controller";
+ device_class_set_props(dc, riscv_cbqri_bc_properties);
+ dc->legacy_reset = riscv_cbqri_bc_reset;
+ dc->vmsd = &vmstate_riscv_cbqri_bc;
+ dc->user_creatable = true;
+}
+
+static const TypeInfo riscv_cbqri_bc_info = {
+ .name = TYPE_RISCV_CBQRI_BC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RiscvCbqriBandwidthState),
+ .class_init = riscv_cbqri_bc_class_init,
+};
+
+static void riscv_cbqri_bc_register_types(void)
+{
+ type_register_static(&riscv_cbqri_bc_info);
+}
+
+DeviceState *riscv_cbqri_bc_create(hwaddr addr,
+ const RiscvCbqriBandwidthCaps *caps)
+{
+ DeviceState *dev = qdev_new(TYPE_RISCV_CBQRI_BC);
+
+ qdev_prop_set_uint64(dev, "mmio_base", addr);
+ qdev_prop_set_uint16(dev, "max_mcids", caps->nb_mcids);
+ qdev_prop_set_uint16(dev, "max_rcids", caps->nb_rcids);
+ qdev_prop_set_uint16(dev, "nbwblks", caps->nbwblks);
+ qdev_prop_set_uint16(dev, "mrbwb", caps->mrbwb);
+
+ qdev_prop_set_bit(dev, "at_code",
+ caps->supports_at_code);
+ qdev_prop_set_bit(dev, "alloc_op_config_limit",
+ caps->supports_alloc_op_config_limit);
+ qdev_prop_set_bit(dev, "alloc_op_read_limit",
+ caps->supports_alloc_op_read_limit);
+ qdev_prop_set_bit(dev, "mon_op_config_event",
+ caps->supports_mon_op_config_event);
+ qdev_prop_set_bit(dev, "mon_op_read_counter",
+ caps->supports_mon_op_read_counter);
+ qdev_prop_set_bit(dev, "mon_evt_id_none",
+ caps->supports_mon_evt_id_none);
+ qdev_prop_set_bit(dev, "mon_evt_id_rdwr_count",
+ caps->supports_mon_evt_id_rdwr_count);
+ qdev_prop_set_bit(dev, "mon_evt_id_rdonly_count",
+ caps->supports_mon_evt_id_rdonly_count);
+ qdev_prop_set_bit(dev, "mon_evt_id_wronly_count",
+ caps->supports_mon_evt_id_wronly_count);
+
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+ return dev;
+}
+
+type_init(riscv_cbqri_bc_register_types)
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 5/6] hw/riscv: add CBQRI to Kconfig and build if enabled
2026-11-01 20:25 [PATCH v7 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
` (3 preceding siblings ...)
2026-11-01 20:25 ` [PATCH v7 4/6] hw/riscv: implement CBQRI bandwidth controller Drew Fustini
@ 2026-11-01 20:25 ` Drew Fustini
2026-11-09 13:14 ` Daniel Henrique Barboza
2026-11-01 20:25 ` [PATCH v7 6/6] hw/riscv: add CBQRI controllers to virt machine Drew Fustini
5 siblings, 1 reply; 10+ messages in thread
From: Drew Fustini @ 2026-11-01 20:25 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, Palmer Dabbelt, Alistair Francis, Sunil V L,
Anup Patel, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
Chao Liu, Paolo Bonzini, Nicolas Pitre, Kornel Dulęba,
Atish Patra, Radim Krčmář, yunhui cui, Chen Pei,
guo.wenjia23, liu.qingtao2, Drew Fustini
From: Nicolas Pitre <npitre@baylibre.com>
Add a boolean Kconfig symbol for CBQRI and imply it should be enabled
for the RISC-V virt machine. Build the CBQRI controllers when RISC-V
CBQRI is enabled.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
hw/riscv/Kconfig | 4 ++++
hw/riscv/meson.build | 2 ++
2 files changed, 6 insertions(+)
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index d89fdfcc8671..98bdebb4b814 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -1,3 +1,6 @@
+config RISCV_CBQRI
+ bool
+
config RISCV_IOMMU
bool
@@ -68,6 +71,7 @@ config RISCV_VIRT
select PLATFORM_BUS
select ACPI
select ACPI_PCI
+ imply RISCV_CBQRI
config RISCV_SERVER_PLATFORM_REF
bool
diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index 6bdabb954473..c7952d67755e 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -19,6 +19,8 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
+riscv_ss.add(when: 'CONFIG_RISCV_CBQRI', if_true: files('cbqri_capacity.c', 'cbqri_bandwidth.c'))
+
riscv_ss.add(when: 'CONFIG_RISCV_MIPS_CPS', if_true: files('cps.c'))
riscv_ss.add(when: 'CONFIG_MIPS_BOSTON_AIA', if_true: files('boston-aia.c'))
riscv_ss.add(when: 'CONFIG_K230', if_true: files('k230.c'))
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v7 6/6] hw/riscv: add CBQRI controllers to virt machine
2026-11-01 20:25 [PATCH v7 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
` (4 preceding siblings ...)
2026-11-01 20:25 ` [PATCH v7 5/6] hw/riscv: add CBQRI to Kconfig and build if enabled Drew Fustini
@ 2026-11-01 20:25 ` Drew Fustini
5 siblings, 0 replies; 10+ messages in thread
From: Drew Fustini @ 2026-11-01 20:25 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-riscv, Palmer Dabbelt, Alistair Francis, Sunil V L,
Anup Patel, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
Chao Liu, Paolo Bonzini, Nicolas Pitre, Kornel Dulęba,
Atish Patra, Radim Krčmář, yunhui cui, Chen Pei,
guo.wenjia23, liu.qingtao2, Drew Fustini, Daniel Henrique Barboza
From: Nicolas Pitre <npitre@baylibre.com>
Add CBQRI controllers to the RISC-V virt machine. The device properties
can be fully configured from the command line:
$ qemu-system-riscv64 -M virt ... \
-device riscv.cbqri.capacity,mmio_base=0x04820000[,...] \
-device riscv.cbqri.bandwidth,mmio_base=0x04829000[,...]
The mmio_base option is mandatory. The others are optional.
As many -device arguments as wanted can be provided as long as their
mmio regions don't conflict.
To see all possible options:
$ qemu-system-riscv64 -device riscv.cbqri.capacity,help
riscv.cbqri.capacity options:
alloc_op_config_limit=<bool> - on/off (default: on)
alloc_op_flush_rcid=<bool> - on/off (default: on)
alloc_op_read_limit=<bool> - on/off (default: on)
at_code=<bool> - on/off (default: on)
max_mcids=<uint16> - (default: 256)
max_rcids=<uint16> - (default: 64)
mmio_base=<uint64> - (default: 0)
mon_evt_id_none=<bool> - on/off (default: on)
mon_evt_id_occupancy=<bool> - on/off (default: on)
mon_op_config_event=<bool> - on/off (default: on)
mon_op_read_counter=<bool> - on/off (default: on)
ncblks=<uint16> - (default: 16)
$ qemu-system-riscv64 -device riscv.cbqri.bandwidth,help
riscv.cbqri.bandwidth options:
alloc_op_config_limit=<bool> - on/off (default: on)
alloc_op_read_limit=<bool> - on/off (default: on)
at_code=<bool> - on/off (default: on)
max_mcids=<uint16> - (default: 256)
max_rcids=<uint16> - (default: 64)
mmio_base=<uint64> - (default: 0)
mon_evt_id_none=<bool> - on/off (default: on)
mon_evt_id_rdonly_count=<bool> - on/off (default: on)
mon_evt_id_rdwr_count=<bool> - on/off (default: on)
mon_evt_id_wronly_count=<bool> - on/off (default: on)
mon_op_config_event=<bool> - on/off (default: on)
mon_op_read_counter=<bool> - on/off (default: on)
mrbwb=<uint16> - (default: 819)
nbwblks=<uint16> - (default: 1024)
Boolean options correspond to hardware capabilities that can be
disabled.
Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
Signed-off-by: Drew Fustini <fustini@kernel.org>
---
hw/core/sysbus-fdt.c | 4 ++++
hw/riscv/virt.c | 3 +++
2 files changed, 7 insertions(+)
diff --git a/hw/core/sysbus-fdt.c b/hw/core/sysbus-fdt.c
index 12238570b5e1..265917ef573e 100644
--- a/hw/core/sysbus-fdt.c
+++ b/hw/core/sysbus-fdt.c
@@ -35,6 +35,7 @@
#include "hw/core/platform-bus.h"
#include "hw/display/ramfb.h"
#include "hw/uefi/var-service-api.h"
+#include "hw/riscv/cbqri.h"
#include "hw/arm/fdt.h"
#include "hw/watchdog/sbsa_gwdt.h"
@@ -142,6 +143,9 @@ static const BindingEntry bindings[] = {
TYPE_BINDING(TYPE_RAMFB_DEVICE, no_fdt_node),
TYPE_BINDING(TYPE_UEFI_VARS_SYSBUS, add_uefi_vars_node),
TYPE_BINDING(TYPE_WDT_SBSA, no_fdt_node),
+ /* RISC-V CBQRI controllers have no generic device tree node */
+ TYPE_BINDING(TYPE_RISCV_CBQRI_BC, no_fdt_node),
+ TYPE_BINDING(TYPE_RISCV_CBQRI_CC, no_fdt_node),
TYPE_BINDING("", NULL), /* last element */
};
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index d4b501802f60..dbc0f8cfc8d7 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -57,6 +57,7 @@
#include "hw/pci-host/gpex.h"
#include "hw/display/ramfb.h"
#include "hw/acpi/aml-build.h"
+#include "hw/riscv/cbqri.h"
#include "qapi/qapi-visit-common.h"
#include "hw/virtio/virtio-iommu.h"
#include "hw/uefi/var-service-api.h"
@@ -1157,6 +1158,8 @@ static void virt_machine_class_init(ObjectClass *oc, const void *data)
#ifdef CONFIG_TPM
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS);
#endif
+ machine_class_allow_dynamic_sysbus_dev(mc, TYPE_RISCV_CBQRI_BC);
+ machine_class_allow_dynamic_sysbus_dev(mc, TYPE_RISCV_CBQRI_CC);
object_class_property_add_bool(oc, "aclint", virt_get_aclint,
virt_set_aclint);
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v7 4/6] hw/riscv: implement CBQRI bandwidth controller
2026-11-01 20:25 ` [PATCH v7 4/6] hw/riscv: implement CBQRI bandwidth controller Drew Fustini
@ 2026-11-09 13:09 ` Daniel Henrique Barboza
2026-11-11 18:48 ` Drew Fustini
0 siblings, 1 reply; 10+ messages in thread
From: Daniel Henrique Barboza @ 2026-11-09 13:09 UTC (permalink / raw)
To: Drew Fustini, qemu-devel
Cc: qemu-riscv, Palmer Dabbelt, Alistair Francis, Sunil V L,
Anup Patel, Weiwei Li, Liu Zhiwei, Chao Liu, Paolo Bonzini,
Nicolas Pitre, Kornel Dulęba, Atish Patra,
Radim Krčmář, yunhui cui, Chen Pei, guo.wenjia23,
liu.qingtao2
On 11/1/2026 5:25 PM, Drew Fustini wrote:
> From: Nicolas Pitre <npitre@baylibre.com>
>
> Implement a bandwidth controller according to the Capacity and Bandwidth
> QoS Register Interface (CBQRI) which supports these capabilities:
>
> - Number of access types: 2 (code and data)
> - Usage monitoring operations: CONFIG_EVENT, READ_COUNTER
> - Event IDs supported: None, Total read/write byte count, Total
> read byte count, Total write byte count
> - Bandwidth allocation operations: CONFIG_LIMIT, READ_LIMIT
>
> The controller gives the Linux resctrl support for CBQRI a device to
> configure and read back bandwidth limits per RCID on the virt machine.
> Only the register interface is modeled. The configured limits are not
> enforced and the monitoring counters are always marked INVALID because
> QEMU does not model memory bandwidth usage.
>
> Link: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
> Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
> [fustini: add fields introduced in the ratified spec: rpfx and p]
> Signed-off-by: Drew Fustini <fustini@kernel.org>
> ---
LGTM overall. A few comments:
> MAINTAINERS | 1 +
> hw/riscv/cbqri_bandwidth.c | 650 +++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 651 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f230cdbbe1b8..6d68b839febc 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -408,6 +408,7 @@ M: Nicolas Pitre <npitre@baylibre.com>
> M: Drew Fustini <fustini@kernel.org>
> L: qemu-riscv@nongnu.org
> S: Supported
> +F: hw/riscv/cbqri_bandwidth.c
> F: hw/riscv/cbqri_capacity.c
> F: include/hw/riscv/cbqri.h
>
> diff --git a/hw/riscv/cbqri_bandwidth.c b/hw/riscv/cbqri_bandwidth.c
> new file mode 100644
> index 000000000000..57f8897252bd
> --- /dev/null
> +++ b/hw/riscv/cbqri_bandwidth.c
> @@ -0,0 +1,650 @@
> +/*
> + * RISC-V Capacity and Bandwidth QoS Register Interface
> + * URL: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
> + *
> + * Copyright (c) 2023 BayLibre SAS
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * This file contains the Bandwidth-controller QoS Register Interface.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/error-report.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/core/sysbus.h"
> +#include "migration/vmstate.h"
> +#include "target/riscv/cpu.h"
> +#include "hw/riscv/cbqri.h"
> +
> +/* Encodings of `AT` field */
> +enum {
> + BC_AT_DATA = 0,
> + BC_AT_CODE = 1,
> + BC_NB_ATS,
> +};
> +
> +/* Capabilities */
> +REG64(BC_CAPABILITIES, 0);
> +FIELD(BC_CAPABILITIES, VER, 0, 8);
> +FIELD(BC_CAPABILITIES, VER_MINOR, 0, 4);
> +FIELD(BC_CAPABILITIES, VER_MAJOR, 4, 4);
> +FIELD(BC_CAPABILITIES, NBWBLKS, 8, 16);
> +FIELD(BC_CAPABILITIES, RPFX, 24, 1);
> +FIELD(BC_CAPABILITIES, P, 25, 4);
> +FIELD(BC_CAPABILITIES, MRBWB, 32, 16);
> +
> +/* Usage monitoring control */
> +REG64(BC_MON_CTL, 8);
> +FIELD(BC_MON_CTL, OP, 0, 5);
> +FIELD(BC_MON_CTL, AT, 5, 3);
> +FIELD(BC_MON_CTL, MCID, 8, 12);
> +FIELD(BC_MON_CTL, EVT_ID, 20, 8);
> +FIELD(BC_MON_CTL, ATV, 28, 1);
> +FIELD(BC_MON_CTL, STATUS, 32, 7);
> +FIELD(BC_MON_CTL, BUSY, 39, 1);
> +
> +/* Usage monitoring operations */
> +enum {
> + BC_MON_OP_CONFIG_EVENT = 1,
> + BC_MON_OP_READ_COUNTER = 2,
> +};
> +
> +/* Bandwidth monitoring event ID */
> +enum {
> + BC_EVT_ID_None = 0,
> + BC_EVT_ID_RDWR_count = 1,
> + BC_EVT_ID_RDONLY_count = 2,
> + BC_EVT_ID_WRONLY_count = 3,
> +};
> +
> +/* BC_MON_CTL.STATUS field encodings */
> +enum {
> + BC_MON_CTL_STATUS_SUCCESS = 1,
> + BC_MON_CTL_STATUS_INVAL_OP = 2,
> + BC_MON_CTL_STATUS_INVAL_MCID = 3,
> + BC_MON_CTL_STATUS_INVAL_EVT_ID = 4,
> + BC_MON_CTL_STATUS_INVAL_AT = 5,
> +};
> +
> +/* Monitoring counter value */
> +REG64(BC_MON_CTR_VAL, 16);
> +FIELD(BC_MON_CTR_VAL, CTR, 0, 62);
> +FIELD(BC_MON_CTR_VAL, INVALID, 62, 1);
> +FIELD(BC_MON_CTR_VAL, OVF, 63, 1);
> +
> +/* Bandwidth Allocation control */
> +REG64(BC_ALLOC_CTL, 24);
> +FIELD(BC_ALLOC_CTL, OP, 0, 5);
> +FIELD(BC_ALLOC_CTL, AT, 5, 3);
> +FIELD(BC_ALLOC_CTL, RCID, 8, 12);
> +FIELD(BC_ALLOC_CTL, STATUS, 32, 7);
> +FIELD(BC_ALLOC_CTL, BUSY, 39, 1);
> +
> +/* Bandwidth allocation operations */
> +enum {
> + BC_ALLOC_OP_CONFIG_LIMIT = 1,
> + BC_ALLOC_OP_READ_LIMIT = 2,
> +};
> +
> +/* BC_ALLOC_CTL.STATUS field encodings */
> +enum {
> + BC_ALLOC_STATUS_SUCCESS = 1,
> + BC_ALLOC_STATUS_INVAL_OP = 2,
> + BC_ALLOC_STATUS_INVAL_RCID = 3,
> + BC_ALLOC_STATUS_INVAL_AT = 4,
> + BC_ALLOC_STATUS_INVAL_BLKS = 5,
> +};
> +
> +/* Bandwidth allocation */
> +REG64(BC_BW_ALLOC, 32);
> +FIELD(BC_BW_ALLOC, Rbwb, 0, 16);
> +FIELD(BC_BW_ALLOC, Mweight, 20, 8);
> +FIELD(BC_BW_ALLOC, sharedAT, 28, 3);
> +FIELD(BC_BW_ALLOC, useShared, 31, 1);
> +
> +
> +typedef struct BandwidthAllocation {
> + uint32_t Rbwb:16;
> + uint32_t Mweight:8;
> + uint32_t sharedAT:3;
> + bool useShared:1;
> +} BandwidthAllocation;
> +
> +typedef struct RiscvCbqriBandwidthState {
> + SysBusDevice parent_obj;
> + MemoryRegion mmio;
> +
> + /* cached value of some registers */
> + uint64_t bc_mon_ctl;
> + uint64_t bc_mon_ctr_val;
> + uint64_t bc_alloc_ctl;
> +
> + MonitorCounter *mon_counters;
> + BandwidthAllocation *bw_allocations;
> +
> + /* properties */
> +
> + uint64_t mmio_base;
> + uint16_t nb_mcids;
> + uint16_t nb_rcids;
> +
> + uint16_t nbwblks;
> + uint16_t mrbwb;
> +
> + bool supports_at_code;
> +
> + bool supports_alloc_op_config_limit;
> + bool supports_alloc_op_read_limit;
> +
> + bool supports_mon_op_config_event;
> + bool supports_mon_op_read_counter;
> +
> + bool supports_mon_evt_id_none;
> + bool supports_mon_evt_id_rdwr_count;
> + bool supports_mon_evt_id_rdonly_count;
> + bool supports_mon_evt_id_wronly_count;
> +} RiscvCbqriBandwidthState;
> +
I believe BandwidthAllocation could be renamed to RiscvCbrqiBandwidthAllocation for
naming consistency.
> +#define RISCV_CBQRI_BC(obj) \
> + OBJECT_CHECK(RiscvCbqriBandwidthState, (obj), TYPE_RISCV_CBQRI_BC)
> +
> +/*
> + * All bandwidth allocation records are contiguous to simplify
> + * allocation. The first one is used to hold the BC_BW_ALLOC register
> + * content, followed by respective records for each AT per RCID.
> + *
> + * Records are indexed by the AT encoding, so each RCID has one record
> + * per AT value whether or not the controller supports the code access
> + * type. is_valid_at() rejects an unsupported AT before a record is
> + * selected.
> + */
> +static unsigned int bw_alloc_index(uint32_t rcid, uint32_t at)
> +{
> + assert(at < BC_NB_ATS);
Please use g_assert() instead of assert().
> +
> + return 1 + rcid * BC_NB_ATS + at;
> +}
> +
> +static BandwidthAllocation *get_bw_alloc(RiscvCbqriBandwidthState *bc,
> + uint32_t rcid, uint32_t at)
> +{
> + return &bc->bw_allocations[bw_alloc_index(rcid, at)];
> +}
> +
[...]
> +
> +static void riscv_cbqri_bc_reset(DeviceState *dev)
> +{
> + RiscvCbqriBandwidthState *bc = RISCV_CBQRI_BC(dev);
> +
> + bc->bc_mon_ctl = 0;
> + bc->bc_alloc_ctl = 0;
> +}
> +
> +static const Property riscv_cbqri_bc_properties[] = {
> + DEFINE_PROP_UINT64("mmio_base", RiscvCbqriBandwidthState, mmio_base, 0),
> +
> + DEFINE_PROP_UINT16("max_mcids", RiscvCbqriBandwidthState, nb_mcids, 256),
> + DEFINE_PROP_UINT16("max_rcids", RiscvCbqriBandwidthState, nb_rcids, 64),
> + DEFINE_PROP_UINT16("nbwblks", RiscvCbqriBandwidthState, nbwblks, 1024),
> + DEFINE_PROP_UINT16("mrbwb", RiscvCbqriBandwidthState, mrbwb, 819),
> +
> + DEFINE_PROP_BOOL("at_code", RiscvCbqriBandwidthState,
> + supports_at_code, true),
> +
> + DEFINE_PROP_BOOL("alloc_op_config_limit", RiscvCbqriBandwidthState,
> + supports_alloc_op_config_limit, true),
> + DEFINE_PROP_BOOL("alloc_op_read_limit", RiscvCbqriBandwidthState,
> + supports_alloc_op_read_limit, true),
> +
> + DEFINE_PROP_BOOL("mon_op_config_event", RiscvCbqriBandwidthState,
> + supports_mon_op_config_event, true),
> + DEFINE_PROP_BOOL("mon_op_read_counter", RiscvCbqriBandwidthState,
> + supports_mon_op_read_counter, true),
> +
> + DEFINE_PROP_BOOL("mon_evt_id_none", RiscvCbqriBandwidthState,
> + supports_mon_evt_id_none, true),
> + DEFINE_PROP_BOOL("mon_evt_id_rdwr_count", RiscvCbqriBandwidthState,
> + supports_mon_evt_id_rdwr_count, true),
> + DEFINE_PROP_BOOL("mon_evt_id_rdonly_count", RiscvCbqriBandwidthState,
> + supports_mon_evt_id_rdonly_count, true),
> + DEFINE_PROP_BOOL("mon_evt_id_wronly_count", RiscvCbqriBandwidthState,
> + supports_mon_evt_id_wronly_count, true),
> +};
> +
> +static const VMStateDescription vmstate_riscv_cbqri_bc = {
> + .name = "riscv-cbqri-bandwidth",
> + .unmigratable = 1,
> +};
> +
> +static void riscv_cbqri_bc_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + dc->realize = riscv_cbqri_bc_realize;
> + set_bit(DEVICE_CATEGORY_MISC, dc->categories);
> + dc->desc = "RISC-V CBQRI Bandwidth Controller";
> + device_class_set_props(dc, riscv_cbqri_bc_properties);
> + dc->legacy_reset = riscv_cbqri_bc_reset;
We're moving away from legacy_reset for new devices. We're now declaring a Resettable
interface and setting reset phases.
For this device a simple way of doing it is to slightly change th reset callback params:
static void riscv_cbqri_bc_reset(Object *obj, ResetType type)
And then during class_init:
ResettableClass *rc = RESETTABLE_CLASS(klass);
rc->phases.hold = riscv_cbqri_bc_reset;
> + dc->vmsd = &vmstate_riscv_cbqri_bc;
> + dc->user_creatable = true;
> +}
> +
> +static const TypeInfo riscv_cbqri_bc_info = {
> + .name = TYPE_RISCV_CBQRI_BC,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(RiscvCbqriBandwidthState),
> + .class_init = riscv_cbqri_bc_class_init,
> +};
> +
> +static void riscv_cbqri_bc_register_types(void)
> +{
> + type_register_static(&riscv_cbqri_bc_info);
> +}
> +
> +DeviceState *riscv_cbqri_bc_create(hwaddr addr,
> + const RiscvCbqriBandwidthCaps *caps)
> +{
> + DeviceState *dev = qdev_new(TYPE_RISCV_CBQRI_BC);
> +
> + qdev_prop_set_uint64(dev, "mmio_base", addr);
> + qdev_prop_set_uint16(dev, "max_mcids", caps->nb_mcids);
> + qdev_prop_set_uint16(dev, "max_rcids", caps->nb_rcids);
> + qdev_prop_set_uint16(dev, "nbwblks", caps->nbwblks);
> + qdev_prop_set_uint16(dev, "mrbwb", caps->mrbwb);
> +
> + qdev_prop_set_bit(dev, "at_code",
> + caps->supports_at_code);
> + qdev_prop_set_bit(dev, "alloc_op_config_limit",
> + caps->supports_alloc_op_config_limit);
> + qdev_prop_set_bit(dev, "alloc_op_read_limit",
> + caps->supports_alloc_op_read_limit);
> + qdev_prop_set_bit(dev, "mon_op_config_event",
> + caps->supports_mon_op_config_event);
> + qdev_prop_set_bit(dev, "mon_op_read_counter",
> + caps->supports_mon_op_read_counter);
> + qdev_prop_set_bit(dev, "mon_evt_id_none",
> + caps->supports_mon_evt_id_none);
> + qdev_prop_set_bit(dev, "mon_evt_id_rdwr_count",
> + caps->supports_mon_evt_id_rdwr_count);
> + qdev_prop_set_bit(dev, "mon_evt_id_rdonly_count",
> + caps->supports_mon_evt_id_rdonly_count);
> + qdev_prop_set_bit(dev, "mon_evt_id_wronly_count",
> + caps->supports_mon_evt_id_wronly_count);
> +
> + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> +
> + return dev;
> +}
We don't have any callers for riscv_cbqri_bc_create() in the series so I can't
see how the intended use would be. In particular with the RiscvCbqriBandwidthCaps *caps
attribute.
Note that riscv_cbqri_bc_create() is blindly applying all settings from 'caps' to
the device. This is fine, but note that extra care is required when retrieving 'caps'
w.r.t the defaults established.
E.g. in riscv_cbqri_bc_properties[] we have:
> + DEFINE_PROP_UINT16("max_mcids", RiscvCbqriBandwidthState, nb_mcids, 256),
> + DEFINE_PROP_UINT16("mrbwb", RiscvCbqriBandwidthState, mrbwb, 819),
> +
> + DEFINE_PROP_BOOL("at_code", RiscvCbqriBandwidthState,
> + supports_at_code, true),
Now let's say that we'll add the device in the 'virt' machine, and I want to change just
nb_mcids to 512 and nothing else. If I do this:
RiscvCbqriBandwidthCaps caps = {.nb_mcids = 512};
(...)
riscv_cbqri_bc_create(addr, &caps);
I'll create a TYPE_RISCV_CBQRI_BC nb_mcids = 512 and everything else will be zeroed, disregarding
all defaults from riscv_cbqri_bc_properties[].
My suggestion here is to create a riscv_cbqri_bc_caps_init(RiscvCbqriBandwidthCaps *caps) helper
that callers can use to set the struct defaults first. We would then do:
RiscvCbqriBandwidthCaps caps;
riscv_cbqri_bc_caps_init(caps);
caps.nb_mcids = 512;
(...)
riscv_cbqri_bc_create(addr, &caps);
We have precedent for this pattern - most RISC-V boards use riscv_boot_info_init() to init
a boot_info struct.
Thanks,
Daniel
> +
> +type_init(riscv_cbqri_bc_register_types)
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 5/6] hw/riscv: add CBQRI to Kconfig and build if enabled
2026-11-01 20:25 ` [PATCH v7 5/6] hw/riscv: add CBQRI to Kconfig and build if enabled Drew Fustini
@ 2026-11-09 13:14 ` Daniel Henrique Barboza
0 siblings, 0 replies; 10+ messages in thread
From: Daniel Henrique Barboza @ 2026-11-09 13:14 UTC (permalink / raw)
To: Drew Fustini, qemu-devel
Cc: qemu-riscv, Palmer Dabbelt, Alistair Francis, Sunil V L,
Anup Patel, Weiwei Li, Liu Zhiwei, Chao Liu, Paolo Bonzini,
Nicolas Pitre, Kornel Dulęba, Atish Patra,
Radim Krčmář, yunhui cui, Chen Pei, guo.wenjia23,
liu.qingtao2
On 11/1/2026 5:25 PM, Drew Fustini wrote:
> From: Nicolas Pitre <npitre@baylibre.com>
>
> Add a boolean Kconfig symbol for CBQRI and imply it should be enabled
> for the RISC-V virt machine. Build the CBQRI controllers when RISC-V
> CBQRI is enabled.
>
> Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
> Signed-off-by: Drew Fustini <fustini@kernel.org>
> ---
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
> hw/riscv/Kconfig | 4 ++++
> hw/riscv/meson.build | 2 ++
> 2 files changed, 6 insertions(+)
>
> diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
> index d89fdfcc8671..98bdebb4b814 100644
> --- a/hw/riscv/Kconfig
> +++ b/hw/riscv/Kconfig
> @@ -1,3 +1,6 @@
> +config RISCV_CBQRI
> + bool
> +
> config RISCV_IOMMU
> bool
>
> @@ -68,6 +71,7 @@ config RISCV_VIRT
> select PLATFORM_BUS
> select ACPI
> select ACPI_PCI
> + imply RISCV_CBQRI
>
> config RISCV_SERVER_PLATFORM_REF
> bool
> diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
> index 6bdabb954473..c7952d67755e 100644
> --- a/hw/riscv/meson.build
> +++ b/hw/riscv/meson.build
> @@ -19,6 +19,8 @@ riscv_ss.add(when: 'CONFIG_RISCV_IOMMU', if_true: files(
> 'riscv-iommu.c', 'riscv-iommu-pci.c', 'riscv-iommu-sys.c', 'riscv-iommu-hpm.c'))
> riscv_ss.add(when: 'CONFIG_MICROBLAZE_V', if_true: files('microblaze-v-generic.c'))
> riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c'))
> +riscv_ss.add(when: 'CONFIG_RISCV_CBQRI', if_true: files('cbqri_capacity.c', 'cbqri_bandwidth.c'))
> +
> riscv_ss.add(when: 'CONFIG_RISCV_MIPS_CPS', if_true: files('cps.c'))
> riscv_ss.add(when: 'CONFIG_MIPS_BOSTON_AIA', if_true: files('boston-aia.c'))
> riscv_ss.add(when: 'CONFIG_K230', if_true: files('k230.c'))
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v7 4/6] hw/riscv: implement CBQRI bandwidth controller
2026-11-09 13:09 ` Daniel Henrique Barboza
@ 2026-11-11 18:48 ` Drew Fustini
0 siblings, 0 replies; 10+ messages in thread
From: Drew Fustini @ 2026-11-11 18:48 UTC (permalink / raw)
To: Daniel Henrique Barboza
Cc: qemu-devel, qemu-riscv, Palmer Dabbelt, Alistair Francis,
Sunil V L, Anup Patel, Weiwei Li, Liu Zhiwei, Chao Liu,
Paolo Bonzini, Nicolas Pitre, Kornel Dulęba, Atish Patra,
Radim Krčmář, yunhui cui, Chen Pei, guo.wenjia23,
liu.qingtao2
On Mon, Nov 09, 2026 at 10:09:29AM -0300, Daniel Henrique Barboza wrote:
> On 11/1/2026 5:25 PM, Drew Fustini wrote:
> > From: Nicolas Pitre <npitre@baylibre.com>
> >
> > Implement a bandwidth controller according to the Capacity and Bandwidth
> > QoS Register Interface (CBQRI) which supports these capabilities:
> >
> > - Number of access types: 2 (code and data)
> > - Usage monitoring operations: CONFIG_EVENT, READ_COUNTER
> > - Event IDs supported: None, Total read/write byte count, Total
> > read byte count, Total write byte count
> > - Bandwidth allocation operations: CONFIG_LIMIT, READ_LIMIT
> >
> > The controller gives the Linux resctrl support for CBQRI a device to
> > configure and read back bandwidth limits per RCID on the virt machine.
> > Only the register interface is modeled. The configured limits are not
> > enforced and the monitoring counters are always marked INVALID because
> > QEMU does not model memory bandwidth usage.
> >
> > Link: https://github.com/riscv-non-isa/riscv-cbqri/releases/tag/v1.0
> > Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
> > [fustini: add fields introduced in the ratified spec: rpfx and p]
> > Signed-off-by: Drew Fustini <fustini@kernel.org>
> > ---
>
> LGTM overall. A few comments:
Thanks for reviewing!
[..]
> > +typedef struct BandwidthAllocation {
> > + uint32_t Rbwb:16;
> > + uint32_t Mweight:8;
> > + uint32_t sharedAT:3;
> > + bool useShared:1;
> > +} BandwidthAllocation;
[..]
> I believe BandwidthAllocation could be renamed to RiscvCbrqiBandwidthAllocation for
> naming consistency.
Good idea, I'll change it.
> > +#define RISCV_CBQRI_BC(obj) \
> > + OBJECT_CHECK(RiscvCbqriBandwidthState, (obj), TYPE_RISCV_CBQRI_BC)
> > +
> > +/*
> > + * All bandwidth allocation records are contiguous to simplify
> > + * allocation. The first one is used to hold the BC_BW_ALLOC register
> > + * content, followed by respective records for each AT per RCID.
> > + *
> > + * Records are indexed by the AT encoding, so each RCID has one record
> > + * per AT value whether or not the controller supports the code access
> > + * type. is_valid_at() rejects an unsupported AT before a record is
> > + * selected.
> > + */
> > +static unsigned int bw_alloc_index(uint32_t rcid, uint32_t at)
> > +{
> > + assert(at < BC_NB_ATS);
>
> Please use g_assert() instead of assert().
Okay, I will change it.
[..]
> > +static void riscv_cbqri_bc_class_init(ObjectClass *klass, const void *data)
> > +{
> > + DeviceClass *dc = DEVICE_CLASS(klass);
> > + dc->realize = riscv_cbqri_bc_realize;
> > + set_bit(DEVICE_CATEGORY_MISC, dc->categories);
> > + dc->desc = "RISC-V CBQRI Bandwidth Controller";
> > + device_class_set_props(dc, riscv_cbqri_bc_properties);
> > + dc->legacy_reset = riscv_cbqri_bc_reset;
>
> We're moving away from legacy_reset for new devices. We're now declaring a Resettable
> interface and setting reset phases.
>
> For this device a simple way of doing it is to slightly change th reset callback params:
>
>
> static void riscv_cbqri_bc_reset(Object *obj, ResetType type)
>
> And then during class_init:
>
> ResettableClass *rc = RESETTABLE_CLASS(klass);
>
> rc->phases.hold = riscv_cbqri_bc_reset;
Thanks for letting me know. I will switch to the Resettable interface.
[..]
> > +DeviceState *riscv_cbqri_bc_create(hwaddr addr,
> > + const RiscvCbqriBandwidthCaps *caps)
> > +{
> > + DeviceState *dev = qdev_new(TYPE_RISCV_CBQRI_BC);
> > +
> > + qdev_prop_set_uint64(dev, "mmio_base", addr);
> > + qdev_prop_set_uint16(dev, "max_mcids", caps->nb_mcids);
> > + qdev_prop_set_uint16(dev, "max_rcids", caps->nb_rcids);
> > + qdev_prop_set_uint16(dev, "nbwblks", caps->nbwblks);
> > + qdev_prop_set_uint16(dev, "mrbwb", caps->mrbwb);
> > +
> > + qdev_prop_set_bit(dev, "at_code",
> > + caps->supports_at_code);
> > + qdev_prop_set_bit(dev, "alloc_op_config_limit",
> > + caps->supports_alloc_op_config_limit);
> > + qdev_prop_set_bit(dev, "alloc_op_read_limit",
> > + caps->supports_alloc_op_read_limit);
> > + qdev_prop_set_bit(dev, "mon_op_config_event",
> > + caps->supports_mon_op_config_event);
> > + qdev_prop_set_bit(dev, "mon_op_read_counter",
> > + caps->supports_mon_op_read_counter);
> > + qdev_prop_set_bit(dev, "mon_evt_id_none",
> > + caps->supports_mon_evt_id_none);
> > + qdev_prop_set_bit(dev, "mon_evt_id_rdwr_count",
> > + caps->supports_mon_evt_id_rdwr_count);
> > + qdev_prop_set_bit(dev, "mon_evt_id_rdonly_count",
> > + caps->supports_mon_evt_id_rdonly_count);
> > + qdev_prop_set_bit(dev, "mon_evt_id_wronly_count",
> > + caps->supports_mon_evt_id_wronly_count);
> > +
> > + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
> > +
> > + return dev;
> > +}
>
> We don't have any callers for riscv_cbqri_bc_create() in the series so I can't
> see how the intended use would be. In particular with the RiscvCbqriBandwidthCaps *caps
> attribute.
Good catch. They were for the fixed example SoC for v1 back in the 2023
v1. v2 switched to -device and nothing has called them since.
> Note that riscv_cbqri_bc_create() is blindly applying all settings from 'caps' to
> the device. This is fine, but note that extra care is required when retrieving 'caps'
> w.r.t the defaults established.
>
> E.g. in riscv_cbqri_bc_properties[] we have:
>
> > + DEFINE_PROP_UINT16("max_mcids", RiscvCbqriBandwidthState, nb_mcids, 256),
>
> > + DEFINE_PROP_UINT16("mrbwb", RiscvCbqriBandwidthState, mrbwb, 819),
> > +
> > + DEFINE_PROP_BOOL("at_code", RiscvCbqriBandwidthState,
> > + supports_at_code, true),
>
>
> Now let's say that we'll add the device in the 'virt' machine, and I want to change just
> nb_mcids to 512 and nothing else. If I do this:
>
>
> RiscvCbqriBandwidthCaps caps = {.nb_mcids = 512};
>
> (...)
>
> riscv_cbqri_bc_create(addr, &caps);
>
>
>
> I'll create a TYPE_RISCV_CBQRI_BC nb_mcids = 512 and everything else will be zeroed, disregarding
> all defaults from riscv_cbqri_bc_properties[].
>
>
> My suggestion here is to create a riscv_cbqri_bc_caps_init(RiscvCbqriBandwidthCaps *caps) helper
> that callers can use to set the struct defaults first. We would then do:
>
>
> RiscvCbqriBandwidthCaps caps;
>
> riscv_cbqri_bc_caps_init(caps);
> caps.nb_mcids = 512;
>
> (...)
>
> riscv_cbqri_bc_create(addr, &caps);
>
>
> We have precedent for this pattern - most RISC-V boards use riscv_boot_info_init() to init
> a boot_info struct.
Thanks for the guidance. I will add riscv_cbqri_bc_caps_init() and
riscv_cbqri_cc_caps_init() to set the property defaults in the caps
struct.
Thanks,
Drew
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-11-11 18:49 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-01 20:25 [PATCH v7 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
2026-11-01 20:25 ` [PATCH v7 1/6] riscv: implement Ssqosid extension and srmcfg CSR Drew Fustini
2026-11-01 20:25 ` [PATCH v7 2/6] hw/riscv: define capabilities of CBQRI controllers Drew Fustini
2026-11-01 20:25 ` [PATCH v7 3/6] hw/riscv: implement CBQRI capacity controller Drew Fustini
2026-11-01 20:25 ` [PATCH v7 4/6] hw/riscv: implement CBQRI bandwidth controller Drew Fustini
2026-11-09 13:09 ` Daniel Henrique Barboza
2026-11-11 18:48 ` Drew Fustini
2026-11-01 20:25 ` [PATCH v7 5/6] hw/riscv: add CBQRI to Kconfig and build if enabled Drew Fustini
2026-11-09 13:14 ` Daniel Henrique Barboza
2026-11-01 20:25 ` [PATCH v7 6/6] hw/riscv: add CBQRI controllers to virt machine Drew Fustini
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.