On Thu May 8 17:00:15 2025 -0400, Nicolas Dufresne wrote: > On errors, the rkvdec chip self resets. This can clear the addresses > programmed in the iommu. This case is signaled by the > RKVDEC_SOFTRESET_RDY status bit. > > Since the iommu framework does not have a restore functionality, and > as recommended by the iommu subsystem maintainers, this patch > restores the iommu programming by attaching and detaching an empty > domain, which will clear and restore the default domain. > > Suggested-by: Detlev Casanova <detlev.casan...@collabora.com> > Tested-by: Detlev Casanova <detlev.casan...@collabora.com> > Reviewed-by: Detlev Casanova <detlev.casan...@collabora.com> > Signed-off-by: Nicolas Dufresne <nicolas.dufre...@collabora.com> > Signed-off-by: Hans Verkuil <hverk...@xs4all.nl>
Patch committed. Thanks, Hans Verkuil drivers/staging/media/rkvdec/rkvdec.c | 43 +++++++++++++++++++++++++++++------ drivers/staging/media/rkvdec/rkvdec.h | 1 + 2 files changed, 37 insertions(+), 7 deletions(-) --- diff --git a/drivers/staging/media/rkvdec/rkvdec.c b/drivers/staging/media/rkvdec/rkvdec.c index 3367902f22de..d707088ec0dc 100644 --- a/drivers/staging/media/rkvdec/rkvdec.c +++ b/drivers/staging/media/rkvdec/rkvdec.c @@ -11,6 +11,7 @@ #include <linux/clk.h> #include <linux/interrupt.h> +#include <linux/iommu.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> @@ -1055,24 +1056,42 @@ static void rkvdec_v4l2_cleanup(struct rkvdec_dev *rkvdec) v4l2_device_unregister(&rkvdec->v4l2_dev); } +static void rkvdec_iommu_restore(struct rkvdec_dev *rkvdec) +{ + if (rkvdec->empty_domain) { + /* + * To rewrite mapping into the attached IOMMU core, attach a new empty domain that + * will program an empty table, then detach it to restore the default domain and + * all cached mappings. + * This is safely done in this interrupt handler to make sure no memory get mapped + * through the IOMMU while the empty domain is attached. + */ + iommu_attach_device(rkvdec->empty_domain, rkvdec->dev); + iommu_detach_device(rkvdec->empty_domain, rkvdec->dev); + } +} + static irqreturn_t rkvdec_irq_handler(int irq, void *priv) { struct rkvdec_dev *rkvdec = priv; + struct rkvdec_ctx *ctx = v4l2_m2m_get_curr_priv(rkvdec->m2m_dev); enum vb2_buffer_state state; u32 status; status = readl(rkvdec->regs + RKVDEC_REG_INTERRUPT); - state = (status & RKVDEC_RDY_STA) ? - VB2_BUF_STATE_DONE : VB2_BUF_STATE_ERROR; - writel(0, rkvdec->regs + RKVDEC_REG_INTERRUPT); - if (cancel_delayed_work(&rkvdec->watchdog_work)) { - struct rkvdec_ctx *ctx; - ctx = v4l2_m2m_get_curr_priv(rkvdec->m2m_dev); - rkvdec_job_finish(ctx, state); + if (status & RKVDEC_RDY_STA) { + state = VB2_BUF_STATE_DONE; + } else { + state = VB2_BUF_STATE_ERROR; + if (status & RKVDEC_SOFTRESET_RDY) + rkvdec_iommu_restore(rkvdec); } + if (cancel_delayed_work(&rkvdec->watchdog_work)) + rkvdec_job_finish(ctx, state); + return IRQ_HANDLED; } @@ -1140,6 +1159,13 @@ static int rkvdec_probe(struct platform_device *pdev) return ret; } + if (iommu_get_domain_for_dev(&pdev->dev)) { + rkvdec->empty_domain = iommu_paging_domain_alloc(rkvdec->dev); + + if (!rkvdec->empty_domain) + dev_warn(rkvdec->dev, "cannot alloc new empty domain\n"); + } + vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32)); irq = platform_get_irq(pdev, 0); @@ -1179,6 +1205,9 @@ static void rkvdec_remove(struct platform_device *pdev) rkvdec_v4l2_cleanup(rkvdec); pm_runtime_disable(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); + + if (rkvdec->empty_domain) + iommu_domain_free(rkvdec->empty_domain); } #ifdef CONFIG_PM diff --git a/drivers/staging/media/rkvdec/rkvdec.h b/drivers/staging/media/rkvdec/rkvdec.h index 9a9f4fced7a1..f6e8bf38add3 100644 --- a/drivers/staging/media/rkvdec/rkvdec.h +++ b/drivers/staging/media/rkvdec/rkvdec.h @@ -110,6 +110,7 @@ struct rkvdec_dev { void __iomem *regs; struct mutex vdev_lock; /* serializes ioctls */ struct delayed_work watchdog_work; + struct iommu_domain *empty_domain; }; struct rkvdec_ctx {