* [PATCH v6 1/6] riscv: implement Ssqosid extension and srmcfg CSR
2026-10-26 17:47 [PATCH v6 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
@ 2026-10-26 17:47 ` Drew Fustini
2026-10-27 0:22 ` Alistair Francis
2026-10-26 17:47 ` [PATCH v6 2/6] hw/riscv: define capabilities of CBQRI controllers Drew Fustini
` (4 subsequent siblings)
5 siblings, 1 reply; 14+ messages in thread
From: Drew Fustini @ 2026-10-26 17:47 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 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 on v10.1.50, 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/tcg/csr.c | 37 +++++++++++++++++++++++++++++++++++++
6 files changed, 54 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..628183ae0fe2 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 extensions) */
+#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/tcg/csr.c b/target/riscv/tcg/csr.c
index af444e3e6f32..fc94f569ea8a 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
+ * and virt 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
/*
@@ -6377,6 +6411,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] 14+ messages in thread* Re: [PATCH v6 1/6] riscv: implement Ssqosid extension and srmcfg CSR
2026-10-26 17:47 ` [PATCH v6 1/6] riscv: implement Ssqosid extension and srmcfg CSR Drew Fustini
@ 2026-10-27 0:22 ` Alistair Francis
2026-10-28 2:59 ` Drew Fustini
0 siblings, 1 reply; 14+ messages in thread
From: Alistair Francis @ 2026-10-27 0:22 UTC (permalink / raw)
To: fustini@kernel.org, qemu-devel@nongnu.org
Cc: mindal@semihalf.com, sunilvl@oss.qualcomm.com,
zhiwei_liu@linux.alibaba.com, liu.qingtao2@zte.com.cn,
anup.patel@oss.qualcomm.com, cp0613@linux.alibaba.com,
palmer@dabbelt.com, daniel.barboza@oss.qualcomm.com,
radim.krcmar@oss.qualcomm.com, qemu-riscv@nongnu.org,
pbonzini@redhat.com, guo.wenjia23@zte.com.cn, npitre@baylibre.com,
cuiyunhui@bytedance.com, atish.patra@linux.dev,
liwei1518@gmail.com, chao.liu@processmission.com
On Mon, 2026-10-26 at 10:47 -0700, Drew Fustini wrote:
> 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 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 on v10.1.50, fix check_srmcfg]
> Reviewed-by: Radim Krčmář <radim.krcmar@oss.qualcomm.com>
> Signed-off-by: Drew Fustini <fustini@kernel.org>
I'm just meat proxying this one, but it looks believable, let me know
if it's wrong though:
- [HIGH] In target/riscv/tcg/csr.c, none of write_mstateen0,
write_mstateen0h, write_hstateen0, or write_hstateen0h ever ORs
SMSTATEEN0_SRMCFG into wr_mask when ext_ssqosid is set, so bit 55 of
mstateen0/hstateen0 stays read-only zero and M-mode firmware writes
that
enable srmcfg access are silently dropped, violating the Smstateen
spec (a
bit for an implemented extension must not be read-only zero).
check_srmcfg
gates the new CSR on smstateen_acc_ok(..., SMSTATEEN0_SRMCFG), and
Smstateen is enabled by default on the same dynamic CPUs that get
ext_ssqosid by default, so on the stock virt machine every S-mode
(V=0)
srmcfg access traps illegal instruction and VS-mode additionally
takes a
virtual instruction fault: the QoS feature this series exists to
expose is
systematically unusable. Fix by ORing the bit into the stateen write
masks
like the existing CTR, P1P13, and SVSLCT cases.
- [MEDIUM] In target/riscv/cpu.h, env->srmcfg is new guest-writable
architected state (written via write_srmcfg in
target/riscv/tcg/csr.c) but
target/riscv/machine.c never migrates it: vmstate_riscv_cpu has no
ssqosid/srmcfg field or subsection. After live migration,
savevm/loadvm, or
snapshot restore the destination CPU resumes with srmcfg = 0 while
the guest
believes its programmed RCID/MCID are still in effect. Add a
versioned
vmstate_ssqosid subsection with a .needed predicate on ext_ssqosid,
following
the vmstate_smstateen/vmstate_envcfg pattern.
- [MEDIUM] In target/riscv/cpu.c (isa_edata_arr), Ssqosid is registered
as a
user-facing extension and enabled by default on BASE32/BASE64 with no
tests/tcg coverage anywhere in the tree. The RISC-V upstreaming rules
require unit tests for new extensions' CSR accesses, and the stateen
gating
and V=1 trap paths added in check_srmcfg are exactly what such tests
would
exercise -- they would have caught the broken SRMCFG stateen bit
above. Add
tests/tcg cases for srmcfg read/write, WARL masking, and the
stateen/virtual-instruction-fault gating.
- [LOW] nit: in target/riscv/tcg/csr.c, the check_srmcfg comment says
"we need to throw and virt instruction fault"; s/and virt/a virtual/.
Alistair
> ---
> 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/tcg/csr.c | 37
> +++++++++++++++++++++++++++++++++++++
> 6 files changed, 54 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..628183ae0fe2 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 extensions) */
> +#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/tcg/csr.c b/target/riscv/tcg/csr.c
> index af444e3e6f32..fc94f569ea8a 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
> + * and virt 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
>
> /*
> @@ -6377,6 +6411,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 },
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v6 1/6] riscv: implement Ssqosid extension and srmcfg CSR
2026-10-27 0:22 ` Alistair Francis
@ 2026-10-28 2:59 ` Drew Fustini
2026-10-28 3:55 ` Alistair Francis
0 siblings, 1 reply; 14+ messages in thread
From: Drew Fustini @ 2026-10-28 2:59 UTC (permalink / raw)
To: Alistair Francis
Cc: qemu-devel@nongnu.org, mindal@semihalf.com,
sunilvl@oss.qualcomm.com, zhiwei_liu@linux.alibaba.com,
liu.qingtao2@zte.com.cn, anup.patel@oss.qualcomm.com,
cp0613@linux.alibaba.com, palmer@dabbelt.com,
daniel.barboza@oss.qualcomm.com, radim.krcmar@oss.qualcomm.com,
qemu-riscv@nongnu.org, pbonzini@redhat.com,
guo.wenjia23@zte.com.cn, npitre@baylibre.com,
cuiyunhui@bytedance.com, atish.patra@linux.dev,
liwei1518@gmail.com, chao.liu@processmission.com
On Tue, Oct 27, 2026 at 12:22:02AM +0000, Alistair Francis wrote:
> On Mon, 2026-10-26 at 10:47 -0700, Drew Fustini wrote:
> > 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 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 on v10.1.50, fix check_srmcfg]
> > Reviewed-by: Radim Krčmář <radim.krcmar@oss.qualcomm.com>
> > Signed-off-by: Drew Fustini <fustini@kernel.org>
>
> I'm just meat proxying this one, but it looks believable, let me know
> if it's wrong though:
Is this something I can run myself in the future?
I can run local sashiko for my linux patches but I wasn't sure what to
do for qemu.
> - [HIGH] In target/riscv/tcg/csr.c, none of write_mstateen0,
> write_mstateen0h, write_hstateen0, or write_hstateen0h ever ORs
> SMSTATEEN0_SRMCFG into wr_mask when ext_ssqosid is set, so bit 55 of
> mstateen0/hstateen0 stays read-only zero and M-mode firmware writes
> that enable srmcfg access are silently dropped, violating the
> Smstateen spec (a bit for an implemented extension must not be
> read-only zero). check_srmcfg gates the new CSR on
> smstateen_acc_ok(..., SMSTATEEN0_SRMCFG), and Smstateen is enabled by
> default on the same dynamic CPUs that get ext_ssqosid by default, so
> on the stock virt machine every S-mode (V=0) srmcfg access traps
> illegal instruction and VS-mode additionally takes a virtual
> instruction fault: the QoS feature this series exists to expose is
> systematically unusable. Fix by ORing the bit into the stateen write
> masks like the existing CTR, P1P13, and SVSLCT cases.
This is a real issue for mstateen0 and mstateen0h. I will add
SMSTATEEN0_SRMCFG to wr_mask in write_mstateen0() and
write_mstateen0h(). However, I believe hstateen0 bit 55 is WPRI so I
think the current behavior is correct.
> - [MEDIUM] In target/riscv/cpu.h, env->srmcfg is new guest-writable
> architected state (written via write_srmcfg in
> target/riscv/tcg/csr.c) but target/riscv/machine.c never migrates
> it: vmstate_riscv_cpu has no ssqosid/srmcfg field or subsection.
> After live migration, savevm/loadvm, or snapshot restore the
> destination CPU resumes with srmcfg = 0 while the guest believes its
> programmed RCID/MCID are still in effect. Add a versioned
> vmstate_ssqosid subsection with a .needed predicate on ext_ssqosid,
> following the vmstate_smstateen/vmstate_envcfg pattern.
I'll add a VMStateDescription for Ssqosid.
> - [MEDIUM] In target/riscv/cpu.c (isa_edata_arr), Ssqosid is
> registered as a user-facing extension and enabled by default on
> BASE32/BASE64 with no tests/tcg coverage anywhere in the tree. The
> RISC-V upstreaming rules require unit tests for new extensions' CSR
> accesses, and the stateen gating and V=1 trap paths added in
> check_srmcfg are exactly what such tests would exercise -- they would
> have caught the broken SRMCFG stateen bit above. Add tests/tcg cases
> for srmcfg read/write, WARL masking, and the
> stateen/virtual-instruction-fault gating.
I'll add tests for srmcfg.
> - [LOW] nit: in target/riscv/tcg/csr.c, the check_srmcfg comment says
> "we need to throw and virt instruction fault"; s/and virt/a virtual/.
I'll fix.
Thanks,
Drew
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v6 1/6] riscv: implement Ssqosid extension and srmcfg CSR
2026-10-28 2:59 ` Drew Fustini
@ 2026-10-28 3:55 ` Alistair Francis
0 siblings, 0 replies; 14+ messages in thread
From: Alistair Francis @ 2026-10-28 3:55 UTC (permalink / raw)
To: fustini@kernel.org
Cc: mindal@semihalf.com, qemu-devel@nongnu.org,
zhiwei_liu@linux.alibaba.com, sunilvl@oss.qualcomm.com,
liu.qingtao2@zte.com.cn, anup.patel@oss.qualcomm.com,
cp0613@linux.alibaba.com, qemu-riscv@nongnu.org,
npitre@baylibre.com, radim.krcmar@oss.qualcomm.com,
daniel.barboza@oss.qualcomm.com, guo.wenjia23@zte.com.cn,
pbonzini@redhat.com, palmer@dabbelt.com, cuiyunhui@bytedance.com,
atish.patra@linux.dev, liwei1518@gmail.com,
chao.liu@processmission.com
On Tue, 2026-10-27 at 19:59 -0700, Drew Fustini wrote:
> On Tue, Oct 27, 2026 at 12:22:02AM +0000, Alistair Francis wrote:
> > On Mon, 2026-10-26 at 10:47 -0700, Drew Fustini wrote:
> > > 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 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 on v10.1.50, fix check_srmcfg]
> > > Reviewed-by: Radim Krčmář <radim.krcmar@oss.qualcomm.com>
> > > Signed-off-by: Drew Fustini <fustini@kernel.org>
> >
> > I'm just meat proxying this one, but it looks believable, let me
> > know
> > if it's wrong though:
>
> Is this something I can run myself in the future?
Kind of! Working on it
>
> I can run local sashiko for my linux patches but I wasn't sure what
> to
> do for qemu.
Yeah, it's Sashiko as well:
https://github.com/sashiko-dev/sashiko/pull/753
Alistair
>
> > - [HIGH] In target/riscv/tcg/csr.c, none of write_mstateen0,
> > write_mstateen0h, write_hstateen0, or write_hstateen0h ever ORs
> > SMSTATEEN0_SRMCFG into wr_mask when ext_ssqosid is set, so bit 55
> > of
> > mstateen0/hstateen0 stays read-only zero and M-mode firmware writes
> > that enable srmcfg access are silently dropped, violating the
> > Smstateen spec (a bit for an implemented extension must not be
> > read-only zero). check_srmcfg gates the new CSR on
> > smstateen_acc_ok(..., SMSTATEEN0_SRMCFG), and Smstateen is enabled
> > by
> > default on the same dynamic CPUs that get ext_ssqosid by default,
> > so
> > on the stock virt machine every S-mode (V=0) srmcfg access traps
> > illegal instruction and VS-mode additionally takes a virtual
> > instruction fault: the QoS feature this series exists to expose is
> > systematically unusable. Fix by ORing the bit into the stateen
> > write
> > masks like the existing CTR, P1P13, and SVSLCT cases.
>
> This is a real issue for mstateen0 and mstateen0h. I will add
> SMSTATEEN0_SRMCFG to wr_mask in write_mstateen0() and
> write_mstateen0h(). However, I believe hstateen0 bit 55 is WPRI so I
> think the current behavior is correct.
>
> > - [MEDIUM] In target/riscv/cpu.h, env->srmcfg is new guest-writable
> > architected state (written via write_srmcfg in
> > target/riscv/tcg/csr.c) but target/riscv/machine.c never migrates
> > it: vmstate_riscv_cpu has no ssqosid/srmcfg field or subsection.
> > After live migration, savevm/loadvm, or snapshot restore the
> > destination CPU resumes with srmcfg = 0 while the guest believes
> > its
> > programmed RCID/MCID are still in effect. Add a versioned
> > vmstate_ssqosid subsection with a .needed predicate on
> > ext_ssqosid,
> > following the vmstate_smstateen/vmstate_envcfg pattern.
>
> I'll add a VMStateDescription for Ssqosid.
>
> > - [MEDIUM] In target/riscv/cpu.c (isa_edata_arr), Ssqosid is
> > registered as a user-facing extension and enabled by default on
> > BASE32/BASE64 with no tests/tcg coverage anywhere in the tree. The
> > RISC-V upstreaming rules require unit tests for new extensions' CSR
> > accesses, and the stateen gating and V=1 trap paths added in
> > check_srmcfg are exactly what such tests would exercise -- they
> > would
> > have caught the broken SRMCFG stateen bit above. Add tests/tcg
> > cases
> > for srmcfg read/write, WARL masking, and the
> > stateen/virtual-instruction-fault gating.
>
> I'll add tests for srmcfg.
>
> > - [LOW] nit: in target/riscv/tcg/csr.c, the check_srmcfg comment
> > says
> > "we need to throw and virt instruction fault"; s/and virt/a
> > virtual/.
>
> I'll fix.
>
> Thanks,
> Drew
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v6 2/6] hw/riscv: define capabilities of CBQRI controllers
2026-10-26 17:47 [PATCH v6 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
2026-10-26 17:47 ` [PATCH v6 1/6] riscv: implement Ssqosid extension and srmcfg CSR Drew Fustini
@ 2026-10-26 17:47 ` Drew Fustini
2026-10-26 17:47 ` [PATCH v6 3/6] hw/riscv: implement CBQRI capacity controller Drew Fustini
` (3 subsequent siblings)
5 siblings, 0 replies; 14+ messages in thread
From: Drew Fustini @ 2026-10-26 17:47 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 | 70 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 77 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..68d894f97220
--- /dev/null
+++ b/include/hw/riscv/cbqri.h
@@ -0,0 +1,70 @@
+/*
+ * 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_data:1;
+ 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_data:1;
+ 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,
+ const char *target_name);
+DeviceState *riscv_cbqri_bc_create(hwaddr addr,
+ const RiscvCbqriBandwidthCaps *caps,
+ const char *target_name);
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v6 3/6] hw/riscv: implement CBQRI capacity controller
2026-10-26 17:47 [PATCH v6 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
2026-10-26 17:47 ` [PATCH v6 1/6] riscv: implement Ssqosid extension and srmcfg CSR Drew Fustini
2026-10-26 17:47 ` [PATCH v6 2/6] hw/riscv: define capabilities of CBQRI controllers Drew Fustini
@ 2026-10-26 17:47 ` Drew Fustini
2026-10-28 13:32 ` Prajwal N
2026-10-26 17:47 ` [PATCH v6 4/6] hw/riscv: implement CBQRI bandwidth controller Drew Fustini
` (2 subsequent siblings)
5 siblings, 1 reply; 14+ messages in thread
From: Drew Fustini @ 2026-10-26 17:47 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 | 727 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 728 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..7ea20532b950
--- /dev/null
+++ b/hw/riscv/cbqri_capacity.c
@@ -0,0 +1,727 @@
+/*
+ * 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 "target/riscv/cpu.h"
+#include "hw/riscv/cbqri.h"
+
+/* Encodings of `AT` field */
+enum {
+ CC_AT_DATA = 0,
+ CC_AT_CODE = 1,
+};
+
+/* 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 MonitorCounter {
+ uint64_t ctr_val;
+ int at;
+ int evt_id;
+ bool active;
+} MonitorCounter;
+
+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;
+ char *target;
+ uint16_t nb_mcids;
+ uint16_t nb_rcids;
+
+ uint16_t ncblks;
+
+ bool supports_at_data;
+ 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 great than 64)
+ * holds the CC_BLOCK_MASK register content.
+ *
+ * The following slot holds the 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 which has AT types Data and Code enabled and
+ * 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 and AT types Data
+ * and Code. 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
+ *
+ */
+ unsigned int nb_ats = 0;
+ nb_ats += !!cc->supports_at_data;
+ nb_ats += !!cc->supports_at_code;
+ nb_ats = MAX(nb_ats, 1);
+ assert(at < nb_ats);
+
+ unsigned int blockmask_offset;
+ unsigned int blockmask_slots = get_slots(cc);
+ blockmask_offset = blockmask_slots * ((rcid * 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 cc->supports_at_data;
+ 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_data &&
+ !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_data &&
+ !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 registers 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-type 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_STRING("target", RiscvCbqriCapacityState, target),
+
+ 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_data", RiscvCbqriCapacityState,
+ supports_at_data, true),
+ 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 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->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,
+ const char *target_name)
+{
+ DeviceState *dev = qdev_new(TYPE_RISCV_CBQRI_CC);
+
+ qdev_prop_set_uint64(dev, "mmio_base", addr);
+ qdev_prop_set_string(dev, "target", target_name);
+ 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_data",
+ caps->supports_at_data);
+ 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)
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v6 3/6] hw/riscv: implement CBQRI capacity controller
2026-10-26 17:47 ` [PATCH v6 3/6] hw/riscv: implement CBQRI capacity controller Drew Fustini
@ 2026-10-28 13:32 ` Prajwal N
0 siblings, 0 replies; 14+ messages in thread
From: Prajwal N @ 2026-10-28 13:32 UTC (permalink / raw)
To: Drew Fustini, 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, prajwal.n.oss
Hi Drew,
On 26-10-2026 23:17, Drew Fustini wrote:
> 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 | 727 ++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 728 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..7ea20532b950
> --- /dev/null
> +++ b/hw/riscv/cbqri_capacity.c
> @@ -0,0 +1,727 @@
> +/*
> + * 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 "target/riscv/cpu.h"
> +#include "hw/riscv/cbqri.h"
[...]
> +static void riscv_cbqri_cc_write(void *opaque, hwaddr addr,
> + uint64_t value, unsigned size)
> +{
> + RiscvCbqriCapacityState *cc = opaque;
> +
> + assert((addr % 8) == 0);
> +
The same comments in the riscv_cbqri_cc_read() below are applicable
here as well.
> + 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);
> +
To access the 64 bit CBQRI register, the Linux kernel is performing two
32 bit reads. The register offsets that are passed to this function are 0x00,
0x04, 0x08 ... The assert is triggered for the addr 0x04, 0x0c.. and the
QEMU exists.
This check should have been:
assert((addr %4) == 0)
The switch statement uses the register offsets that are 8-byte aligned.
A_CC_CAPABILITIES (0x00), A_CC_MON_CTL (0x08) etc.
Since the kernel invokes read twice, the second readl() has the offset addr
0x04. This does not match any offset and falls into the default case.
The following change fixes the problem.
hwaddr reg_addr = addr & ~7ULL; /* align down to 8-byte boundary */> + switch (addr) {
switch (reg_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];
> + }
> +
The logic within the switch statement always computes the full 64-bit register
value. But the Linux kernel requested for 32 bits. The kernel is creating a
64-bit value with the two 32-bit values. This should return only the relevant half.
The following code snippet fixed the problem.
/* Return the correct 32-bit */
if (size == 4) {
if (addr & 4)
return value >> 32; // high word
else
return value & 0xffffffff; // low word
}
Thanks,
Prajwal N
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v6 4/6] hw/riscv: implement CBQRI bandwidth controller
2026-10-26 17:47 [PATCH v6 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
` (2 preceding siblings ...)
2026-10-26 17:47 ` [PATCH v6 3/6] hw/riscv: implement CBQRI capacity controller Drew Fustini
@ 2026-10-26 17:47 ` Drew Fustini
2026-10-27 0:18 ` Alistair Francis
2026-10-26 17:47 ` [PATCH v6 5/6] hw/riscv: add CBQRI to Kconfig and build if enabled Drew Fustini
2026-10-26 17:47 ` [PATCH v6 6/6] hw/riscv: add CBQRI controllers to virt machine Drew Fustini
5 siblings, 1 reply; 14+ messages in thread
From: Drew Fustini @ 2026-10-26 17:47 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
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 | 658 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 659 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..38b41301421b
--- /dev/null
+++ b/hw/riscv/cbqri_bandwidth.c
@@ -0,0 +1,658 @@
+/*
+ * 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 "target/riscv/cpu.h"
+#include "hw/riscv/cbqri.h"
+
+/* Encodings of `AT` field */
+enum {
+ BC_AT_DATA = 0,
+ BC_AT_CODE = 1,
+};
+
+/* 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 MonitorCounter {
+ uint64_t ctr_val;
+ int at;
+ int evt_id;
+ bool active;
+} MonitorCounter;
+
+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;
+ uint64_t bc_bw_alloc;
+
+ MonitorCounter *mon_counters;
+ BandwidthAllocation *bw_allocations;
+
+ /* properties */
+
+ uint64_t mmio_base;
+ char *target;
+ uint16_t nb_mcids;
+ uint16_t nb_rcids;
+
+ uint16_t nbwblks;
+ uint16_t mrbwb;
+
+ bool supports_at_data;
+ 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)
+
+static BandwidthAllocation *get_bw_alloc(RiscvCbqriBandwidthState *bc,
+ uint32_t rcid, uint32_t at)
+{
+ /*
+ * 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.
+ */
+
+ unsigned int nb_ats = 0;
+ nb_ats += !!bc->supports_at_data;
+ nb_ats += !!bc->supports_at_code;
+ nb_ats = MAX(nb_ats, 1);
+ assert(at < nb_ats);
+
+ return &bc->bw_allocations[1 + rcid * nb_ats + 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 bc->supports_at_data;
+ 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_data &&
+ !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)) {
+ /* capacity 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_data &&
+ !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) {
+ /* capacity 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_data &&
+ !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);
+ BandwidthAllocation *bw_alloc_end = get_bw_alloc(bc, bc->nb_rcids, 0);
+ unsigned int bw_alloc_size = bw_alloc_end - bc->bw_allocations;
+ bc->bw_allocations = g_new0(BandwidthAllocation, bw_alloc_size);
+
+ 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_STRING("target", RiscvCbqriBandwidthState, target),
+
+ 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_data", RiscvCbqriBandwidthState,
+ supports_at_data, true),
+ 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 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->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,
+ const char *target_name)
+{
+ DeviceState *dev = qdev_new(TYPE_RISCV_CBQRI_BC);
+
+ qdev_prop_set_uint64(dev, "mmio_base", addr);
+ qdev_prop_set_string(dev, "target", target_name);
+ 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_data",
+ caps->supports_at_data);
+ 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] 14+ messages in thread* Re: [PATCH v6 4/6] hw/riscv: implement CBQRI bandwidth controller
2026-10-26 17:47 ` [PATCH v6 4/6] hw/riscv: implement CBQRI bandwidth controller Drew Fustini
@ 2026-10-27 0:18 ` Alistair Francis
2026-10-29 21:00 ` Drew Fustini
0 siblings, 1 reply; 14+ messages in thread
From: Alistair Francis @ 2026-10-27 0:18 UTC (permalink / raw)
To: fustini@kernel.org, qemu-devel@nongnu.org
Cc: mindal@semihalf.com, sunilvl@oss.qualcomm.com,
zhiwei_liu@linux.alibaba.com, liu.qingtao2@zte.com.cn,
anup.patel@oss.qualcomm.com, cp0613@linux.alibaba.com,
palmer@dabbelt.com, daniel.barboza@oss.qualcomm.com,
radim.krcmar@oss.qualcomm.com, qemu-riscv@nongnu.org,
pbonzini@redhat.com, guo.wenjia23@zte.com.cn, npitre@baylibre.com,
cuiyunhui@bytedance.com, atish.patra@linux.dev,
liwei1518@gmail.com, chao.liu@processmission.com
On Mon, 2026-10-26 at 10:47 -0700, 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
>
> 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>
My review bot doesn't like the assert:
- [CRITICAL] In hw/riscv/cbqri_bandwidth.c (get_bw_alloc), the array
index
mixes the architectural AT encoding (data=0, code=1) with a compacted
count of supported ATs. at_data and at_code are independent bool
properties, so at_data=false/at_code=true is a legal configuration; a
guest BC_ALLOC_CTL write with AT=code then passes is_valid_at in
riscv_cbqri_bc_write_alloc_ctl and funnels into get_bw_alloc(rcid,
1),
which evaluates g_assert(1 < 1) and aborts the QEMU process on a
guest
MMIO write (QEMU maps assert to g_assert, so release builds abort
too).
Even with the assert compiled out, realize sizes the table to
nb_rcids+1
entries while the code-only index for the last RCID is nb_rcids+2, a
guest-triggered one-past-the-end access. Reject the asymmetric
property
combination in realize, or index by AT encoding instead of the
supported-count.
- [MEDIUM] In riscv_cbqri_bc_class_init (hw/riscv/cbqri_bandwidth.c)
the
device registers neither dc->vmsd nor .unmigratable, yet it is
user_creatable and (per series patch 6/6) pluggable on the
migration-capable virt machine. None of its guest-visible state --
the
cached bc_mon_ctl/bc_mon_ctr_val/bc_alloc_ctl registers and the
mon_counters[] and bw_allocations[] tables -- is migrated; the
destination comes up in power-on state and the guest's QoS
programming
silently vanishes mid-workload. Add a VMStateDescription covering the
caches and both arrays, or set unmigratable with justification (the
sibling riscv-iommu-pci device already takes the latter route).
- [MEDIUM] riscv_cbqri_bc_realize (hw/riscv/cbqri_bandwidth.c)
allocates
mon_counters and bw_allocations with g_new0 and initializes the MMIO
MemoryRegion, but the class registers no dc->unrealize and no
instance_finalize. With user_creatable = true, QMP/HMP device_del
cycles
orphan both arrays and the region backing store on every hot-unplug.
Add an unrealize that unparents the region and frees both arrays
(NULL-guarded, since realize can bail early on the mmio_base check).
Related: the bc_bw_alloc struct field is dead state -- BC_BW_ALLOC
traffic goes through bw_allocations[0] and nothing reads or writes
the
field -- so use it or drop it.
- [MEDIUM] Commit message: the body is a bullet list of supported
access
types, monitoring ops, event IDs and allocation ops -- a restatement
of
the register encodings the patch adds -- with no sentence explaining
why
QEMU should carry a CBQRI bandwidth controller. That omission matters
here because the model deliberately performs no bandwidth enforcement
and marks every counter INVALID; without the motivation (e.g. the
Linux
RISC-V resctrl/CBQRI consumer) a reviewer cannot judge whether the
stub
level is intended. The spec Link and DCO trailers are fine; add one
or
two motivation sentences.
- [LOW] In riscv_cbqri_bc_realize (hw/riscv/cbqri_bandwidth.c), the
allocation size is computed as get_bw_alloc(bc, nb_rcids, 0) -
bc->bw_allocations while the pointer is still NULL: forming NULL + n
and subtracting pointers that do not point into the same array object
is undefined behavior, latent today but trapped by UBSan at realize
time. The capacity controller's sibling code avoids this pattern with
the pure-integer get_blockmask_offset helper. Compute the count
arithmetically (1 + nb_rcids * MAX(nb_ats, 1)) before g_new0 instead
of
through the pointer-returning accessor.
- [LOW] The target qdev property (hw/riscv/cbqri_bandwidth.c) is stored
into the target struct field by the property setter and by
riscv_cbqri_bc_create, but nothing in the device model ever reads it:
users passing target=<str> get a silently ignored option, and the
device never associates itself with the controlled resource. Either
wire it up to a real association, or drop the property, the field,
and
the exported factory if series patch 6/6 confirms no in-tree board
code
calls riscv_cbqri_bc_create.
- [LOW] riscv_cbqri_bc_realize (hw/riscv/cbqri_bandwidth.c) hard-
requires
mmio_base and self-maps its MMIO region via sysbus_mmio_map, which
clashes with the series-end machine_class_allow_dynamic_sysbus_dev
registration on virt, where the platform bus normally assigns
addresses
for -device-created sysbus devices. Stated conditionally on patch
6/6's
dynamic-sysbus wiring: if that registration stands, either make
mmio_base optional and let the platform bus map the device, or do not
register it for dynamic sysbus hot-plug.
- [LOW] hw/riscv/cbqri_bandwidth.c re-declares MonitorCounter and the
64/32-bit align-down/RMW MMIO read-write wrapper pair verbatim from
hw/riscv/cbqri_capacity.c and re-implements the same property-table
plumbing; declare the type once in cbqri.h and share the wrapper
helpers. The duplication is already diverging (capacity uses the
pure-integer get_blockmask_offset while bandwidth computes the table
size through a NULL pointer, per the lifecycle finding above).
From quickly reading it, it seems like the assert in get_bw_alloc() can
be triggered from guest code, so it shouldn't assert
Alistair
> ---
> MAINTAINERS | 1 +
> hw/riscv/cbqri_bandwidth.c | 658
> +++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 659 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..38b41301421b
> --- /dev/null
> +++ b/hw/riscv/cbqri_bandwidth.c
> @@ -0,0 +1,658 @@
> +/*
> + * 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 "target/riscv/cpu.h"
> +#include "hw/riscv/cbqri.h"
> +
> +/* Encodings of `AT` field */
> +enum {
> + BC_AT_DATA = 0,
> + BC_AT_CODE = 1,
> +};
> +
> +/* 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 MonitorCounter {
> + uint64_t ctr_val;
> + int at;
> + int evt_id;
> + bool active;
> +} MonitorCounter;
> +
> +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;
> + uint64_t bc_bw_alloc;
> +
> + MonitorCounter *mon_counters;
> + BandwidthAllocation *bw_allocations;
> +
> + /* properties */
> +
> + uint64_t mmio_base;
> + char *target;
> + uint16_t nb_mcids;
> + uint16_t nb_rcids;
> +
> + uint16_t nbwblks;
> + uint16_t mrbwb;
> +
> + bool supports_at_data;
> + 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)
> +
> +static BandwidthAllocation *get_bw_alloc(RiscvCbqriBandwidthState
> *bc,
> + uint32_t rcid, uint32_t at)
> +{
> + /*
> + * 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.
> + */
> +
> + unsigned int nb_ats = 0;
> + nb_ats += !!bc->supports_at_data;
> + nb_ats += !!bc->supports_at_code;
> + nb_ats = MAX(nb_ats, 1);
> + assert(at < nb_ats);
> +
> + return &bc->bw_allocations[1 + rcid * nb_ats + 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 bc->supports_at_data;
> + 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_data &&
> + !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)) {
> + /* capacity 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_data &&
> + !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) {
> + /* capacity 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_data &&
> + !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);
> + BandwidthAllocation *bw_alloc_end = get_bw_alloc(bc, bc-
> >nb_rcids, 0);
> + unsigned int bw_alloc_size = bw_alloc_end - bc->bw_allocations;
> + bc->bw_allocations = g_new0(BandwidthAllocation, bw_alloc_size);
> +
> + 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_STRING("target", RiscvCbqriBandwidthState, target),
> +
> + 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_data", RiscvCbqriBandwidthState,
> + supports_at_data, true),
> + 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 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->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,
> + const char *target_name)
> +{
> + DeviceState *dev = qdev_new(TYPE_RISCV_CBQRI_BC);
> +
> + qdev_prop_set_uint64(dev, "mmio_base", addr);
> + qdev_prop_set_string(dev, "target", target_name);
> + 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_data",
> + caps->supports_at_data);
> + 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)
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH v6 4/6] hw/riscv: implement CBQRI bandwidth controller
2026-10-27 0:18 ` Alistair Francis
@ 2026-10-29 21:00 ` Drew Fustini
0 siblings, 0 replies; 14+ messages in thread
From: Drew Fustini @ 2026-10-29 21:00 UTC (permalink / raw)
To: Alistair Francis
Cc: qemu-devel@nongnu.org, mindal@semihalf.com,
sunilvl@oss.qualcomm.com, zhiwei_liu@linux.alibaba.com,
liu.qingtao2@zte.com.cn, anup.patel@oss.qualcomm.com,
cp0613@linux.alibaba.com, palmer@dabbelt.com,
daniel.barboza@oss.qualcomm.com, radim.krcmar@oss.qualcomm.com,
qemu-riscv@nongnu.org, pbonzini@redhat.com,
guo.wenjia23@zte.com.cn, npitre@baylibre.com,
cuiyunhui@bytedance.com, atish.patra@linux.dev,
liwei1518@gmail.com, chao.liu@processmission.com
On Tue, Oct 27, 2026 at 12:18:02AM +0000, Alistair Francis wrote:
> On Mon, 2026-10-26 at 10:47 -0700, 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
> >
> > 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>
>
> My review bot doesn't like the assert:
>
> - [CRITICAL] In hw/riscv/cbqri_bandwidth.c (get_bw_alloc), the array
> index mixes the architectural AT encoding (data=0, code=1) with a
> compacted count of supported ATs. at_data and at_code are independent
> bool properties, so at_data=false/at_code=true is a legal
> configuration; a guest BC_ALLOC_CTL write with AT=code then passes
> is_valid_at in riscv_cbqri_bc_write_alloc_ctl and funnels into
> get_bw_alloc(rcid, 1), which evaluates g_assert(1 < 1) and aborts the
> QEMU process on a guest MMIO write (QEMU maps assert to g_assert, so
> release builds abort too). Even with the assert compiled out, realize
> sizes the table to nb_rcids+1 entries while the code-only index for
> the last RCID is nb_rcids+2, a guest-triggered one-past-the-end
> access. Reject the asymmetric property combination in realize, or
> index by AT encoding instead of the supported-count.
This is a good point. I should not have allowed at_data=false as the
CBQRI spec states that unsupported AT values behave as AT 0. I'll remove
the at_data property and index by the AT encoding in both controllers.
> - [MEDIUM] In riscv_cbqri_bc_class_init (hw/riscv/cbqri_bandwidth.c)
> the device registers neither dc->vmsd nor .unmigratable, yet it is
> user_creatable and (per series patch 6/6) pluggable on the
> migration-capable virt machine. None of its guest-visible state -- the
> cached bc_mon_ctl/bc_mon_ctr_val/bc_alloc_ctl registers and the
> mon_counters[] and bw_allocations[] tables -- is migrated; the
> destination comes up in power-on state and the guest's QoS programming
> silently vanishes mid-workload. Add a VMStateDescription covering the
> caches and both arrays, or set unmigratable with justification (the
> sibling riscv-iommu-pci device already takes the latter route).
I'll mark both controllers unmigratable.
> - [MEDIUM] riscv_cbqri_bc_realize (hw/riscv/cbqri_bandwidth.c)
> allocates mon_counters and bw_allocations with g_new0 and initializes
> the MMIO MemoryRegion, but the class registers no dc->unrealize and no
> instance_finalize. With user_creatable = true, QMP/HMP device_del
> cycles orphan both arrays and the region backing store on every
> hot-unplug. Add an unrealize that unparents the region and frees both
> arrays (NULL-guarded, since realize can bail early on the mmio_base
> check)
This should not be a problem since device_del is rejected for sysbus
devices.
> Related: the bc_bw_alloc struct field is dead state --
> BC_BW_ALLOC traffic goes through bw_allocations[0] and nothing reads
> or writes the field -- so use it or drop it.
I will remove bc_bw_alloc.
> - [MEDIUM] Commit message: the body is a bullet list of supported
> access types, monitoring ops, event IDs and allocation ops -- a
> restatement of the register encodings the patch adds -- with no
> sentence explaining why QEMU should carry a CBQRI bandwidth
> controller. That omission matters here because the model deliberately
> performs no bandwidth enforcement and marks every counter INVALID;
> without the motivation (e.g. the Linux RISC-V resctrl/CBQRI consumer)
> a reviewer cannot judge whether the stub level is intended. The spec
> Link and DCO trailers are fine; add one or two motivation sentences.
I'll add more details to the commit message.
> - [LOW] In riscv_cbqri_bc_realize (hw/riscv/cbqri_bandwidth.c), the
> allocation size is computed as get_bw_alloc(bc, nb_rcids, 0) -
> bc->bw_allocations while the pointer is still NULL: forming NULL + n
> and subtracting pointers that do not point into the same array object
> is undefined behavior, latent today but trapped by UBSan at realize
> time. The capacity controller's sibling code avoids this pattern with
> the pure-integer get_blockmask_offset helper. Compute the count
> arithmetically (1 + nb_rcids * MAX(nb_ats, 1)) before g_new0 instead
> of through the pointer-returning accessor.
I'll add a helper that returns the index as an integer like
get_blockmask_offset().
> - [LOW] The target qdev property (hw/riscv/cbqri_bandwidth.c) is
> stored into the target struct field by the property setter and by
> riscv_cbqri_bc_create, but nothing in the device model ever reads it:
> users passing target=<str> get a silently ignored option, and the
> device never associates itself with the controlled resource. Either
> wire it up to a real association, or drop the property, the field, and
> the exported factory if series patch 6/6 confirms no in-tree board
> code calls riscv_cbqri_bc_create.
I'll remove the target property.
> - [LOW] riscv_cbqri_bc_realize (hw/riscv/cbqri_bandwidth.c) hard-
> requires mmio_base and self-maps its MMIO region via sysbus_mmio_map,
> which clashes with the series-end
> machine_class_allow_dynamic_sysbus_dev registration on virt, where the
> platform bus normally assigns addresses for -device-created sysbus
> devices. Stated conditionally on patch 6/6's dynamic-sysbus wiring: if
> that registration stands, either make mmio_base optional and let the
> platform bus map the device, or do not register it for dynamic sysbus
> hot-plug.
platform_bus_map_mmio() skips a region that is already mapped. mmio_base
is required so the address is known for DT nodes and the RQSC table.
> - [LOW] hw/riscv/cbqri_bandwidth.c re-declares MonitorCounter and the
> 64/32-bit align-down/RMW MMIO read-write wrapper pair verbatim from
> hw/riscv/cbqri_capacity.c and re-implements the same property-table
> plumbing; declare the type once in cbqri.h and share the wrapper
> helpers. The duplication is already diverging (capacity uses the
> pure-integer get_blockmask_offset while bandwidth computes the table
> size through a NULL pointer, per the lifecycle finding above).
I'll move the MonitorCounter definition to cbqri.h.
> From quickly reading it, it seems like the assert in get_bw_alloc() can
> be triggered from guest code, so it shouldn't assert
>
> Alistair
That should be resolved by no longer allowing at_data=false.
Thanks,
Drew
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v6 5/6] hw/riscv: add CBQRI to Kconfig and build if enabled
2026-10-26 17:47 [PATCH v6 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
` (3 preceding siblings ...)
2026-10-26 17:47 ` [PATCH v6 4/6] hw/riscv: implement CBQRI bandwidth controller Drew Fustini
@ 2026-10-26 17:47 ` Drew Fustini
2026-10-26 17:47 ` [PATCH v6 6/6] hw/riscv: add CBQRI controllers to virt machine Drew Fustini
5 siblings, 0 replies; 14+ messages in thread
From: Drew Fustini @ 2026-10-26 17:47 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 boolean property 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] 14+ messages in thread* [PATCH v6 6/6] hw/riscv: add CBQRI controllers to virt machine
2026-10-26 17:47 [PATCH v6 0/6] riscv: implement Ssqosid extension and CBQRI controllers Drew Fustini
` (4 preceding siblings ...)
2026-10-26 17:47 ` [PATCH v6 5/6] hw/riscv: add CBQRI to Kconfig and build if enabled Drew Fustini
@ 2026-10-26 17:47 ` Drew Fustini
2026-10-28 13:56 ` Prajwal N
5 siblings, 1 reply; 14+ messages in thread
From: Drew Fustini @ 2026-10-26 17:47 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=0x04828000[,...]
-device riscv.cbqri.bandwidth,mmio_base=0x04829000[,...]
The mmio_base option is mandatory, the others are optional.
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> - (default: true)
alloc_op_flush_rcid=<bool> - (default: true)
alloc_op_read_limit=<bool> - (default: true)
at_code=<bool> - (default: true)
at_data=<bool> - (default: true)
max_mcids=<uint16> - (default: 256)
max_rcids=<uint16> - (default: 64)
mmio_base=<uint64> - (default: 0)
mon_evt_id_none=<bool> - (default: true)
mon_evt_id_occupancy=<bool> - (default: true)
mon_op_config_event=<bool> - (default: true)
mon_op_read_counter=<bool> - (default: true)
ncblks=<uint16> - (default: 16)
target=<str>
$ qemu-system-riscv64 -device riscv.cbqri.bandwidth,help
riscv.cbqri.bandwidth options:
alloc_op_config_limit=<bool> - (default: true)
alloc_op_read_limit=<bool> - (default: true)
at_code=<bool> - (default: true)
at_data=<bool> - (default: true)
max_mcids=<uint16> - (default: 256)
max_rcids=<uint16> - (default: 64)
mmio_base=<uint64> - (default: 0)
mon_evt_id_none=<bool> - (default: true)
mon_evt_id_rdonly_count=<bool> - (default: true)
mon_evt_id_rdwr_count=<bool> - (default: true)
mon_evt_id_wronly_count=<bool> - (default: true)
mon_op_config_event=<bool> - (default: true)
mon_op_read_counter=<bool> - (default: true)
nbwblks=<uint16> - (default: 1024)
target=<str>
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] 14+ messages in thread* Re: [PATCH v6 6/6] hw/riscv: add CBQRI controllers to virt machine
2026-10-26 17:47 ` [PATCH v6 6/6] hw/riscv: add CBQRI controllers to virt machine Drew Fustini
@ 2026-10-28 13:56 ` Prajwal N
0 siblings, 0 replies; 14+ messages in thread
From: Prajwal N @ 2026-10-28 13:56 UTC (permalink / raw)
To: Drew Fustini, 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, prajwal.n.oss
Hi Drew,
On 26-10-2026 23:17, Drew Fustini wrote:
> 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=0x04828000[,...]
> -device riscv.cbqri.bandwidth,mmio_base=0x04829000[,...]
>
> The mmio_base option is mandatory, the others are optional.
>
> 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> - (default: true)
> alloc_op_flush_rcid=<bool> - (default: true)
> alloc_op_read_limit=<bool> - (default: true)
> at_code=<bool> - (default: true)
> at_data=<bool> - (default: true)
> max_mcids=<uint16> - (default: 256)
> max_rcids=<uint16> - (default: 64)
> mmio_base=<uint64> - (default: 0)
> mon_evt_id_none=<bool> - (default: true)
> mon_evt_id_occupancy=<bool> - (default: true)
> mon_op_config_event=<bool> - (default: true)
> mon_op_read_counter=<bool> - (default: true)
> ncblks=<uint16> - (default: 16)
> target=<str>
>
> $ qemu-system-riscv64 -device riscv.cbqri.bandwidth,help
> riscv.cbqri.bandwidth options:
> alloc_op_config_limit=<bool> - (default: true)
> alloc_op_read_limit=<bool> - (default: true)
> at_code=<bool> - (default: true)
> at_data=<bool> - (default: true)
> max_mcids=<uint16> - (default: 256)
> max_rcids=<uint16> - (default: 64)
> mmio_base=<uint64> - (default: 0)
> mon_evt_id_none=<bool> - (default: true)
> mon_evt_id_rdonly_count=<bool> - (default: true)
> mon_evt_id_rdwr_count=<bool> - (default: true)
> mon_evt_id_wronly_count=<bool> - (default: true)
> mon_op_config_event=<bool> - (default: true)
> mon_op_read_counter=<bool> - (default: true)
> nbwblks=<uint16> - (default: 1024)
> target=<str>
>
> 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);
When the controller device is created dynamically through the QEMU command line, the
corresponding device tree node must also be created. As I do not see the implementation
to add the DT node in this series, Could you please clarify how it is created.?
Thanks,
Prajwal N
>
> object_class_property_add_bool(oc, "aclint", virt_get_aclint,
> virt_set_aclint);
>
^ permalink raw reply [flat|nested] 14+ messages in thread