Hi Tao,

On 8/13/26 6:25 PM, Tao Tang wrote:
> Add access control for SMMU_STRTAB_BASE and SMMU_CR2 registers to
> ensure they can only be modified when the SMMU is disabled.

About SMMU_STRTAB_BASE "
SMMU_STRTAB_BASE is Guarded by SMMU_CR0.SMMUEN and must only be written when
SMMU_CR0.SMMUEN == 0
.../..
In SMMUv3.2 and later, a write while SMMU_CR0.SMMUEN == 1 is IGNORED.
../..
Accessible at offset 0x0080 from SMMUv3_PAGE_0
• When SMMU_IDR1.TABLES_PRESET == 1, accesses to this register are RO.
• When SMMU_CR0.SMMUEN == 0 and SMMU_CR0ACK.SMMUEN == 0, accesses to
this register are
RW.
• Otherwise, accesses to this register are RO.
"

About SMMU_CR2:
"
This register is made read-only when the associated SMMU_CR0.SMMUEN is
Updated to 1. This register must
only be changed when SMMU_CR0.SMMUEN == 0.
A write to this register after SMMU_CR0.SMMUEN has been changed but
before its Update completes is
CONSTRAINED UNPREDICTABLE and has one of the following behaviors:
• Apply the new value.
• Ignore the write. In SMMUv3.2 and later, this is the only permitted
behavior.
"


>
> This implements:
> - smmuv3_smmu_disabled_stable(): Check whether the SMMU is in a stable
>    disabled state (CR0.SMMUEN == 0 and CR0ACK.SMMUEN == 0);
> - smmu_strtab_base_writable(): returns true only when IDR1.TABLES_PRESET==0
>    and SMMU is completely disabled.
>
> Additionally, mask reserved bits on writes to SMMU_STRTAB_BASE using
> SMMU_STRTAB_BASE_RESERVED.
>
> Fixes: fae4be38b35d ("hw/arm/smmuv3: Implement MMIO write operations")
> Signed-off-by: Tao Tang <[email protected]>
> Reviewed-by: Pierrick Bouvier <[email protected]>
> ---
>  hw/arm/smmuv3.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 67 insertions(+), 2 deletions(-)
>
> diff --git a/hw/arm/smmuv3.c b/hw/arm/smmuv3.c
> index 628911d3f21..de8ca7cda48 100644
> --- a/hw/arm/smmuv3.c
> +++ b/hw/arm/smmuv3.c
> @@ -1484,6 +1484,36 @@ smmu_gerror_irq_cfg_writable(SMMUv3State *s, 
> SMMUSecSID sec_sid)
>      return smmu_irq_cfg_writable(s, sec_sid, SMMU_IRQ_GERROR);
>  }
>  
> +static inline int smmuv3_get_cr0ack_smmuen(SMMUv3State *s, SMMUSecSID 
> sec_sid)
> +{
> +    /*
> +     * CR0, CR0ACK, S_CR0 and S_CR0ACK are bit-layout compatible, so we reuse
> +     * the CR0 field definitions and only switch banks via sec_sid to reduce
> +     * code duplication. Also the other bits in CR0/CR0ACK are relevant here.
> +     */
> +    return FIELD_EX32(s->bank[sec_sid].cr0ack, CR0, SMMUEN);
> +}
> +
> +/* Check if SMMU is disabled in stable status */
> +static inline bool smmuv3_smmu_disabled_stable(SMMUv3State *s, SMMUSecSID 
> sec_sid)
s/stable/updated?
SMMU_CR0 spec quote:

> +{
> +    int cr0_smmuen = smmu_enabled(s, sec_sid);
> +    int cr0ack_smmuen = smmuv3_get_cr0ack_smmuen(s, sec_sid);
> +    return (cr0_smmuen == 0 && cr0ack_smmuen == 0);

Each field in this register has a corresponding field in SMMU_CR0ACK. An
individual field is described as
Updated after the value of the field observed in SMMU_CR0ACK matches the
value that was written to the field
in SMMU_CR0.

Besides I don't if its wort that complexity here because the update is
done in the emulation
    case A_CR0:
        bank->cr[0] = data;
        bank->cr0ack = data & ~SMMU_CR0_RESERVED;

Just adding a coment would be enough I think

I would split this patch into 2 sparate patches, first one CR2 and then
STRTAB_BASE




> +}
> +
> +/* Check if STRTAB_BASE register is writable */
> +static bool smmu_strtab_base_writable(SMMUv3State *s, SMMUSecSID sec_sid)
> +{
> +    /* SMMU_IDR1.TABLES_PRESET applies to both modeled interfaces. */
> +    if (FIELD_EX32(s->bank[SMMU_SEC_SID_NS].idr[1], IDR1, TABLES_PRESET)) {
> +        return false;
> +    }
> +
> +    /* Check SMMUEN conditions for the specific security domain */
> +    return smmuv3_smmu_disabled_stable(s, sec_sid);
> +}
> +
>  static int smmuv3_cmdq_consume(SMMUv3State *s, Error **errp, SMMUSecSID 
> sec_sid)
>  {
>      SMMUState *bs = ARM_SMMU(s);
> @@ -1801,7 +1831,14 @@ static MemTxResult smmu_writell(SMMUv3State *s, hwaddr 
> offset,
>          bank->gerror_irq_cfg0 = data & SMMU_GERROR_IRQ_CFG0_RESERVED;
>          return MEMTX_OK;
>      case A_STRTAB_BASE:
> -        bank->strtab_base = data;
> +        if (!smmu_strtab_base_writable(s, reg_sec_sid)) {
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                          "STRTAB_BASE write ignored: register is RO\n");
> +            return MEMTX_OK;
> +        }
> +
> +        /* Clear reserved bits according to spec */
> +        bank->strtab_base = data & SMMU_STRTAB_BASE_RESERVED;
>          return MEMTX_OK;
>      case A_CMDQ_BASE:
>          bank->cmdq.base = data;
> @@ -1852,7 +1889,15 @@ static MemTxResult smmu_writel(SMMUv3State *s, hwaddr 
> offset,
>          bank->cr[1] = data;
>          break;
>      case A_CR2:
> -        bank->cr[2] = data;
> +        if (smmuv3_smmu_disabled_stable(s, reg_sec_sid)) {
> +            /* Allow write: SMMUEN is 0 in both CR0 and CR0ACK */
> +            bank->cr[2] = data;
> +        } else {
> +            /* CONSTRAINED UNPREDICTABLE behavior: Ignore this write */
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                          "CR2 write ignored: register is read-only when "
> +                          "CR0.SMMUEN or CR0ACK.SMMUEN is set\n");
not sure those logs are requested.
> +        }
>          break;
>      case A_IRQ_CTRL:
>          bank->irq_ctrl = data;
> @@ -1915,12 +1960,32 @@ static MemTxResult smmu_writel(SMMUv3State *s, hwaddr 
> offset,
>          }
>          break;
>      case A_STRTAB_BASE: /* 64b */
> +        if (!smmu_strtab_base_writable(s, reg_sec_sid)) {
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                          "STRTAB_BASE write ignored: register is RO\n");
> +            return MEMTX_OK;
> +        }
> +
> +        data &= SMMU_STRTAB_BASE_RESERVED;
>          bank->strtab_base = deposit64(bank->strtab_base, 0, 32, data);
>          break;
>      case A_STRTAB_BASE + 4:
> +        if (!smmu_strtab_base_writable(s, reg_sec_sid)) {
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                          "STRTAB_BASE + 4 write ignored: register is RO\n");
> +            return MEMTX_OK;
> +        }
> +
> +        data &= SMMU_STRTAB_BASE_RESERVED;
>          bank->strtab_base = deposit64(bank->strtab_base, 32, 32, data);
>          break;
>      case A_STRTAB_BASE_CFG:
> +        if (!smmu_strtab_base_writable(s, reg_sec_sid)) {
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                          "STRTAB_BASE_CFG write ignored: register is RO\n");
> +            return MEMTX_OK;
> +        }
> +
>          bank->strtab_base_cfg = data;
>          if (FIELD_EX32(data, STRTAB_BASE_CFG, FMT) == 1) {
>              bank->sid_split = FIELD_EX32(data, STRTAB_BASE_CFG, SPLIT);
Thanks

Eric


Reply via email to