[PATCH 2/2] media: venus: Fix iova allocation from restrict region
From: Vishnu Reddy
Date: Wed Aug 12 2026 - 07:12:20 EST
The VPU issues DMA through several SMMU streams, and the hardware does
not give every stream the same addressable range. The non-pixel stream
cannot address the low 600MB of IOVA space, while the pixel stream can
address the full range:
+-----------------------------------------------------------+
| non-pixel stream addressable range (600 MB - 3.5 GB) |
| 0x25800000 - 0xe0000000 |
+-----------------------------------------------------------+
| pixel stream addressable range (0 - 3.5 GB) |
| 0x00000000 - 0xe0000000 |
+-----------------------------------------------------------+
A single "iommus" property on the video-codec node puts every stream
in one IOMMU domain sharing one IOVA allocator, so nothing restricts a
non-pixel buffer to avoid 0 to 600MB. Once an allocation lands below that
boundary the hardware faults, which shows up as unhandled SMMU page
faults and spontaneous reboots.
https://gitlab.freedesktop.org/drm/msm/-/work_items/100
A series to reserve the 0-600MB IOVA range via "iommu-addresses" was
already posted here:
https://lore.kernel.org/all/20260807-iris_iova_600mb_fix-v1-0-3996f67e33f9@xxxxxxxxxxxxxxxx
Those changes involve DT binding and DT node changes, and discussion is
still ongoing on how to handle those for stable and for the upcoming
sub-node design, with no conclusion reached yet. Thereby a critical reset
issue is still open.
This is an alternate solution to fix the unhandled SMMU page fault
by restricting the IOVA range in the video driver, which also makes it
easier and faster to land on mainline and stable kernels. At the same
time the patch only reserves in the IOVA space without allocating
any physical memory.
Currently sub-nodes are not yet present, and only a single device is
available, so the restriction is applied to both non-pixel and pixel
stream IDs. This makes the solution unoptimal while fixing the issue
considering all scenarios.
Once sub-nodes for non-pixel, pixel, and secure streams become available,
the restriction can be made stream specific.
Fixes: af2c3834c8ca ("[media] media: venus: adding core part and helper functions")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Vishnu Reddy <busanna.reddy@xxxxxxxxxxxxxxxx>
---
drivers/media/platform/qcom/venus/core.c | 71 +++++++++++++++++++++++++++++++-
drivers/media/platform/qcom/venus/core.h | 5 +++
2 files changed, 74 insertions(+), 2 deletions(-)
diff --git a/drivers/media/platform/qcom/venus/core.c b/drivers/media/platform/qcom/venus/core.c
index 243e342b0ae7..d70f7c3325b4 100644
--- a/drivers/media/platform/qcom/venus/core.c
+++ b/drivers/media/platform/qcom/venus/core.c
@@ -377,6 +377,64 @@ static int venus_add_dynamic_nodes(struct venus_core *core)
static void venus_remove_dynamic_nodes(struct venus_core *core) {}
#endif
+static int venus_reserve_iova_region(struct device *dev, struct dma_iova_state **iova_state,
+ unsigned long start, unsigned long size)
+{
+ struct dma_iova_state *state;
+ unsigned long mask = dma_get_mask(dev);
+ unsigned long end, rem, chunk;
+ unsigned int count = 0;
+ int ret;
+
+ state = kcalloc(BITS_PER_TYPE(dma_addr_t) + 1, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ end = start + size;
+ rem = end - max(start, PAGE_SIZE);
+
+ ret = dma_set_mask_and_coherent(dev, end - 1);
+ if (ret)
+ goto err_free_mem;
+
+ while (rem) {
+ chunk = min(end & -end, (u64)1 << (fls64(rem) - 1));
+
+ if (!dma_iova_try_alloc(dev, &state[count], 0, chunk)) {
+ ret = -ENOMEM;
+ goto err_free_iova;
+ }
+
+ rem -= chunk;
+ end -= chunk;
+ count++;
+ }
+
+ *iova_state = state;
+ dma_set_mask_and_coherent(dev, mask);
+
+ return 0;
+
+err_free_iova:
+ while (count--)
+ dma_iova_free(dev, &state[count]);
+ dma_set_mask_and_coherent(dev, mask);
+err_free_mem:
+ kfree(state);
+
+ return ret;
+}
+
+static void venus_unreserve_iova_region(struct device *dev, struct dma_iova_state *state)
+{
+ unsigned int i;
+
+ for (i = 0; dma_iova_size(&state[i]); i++)
+ dma_iova_free(dev, &state[i]);
+
+ kfree(state);
+}
+
static int venus_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -427,6 +485,11 @@ static int venus_probe(struct platform_device *pdev)
dma_set_max_seg_size(dev, UINT_MAX);
+ ret = venus_reserve_iova_region(dev, &core->iova_state, VENUS_NP_RESERVE_IOVA_START,
+ VENUS_NP_RESERVE_IOVA_SIZE);
+ if (ret)
+ goto err_core_put;
+
INIT_LIST_HEAD(&core->instances);
mutex_init(&core->lock);
INIT_DELAYED_WORK(&core->work, venus_sys_error_handler);
@@ -434,13 +497,13 @@ static int venus_probe(struct platform_device *pdev)
ret = hfi_create(core, &venus_core_ops);
if (ret)
- goto err_core_put;
+ goto err_unresv_iova_region;
ret = devm_request_threaded_irq(dev, core->irq, hfi_isr, venus_isr_thread,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"venus", core);
if (ret)
- goto err_core_put;
+ goto err_unresv_iova_region;
venus_assign_register_offsets(core);
@@ -525,6 +588,8 @@ static int venus_probe(struct platform_device *pdev)
v4l2_device_unregister(&core->v4l2_dev);
err_hfi_destroy:
hfi_destroy(core);
+err_unresv_iova_region:
+ venus_unreserve_iova_region(dev, core->iova_state);
err_core_put:
if (core->pm_ops->core_put)
core->pm_ops->core_put(core);
@@ -562,6 +627,8 @@ static void venus_remove(struct platform_device *pdev)
hfi_destroy(core);
+ venus_unreserve_iova_region(dev, core->iova_state);
+
mutex_destroy(&core->pm_lock);
mutex_destroy(&core->lock);
venus_dbgfs_deinit(core);
diff --git a/drivers/media/platform/qcom/venus/core.h b/drivers/media/platform/qcom/venus/core.h
index 46705a666776..30b8cababe86 100644
--- a/drivers/media/platform/qcom/venus/core.h
+++ b/drivers/media/platform/qcom/venus/core.h
@@ -8,6 +8,7 @@
#define __VENUS_CORE_H_
#include <linux/bitops.h>
+#include <linux/dma-mapping.h>
#include <linux/list.h>
#include <media/videobuf2-v4l2.h>
#include <media/v4l2-ctrls.h>
@@ -30,6 +31,9 @@
#define VENUS_MAX_FPS 240
+#define VENUS_NP_RESERVE_IOVA_START 0x0
+#define VENUS_NP_RESERVE_IOVA_SIZE 0x25800000
+
extern int venus_fw_debug;
struct freq_tbl {
@@ -250,6 +254,7 @@ struct venus_core {
unsigned long dump_core;
struct of_changeset *ocs;
bool hwmode_dev;
+ struct dma_iova_state *iova_state;
};
struct vdec_controls {
--
2.34.1