[PATCH RFC v6 08/18] riscv_cbqri: Add capacity controller monitoring device ops
Drew Fustini <[email protected]> Mon, 01 Jun 2026 13:36:02 -0700
| Newsgroups | dev.linux.lists.acpica-devel,dev.linux.lists.linux-rt-devel,org.infradead.lists.linux-riscv,org.kernel.vger.linux-acpi,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Add capacity monitoring operations. cbqri_init_mon_counters() pre-arms every MCID with the Occupancy event so a subsequent READ_COUNTER just snapshots the live counter without re-configuring the slot. cbqri_probe_cc() leaves ctrl->mon_capable false when cacheinfo has not given a non-zero cache_size, since the byte conversion would be meaningless. cbqri_mon_op() takes a reg_offset and serves both capacity and bandwidth mon_ctl registers as they share an identical layout. Assisted-by: Claude:claude-opus-4-7 Co-developed-by: Adrien Ricciardi <[email protected]> Signed-off-by: Adrien Ricciardi <[email protected]> Signed-off-by: Drew Fustini <[email protected]> --- drivers/resctrl/cbqri_devices.c | 124 ++++++++++++++++++++++++++++++++++----- drivers/resctrl/cbqri_internal.h | 14 +++++ 2 files changed, 124 insertions(+), 14 deletions(-) diff --git a/drivers/resctrl/cbqri_devices.c b/drivers/resctrl/cbqri_devices.c index 65453e614a43..c44bc50c31ba 100644 --- a/drivers/resctrl/cbqri_devices.c +++ b/drivers/resctrl/cbqri_devices.c @@ -98,6 +98,43 @@ static int cbqri_cc_alloc_op(struct cbqri_controller *ctrl, int operation, return 0; } +/* + * Issue a monitoring op on a CC or BC controller's mon_ctl register at + * reg_offset (CBQRI_CC_MON_CTL_OFF or CBQRI_BC_MON_CTL_OFF). The CC and + * BC mon_ctl registers share an identical OP/MCID/EVT_ID/STATUS layout, so + * one helper covers both. Caller must hold ctrl->lock. + */ +int cbqri_mon_op(struct cbqri_controller *ctrl, int reg_offset, + int operation, int mcid, int evt_id, u64 *out_reg) +{ + u64 reg; + + lockdep_assert_held(&ctrl->lock); + + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) { + pr_err_ratelimited("BUSY timeout before starting operation\n"); + return -EIO; + } + FIELD_MODIFY(CBQRI_MON_CTL_OP_MASK, ®, operation); + FIELD_MODIFY(CBQRI_MON_CTL_MCID_MASK, ®, mcid); + FIELD_MODIFY(CBQRI_MON_CTL_EVT_ID_MASK, ®, evt_id); + iowrite64(reg, ctrl->base + reg_offset); + + if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) { + pr_err_ratelimited("BUSY timeout\n"); + return -EIO; + } + + if (FIELD_GET(CBQRI_MON_CTL_STATUS_MASK, reg) != + CBQRI_MON_CTL_STATUS_SUCCESS) + return -EIO; + + if (out_reg) + *out_reg = reg; + + return 0; +} + /* * Apply a capacity block mask and verify via CONFIG_LIMIT + READ_LIMIT. * @@ -230,7 +267,8 @@ int cbqri_read_cache_config(struct cbqri_controller *ctrl, u32 closid, } static int cbqri_probe_feature(struct cbqri_controller *ctrl, int reg_offset, - int operation, int *status, bool *access_type_supported) + int operation, int evt_id, int *status, + bool *access_type_supported) { const u64 active_mask = CBQRI_CONTROL_REGISTERS_OP_MASK | CBQRI_CONTROL_REGISTERS_AT_MASK | @@ -242,9 +280,11 @@ static int cbqri_probe_feature(struct cbqri_controller *ctrl, int reg_offset, /* * Default the output to false so the status==0 (feature not * implemented) path returns a deterministic value to the caller - * rather than leaving an uninitialized bool. + * rather than leaving an uninitialized bool. mon_ctl probes pass + * NULL: the register has no AT field, so the AT probe is skipped. */ - *access_type_supported = false; + if (access_type_supported) + *access_type_supported = false; /* Keep the initial register value to preserve the WPRI fields */ reg = ioread64(ctrl->base + reg_offset); @@ -257,15 +297,14 @@ static int cbqri_probe_feature(struct cbqri_controller *ctrl, int reg_offset, } /* - * Execute the requested operation with all active fields - * (OP/AT/RCID/EVT_ID) zeroed except OP itself. The same builder - * works for ALLOC_CTL and MON_CTL because every bit not in - * active_mask is WPRI and gets carried over from saved_reg. The - * AT and EVT_ID positions are reserved for the other register - * type, where writing zero is harmless. + * Execute the requested operation with the active fields + * (OP/AT/RCID/EVT_ID) cleared, then set OP and, for mon_ctl, the + * probe-safe evt_id. WPRI bits outside active_mask carry over from + * saved_reg. alloc_ctl callers pass evt_id 0. */ reg = (saved_reg & ~active_mask) | - FIELD_PREP(CBQRI_CONTROL_REGISTERS_OP_MASK, operation); + FIELD_PREP(CBQRI_CONTROL_REGISTERS_OP_MASK, operation) | + FIELD_PREP(CBQRI_MON_CTL_EVT_ID_MASK, evt_id); iowrite64(reg, ctrl->base + reg_offset); if (cbqri_wait_busy_flag(ctrl, reg_offset, ®) < 0) { pr_err_ratelimited("BUSY timeout during operation\n"); @@ -276,10 +315,11 @@ static int cbqri_probe_feature(struct cbqri_controller *ctrl, int reg_offset, *status = FIELD_GET(CBQRI_CONTROL_REGISTERS_STATUS_MASK, reg); /* - * Check for the AT support if the register is implemented - * (if not, the status value will remain 0) + * Probe AT support only on alloc_ctl registers (mon_ctl has no AT + * field, so access_type_supported is NULL there). Skipped when the + * register is unimplemented (status stays 0). */ - if (*status != 0) { + if (access_type_supported && *status != 0) { /* * Re-issue operation with AT=CODE so the controller * latches AT=CODE on supported hardware (or resets it to 0 @@ -372,9 +412,31 @@ static int cbqri_probe_cc(struct cbqri_controller *ctrl) } cpus_read_unlock(); + /* Probe monitoring features */ + err = cbqri_probe_feature(ctrl, CBQRI_CC_MON_CTL_OFF, + CBQRI_CC_MON_CTL_OP_CONFIG_EVENT, + CBQRI_CC_EVT_ID_NONE, &status, NULL); + if (err) + return err; + + if (status == CBQRI_MON_CTL_STATUS_SUCCESS) { + /* + * Occupancy is reported to userspace in bytes, computed as + * cache_size * counter / ncblks by the resctrl glue. If + * cacheinfo has no cache_size, leave mon_capable false so + * the file is not exposed at all rather than silently + * returning 0. + */ + if (!ctrl->cache.cache_size) + pr_debug("CC @%pa: cache_size unknown, occupancy monitoring disabled\n", + &ctrl->addr); + else + ctrl->mon_capable = true; + } + /* Probe allocation features */ err = cbqri_probe_feature(ctrl, CBQRI_CC_ALLOC_CTL_OFF, - CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT, + CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT, 0, &status, &ctrl->cc.supports_alloc_at_code); if (err) return err; @@ -439,6 +501,28 @@ static int cbqri_probe_controller(struct cbqri_controller *ctrl) return err; } +/* + * Pre-arm every MCID with the Occupancy event so a subsequent READ_COUNTER + * just snapshots the live counter rather than re-configuring the slot. + * Called once per CC during resctrl-side cpuhp online for the L3 monitoring + * domain. + */ +int cbqri_init_mon_counters(struct cbqri_controller *ctrl) +{ + int i, err; + + for (i = 0; i < ctrl->mcid_count; i++) { + mutex_lock(&ctrl->lock); + err = cbqri_mon_op(ctrl, CBQRI_CC_MON_CTL_OFF, + CBQRI_CC_MON_CTL_OP_CONFIG_EVENT, + i, CBQRI_CC_EVT_ID_OCCUPANCY, NULL); + mutex_unlock(&ctrl->lock); + if (err) + return err; + } + return 0; +} + void cbqri_controller_destroy(struct cbqri_controller *ctrl) { /* @@ -518,6 +602,18 @@ int riscv_cbqri_register_controller(const struct cbqri_controller_info *info) return -EINVAL; } + /* + * mon_ctl encodes MCID in 12 bits. acpi_parse_rqsc() caps + * info->mcid_count at CBQRI_MAX_MCID (1024), but a future discovery + * path could bypass that. Reject an out-of-range count so + * cbqri_init_mon_counters() iterates a trusted bound and no MCID + * aliases another slot through FIELD_MODIFY(MON_CTL_MCID_MASK). + */ + if (WARN_ON_ONCE(ctrl->mcid_count > FIELD_MAX(CBQRI_MON_CTL_MCID_MASK) + 1)) { + cbqri_controller_destroy(ctrl); + return -EINVAL; + } + switch (info->type) { case CBQRI_CONTROLLER_TYPE_CAPACITY: { int level; diff --git a/drivers/resctrl/cbqri_internal.h b/drivers/resctrl/cbqri_internal.h index 518955963403..1d2007a0c15b 100644 --- a/drivers/resctrl/cbqri_internal.h +++ b/drivers/resctrl/cbqri_internal.h @@ -11,6 +11,8 @@ /* Capacity Controller (CC) MMIO register offsets. */ #define CBQRI_CC_CAPABILITIES_OFF 0 +#define CBQRI_CC_MON_CTL_OFF 8 +#define CBQRI_CC_MON_CTL_VAL_OFF 16 #define CBQRI_CC_ALLOC_CTL_OFF 24 #define CBQRI_CC_BLOCK_MASK_OFF 32 @@ -45,6 +47,9 @@ #define CBQRI_CC_ALLOC_CTL_OP_READ_LIMIT 2 #define CBQRI_CC_ALLOC_CTL_STATUS_SUCCESS 1 +#define CBQRI_CC_MON_CTL_OP_CONFIG_EVENT 1 +#define CBQRI_CC_MON_CTL_OP_READ_COUNTER 2 + /* mon_ctl field masks (CC and BC share an identical OP/MCID/EVT_ID/STATUS layout) */ #define CBQRI_MON_CTL_OP_MASK GENMASK_ULL(4, 0) #define CBQRI_MON_CTL_MCID_MASK GENMASK_ULL(19, 8) @@ -52,6 +57,10 @@ #define CBQRI_MON_CTL_STATUS_MASK GENMASK_ULL(38, 32) #define CBQRI_MON_CTL_STATUS_SUCCESS 1 +/* Capacity usage monitoring event IDs (CBQRI spec Table 4) */ +#define CBQRI_CC_EVT_ID_NONE 0 +#define CBQRI_CC_EVT_ID_OCCUPANCY 1 + /* Capacity Controller hardware capabilities */ struct riscv_cbqri_capacity_caps { u16 ncblks; @@ -138,4 +147,9 @@ int cbqri_apply_cache_config(struct cbqri_controller *ctrl, u32 closid, int cbqri_read_cache_config(struct cbqri_controller *ctrl, u32 closid, enum cbqri_at at, u32 *cbm_out); +int cbqri_mon_op(struct cbqri_controller *ctrl, int reg_offset, + int operation, int mcid, int evt_id, u64 *out_reg); + +int cbqri_init_mon_counters(struct cbqri_controller *ctrl); + #endif /* _DRIVERS_RESCTRL_CBQRI_INTERNAL_H */ -- 2.43.0