From: Jinjie Ruan <[email protected]> Add MPAMF_ESR error recording with the IHI 0099 Table 7-1 encodings this model can raise, injected on the check-and-reject write paths: Undefined_RIS_PART_SEL / Undefined_RIS_MON_SEL on out-of-range selector RIS (the range check also subsumes nonzero RIS on an MSC without RIS support), intPARTID_Range on INTERNAL=0 or out-of-range intPARTID, Monitor_Range on MON_SEL beyond every monitor type, and MSMONCFG_ID_RANGE on out-of-range filter PARTID. All are paths the kernel driver never hits; normal boot raises nothing (verified). A recorded error overwrites PARTID_MON/PMG/RIS and sets OVRWR if a previous error was still latched.
Signed-off-by: Jinjie Ruan <[email protected]> --- hw/arm/mpam.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 117 insertions(+), 7 deletions(-) diff --git a/hw/arm/mpam.c b/hw/arm/mpam.c index 0389dad..6c43e3e 100644 --- a/hw/arm/mpam.c +++ b/hw/arm/mpam.c @@ -173,6 +173,14 @@ REG64(MPAMF_ESR, 0x00f8) FIELD(MPAMF_ESR, OVRWR, 31, 1) FIELD(MPAMF_ESR, RIS, 32, 4) +/* IHI 0099 Table 7-1: MPAMF_ESR.ERR_CODE encodings this model can raise */ +#define MPAM_ERRCODE_PARTID_SEL_RANGE 1 +#define MPAM_ERRCODE_MSMONCFG_ID_RANGE 3 +#define MPAM_ERRCODE_MONITOR_RANGE 5 +#define MPAM_ERRCODE_INTPARTID_RANGE 6 +#define MPAM_ERRCODE_UNDEF_RIS_PART_SEL 8 +#define MPAM_ERRCODE_UNDEF_RIS_MON_SEL 10 + REG32(MPAMF_CFG_PART_SEL, 0x0100) FIELD(MPAMF_CFG_PART_SEL, PARTID_SEL, 0, 16) FIELD(MPAMF_CFG_PART_SEL, INTERNAL, 16, 1) @@ -448,6 +456,33 @@ static void mpam_msc_update_err_irq(MPAMMSCState *s) qemu_set_irq(s->err_irq, int_en && active); } +/* + * Record an error in MPAMF_ESR and raise the error interrupt if enabled. + * PARTID_MON also carries a selector value (e.g. MON_SEL) for the errors + * where Table 7-1 lists "MON_SEL and RIS" as the fields in effect. + */ +static void mpam_msc_inject_error(MPAMMSCState *s, uint32_t errcode, + uint16_t partid_mon, uint8_t pmg, + uint8_t ris) +{ + uint64_t esr = 0; + + if (FIELD_EX64(s->esr, MPAMF_ESR, ERR_CODE)) { + esr = FIELD_DP64(esr, MPAMF_ESR, OVRWR, 1); + } + esr = FIELD_DP64(esr, MPAMF_ESR, PARID_MON, partid_mon); + esr = FIELD_DP64(esr, MPAMF_ESR, PMG, pmg); + esr = FIELD_DP64(esr, MPAMF_ESR, ERR_CODE, errcode); + esr = FIELD_DP64(esr, MPAMF_ESR, RIS, ris); + s->esr = esr; + mpam_msc_sync_esr_ecr(s); + + qemu_log_mask(LOG_GUEST_ERROR, + "MPAM: error 0x%x partid/mon %u pmg %u ris %u\n", + errcode, partid_mon, pmg, ris); + mpam_msc_update_err_irq(s); +} + /* * IHI 0099 8.3: the overflow interrupt is signalled while any monitor has * latched OFLOW_STATUS with OFLOW_INTR set; clearing OFLOW_STATUS of all @@ -852,18 +887,21 @@ static void mpam_msc_write_reg(void *opaque, hwaddr offset, uint64_t value, "MPAM: Unexpected write to CFG_PART_SEL Mask=%x Value=%lx\n", MPAMF_CFG_PART_SEL_WRITE_MASK, value); } - /* Field matches for all RIS */ - if (!FIELD_EX64(mpamf->idr, MPAMF_IDR, HAS_RIS) && - FIELD_EX32(value, MPAMF_CFG_PART_SEL, RIS) != 0) { - qemu_log_mask(LOG_UNIMP, - "MPAM: Unexpected write of non 0 RIS on MSC with !HAS_RIS\n"); - return; - } + /* + * Field matches for all RIS. The range check subsumes the !HAS_RIS + * case: such an MSC implements only RIS 0, so any nonzero RIS is + * undefined (IHI 0099 Table 7-1: Undefined_RIS_PART_SEL). + */ if (FIELD_EX32(value, MPAMF_CFG_PART_SEL, RIS) >= s->num_ris) { qemu_log_mask(LOG_GUEST_ERROR, "MPAM: RIS %d out of range (num-ris %d)\n", FIELD_EX32(value, MPAMF_CFG_PART_SEL, RIS), s->num_ris); + mpam_msc_inject_error(s, MPAM_ERRCODE_UNDEF_RIS_PART_SEL, + FIELD_EX32(value, MPAMF_CFG_PART_SEL, + PARTID_SEL), + 0, + FIELD_EX32(value, MPAMF_CFG_PART_SEL, RIS)); return; } { @@ -894,6 +932,23 @@ static void mpam_msc_write_reg(void *opaque, hwaddr offset, uint64_t value, "MPAM: Unexpected write to INTPARTID when !HAS_PARTID_NRW\n"); return; } + /* + * Table 7-1: writing INTERNAL as zero, or an out-of-range + * intPARTID, raises intPARTID_Range and is not written. + */ + if (!FIELD_EX32(value, MPAMF_MPAMCFG_INTPARTID, INTERNAL) || + FIELD_EX32(value, MPAMF_MPAMCFG_INTPARTID, INTPARTID) >= + s->num_int_partid) { + qemu_log_mask(LOG_GUEST_ERROR, + "MPAM: INTPARTID write invalid (value %x, max %d)\n", + FIELD_EX32(value, MPAMF_MPAMCFG_INTPARTID, INTPARTID), + s->num_int_partid - 1); + mpam_msc_inject_error(s, MPAM_ERRCODE_INTPARTID_RANGE, + FIELD_EX32(value, MPAMF_MPAMCFG_INTPARTID, + INTPARTID), + 0, s->ris); + return; + } mpamf->cfg_intpartid[s->part_sel] = FIELD_EX32(value, MPAMF_MPAMCFG_INTPARTID, INTPARTID); @@ -996,12 +1051,53 @@ static void mpam_msc_write_reg(void *opaque, hwaddr offset, uint64_t value, return; } case A_MSMON_CFG_MON_SEL: + { + uint32_t mon = FIELD_EX32(value, MSMON_CFG_MON_SEL, MON_SEL); + uint32_t ris = FIELD_EX32(value, MSMON_CFG_MON_SEL, RIS); + uint32_t num_mon = MAX(s->num_csu_mon, s->num_mbwu_mon); + + /* + * Check-and-reject (IHI 0099 7.2.9): RIS selects a nonexistent + * resource instance or MON_SEL exceeds every monitor type this + * MSC implements. + */ + if (ris >= s->num_ris) { + qemu_log_mask(LOG_GUEST_ERROR, + "MPAM: MON_SEL RIS %d out of range (num-ris %d)\n", + ris, s->num_ris); + mpam_msc_inject_error(s, MPAM_ERRCODE_UNDEF_RIS_MON_SEL, + mon, 0, ris); + return; + } + if (mon >= num_mon) { + qemu_log_mask(LOG_GUEST_ERROR, + "MPAM: MON_SEL %d out of range (max %d)\n", + mon, num_mon - 1); + mpam_msc_inject_error(s, MPAM_ERRCODE_MONITOR_RANGE, + mon, 0, ris); + return; + } s->mon_sel = value & (R_MSMON_CFG_MON_SEL_MON_SEL_MASK | R_MSMON_CFG_MON_SEL_RIS_MASK); return; + } case A_MSMON_CFG_MBWU_FLT: + { + uint32_t partid = FIELD_EX32(value, MSMON_CFG_MBWU_FLT, PARTID); + + if (partid >= s->num_partid) { + qemu_log_mask(LOG_GUEST_ERROR, + "MPAM: MBWU_FLT PARTID %d out of range\n", partid); + mpam_msc_inject_error(s, MPAM_ERRCODE_MSMONCFG_ID_RANGE, + partid, + FIELD_EX32(value, MSMON_CFG_MBWU_FLT, PMG), + FIELD_EX32(s->mon_sel, MSMON_CFG_MON_SEL, + RIS)); + return; + } mpam_mbwu_mon(s, mpam_mon_ris(s))->flt = value; return; + } case A_MSMON_CFG_MBWU_CTL: { MpamMbwuMon *m = mpam_mbwu_mon(s, mpam_mon_ris(s)); @@ -1025,8 +1121,22 @@ static void mpam_msc_write_reg(void *opaque, hwaddr offset, uint64_t value, return; } case A_MSMON_CFG_CSU_FLT: + { + uint32_t partid = FIELD_EX32(value, MSMON_CFG_CSU_FLT, PARTID); + + if (partid >= s->num_partid) { + qemu_log_mask(LOG_GUEST_ERROR, + "MPAM: CSU_FLT PARTID %d out of range\n", partid); + mpam_msc_inject_error(s, MPAM_ERRCODE_MSMONCFG_ID_RANGE, + partid, + FIELD_EX32(value, MSMON_CFG_CSU_FLT, PMG), + FIELD_EX32(s->mon_sel, MSMON_CFG_MON_SEL, + RIS)); + return; + } mpam_csu_mon(s, mpam_mon_ris(s))->flt = value; return; + } case A_MSMON_CFG_CSU_CTL: mpam_csu_mon(s, mpam_mon_ris(s))->ctl = value; return; -- 2.43.0
