diff options
| author | Nicolin Chen <nicolinc@nvidia.com> | 2026-07-14 13:55:00 -0700 |
|---|---|---|
| committer | Will Deacon <will@kernel.org> | 2026-07-28 09:51:48 +0000 |
| commit | a2ee315db42610c8bb0fa8f37b4cc8082b7c8f42 (patch) | |
| tree | 2c59d158828bbaa255733c9576bb34651cefc8b1 | |
| parent | a491be376abd1c80a314cdd658632c85cd660b73 (diff) | |
| download | linux-a2ee315db42610c8bb0fa8f37b4cc8082b7c8f42.tar.gz linux-a2ee315db42610c8bb0fa8f37b4cc8082b7c8f42.zip | |
iommu/tegra241-cmdqv: Harden error-map index handling in the error ISR
tegra241_vintf0_handle_error() reads both 64-bit LVCMDQ error-map registers
but used the register-local __ffs64() bit directly as the vintf->lvcmdqs[]
index. For the second register that selects the wrong queue instead of 64 *
i + bit, clearing the wrong queue's error status.
The index is unbounded too: a bit at or beyond num_lvcmdqs_per_vintf would
walk the read off vintf->lvcmdqs[].
tegra241_cmdqv_isr() has the same flaw one level up: a VINTF_ERR_MAP bit
at or beyond num_vintfs would walk the read off cmdqv->vintfs[].
Use 64 * i + bit for the index and clear the snapshot with the local bit.
In both handlers, WARN_ON_ONCE() and skip an out-of-bounds index. Only a
malfunctioning device sets such a bit, so the _ONCE form keeps a wedged map
from flooding the log.
Note that 64 * i + bit is not reachable with the current configuration as a
VINTF is pre-assigned with 2 lvcmdqs, this is not treated as bug fix but an
defensive hardening.
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Will Deacon <will@kernel.org>
| -rw-r--r-- | drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 14 |
1 files changed, 11 insertions, 3 deletions
diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c index 49f085c11edb..cc80426558d5 100644 --- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c +++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c @@ -311,25 +311,30 @@ static void tegra241_vintf_user_handle_error(struct tegra241_vintf *vintf) static void tegra241_vintf0_handle_error(struct tegra241_vintf *vintf) { + struct tegra241_cmdqv *cmdqv = vintf->cmdqv; int i; for (i = 0; i < LVCMDQ_ERR_MAP_NUM_64; i++) { u64 map = readq_relaxed(REG_VINTF(vintf, LVCMDQ_ERR_MAP_64(i))); while (map) { - unsigned long lidx = __ffs64(map); + unsigned long map_bit = __ffs64(map); + unsigned long lidx = 64 * i + map_bit; struct tegra241_vcmdq *vcmdq; u32 gerror; - map &= ~BIT_ULL(lidx); + map &= ~BIT_ULL(map_bit); + /* A bit beyond the count means a HW error; skip it */ + if (WARN_ON_ONCE(lidx >= cmdqv->num_lvcmdqs_per_vintf)) + continue; /* Pairs with smp_store_release() publishing it */ vcmdq = smp_load_acquire(&vintf->lvcmdqs[lidx]); if (!vcmdq) continue; gerror = readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, GERROR)); - __arm_smmu_cmdq_skip_err(&vintf->cmdqv->smmu, &vcmdq->cmdq); + __arm_smmu_cmdq_skip_err(&cmdqv->smmu, &vcmdq->cmdq); writel(gerror, REG_VCMDQ_PAGE0(vcmdq, GERRORN)); } } @@ -381,6 +386,9 @@ static irqreturn_t tegra241_cmdqv_isr(int irq, void *devid) vintf_map &= ~BIT_ULL(idx); + /* A bit beyond the count means a HW error; skip it */ + if (WARN_ON_ONCE(idx >= cmdqv->num_vintfs)) + continue; /* The slot may be published or torn down (NULL'd) concurrently */ vintf = smp_load_acquire(&cmdqv->vintfs[idx]); if (vintf) |
