From cbc41aacd49e695338940196e7084770365e1b68 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Tue, 14 Jul 2026 13:54:58 -0700 Subject: [PATCH] iommu/tegra241-cmdqv: Publish an LVCMDQ only after it is fully initialized tegra241_vintf_init_lvcmdq() stores the freshly allocated vcmdq pointer to the vintf->lvcmdqs[] array, before tegra241_vcmdq_alloc_smmu_cmdq() builds the vcmdq->cmdq. The error ISR dereferences that cmdq, so a latched LVCMDQ error (e.g. one inherited across a kexec) firing in this window would make tegra241_vintf0_handle_error() pass the still-zeroed arm_smmu_cmdq down to __arm_smmu_cmdq_skip_err(), dereferencing NULL queue register pointers. Drop the store from tegra241_vintf_init_lvcmdq() and publish the vcmdq at the end of the allocation instead, with an smp_store_release() that pairs with an smp_load_acquire() in the ISR, which can see a fully built LVCMDQ or NULL. The user-owned LVCMDQ allocation moves accordingly, publishing the vcmdq once tegra241_vcmdq_hw_init_user() succeeds, using a plain store since a user VINTF's lvcmdqs[] has no lockless reader -- the error ISR only walks the VINTF0 array. Fixes: 918eb5c856f6 ("iommu/arm-smmu-v3: Add in-kernel support for NVIDIA Tegra241 (Grace) CMDQV") Assisted-by: Claude:claude-fable-5 Signed-off-by: Nicolin Chen Signed-off-by: Will Deacon --- .../iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c index aaf9ce38bd93..71c7ef52dad6 100644 --- a/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c +++ b/drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c @@ -318,12 +318,19 @@ static void tegra241_vintf0_handle_error(struct tegra241_vintf *vintf) while (map) { unsigned long lidx = __ffs64(map); - struct tegra241_vcmdq *vcmdq = vintf->lvcmdqs[lidx]; - u32 gerror = readl_relaxed(REG_VCMDQ_PAGE0(vcmdq, GERROR)); + struct tegra241_vcmdq *vcmdq; + u32 gerror; + map &= ~BIT_ULL(lidx); + + /* 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); writel(gerror, REG_VCMDQ_PAGE0(vcmdq, GERRORN)); - map &= ~BIT_ULL(lidx); } } } @@ -666,7 +673,6 @@ static int tegra241_vintf_init_lvcmdq(struct tegra241_vintf *vintf, u16 lidx, vcmdq->page0 = cmdqv->base + TEGRA241_VINTFi_LVCMDQ_PAGE0(idx, lidx); vcmdq->page1 = cmdqv->base + TEGRA241_VINTFi_LVCMDQ_PAGE1(idx, lidx); - vintf->lvcmdqs[lidx] = vcmdq; return 0; } @@ -705,14 +711,15 @@ tegra241_vintf_alloc_lvcmdq(struct tegra241_vintf *vintf, u16 lidx) /* Build an arm_smmu_cmdq for each LVCMDQ */ ret = tegra241_vcmdq_alloc_smmu_cmdq(vcmdq); if (ret) - goto deinit_lvcmdq; + goto free_vcmdq; + + /* Pairs with the smp_load_acquire() in the error ISR */ + smp_store_release(&vintf->lvcmdqs[lidx], vcmdq); dev_dbg(cmdqv->dev, "%sallocated\n", lvcmdq_error_header(vcmdq, header, 64)); return vcmdq; -deinit_lvcmdq: - tegra241_vintf_deinit_lvcmdq(vintf, lidx); free_vcmdq: kfree(vcmdq); return ERR_PTR(ret); @@ -1140,13 +1147,15 @@ static int tegra241_vintf_alloc_lvcmdq_user(struct iommufd_hw_queue *hw_queue, if (ret) goto unmap_lvcmdq; + /* No lockless reader of a user VINTF's lvcmdqs[]; mutex-serialized */ + vintf->lvcmdqs[lidx] = vcmdq; + hw_queue->destroy = &tegra241_vintf_destroy_lvcmdq_user; mutex_unlock(&vintf->lvcmdq_mutex); return 0; unmap_lvcmdq: tegra241_vcmdq_unmap_lvcmdq(vcmdq); - tegra241_vintf_deinit_lvcmdq(vintf, lidx); undepend_vcmdq: if (vcmdq->prev) iommufd_hw_queue_undepend(vcmdq, vcmdq->prev, core);