[PATCH 7/7] media: rkvdec: Add multicore IOMMU support
From: Detlev Casanova
Date: Thu Apr 09 2026 - 09:54:39 EST
As each core has its own IOMMU core, buffers must be mapped in each
core's IOMMU so that any run() call can use any core without having to
remap everything.
To do that, we use rockchip iommu domain's iommu devices list.
With that, one IOMMU domain can be mapped on multiple devices, meaning
that each call to iommu_map() will flush the new mapping on all devices
in the list.
The IOMMU domain that will have all devices in its list is the first
core's default domain.
Another domain cannot be used because VB2 allocates buffers through the
DMA engine, which uses iommu_get_dma_domain() to find the domain to map
buffers through.
The IOMMU restore function can still work as before, but needs to be more
explicit in what domain to attach the device to.
That is because detaching the empty domain will reattach the core's default
domain, which is wrong (except for the first "main" core).
The RCB temporary buffers are allocated in a dedicated SRAM, each
core has its own SRAM, so the mapping for each core's SRAM is added in the
global domain.
Everything else is mapped through the first core's default domain, making
the driver write the mappings on both IOMMU cores.
Signed-off-by: Detlev Casanova <detlev.casanova@xxxxxxxxxxxxx>
---
.../media/platform/rockchip/rkvdec/rkvdec-rcb.c | 21 ++++++-------
.../media/platform/rockchip/rkvdec/rkvdec-rcb.h | 6 ++--
drivers/media/platform/rockchip/rkvdec/rkvdec.c | 35 +++++++++++++++++-----
drivers/media/platform/rockchip/rkvdec/rkvdec.h | 2 +-
4 files changed, 44 insertions(+), 20 deletions(-)
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.c
index 190fb7438e8c..977e37cf209b 100644
--- a/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.c
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.c
@@ -57,7 +57,7 @@ bool rkvdec_rcb_buf_validate_size(struct rkvdec_ctx *ctx)
return ret;
}
-void rkvdec_free_rcb(struct rkvdec_core *core)
+void rkvdec_free_rcb(struct rkvdec_dev *rkvdec, struct rkvdec_core *core)
{
struct rkvdec_rcb_config *cfg = core->rcb_config;
unsigned long virt_addr;
@@ -76,12 +76,12 @@ void rkvdec_free_rcb(struct rkvdec_core *core)
case RKVDEC_ALLOC_SRAM:
virt_addr = (unsigned long)cfg->rcb_bufs[i].cpu;
- if (core->iommu_domain)
- iommu_unmap(core->iommu_domain, virt_addr, rcb_size);
+ if (rkvdec->iommu_global_domain)
+ iommu_unmap(rkvdec->iommu_global_domain, virt_addr, rcb_size);
gen_pool_free(core->sram_pool, virt_addr, rcb_size);
break;
case RKVDEC_ALLOC_DMA:
- dma_free_coherent(core->dev,
+ dma_free_coherent(rkvdec->main_core->dev,
rcb_size,
cfg->rcb_bufs[i].cpu,
cfg->rcb_bufs[i].dma);
@@ -97,7 +97,8 @@ void rkvdec_free_rcb(struct rkvdec_core *core)
core->rcb_config = NULL;
}
-int rkvdec_allocate_rcb(struct rkvdec_core *core, u32 width, u32 height,
+int rkvdec_allocate_rcb(struct rkvdec_dev *rkvdec, struct rkvdec_core *core,
+ u32 width, u32 height,
const struct rcb_size_info *size_info,
size_t rcb_count)
{
@@ -132,7 +133,7 @@ int rkvdec_allocate_rcb(struct rkvdec_core *core, u32 width, u32 height,
/* Try allocating an SRAM buffer */
if (core->sram_pool) {
- if (core->iommu_domain)
+ if (rkvdec->iommu_global_domain)
rcb_size = ALIGN(rcb_size, SZ_4K);
cpu = gen_pool_dma_zalloc_align(core->sram_pool,
@@ -142,11 +143,11 @@ int rkvdec_allocate_rcb(struct rkvdec_core *core, u32 width, u32 height,
}
/* If an IOMMU is used, map the SRAM address through it */
- if (cpu && core->iommu_domain) {
+ if (cpu && rkvdec->iommu_global_domain) {
unsigned long virt_addr = (unsigned long)cpu;
phys_addr_t phys_addr = dma;
- ret = iommu_map(core->iommu_domain, virt_addr, phys_addr,
+ ret = iommu_map(rkvdec->iommu_global_domain, virt_addr, phys_addr,
rcb_size, IOMMU_READ | IOMMU_WRITE, 0);
if (ret) {
gen_pool_free(core->sram_pool,
@@ -166,7 +167,7 @@ int rkvdec_allocate_rcb(struct rkvdec_core *core, u32 width, u32 height,
ram_fallback:
/* Fallback to RAM */
if (!cpu) {
- cpu = dma_alloc_coherent(core->dev,
+ cpu = dma_alloc_coherent(rkvdec->main_core->dev,
rcb_size,
&dma,
GFP_KERNEL);
@@ -189,7 +190,7 @@ int rkvdec_allocate_rcb(struct rkvdec_core *core, u32 width, u32 height,
return 0;
err_alloc:
- rkvdec_free_rcb(core);
+ rkvdec_free_rcb(rkvdec, core);
return ret;
}
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.h
index a12af9b7dc2b..d1149afe7fda 100644
--- a/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.h
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec-rcb.h
@@ -8,6 +8,7 @@
#include <linux/types.h>
+struct rkvdec_dev;
struct rkvdec_ctx;
struct rkvdec_core;
@@ -21,11 +22,12 @@ struct rcb_size_info {
enum rcb_axis axis;
};
-int rkvdec_allocate_rcb(struct rkvdec_core *core, u32 width, u32 height,
+int rkvdec_allocate_rcb(struct rkvdec_dev *rkvdec, struct rkvdec_core *core,
+ u32 width, u32 height,
const struct rcb_size_info *size_info,
size_t rcb_count);
dma_addr_t rkvdec_rcb_buf_dma_addr(struct rkvdec_ctx *ctx, int id);
size_t rkvdec_rcb_buf_size(struct rkvdec_ctx *ctx, int id);
int rkvdec_rcb_buf_count(struct rkvdec_ctx *ctx);
bool rkvdec_rcb_buf_validate_size(struct rkvdec_ctx *ctx);
-void rkvdec_free_rcb(struct rkvdec_core *core);
+void rkvdec_free_rcb(struct rkvdec_dev *rkvdec, struct rkvdec_core *core);
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c
index c2818f1575ef..2930e9b64906 100644
--- a/drivers/media/platform/rockchip/rkvdec/rkvdec.c
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.c
@@ -1204,9 +1204,9 @@ static void rkvdec_device_run(void *priv)
}
if (!rkvdec_rcb_buf_validate_size(ctx)) {
- rkvdec_free_rcb(ctx->core);
+ rkvdec_free_rcb(ctx->dev, ctx->core);
- ret = rkvdec_allocate_rcb(ctx->core,
+ ret = rkvdec_allocate_rcb(ctx->dev, ctx->core,
ctx->decoded_fmt.fmt.pix_mp.width,
ctx->decoded_fmt.fmt.pix_mp.height,
ctx->dev->variant->rcb_sizes,
@@ -1486,6 +1486,7 @@ static void rkvdec_v4l2_cleanup(struct rkvdec_dev *rkvdec)
static void rkvdec_iommu_restore(struct rkvdec_core *core)
{
+ int ret;
if (core->empty_domain) {
/*
* To rewrite mapping into the attached IOMMU core, attach a new empty domain that
@@ -1494,8 +1495,14 @@ static void rkvdec_iommu_restore(struct rkvdec_core *core)
* 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(core->empty_domain, core->dev);
+ iommu_detach_device(core->curr_ctx->dev->iommu_global_domain, core->dev);
+ ret = iommu_attach_device(core->empty_domain, core->dev);
+ if (ret)
+ dev_warn(core->dev, "Cannot attach empty domain: %d\n", ret);
iommu_detach_device(core->empty_domain, core->dev);
+ ret = iommu_attach_device(core->curr_ctx->dev->iommu_global_domain, core->dev);
+ if (ret)
+ dev_warn(core->dev, "Cannot attach global domain: %d\n", ret);
}
}
@@ -1858,6 +1865,8 @@ static int rkvdec_probe(struct platform_device *pdev)
core = &rkvdec->cores[rkvdec->core_count++];
+ core->id = rkvdec->core_count - 1;
+
platform_set_drvdata(pdev, rkvdec);
core->dev = &pdev->dev;
INIT_DELAYED_WORK(&core->watchdog_work, rkvdec_watchdog_func);
@@ -1883,12 +1892,24 @@ static int rkvdec_probe(struct platform_device *pdev)
return PTR_ERR(core->link);
}
- core->iommu_domain = iommu_get_domain_for_dev(&pdev->dev);
- if (core->iommu_domain) {
+ if (iommu_get_domain_for_dev(&pdev->dev)) {
core->empty_domain = iommu_paging_domain_alloc(core->dev);
- if (!core->empty_domain)
+ if (IS_ERR(core->empty_domain))
dev_warn(core->dev, "cannot alloc new empty domain\n");
+
+ if (!rkvdec->iommu_global_domain) {
+ rkvdec->iommu_global_domain = iommu_get_domain_for_dev(core->dev);
+
+ if (IS_ERR(rkvdec->iommu_global_domain)) {
+ rkvdec->iommu_global_domain = NULL;
+ dev_warn_once(core->dev, "cannot alloc new global domain\n");
+ }
+ }
+
+ ret = iommu_attach_device(rkvdec->iommu_global_domain, core->dev);
+ if (ret)
+ dev_warn(core->dev, "cannot attach global domain to core %d\n", core->id);
}
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
@@ -1961,7 +1982,7 @@ static void rkvdec_remove(struct platform_device *pdev)
if (rkvdec->cores[i].empty_domain)
iommu_domain_free(rkvdec->cores[i].empty_domain);
- rkvdec_free_rcb(&rkvdec->cores[i]);
+ rkvdec_free_rcb(rkvdec, &rkvdec->cores[i]);
}
}
diff --git a/drivers/media/platform/rockchip/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h
index 4f042a367dc0..ccd766b220c7 100644
--- a/drivers/media/platform/rockchip/rkvdec/rkvdec.h
+++ b/drivers/media/platform/rockchip/rkvdec/rkvdec.h
@@ -135,7 +135,6 @@ struct rkvdec_core {
void __iomem *link;
struct delayed_work watchdog_work;
struct gen_pool *sram_pool;
- struct iommu_domain *iommu_domain;
struct iommu_domain *empty_domain;
struct rkvdec_rcb_config *rcb_config;
struct rkvdec_ctx *curr_ctx;
@@ -155,6 +154,7 @@ struct rkvdec_dev {
unsigned int available_core_count;
spinlock_t cores_lock; /* serializes core list access */
struct rkvdec_core *main_core;
+ struct iommu_domain *iommu_global_domain;
};
struct rkvdec_ctx {
--
2.53.0