From: Jinjie Ruan <[email protected]> The MPAMCFG_INTPARTID register (0x600) was defined but never handled, so a guest could not program the request PARTID to internal PARTID mapping that PARTID narrowing is based on. Add read/write support:
- Writing MPAMCFG_INTPARTID stores the mapping for the request PARTID selected by MPAMCFG_PART_SEL; reads return the stored mapping. - At realize, initialise the map to partid % num_int_partid so the indirection is always in range before a driver programs it, mirroring the Linux driver's fallback mapping. MBW_MAX also resets to 0xffff (full bandwidth) for every internal PARTID, matching hardware: an unprogrammed PARTID must not read as zero bandwidth. - Clamp the narrowing indirection lookup to num_int_partid, and wrap MPAMCFG_PART_SEL writes into the supported PARTID space (the narrowed space for INTERNAL selectors) so a guest cannot trigger out-of-bounds accesses on the emulated arrays. A guest driver can now exercise PARTID narrowing: several request PARTIDs share one internal PARTID's configuration, and the guest can re-map them at runtime. Signed-off-by: Jinjie Ruan <[email protected]> --- hw/arm/mpam.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/hw/arm/mpam.c b/hw/arm/mpam.c index 441356e..91d1c31 100644 --- a/hw/arm/mpam.c +++ b/hw/arm/mpam.c @@ -285,14 +285,18 @@ typedef struct MPAMMSCState { static uint32_t mpam_get_nrw_id(MPAMMSCState *s) { Mpamf *mpamf = &s->mpamf[s->ris]; + uint32_t intpartid; if (!FIELD_EX32(mpamf->idr, MPAMF_IDR, HAS_PARTID_NRW)) { return s->part_sel; } if (s->internal_part_sel) { - return s->part_sel; + intpartid = s->part_sel; + } else { + intpartid = mpamf->cfg_intpartid[s->part_sel]; } - return mpamf->cfg_intpartid[s->part_sel]; + + return intpartid % s->num_int_partid; } typedef struct MPAMMSCMemState { @@ -404,6 +408,17 @@ static uint64_t mpam_msc_read_reg(void *opaque, hwaddr offset, return mpamf->esr; case A_MPAMF_CFG_PART_SEL: return mpamf->cfg_part_sel; + case A_MPAMF_MPAMCFG_INTPARTID: { + /* + * The map is only meaningful for resources that implement + * narrowing; others read as zero. + */ + if (!FIELD_EX32(mpamf->idr, MPAMF_IDR, HAS_PARTID_NRW)) { + return 0; + } + + return mpamf->cfg_intpartid[s->part_sel]; + } case A_MPAMF_MPAMCFG_CMAX: if (!FIELD_EX32(mpamf->idr, MPAMF_IDR, HAS_CCAP_PART)) { qemu_log_mask(LOG_UNIMP, @@ -482,12 +497,38 @@ static void mpam_msc_write_reg(void *opaque, hwaddr offset, uint64_t value, s->num_ris); return; } + { + /* + * IHI 0099 7.2.1: the MSC does not check PARTID_SEL on write + * (the first IMPLEMENTATION DEFINED behaviour); the selector + * wraps into the supported PARTID space instead. + */ + uint32_t limit = FIELD_EX32(value, MPAMF_CFG_PART_SEL, INTERNAL) ? + s->num_int_partid : s->num_partid; + + s->part_sel = FIELD_EX32(value, MPAMF_CFG_PART_SEL, + PARTID_SEL) % limit; + } s->ris = FIELD_EX32(value, MPAMF_CFG_PART_SEL, RIS); - s->part_sel = FIELD_EX32(value, MPAMF_CFG_PART_SEL, PARTID_SEL); s->internal_part_sel = FIELD_EX32(value, MPAMF_CFG_PART_SEL, INTERNAL); mpamf = &s->mpamf[s->ris]; mpamf->cfg_part_sel = value; return; + case A_MPAMF_MPAMCFG_INTPARTID: + if (value & ~MPAMF_MPAMCFG_INTPARTID_WRITE_MASK) { + qemu_log_mask(LOG_UNIMP, + "MPAM: Unexpected write to INTPARTID Mask=%x Value=%lx\n", + MPAMF_MPAMCFG_INTPARTID_WRITE_MASK, value); + } + if (!FIELD_EX32(mpamf->idr, MPAMF_IDR, HAS_PARTID_NRW)) { + qemu_log_mask(LOG_UNIMP, + "MPAM: Unexpected write to INTPARTID when !HAS_PARTID_NRW\n"); + return; + } + + mpamf->cfg_intpartid[s->part_sel] = + FIELD_EX32(value, MPAMF_MPAMCFG_INTPARTID, INTPARTID); + return; case A_MPAMF_MPAMCFG_CMAX: if (value & ~MPAMF_MPAMCFG_CMAX_WRITE_MASK) { qemu_log_mask(LOG_UNIMP, @@ -619,8 +660,25 @@ static void mpam_msc_realize(DeviceState *dev, Error **errp) s->mpamf = g_new0(Mpamf, s->num_ris); for (i = 0; i < s->num_ris; i++) { + int p; + s->mpamf[i].per_nrw_id = g_new0(MpamfPerNrwId, s->num_int_partid); s->mpamf[i].cfg_intpartid = g_new0(uint32_t, s->num_partid); + /* + * Until resctrl programs the map, wrap each request PARTID onto an + * internal PARTID so the narrowing indirection is always in range. + */ + for (p = 0; p < s->num_partid; p++) { + s->mpamf[i].cfg_intpartid[p] = p % s->num_int_partid; + } + /* + * per_nrw_id is indexed by INTERNAL PARTID: it has num_int_partid + * entries, not num_partid. + */ + for (p = 0; p < s->num_int_partid; p++) { + /* MBW_MAX resets to full bandwidth on real hardware */ + s->mpamf[i].per_nrw_id[p].cfg_mbw_max = 0xffff; + } } memory_region_init_io(&s->mr, OBJECT(s), &mpam_msc_ops, s, "mpam_msc", -- 2.43.0
