iommu/tegra241-cmdqv: Don't fall back to a freed smmu after devm_krealloc()

__tegra241_cmdqv_probe() uses devm_krealloc() to grow @smmu into the larger
tegra241_cmdqv, which frees the original @smmu once it relocates. A failure
after that returned NULL, and the caller then dereferenced the freed @smmu
on its fallback path.

Return an int and take @smmu by reference instead, then update *smmu to the
reallocated pointer after devm_krealloc() succeeds, so the caller and its
fallback path both use the live @smmu rather than the freed original.

Fixes: 918eb5c856 ("iommu/arm-smmu-v3: Add in-kernel support for NVIDIA Tegra241 (Grace) CMDQV")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Will Deacon <will@kernel.org>
This commit is contained in:
Nicolin Chen 2026-07-14 13:55:02 -07:00 committed by Will Deacon
parent 5acd67ceb3
commit d4d05f55e9

View File

@ -944,16 +944,22 @@ static int tegra241_cmdqv_init_structures(struct arm_smmu_device *smmu)
static struct dentry *cmdqv_debugfs_dir;
#endif
static struct arm_smmu_device *
__tegra241_cmdqv_probe(struct arm_smmu_device *smmu, struct resource *res,
int irq)
/*
* Probe the CMDQV and reallocate @smmu into the larger cmdqv->smmu.
*
* devm_krealloc() may relocate and free the original @smmu, so update *smmu to
* the new pointer once it succeeds. The error paths after it do the same, so a
* caller falling back keeps a live @smmu instead of the freed original.
*/
static int __tegra241_cmdqv_probe(struct arm_smmu_device **smmu,
struct resource *res, int irq)
{
static const struct arm_smmu_impl_ops init_ops = {
.init_structures = tegra241_cmdqv_init_structures,
.device_remove = tegra241_cmdqv_remove,
};
struct tegra241_cmdqv *cmdqv = NULL;
struct arm_smmu_device *new_smmu;
struct device *dev = (*smmu)->dev;
struct tegra241_cmdqv *cmdqv;
void __iomem *base;
u32 regval;
int ret;
@ -962,25 +968,28 @@ __tegra241_cmdqv_probe(struct arm_smmu_device *smmu, struct resource *res,
base = ioremap(res->start, resource_size(res));
if (!base) {
dev_err(smmu->dev, "failed to ioremap\n");
return NULL;
dev_err(dev, "failed to ioremap\n");
return -ENOMEM;
}
regval = readl(base + TEGRA241_CMDQV_CONFIG);
if (disable_cmdqv) {
dev_info(smmu->dev, "Detected disable_cmdqv=true\n");
dev_info(dev, "Detected disable_cmdqv=true\n");
writel(regval & ~CMDQV_EN, base + TEGRA241_CMDQV_CONFIG);
ret = -ENODEV;
goto iounmap;
}
cmdqv = devm_krealloc(smmu->dev, smmu, sizeof(*cmdqv), GFP_KERNEL);
if (!cmdqv)
cmdqv = devm_krealloc(dev, *smmu, sizeof(*cmdqv), GFP_KERNEL);
if (!cmdqv) {
ret = -ENOMEM;
goto iounmap;
new_smmu = &cmdqv->smmu;
}
*smmu = &cmdqv->smmu;
cmdqv->irq = irq;
cmdqv->base = base;
cmdqv->dev = smmu->impl_dev;
cmdqv->dev = (*smmu)->impl_dev;
cmdqv->base_phys = res->start;
regval = readl_relaxed(REG_CMDQV(cmdqv, PARAM));
@ -992,8 +1001,10 @@ __tegra241_cmdqv_probe(struct arm_smmu_device *smmu, struct resource *res,
cmdqv->vintfs =
kzalloc_objs(*cmdqv->vintfs, cmdqv->num_vintfs);
if (!cmdqv->vintfs)
if (!cmdqv->vintfs) {
ret = -ENOMEM;
goto iounmap;
}
ida_init(&cmdqv->vintf_ids);
@ -1022,24 +1033,23 @@ __tegra241_cmdqv_probe(struct arm_smmu_device *smmu, struct resource *res,
#endif
/* Provide init-level ops only, until tegra241_cmdqv_init_structures */
new_smmu->impl_ops = &init_ops;
cmdqv->smmu.impl_ops = &init_ops;
return new_smmu;
return 0;
free_vintfs:
ida_destroy(&cmdqv->vintf_ids);
kfree(cmdqv->vintfs);
iounmap:
iounmap(base);
return NULL;
return ret;
}
struct arm_smmu_device *tegra241_cmdqv_probe(struct arm_smmu_device *smmu)
{
struct platform_device *pdev = to_platform_device(smmu->impl_dev);
struct arm_smmu_device *new_smmu;
struct resource *res;
int irq;
int irq, ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
@ -1052,15 +1062,15 @@ struct arm_smmu_device *tegra241_cmdqv_probe(struct arm_smmu_device *smmu)
dev_warn(&pdev->dev,
"no interrupt. errors will not be reported\n");
new_smmu = __tegra241_cmdqv_probe(smmu, res, irq);
if (new_smmu)
return new_smmu;
ret = __tegra241_cmdqv_probe(&smmu, res, irq);
if (!ret)
return smmu;
out_fallback:
dev_info(smmu->impl_dev, "Falling back to standard SMMU CMDQ\n");
smmu->options &= ~ARM_SMMU_OPT_TEGRA241_CMDQV;
put_device(smmu->impl_dev);
return ERR_PTR(-ENODEV);
return smmu;
}
/* User space VINTF and VCMDQ Functions */