Thanks for pointing that out. I checked the v6 series on virt machine.
LGTM.

Thanks,
Prajwal

On Fri, Oct 30, 2026 at 5:17 AM Drew Fustini <[email protected]> wrote:
>
> On Wed, Oct 28, 2026 at 07:02:32PM +0530, Prajwal N wrote:
> > Hi Drew,
> >
> > On 26-10-2026 23:17, Drew Fustini wrote:
> > > From: Nicolas Pitre <[email protected]>
> > >
> > > 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 <[email protected]>
> > > [fustini: add fields introduced in the ratified spec: cunits, rpfx, p]
> > > Reviewed-by: Radim Krčmář <[email protected]>
> > > Signed-off-by: Drew Fustini <[email protected]>
> > > ---
> > >  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 <[email protected]>
> > >  M: Drew Fustini <[email protected]>
> > >  L: [email protected]
> > >  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
>
> Thank you for reviewing. Did you see that riscv_cbqri_cc_ops actually
> invokes riscv_cbqri_cc_read_wrapper() and
> riscv_cbqri_cc_write_wrapper()?
>
> Those wrappers were added in v5 based on Radim's feedback on v4.
>
> Thanks,
> Drew

Reply via email to