On Mon, 2026-10-26 at 10:47 -0700, Drew Fustini wrote: > From: Nicolas Pitre <[email protected]> > > 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 <[email protected]> > [fustini: add fields introduced in the ratified spec: rpfx and p] > Signed-off-by: Drew Fustini <[email protected]>
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 <[email protected]> > M: Drew Fustini <[email protected]> > L: [email protected] > 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)
