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


Reply via email to