* [PATCH v3 01/22] accel: ethosu: Suspend after initialization
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:18 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 02/22] accel: ethosu: Ensure suspended on removal Rob Herring (Arm)
` (20 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The initial runtime-PM reference is held only while initializing the
NPU. Release it synchronously from ethosu_init() after the final
hardware access, before registering the DRM device.
This keeps the runtime-PM setup and initial reference handling together
and leaves the autosuspend configuration in place for subsequent jobs.
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_drv.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c
index 8108622de258..df76253d01a6 100644
--- a/drivers/accel/ethosu/ethosu_drv.c
+++ b/drivers/accel/ethosu/ethosu_drv.c
@@ -338,6 +338,8 @@ static int ethosu_init(struct ethosu_device *ethosudev)
ethosudev->npu_info.sram_size / 1024,
ethosudev->npu_info.pmu_counters);
+ pm_runtime_put_sync_suspend(ethosudev->base.dev);
+
return 0;
}
@@ -376,10 +378,6 @@ static int ethosu_probe(struct platform_device *pdev)
return ret;
ret = drm_dev_register(ðosudev->base, 0);
- if (ret)
- pm_runtime_dont_use_autosuspend(ethosudev->base.dev);
-
- pm_runtime_put_autosuspend(ethosudev->base.dev);
return ret;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 01/22] accel: ethosu: Suspend after initialization
2026-09-08 22:04 ` [PATCH v3 01/22] accel: ethosu: Suspend after initialization Rob Herring (Arm)
@ 2026-09-08 22:18 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:18 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Hardware Access in IRQ Handler
- [High] TOCTOU race on `dev->in_flight_job` between the IRQ handler and the timeout worker causes job tracking corruption
- [Medium] Missing pm_runtime_mark_last_busy() defeats autosuspend functionality
--
commit 70e7c7d4b9e3f59284be594e6021ace41a2927aa
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Suspend after initialization
The initial runtime-PM reference is held only while initializing the
NPU. Release it synchronously from ethosu_init() after the final
hardware access, before registering the DRM device.
> diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c
> index 8108622de258f..df76253d01a64 100644
> --- a/drivers/accel/ethosu/ethosu_drv.c
> +++ b/drivers/accel/ethosu/ethosu_drv.c
> @@ -338,6 +338,8 @@ static int ethosu_init(struct ethosu_device *ethosudev)
> ethosudev->npu_info.sram_size / 1024,
> ethosudev->npu_info.pmu_counters);
>
> + pm_runtime_put_sync_suspend(ethosudev->base.dev);
> +
> return 0;
> }
[Severity: High]
This isn't a bug introduced by this patch, but does explicitly disabling the
NPU clocks here expose a risk of crashing in the IRQ handler?
If a spurious interrupt occurs while the device is suspended, the IRQ handler
will unconditionally read from the unclocked NPU registers:
drivers/accel/ethosu/ethosu_job.c:ethosu_job_irq_handler() {
struct ethosu_device *dev = data;
u32 status = readl_relaxed(dev->regs + NPU_REG_STATUS);
if (!(status & STATUS_IRQ_RAISED))
return IRQ_NONE;
...
}
Subsystem guidelines mandate that IRQ handlers use pm_runtime_get_if_active()
to verify the device is powered on before accessing any registers. Accessing
memory-mapped registers of an unclocked device typically triggers a
synchronous bus fault or asynchronous external abort, resulting in a system
crash.
[ ... ]
[Severity: High]
This is a pre-existing issue, but can the timeout worker and IRQ handler race
and corrupt the job tracking?
ethosu_job_handle_irq() reads dev->in_flight_job without locking:
drivers/accel/ethosu/ethosu_job.c:ethosu_job_handle_irq() {
...
job = READ_ONCE(dev->in_flight_job);
if (job) {
WRITE_ONCE(dev->in_flight_job, NULL);
dma_fence_signal(job->done_fence);
}
}
Concurrently, ethosu_job_timedout() can run in a workqueue, stop the scheduler,
and modify the state without locking:
drivers/accel/ethosu/ethosu_job.c:ethosu_job_timedout() {
...
drm_sched_stop(&dev->sched, bad);
WRITE_ONCE(dev->in_flight_job, NULL);
/* Proceed with reset now. */
pm_runtime_force_suspend(dev->base.dev);
pm_runtime_force_resume(dev->base.dev);
/* Restart the scheduler */
drm_sched_start(&dev->sched, 0);
...
}
If the scheduler immediately pushes a new job, updating dev->in_flight_job,
could the preempted IRQ thread resume and blindly execute
WRITE_ONCE(dev->in_flight_job, NULL), overwriting the new job's tracking?
When the new job completes, the IRQ handler would read NULL and fail to signal
its fence, causing a perfectly valid job to falsely time out and trigger an
unnecessary hardware reset loop.
[Severity: Medium]
This is a pre-existing issue, but does this autosuspend implementation actually
delay powering down the NPU?
In ethosu_job_cleanup(), the driver drops the runtime PM reference:
drivers/accel/ethosu/ethosu_job.c:ethosu_job_cleanup() {
struct ethosu_job *job = container_of(ref, struct ethosu_job,
refcount);
pm_runtime_put_autosuspend(job->dev->base.dev);
...
}
Since pm_runtime_mark_last_busy() is never called, the autosuspend timer
evaluates from the beginning of the active PM transition rather than its
completion.
Will the old last_busy timestamp cause the autosuspend delay to evaluate as
already expired, defeating the autosuspend mechanism and leading to excessive
hardware power cycling?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=1
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 02/22] accel: ethosu: Ensure suspended on removal
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 01/22] accel: ethosu: Suspend after initialization Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:19 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 03/22] accel: ethosu: Fix probe error cleanup Rob Herring (Arm)
` (19 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel, sashiko-bot
If device removal occurs before an auto-suspend timeout occurs, the
suspend is cancelled and the device will be left with clocks running.
Make the remove() callback check this and force a suspend if the device
is currently active.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Reported-by: sashiko-bot@kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v3:
- New patch
---
drivers/accel/ethosu/ethosu_drv.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c
index df76253d01a6..1f6fb5d18cf2 100644
--- a/drivers/accel/ethosu/ethosu_drv.c
+++ b/drivers/accel/ethosu/ethosu_drv.c
@@ -387,6 +387,10 @@ static void ethosu_remove(struct platform_device *pdev)
drm_dev_unregister(ðosudev->base);
ethosu_job_fini(ethosudev);
+
+ if (pm_runtime_get_if_active(&pdev->dev))
+ pm_runtime_put_sync_suspend(&pdev->dev);
+
if (ethosudev->sram)
gen_pool_free(ethosudev->srampool, (unsigned long)ethosudev->sram,
ethosudev->npu_info.sram_size);
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 02/22] accel: ethosu: Ensure suspended on removal
2026-09-08 22:04 ` [PATCH v3 02/22] accel: ethosu: Ensure suspended on removal Rob Herring (Arm)
@ 2026-09-08 22:19 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:19 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] The patch evaluates the return value of `pm_runtime_get_if_active()` as a boolean, which fails to handle the `-EINVAL` error case and causes an unbalanced usage counter decrement when Runtime PM is disabled.
Pre-existing issues:
- [High] In `ethosu_perfmon_stop_locked()`, the return value of `pm_runtime_get_if_active()` is checked with a boolean NOT operator, misinterpreting the `-EINVAL` error as success, leading to unpowered hardware access and an unbalanced PM usage counter.
- [High] The IRQ handler accesses hardware registers without checking the device's runtime PM state, risking a system crash if clocks are disabled.
--
commit 501f3ec67feb35539595151a43ef4d9ff0c26ca3
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Ensure suspended on removal
If device removal occurs before an auto-suspend timeout occurs, the suspend
is cancelled and the device will be left with clocks running. This patch
makes the remove() callback force a suspend if the device is currently active.
> diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c
> --- a/drivers/accel/ethosu/ethosu_drv.c
> +++ b/drivers/accel/ethosu/ethosu_drv.c
> @@ -387,6 +387,10 @@ static void ethosu_remove(struct platform_device *pdev)
>
> drm_dev_unregister(ðosudev->base);
> ethosu_job_fini(ethosudev);
> +
> + if (pm_runtime_get_if_active(&pdev->dev))
> + pm_runtime_put_sync_suspend(&pdev->dev);
[Severity: High]
Does evaluating the return value of pm_runtime_get_if_active() as a boolean
fail to handle the -EINVAL error case?
If Runtime PM is disabled, pm_runtime_get_if_active() returns -EINVAL, which
evaluates to true here. Could this cause an unbalanced usage counter decrement
that corrupts the device PM state, and potentially power off the device
prematurely?
While reviewing this, I noticed a couple of related pre-existing issues in
the subsystem.
In drivers/accel/ethosu/ethosu_perfmon.c:ethosu_perfmon_stop_locked():
> if (!pm_runtime_get_if_active(ethosu->base.dev)) {
> ethosu->perfmon_state.active = NULL;
> return;
> }
[Severity: High]
This is a pre-existing issue, but does using a boolean NOT operator on the
return value of pm_runtime_get_if_active() misinterpret the -EINVAL error
as success?
If Runtime PM is disabled, -EINVAL evaluates as non-zero (so !(-EINVAL) is
false), bypassing this abort condition. Could this lead to reading PMU
registers without clocks, causing an asynchronous external abort (bus fault)
that crashes the system, as well as dropping a PM reference that was never
acquired?
In drivers/accel/ethosu/ethosu_job.c:ethosu_job_irq_handler():
> static irqreturn_t ethosu_job_irq_handler(int irq, void *data)
> {
> struct ethosu_device *dev = data;
> u32 status = readl_relaxed(dev->regs + NPU_REG_STATUS);
[Severity: High]
This is a pre-existing issue, but does this IRQ handler access hardware
registers without checking the device's runtime PM state?
Any spurious or delayed interrupt firing during the suspended state could
trigger this path. Does unconditionally reading NPU_REG_STATUS upon entry
risk a bus fault and kernel panic if the device clocks are disabled?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=2
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 03/22] accel: ethosu: Fix probe error cleanup
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 01/22] accel: ethosu: Suspend after initialization Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 02/22] accel: ethosu: Ensure suspended on removal Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:18 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 04/22] accel: ethosu: Disable clocks on PM setup failure Rob Herring (Arm)
` (18 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
Once the job scheduler has been initialized, failures from ethosu_init()
or drm_dev_register() return from probe without tearing it down. The
registration failure also leaves the SRAM-pool allocation in use, because
the platform remove callback is not called after a failed probe.
Unwind the initialized resources on both paths. Also do not call
drm_sched_fini() after a failed drm_sched_init(): the scheduler initializer
already unwinds its partial setup, while drm_sched_fini() requires a
successfully initialized scheduler.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- Adjust for previous patch reworking runtime-PM suspend
---
drivers/accel/ethosu/ethosu_drv.c | 13 ++++++++++++-
drivers/accel/ethosu/ethosu_job.c | 6 +-----
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c
index 1f6fb5d18cf2..2c8d735767b2 100644
--- a/drivers/accel/ethosu/ethosu_drv.c
+++ b/drivers/accel/ethosu/ethosu_drv.c
@@ -375,9 +375,20 @@ static int ethosu_probe(struct platform_device *pdev)
ret = ethosu_init(ethosudev);
if (ret)
- return ret;
+ goto err_job_fini;
ret = drm_dev_register(ðosudev->base, 0);
+ if (ret)
+ goto err_sram_free;
+
+ return 0;
+
+err_sram_free:
+ if (ethosudev->sram)
+ gen_pool_free(ethosudev->srampool, (unsigned long)ethosudev->sram,
+ ethosudev->npu_info.sram_size);
+err_job_fini:
+ ethosu_job_fini(ethosudev);
return ret;
}
diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c
index 8dce74db0cb4..ec65305e0cd7 100644
--- a/drivers/accel/ethosu/ethosu_job.c
+++ b/drivers/accel/ethosu/ethosu_job.c
@@ -355,14 +355,10 @@ int ethosu_job_init(struct ethosu_device *edev)
ret = drm_sched_init(&edev->sched, &args);
if (ret) {
dev_err(dev, "Failed to create scheduler: %d\n", ret);
- goto err_sched;
+ return ret;
}
return 0;
-
-err_sched:
- drm_sched_fini(&edev->sched);
- return ret;
}
void ethosu_job_fini(struct ethosu_device *dev)
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 03/22] accel: ethosu: Fix probe error cleanup
2026-09-08 22:04 ` [PATCH v3 03/22] accel: ethosu: Fix probe error cleanup Rob Herring (Arm)
@ 2026-09-08 22:18 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:18 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
Pre-existing issues:
- [High] The IRQ is requested and enabled before the DRM scheduler is fully initialized and before device clocks are enabled, causing potential crashes or bus stalls on spurious/pending interrupts.
- [High] The DRM scheduler is torn down while the device IRQ is still active, allowing the IRQ handler to access destroyed scheduler structures.
- [High] `ethosu_job_fini()` clears the `in_flight_job` without signaling its `done_fence`, causing tasks waiting on the fence to hang indefinitely.
- [High] Use-After-Free during device removal due to missing `drm_dev_enter()`/`drm_dev_exit()` in ioctls.
--
commit cee294332b061aafaf416ab459f70a84d756797f
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Fix probe error cleanup
This commit fixes a probe error path bug in the Ethos-U NPU driver.
If ethosu_init() or drm_dev_register() fails, the probe function returns
without tearing down the job scheduler or freeing the SRAM pool.
The commit adds proper resource unwinding for these failure paths.
Cc: stable@vger.kernel.org
> diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c
> index 1f6fb5d18cf22..2c8d735767b2a 100644
> --- a/drivers/accel/ethosu/ethosu_drv.c
> +++ b/drivers/accel/ethosu/ethosu_drv.c
> @@ -375,9 +375,20 @@ static int ethosu_probe(struct platform_device *pdev)
>
> ret = ethosu_init(ethosudev);
> if (ret)
> - return ret;
> + goto err_job_fini;
[Severity: High]
This is a pre-existing issue, but does calling ethosu_job_init() before
ethosu_init() leave us vulnerable to spurious interrupts?
In ethosu_probe(), the IRQ is requested and enabled via
devm_request_threaded_irq() (without IRQF_NO_AUTOEN) during
ethosu_job_init(). However, device clocks are not enabled until
ethosu_init() runs.
If a pending or spurious interrupt fires immediately before clocks are enabled:
ethosu_job_irq_handler() {
u32 status = readl_relaxed(dev->regs + NPU_REG_STATUS);
...
}
Can reading from memory-mapped registers with disabled clocks stall the bus
or cause a synchronous external abort? Furthermore, if the thread handler
executes, it calls drm_sched_fault() on a scheduler that may not be fully
initialized yet.
> ret = drm_dev_register(ðosudev->base, 0);
> + if (ret)
> + goto err_sram_free;
> +
> + return 0;
> +
> +err_sram_free:
> + if (ethosudev->sram)
> + gen_pool_free(ethosudev->srampool, (unsigned long)ethosudev->sram,
> + ethosudev->npu_info.sram_size);
> +err_job_fini:
> + ethosu_job_fini(ethosudev);
[Severity: High]
This is a pre-existing issue, but does calling ethosu_job_fini() here tear
down the scheduler while the device IRQ is still active?
ethosu_job_fini() calls synchronize_irq() to wait for executing handlers,
but does not disable the IRQ line before calling drm_sched_fini().
Because the IRQ is managed by devres, it remains enabled until devres
teardown, which happens after the probe error path completes. If the device
generates an interrupt in this window, the thread handler can execute:
ethosu_job_handle_irq() {
...
drm_sched_fault(&dev->sched);
}
Could this result in a use-after-free by accessing the already-destroyed
DRM scheduler?
[Severity: High]
This is also a pre-existing issue, but does ethosu_job_fini() clear the
in-flight job without signaling its completion fence?
When ethosu_job_fini() checks READ_ONCE(dev->in_flight_job), if it is
populated, it clears the pointer and resets the hardware:
ethosu_job_fini() {
...
if (READ_ONCE(dev->in_flight_job)) {
WRITE_ONCE(dev->in_flight_job, NULL);
ethosu_device_reset(dev);
}
...
}
Because the hardware is reset, the completion interrupt will never arrive.
Without extracting the job and signaling its done_fence (e.g., with
-ECANCELED), could this cause userspace or kernel tasks waiting on the
fence to hang indefinitely during probe failure or driver unbind?
> return ret;
> }
>
> diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c
> index 8dce74db0cb48..ec65305e0cd7d 100644
> --- a/drivers/accel/ethosu/ethosu_job.c
> +++ b/drivers/accel/ethosu/ethosu_job.c
[ ... ]
> @@ -355,14 +355,10 @@ int ethosu_job_init(struct ethosu_device *edev)
> ret = drm_sched_init(&edev->sched, &args);
> if (ret) {
> dev_err(dev, "Failed to create scheduler: %d\n", ret);
> - goto err_sched;
> + return ret;
> }
>
> return 0;
> -
> -err_sched:
> - drm_sched_fini(&edev->sched);
> - return ret;
> }
[Severity: High]
This isn't a bug introduced by this patch, but are the driver's ioctls
missing drm_dev_enter() and drm_dev_exit() protections?
During a hot-unplug event, ethosu_remove() calls drm_dev_unregister(),
which stops new opens but allows existing file descriptors to persist. It
then tears down the DRM scheduler via ethosu_job_fini().
However, ethosu_ioctl_submit() and the ETHOSU_PERFMON_* ioctls do not verify
the device plug state. A concurrent userspace process holding the file
descriptor can still issue the ETHOSU_SUBMIT ioctl:
ethosu_ioctl_submit_job() {
...
ret = drm_sched_job_init(&ejob->base, &file_priv->sched_entity, ...);
...
}
Can this lead to a use-after-free if userspace calls into the DRM scheduler
after dev->sched has been destroyed during device removal?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=3
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 04/22] accel: ethosu: Disable clocks on PM setup failure
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (2 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 03/22] accel: ethosu: Fix probe error cleanup Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:18 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 05/22] accel: ethosu: Quiesce jobs before scheduler teardown Rob Herring (Arm)
` (17 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
ethosu_init() enables the clocks directly to reset and query the NPU
before runtime PM takes over. If runtime-PM setup fails, the error path
returns while those clocks remain enabled.
Disable the clocks before returning the setup error to balance the
initial direct resume.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_drv.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c
index 2c8d735767b2..9d063daa1aa7 100644
--- a/drivers/accel/ethosu/ethosu_drv.c
+++ b/drivers/accel/ethosu/ethosu_drv.c
@@ -311,8 +311,10 @@ static int ethosu_init(struct ethosu_device *ethosudev)
pm_runtime_set_autosuspend_delay(ethosudev->base.dev, 50);
pm_runtime_use_autosuspend(ethosudev->base.dev);
ret = devm_pm_runtime_set_active_enabled(ethosudev->base.dev);
- if (ret)
+ if (ret) {
+ ethosu_device_suspend(ethosudev->base.dev);
return ret;
+ }
pm_runtime_get_noresume(ethosudev->base.dev);
ethosudev->npu_info.id = id = readl_relaxed(ethosudev->regs + NPU_REG_ID);
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 04/22] accel: ethosu: Disable clocks on PM setup failure
2026-09-08 22:04 ` [PATCH v3 04/22] accel: ethosu: Disable clocks on PM setup failure Rob Herring (Arm)
@ 2026-09-08 22:18 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:18 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [High] The IRQ handler accesses hardware registers unconditionally without verifying if the device is active via pm_runtime_get_if_active(), risking a system crash on spurious interrupts.
- [High] DRM scheduler is finalized before its file-bound entities are destroyed, resulting in a Use-After-Free.
- [High] NPU SRAM is not cleared between consecutive jobs from different processes, leading to data leakage.
- [High] Missing upper bound check against UAPI array size leads to out-of-bounds read/write in `ethosu_ioctl_perfmon_create`.
- [High] Unvalidated hardware register `QREAD` used as an array index causes out-of-bounds memory read in `ethosu_job_timedout`.
--
commit 9bfcf97ff7178db9d34db7f9533d5df371a4f57c
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Disable clocks on PM setup failure
ethosu_init() enables the clocks directly to reset and query the NPU
before runtime PM takes over. If runtime-PM setup fails, the error path
returns while those clocks remain enabled. Disable the clocks before
returning the setup error to balance the initial direct resume.
> diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c
> index 2c8d735767b2a..9d063daa1aa7d 100644
> --- a/drivers/accel/ethosu/ethosu_drv.c
> +++ b/drivers/accel/ethosu/ethosu_drv.c
> @@ -311,8 +311,10 @@ static int ethosu_init(struct ethosu_device *ethosudev)
> pm_runtime_set_autosuspend_delay(ethosudev->base.dev, 50);
> pm_runtime_use_autosuspend(ethosudev->base.dev);
> ret = devm_pm_runtime_set_active_enabled(ethosudev->base.dev);
> - if (ret)
> + if (ret) {
> + ethosu_device_suspend(ethosudev->base.dev);
> return ret;
> + }
> pm_runtime_get_noresume(ethosudev->base.dev);
>
> ethosudev->npu_info.id = id = readl_relaxed(ethosudev->regs + NPU_REG_ID);
> drivers/accel/ethosu/ethosu_job.c:ethosu_job_irq_handler()
> {
> struct ethosu_device *dev = data;
> u32 status = readl_relaxed(dev->regs + NPU_REG_STATUS);
>
> if (!(status & STATUS_IRQ_RAISED))
> return IRQ_NONE;
[Severity: High]
This is a pre-existing issue, but does this IRQ handler need to verify if
the device is active?
If a spurious interrupt occurs while the device is in the RPM_SUSPENDED
state with clocks disabled, will this unclocked register access result in a
synchronous external abort and a kernel panic?
Should this check pm_runtime_get_if_active() before accessing the register?
> drivers/accel/ethosu/ethosu_job.c:ethosu_job_fini()
> {
> drm_sched_fini(&dev->sched);
> }
>
> [ ... ]
>
> drivers/accel/ethosu/ethosu_job.c:ethosu_job_close()
> {
> struct drm_sched_entity *entity = ðosu_priv->sched_entity;
>
> drm_sched_entity_destroy(entity);
> }
[Severity: High]
This is a pre-existing issue, but does this lead to a use-after-free when
closing file descriptors?
When the device is unbound, ethosu_remove() tears down the scheduler.
However, if a userspace process holds a file descriptor open, its
sched_entity remains active.
When the file descriptor is later closed, ethosu_job_close() calls
drm_sched_entity_destroy().
Will drm_sched_entity_destroy() attempt to access the already destroyed
and freed scheduler via entity->rq->sched, causing a panic?
> drivers/accel/ethosu/ethosu_job.c:ethosu_job_hw_submit()
> {
> [ ... ]
>
> if (job->sram_size) {
> writel_relaxed(lower_32_bits(dev->sramphys),
> dev->regs + NPU_REG_BASEP(ETHOSU_SRAM_REGION));
[Severity: High]
This is a pre-existing issue, but is there a data leakage risk here?
The NPU SRAM is shared globally across jobs. It appears it is only cleared
during ethosu_device_reset() when runtime PM resumes. Because the driver
uses a 50ms autosuspend delay, consecutive jobs submitted within 50ms do
not trigger a suspend/resume cycle and the SRAM is not cleared.
Can a malicious process submit a job immediately after another process's
job and craft a command stream to DMA the contents of the
ETHOSU_SRAM_REGION to its own memory, exfiltrating the previous process's
weights or intermediate activations?
> drivers/accel/ethosu/ethosu_perfmon.c:ethosu_ioctl_perfmon_create()
> {
> /* Number of monitored counters cannot exceed HW limits. */
> if (req->ncounters > ethosu->npu_info.pmu_counters)
> return -EINVAL;
>
> [ ... ]
>
> perfmon = kzalloc_flex(*perfmon, values, req->ncounters);
> if (!perfmon)
> return -ENOMEM;
>
> for (i = 0; i < req->ncounters - 1; i++)
> perfmon->counters[i] = req->counters[i];
[Severity: High]
This is a pre-existing issue, but does this lack an upper bound check on
the UAPI array size?
The function validates req->ncounters against the hardware-reported count
rather than the static UAPI array size limit
(DRM_ETHOSU_MAX_PERF_EVENT_COUNTERS, which is 8).
If the hardware reports more than 8 counters, and userspace passes
req->ncounters > 8, will this loop read past the end of the req->counters
array and write past the end of the perfmon->counters array, corrupting the
dynamically allocated values array?
> drivers/accel/ethosu/ethosu_job.c:ethosu_job_timedout()
> {
> [ ... ]
>
> cmdaddr = readl_relaxed(dev->regs + NPU_REG_QREAD);
>
> [ ... ]
>
> dev_err(dev->base.dev, "NPU sched timed out: NPU %s, cmdstream offset 0x%x: 0x%x\n",
> running ? "running" : "stopped",
> cmdaddr, bocmds[cmdaddr / 4]);
[Severity: High]
This is a pre-existing issue, but can this cause an out-of-bounds memory
read?
Since cmdaddr is read directly from the hardware and entirely unvalidated,
if userspace crafts a malformed command stream that hangs the NPU and
leaves QREAD with a large value, will cmdaddr / 4 exceed the bounds of the
mapped bocmds buffer and cause an invalid memory access?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=4
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 05/22] accel: ethosu: Quiesce jobs before scheduler teardown
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (3 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 04/22] accel: ethosu: Disable clocks on PM setup failure Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:20 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 06/22] accel: ethosu: Prevent command stream export Rob Herring (Arm)
` (16 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
An NPU job can complete while driver removal tears down the scheduler. Its
IRQ handler could then access scheduler state after it has been
destroyed.
Stop scheduler submission and timeout work, reset the NPU, and
synchronize its IRQ before finalizing the scheduler. Add a cancel_job
callback so drm_sched_fini() signals queued jobs with -ECANCELED; their
runtime-PM references are then released during normal job cleanup.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v3:
- Add dma_fence_was_initialized() check in job cancel (sashiko)
v2:
- new patch
---
drivers/accel/ethosu/ethosu_drv.c | 4 ++--
drivers/accel/ethosu/ethosu_drv.h | 2 ++
drivers/accel/ethosu/ethosu_job.c | 23 ++++++++++++++++++++++-
3 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c
index 9d063daa1aa7..06e432854ce7 100644
--- a/drivers/accel/ethosu/ethosu_drv.c
+++ b/drivers/accel/ethosu/ethosu_drv.c
@@ -216,7 +216,7 @@ static const struct drm_driver ethosu_drm_driver = {
#define U85_MEM_ATTR0_CFG 0x00000000
#define U85_MEM_ATTR2_CFG 0x000000b7
-static int ethosu_reset(struct ethosu_device *ethosudev)
+int ethosu_device_reset(struct ethosu_device *ethosudev)
{
int ret;
u32 reg;
@@ -263,7 +263,7 @@ static int ethosu_device_resume(struct device *dev)
if (ret)
return ret;
- ret = ethosu_reset(ethosudev);
+ ret = ethosu_device_reset(ethosudev);
if (!ret)
return 0;
diff --git a/drivers/accel/ethosu/ethosu_drv.h b/drivers/accel/ethosu/ethosu_drv.h
index 2193bc51d425..f59c845c758b 100644
--- a/drivers/accel/ethosu/ethosu_drv.h
+++ b/drivers/accel/ethosu/ethosu_drv.h
@@ -11,6 +11,8 @@ struct ethosu_device;
struct drm_device;
struct drm_file;
+int ethosu_device_reset(struct ethosu_device *ethosudev);
+
struct ethosu_file_priv {
struct ethosu_device *edev;
struct drm_sched_entity sched_entity;
diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c
index ec65305e0cd7..a1443cd0c5b0 100644
--- a/drivers/accel/ethosu/ethosu_job.c
+++ b/drivers/accel/ethosu/ethosu_job.c
@@ -189,6 +189,17 @@ static void ethosu_job_free(struct drm_sched_job *sched_job)
ethosu_job_put(job);
}
+static void ethosu_job_cancel(struct drm_sched_job *sched_job)
+{
+ struct ethosu_job *job = to_ethosu_job(sched_job);
+
+ if (dma_fence_was_initialized(job->done_fence) &&
+ !dma_fence_is_signaled(job->done_fence)) {
+ dma_fence_set_error(job->done_fence, -ECANCELED);
+ dma_fence_signal(job->done_fence);
+ }
+}
+
static void
ethosu_switch_perfmon(struct ethosu_device *ethosu, struct ethosu_job *job)
{
@@ -315,7 +326,8 @@ static enum drm_gpu_sched_stat ethosu_job_timedout(struct drm_sched_job *bad)
static const struct drm_sched_backend_ops ethosu_sched_ops = {
.run_job = ethosu_job_run,
.timedout_job = ethosu_job_timedout,
- .free_job = ethosu_job_free
+ .free_job = ethosu_job_free,
+ .cancel_job = ethosu_job_cancel,
};
int ethosu_job_init(struct ethosu_device *edev)
@@ -363,6 +375,15 @@ int ethosu_job_init(struct ethosu_device *edev)
void ethosu_job_fini(struct ethosu_device *dev)
{
+ drm_sched_wqueue_stop(&dev->sched);
+ cancel_delayed_work_sync(&dev->sched.work_tdr);
+
+ if (READ_ONCE(dev->in_flight_job)) {
+ WRITE_ONCE(dev->in_flight_job, NULL);
+ ethosu_device_reset(dev);
+ }
+
+ synchronize_irq(dev->irq);
drm_sched_fini(&dev->sched);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 05/22] accel: ethosu: Quiesce jobs before scheduler teardown
2026-09-08 22:04 ` [PATCH v3 05/22] accel: ethosu: Quiesce jobs before scheduler teardown Rob Herring (Arm)
@ 2026-09-08 22:20 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:20 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [Critical] This problem wasn't introduced by this patch, but prematurely tearing down the DRM scheduler during device removal causes a Use-After-Free and unkillable process hang if userspace keeps the DRM file open.
- [Critical] This problem wasn't introduced by this patch, but missing disable_irq() during driver teardown allows the IRQ handler to re-arm the scheduler timeout work after or during scheduler teardown, leading to a Use-After-Free.
--
commit 7894a2554c9bc3dc755e00a1fa982b373faef388
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Quiesce jobs before scheduler teardown
This commit attempts to safely tear down the scheduler during driver removal
by stopping the submission and timeout workqueues, resetting the NPU device,
and synchronizing the IRQ before freeing the scheduler.
> diff --git a/drivers/accel/ethosu/ethosu_job.c b/drivers/accel/ethosu/ethosu_job.c
> index ec65305e0cd7d..a1443cd0c5b05 100644
> --- a/drivers/accel/ethosu/ethosu_job.c
> +++ b/drivers/accel/ethosu/ethosu_job.c
[ ... ]
> @@ -363,6 +375,15 @@ int ethosu_job_init(struct ethosu_device *edev)
>
> void ethosu_job_fini(struct ethosu_device *dev)
> {
> + drm_sched_wqueue_stop(&dev->sched);
> + cancel_delayed_work_sync(&dev->sched.work_tdr);
> +
> + if (READ_ONCE(dev->in_flight_job)) {
> + WRITE_ONCE(dev->in_flight_job, NULL);
> + ethosu_device_reset(dev);
> + }
> +
> + synchronize_irq(dev->irq);
[Severity: Critical]
This isn't a bug introduced by this patch, but does the missing disable_irq()
during driver teardown leave a window for the IRQ handler to re-arm the
scheduler timeout work?
While synchronize_irq() waits for currently running IRQ handlers to finish, it
does not disable the IRQ line itself. Since devm_request_threaded_irq() keeps
the IRQ alive until the devres release phase (which happens after the driver
remove function returns), a late interrupt could still fire.
If an interrupt fires after synchronize_irq() returns, ethosu_job_handle_irq()
could execute and call drm_sched_fault(), which re-arms the delayed timeout
work (work_tdr) on a destroyed scheduler. Could this lead to a
Use-After-Free?
> drm_sched_fini(&dev->sched);
[Severity: Critical]
This is a pre-existing issue, but does prematurely tearing down the DRM
scheduler during device removal cause a Use-After-Free and an unkillable
process hang if userspace keeps the DRM file open?
When the device is unbound (e.g., via sysfs or hotplug), ethosu_job_fini()
halts the workqueues and destroys the scheduler. If a userspace process keeps
the DRM file descriptor open and closes it later, ethosu_postclose() will
initiate the teardown of the entity via ethosu_job_close().
Because the scheduler has already been freed, the entity teardown will access
freed memory and hang forever waiting for jobs to execute on the stopped
scheduler. Can we ensure the scheduler remains alive until all entities
are destroyed?
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=5
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 06/22] accel: ethosu: Prevent command stream export
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (4 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 05/22] accel: ethosu: Quiesce jobs before scheduler teardown Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 07/22] accel: ethosu: Move DMA mode to src/dst struct Rob Herring (Arm)
` (15 subsequent siblings)
21 siblings, 0 replies; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
Command stream BOs are validated at creation time, but the generic PRIME
export path can expose their backing memory to a DMA-BUF importer. The
importer can then modify the command stream before the NPU executes it.
Reject PRIME export for validated command stream BOs while retaining generic
export for data BOs.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v3:
- new patch
---
drivers/accel/ethosu/ethosu_gem.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 9afe2549ec84..c046aee42687 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -6,6 +6,7 @@
#include <linux/slab.h>
#include <drm/ethosu_accel.h>
+#include <drm/drm_prime.h>
#include "ethosu_device.h"
#include "ethosu_gem.h"
@@ -30,9 +31,18 @@ static int ethosu_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vm
return drm_gem_dma_object_mmap(obj, vma);
}
+static struct dma_buf *ethosu_gem_export(struct drm_gem_object *obj, int flags)
+{
+ if (to_ethosu_bo(obj)->info)
+ return ERR_PTR(-EPERM);
+
+ return drm_gem_prime_export(obj, flags);
+}
+
static const struct drm_gem_object_funcs ethosu_gem_funcs = {
.free = ethosu_gem_free_object,
.print_info = drm_gem_dma_object_print_info,
+ .export = ethosu_gem_export,
.get_sg_table = drm_gem_dma_object_get_sg_table,
.vmap = drm_gem_dma_object_vmap,
.mmap = ethosu_gem_mmap,
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 07/22] accel: ethosu: Move DMA mode to src/dst struct
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (5 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 06/22] accel: ethosu: Prevent command stream export Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 08/22] accel: ethosu: Track command stream register setup Rob Herring (Arm)
` (14 subsequent siblings)
21 siblings, 0 replies; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The DMA mode setting is independent for source and destination, so it
should be part of the src/dst struct dma rather than the global DMA
state.
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_gem.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index c046aee42687..2b9c98251c94 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -110,6 +110,7 @@ int ethosu_gem_create_with_handle(struct drm_file *file,
struct dma {
s8 region;
+ s8 mode;
u64 len;
u64 offset;
s64 stride[2];
@@ -118,7 +119,6 @@ struct dma {
struct dma_state {
u16 size0;
u16 size1;
- s8 mode;
struct dma src;
struct dma dst;
};
@@ -171,7 +171,7 @@ static u64 cmd_to_addr(u32 *cmd)
static u64 dma_length(struct ethosu_validated_cmdstream_info *info,
struct dma_state *dma_st, struct dma *dma)
{
- s8 mode = dma_st->mode;
+ s8 mode = dma->mode;
u64 len = dma->len;
if (len == U64_MAX)
@@ -664,13 +664,14 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
st.dma.src.region = -1;
else
st.dma.src.region = param & 0x7;
- st.dma.mode = (param >> 9) & 0x3;
+ st.dma.src.mode = (param >> 9) & 0x3;
break;
case NPU_SET_DMA0_DST_REGION:
if (param & 0x100)
st.dma.dst.region = -1;
else
st.dma.dst.region = param & 0x7;
+ st.dma.dst.mode = (param >> 9) & 0x3;
break;
case NPU_SET_DMA0_SIZE0:
st.dma.size0 = param;
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 08/22] accel: ethosu: Track command stream register setup
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (6 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 07/22] accel: ethosu: Move DMA mode to src/dst struct Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 09/22] accel: ethosu: Factor buffer bounds checks Rob Herring (Arm)
` (13 subsequent siblings)
21 siblings, 0 replies; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The current method of tracking command stream state initializing state
tracking to illegal values and assuming unaccessed registers are 0 is
proving inadequate with additional validation. Instead, track all the
registers in a bitmap as the register address space is fairly small.
CMD1 opcodes overlap CMD0 after bit 14 is stripped, so maintain a
separate bitmap for each bank.
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v3:
- Fix return value in dma_params_valid()
v2:
- new patch
---
drivers/accel/ethosu/ethosu_gem.c | 269 +++++++++++++++++++++++++++++++++-----
1 file changed, 234 insertions(+), 35 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 2b9c98251c94..1e9ffacbc394 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only or MIT
/* Copyright 2025 Arm, Ltd. */
+#include <linux/bitmap.h>
#include <linux/err.h>
#include <linux/overflow.h>
#include <linux/slab.h>
@@ -148,7 +149,12 @@ struct feat_matrix {
u8 pad_right;
};
+#define NPU_CMD0_REGS 0x200
+#define NPU_CMD1_REGS 0x100
+
struct cmd_state {
+ DECLARE_BITMAP(cmd0, NPU_CMD0_REGS);
+ DECLARE_BITMAP(cmd1, NPU_CMD1_REGS);
struct dma_state dma;
struct buffer scale[2];
struct buffer weight[4];
@@ -159,8 +165,29 @@ struct cmd_state {
static void cmd_state_init(struct cmd_state *st)
{
- /* Initialize to all 1s to detect missing setup */
- memset(st, 0xff, sizeof(*st));
+ memset(st, 0, sizeof(*st));
+}
+
+static void cmd_state_set_reg(struct cmd_state *st, u16 cmd)
+{
+ u16 reg = cmd & ~BIT(14);
+
+ if (cmd & BIT(14)) {
+ if (reg < NPU_CMD1_REGS)
+ __set_bit(reg, st->cmd1);
+ } else if (reg < NPU_CMD0_REGS) {
+ __set_bit(reg, st->cmd0);
+ }
+}
+
+static bool cmd_state_reg_is_set(struct cmd_state *st, u16 cmd)
+{
+ u16 reg = cmd & ~BIT(14);
+
+ if (cmd & BIT(14))
+ return reg < NPU_CMD1_REGS && test_bit(reg, st->cmd1);
+
+ return reg < NPU_CMD0_REGS && test_bit(reg, st->cmd0);
}
static u64 cmd_to_addr(u32 *cmd)
@@ -168,13 +195,54 @@ static u64 cmd_to_addr(u32 *cmd)
return (((u64)cmd[0] & 0xff0000) << 16) | cmd[1];
}
-static u64 dma_length(struct ethosu_validated_cmdstream_info *info,
- struct dma_state *dma_st, struct dma *dma)
+static bool dma_use_src_stride(struct ethosu_device *edev,
+ const struct dma_state *dma_st, const struct dma *dma)
+{
+ return ethosu_is_u65(edev) || dma == &dma_st->src;
+}
+
+static bool dma_params_valid(struct ethosu_device *edev, struct cmd_state *st,
+ const struct dma_state *dma_st,
+ const struct dma *dma,
+ u16 region_cmd, u16 addr_cmd)
+{
+ s8 mode = dma->mode;
+
+ if (!cmd_state_reg_is_set(st, region_cmd) ||
+ !cmd_state_reg_is_set(st, addr_cmd) ||
+ !cmd_state_reg_is_set(st, NPU_SET_DMA0_LEN) || mode < 0 || mode > 2)
+ return false;
+
+ if (mode >= 1 &&
+ !cmd_state_reg_is_set(st, dma_use_src_stride(edev, dma_st, dma) ?
+ NPU_SET_DMA0_SRC_STRIDE0 :
+ NPU_SET_DMA0_DST_STRIDE0))
+ return false;
+ if (mode == 2 &&
+ !cmd_state_reg_is_set(st, dma_use_src_stride(edev, dma_st, dma) ?
+ NPU_SET_DMA0_SRC_STRIDE1 :
+ NPU_SET_DMA0_DST_STRIDE1))
+ return false;
+
+ if (mode >= 1 &&
+ (!cmd_state_reg_is_set(st, NPU_SET_DMA0_SIZE0) || !dma_st->size0))
+ return false;
+ if (mode == 2 &&
+ (!cmd_state_reg_is_set(st, NPU_SET_DMA0_SIZE1) || !dma_st->size1))
+ return false;
+
+ return true;
+}
+
+static u64 dma_length(struct ethosu_device *edev,
+ struct ethosu_validated_cmdstream_info *info,
+ struct cmd_state *st, struct dma_state *dma_st,
+ struct dma *dma, u16 region_cmd, u16 addr_cmd)
{
s8 mode = dma->mode;
u64 len = dma->len;
- if (len == U64_MAX)
+ if (!dma_params_valid(edev, st, dma_st, dma, region_cmd, addr_cmd))
return U64_MAX;
if (mode >= 1) {
@@ -209,17 +277,98 @@ static bool feat_matrix_chained(struct ethosu_device *edev, struct feat_matrix *
return !ethosu_is_u65(edev) && storage == 2;
}
+enum feat_matrix_type {
+ FEAT_MATRIX_IFM,
+ FEAT_MATRIX_OFM,
+ FEAT_MATRIX_IFM2,
+};
+
+static u16 feat_matrix_base_cmd(enum feat_matrix_type type)
+{
+ switch (type) {
+ case FEAT_MATRIX_IFM:
+ return NPU_SET_IFM_BASE0;
+ case FEAT_MATRIX_OFM:
+ return NPU_SET_OFM_BASE0;
+ case FEAT_MATRIX_IFM2:
+ return NPU_SET_IFM2_BASE0;
+ }
+
+ return 0;
+}
+
+static int feat_matrix_validate(struct ethosu_device *edev,
+ struct cmd_state *st, struct feat_matrix *fm,
+ enum feat_matrix_type type)
+{
+ u32 format;
+ u16 stride_cmd;
+
+ switch (type) {
+ case FEAT_MATRIX_IFM:
+ if (!cmd_state_reg_is_set(st, NPU_SET_IFM_REGION) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM_PRECISION) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM_DEPTH_M1))
+ return -EINVAL;
+ if (feat_matrix_chained(edev, fm))
+ return 0;
+ if (!cmd_state_reg_is_set(st, NPU_SET_IFM_WIDTH0_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM_HEIGHT0_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM_HEIGHT1_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM_STRIDE_Y))
+ return -EINVAL;
+ break;
+ case FEAT_MATRIX_OFM:
+ if (!cmd_state_reg_is_set(st, NPU_SET_OFM_REGION) ||
+ !cmd_state_reg_is_set(st, NPU_SET_OFM_PRECISION) ||
+ !cmd_state_reg_is_set(st, NPU_SET_OFM_DEPTH_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_OFM_WIDTH_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_OFM_HEIGHT_M1))
+ return -EINVAL;
+ if (feat_matrix_chained(edev, fm))
+ return 0;
+ if (!cmd_state_reg_is_set(st, NPU_SET_OFM_WIDTH0_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_OFM_HEIGHT0_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_OFM_HEIGHT1_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_OFM_STRIDE_Y))
+ return -EINVAL;
+ break;
+ case FEAT_MATRIX_IFM2:
+ if (!cmd_state_reg_is_set(st, NPU_SET_IFM2_REGION) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM2_PRECISION))
+ return -EINVAL;
+ if (feat_matrix_chained(edev, fm))
+ return 0;
+ if (!cmd_state_reg_is_set(st, NPU_SET_IFM2_WIDTH0_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM2_HEIGHT0_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM2_HEIGHT1_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM2_STRIDE_Y))
+ return -EINVAL;
+ break;
+ }
+
+ format = (fm->precision >> 6) & 0x3;
+ stride_cmd = feat_matrix_base_cmd(type) + (format ? 6 : 4);
+ if (!cmd_state_reg_is_set(st, stride_cmd))
+ return -EINVAL;
+
+ return 0;
+}
static u64 feat_matrix_length(struct ethosu_device *edev,
struct ethosu_validated_cmdstream_info *info,
- struct feat_matrix *fm,
+ struct cmd_state *st, struct feat_matrix *fm,
+ enum feat_matrix_type type,
u32 x, u32 y, u32 c, bool ofm)
{
u32 element_size, storage = ethosu_is_u65(edev) ? 0 : fm->precision >> 14;
int tile = 0;
u64 addr;
+ u64 offset;
if (fm->region < 0)
return U64_MAX;
+ if (feat_matrix_validate(edev, st, fm, type))
+ return U64_MAX;
if (feat_matrix_chained(edev, fm))
return 0;
@@ -247,24 +396,39 @@ static u64 feat_matrix_length(struct ethosu_device *edev,
default:
return U64_MAX;
}
- if (fm->base[tile] == U64_MAX)
+ if (!cmd_state_reg_is_set(st, feat_matrix_base_cmd(type) + tile))
return U64_MAX;
- addr = fm->base[tile] + y * fm->stride_y;
+ if (check_mul_overflow(y, (u64)fm->stride_y, &offset) ||
+ check_add_overflow(fm->base[tile], offset, &addr))
+ return U64_MAX;
switch ((fm->precision >> 6) & 0x3) { // format
case 0: //nhwc:
element_size = BIT((fm->precision >> (ofm ? 1 : 2)) & 0x3);
- addr += x * fm->stride_x + c * element_size;
+ if (check_mul_overflow(x, (u64)fm->stride_x, &offset) ||
+ check_add_overflow(addr, offset, &addr) ||
+ check_mul_overflow(c, element_size, &offset) ||
+ check_add_overflow(addr, offset, &addr))
+ return U64_MAX;
break;
case 1: //nhcwb16:
element_size = BIT((fm->precision >> (ofm ? 1 : 2)) & 0x3);
- addr += (c / 16) * fm->stride_c + (16 * x + (c & 0xf)) * element_size;
+ if (check_mul_overflow(c / 16, (u64)fm->stride_c, &offset) ||
+ check_add_overflow(addr, offset, &addr) ||
+ check_mul_overflow(16 * x + (c & 0xf), element_size, &offset) ||
+ check_add_overflow(addr, offset, &addr))
+ return U64_MAX;
break;
+ default:
+ return U64_MAX;
}
- info->region_size[fm->region] = max(info->region_size[fm->region], addr + 1);
+ if (check_add_overflow(addr, 1ULL, &offset))
+ return U64_MAX;
+
+ info->region_size[fm->region] = max(info->region_size[fm->region], offset);
return addr;
}
@@ -278,7 +442,13 @@ static int calc_sizes(struct drm_device *ddev,
u64 len;
if (ifm) {
- if (st->ifm.stride_kernel == U16_MAX)
+ if (!cmd_state_reg_is_set(st, NPU_SET_KERNEL_WIDTH_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_KERNEL_HEIGHT_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_KERNEL_STRIDE) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM_PAD_TOP) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM_PAD_LEFT) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM_PAD_RIGHT) ||
+ !cmd_state_reg_is_set(st, NPU_SET_IFM_PAD_BOTTOM))
return -EINVAL;
u32 stride_y = ((st->ifm.stride_kernel >> 8) & 0x2) +
((st->ifm.stride_kernel >> 1) & 0x1) + 1;
@@ -292,8 +462,9 @@ static int calc_sizes(struct drm_device *ddev,
if (ifm_height < 0 || ifm_width < 0)
return -EINVAL;
- len = feat_matrix_length(edev, info, &st->ifm, ifm_width,
- ifm_height, st->ifm.depth, false);
+ len = feat_matrix_length(edev, info, st, &st->ifm,
+ FEAT_MATRIX_IFM, ifm_width, ifm_height,
+ st->ifm.depth, false);
dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n",
op, st->ifm.region, st->ifm.base[0], len);
if (len == U64_MAX)
@@ -301,8 +472,9 @@ static int calc_sizes(struct drm_device *ddev,
}
if (ifm2) {
- len = feat_matrix_length(edev, info, &st->ifm2, st->ifm.depth,
- 0, st->ofm.depth, false);
+ len = feat_matrix_length(edev, info, st, &st->ifm2,
+ FEAT_MATRIX_IFM2, st->ifm.depth, 0,
+ st->ofm.depth, false);
dev_dbg(ddev->dev, "op %d: IFM2:%d:0x%llx-0x%llx\n",
op, st->ifm2.region, st->ifm2.base[0], len);
if (len == U64_MAX)
@@ -313,8 +485,9 @@ static int calc_sizes(struct drm_device *ddev,
dev_dbg(ddev->dev, "op %d: W:%d:0x%llx-0x%llx\n",
op, st->weight[0].region, st->weight[0].base,
st->weight[0].base + st->weight[0].length - 1);
- if (st->weight[0].region < 0 || st->weight[0].base == U64_MAX ||
- st->weight[0].length == U32_MAX)
+ if (!cmd_state_reg_is_set(st, NPU_SET_WEIGHT_REGION) ||
+ !cmd_state_reg_is_set(st, NPU_SET_WEIGHT_BASE) ||
+ !cmd_state_reg_is_set(st, NPU_SET_WEIGHT_LENGTH))
return -EINVAL;
info->region_size[st->weight[0].region] =
max(info->region_size[st->weight[0].region],
@@ -325,16 +498,18 @@ static int calc_sizes(struct drm_device *ddev,
dev_dbg(ddev->dev, "op %d: S:%d:0x%llx-0x%llx\n",
op, st->scale[0].region, st->scale[0].base,
st->scale[0].base + st->scale[0].length - 1);
- if (st->scale[0].region < 0 || st->scale[0].base == U64_MAX ||
- st->scale[0].length == U32_MAX)
+ if (!cmd_state_reg_is_set(st, NPU_SET_SCALE_REGION) ||
+ !cmd_state_reg_is_set(st, NPU_SET_SCALE_BASE) ||
+ !cmd_state_reg_is_set(st, NPU_SET_SCALE_LENGTH))
return -EINVAL;
info->region_size[st->scale[0].region] =
max(info->region_size[st->scale[0].region],
st->scale[0].base + st->scale[0].length);
}
- len = feat_matrix_length(edev, info, &st->ofm, st->ofm.width,
- st->ofm.height[2], st->ofm.depth, true);
+ len = feat_matrix_length(edev, info, st, &st->ofm, FEAT_MATRIX_OFM,
+ st->ofm.width, st->ofm.height[2], st->ofm.depth,
+ true);
dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n",
op, st->ofm.region, st->ofm.base[0], len);
if (len == U64_MAX)
@@ -359,8 +534,8 @@ static int calc_sizes_elemwise(struct drm_device *ddev,
width = st->ifm.broadcast & 0x2 ? 0 : st->ofm.width;
depth = st->ifm.broadcast & 0x4 ? 0 : st->ofm.depth;
- len = feat_matrix_length(edev, info, &st->ifm, width,
- height, depth, false);
+ len = feat_matrix_length(edev, info, st, &st->ifm,
+ FEAT_MATRIX_IFM, width, height, depth, false);
dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n",
op, st->ifm.region, st->ifm.base[0], len);
if (len == U64_MAX)
@@ -372,16 +547,17 @@ static int calc_sizes_elemwise(struct drm_device *ddev,
width = st->ifm2.broadcast & 0x2 ? 0 : st->ofm.width;
depth = st->ifm2.broadcast & 0x4 ? 0 : st->ofm.depth;
- len = feat_matrix_length(edev, info, &st->ifm2, width,
- height, depth, false);
+ len = feat_matrix_length(edev, info, st, &st->ifm2,
+ FEAT_MATRIX_IFM2, width, height, depth, false);
dev_dbg(ddev->dev, "op %d: IFM2:%d:0x%llx-0x%llx\n",
op, st->ifm2.region, st->ifm2.base[0], len);
if (len == U64_MAX)
return -EINVAL;
}
- len = feat_matrix_length(edev, info, &st->ofm, st->ofm.width,
- st->ofm.height[2], st->ofm.depth, true);
+ len = feat_matrix_length(edev, info, st, &st->ofm, FEAT_MATRIX_OFM,
+ st->ofm.width, st->ofm.height[2], st->ofm.depth,
+ true);
dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n",
op, st->ofm.region, st->ofm.base[0], len);
if (len == U64_MAX)
@@ -436,6 +612,8 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
addr = cmd_to_addr(cmds);
}
+ cmd_state_set_reg(&st, cmd);
+
switch (cmd) {
case NPU_OP_STOP:
if (i != size / 4 - 1)
@@ -443,8 +621,10 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
ends_with_stop = true;
break;
case NPU_OP_DMA_START:
- srclen = dma_length(info, &st.dma, &st.dma.src);
- dstlen = dma_length(info, &st.dma, &st.dma.dst);
+ srclen = dma_length(edev, info, &st, &st.dma, &st.dma.src,
+ NPU_SET_DMA0_SRC_REGION, NPU_SET_DMA0_SRC);
+ dstlen = dma_length(edev, info, &st, &st.dma, &st.dma.dst,
+ NPU_SET_DMA0_DST_REGION, NPU_SET_DMA0_DST);
if (srclen == U64_MAX || dstlen == U64_MAX)
return -EINVAL;
@@ -455,16 +635,28 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
st.dma.dst.region, st.dma.dst.offset, dstlen);
break;
case NPU_OP_CONV:
- case NPU_OP_DEPTHWISE:
use_ifm2 = param & 0x1; // weights_ifm2
+ if (!cmd_state_reg_is_set(&st, NPU_SET_OFM_PRECISION))
+ return -EINVAL;
use_scale = !(st.ofm.precision & 0x100);
ret = calc_sizes(ddev, info, cmd, &st, true, use_ifm2,
!use_ifm2, use_scale);
if (ret)
return ret;
break;
+ case NPU_OP_DEPTHWISE:
+ if (!cmd_state_reg_is_set(&st, NPU_SET_OFM_PRECISION))
+ return -EINVAL;
+ use_scale = !(st.ofm.precision & 0x100);
+ ret = calc_sizes(ddev, info, cmd, &st, true, false, true,
+ use_scale);
+ if (ret)
+ return ret;
+ break;
case NPU_OP_POOL:
use_ifm = param != 0x4; // pooling mode
+ if (!cmd_state_reg_is_set(&st, NPU_SET_OFM_PRECISION))
+ return -EINVAL;
use_scale = !(st.ofm.precision & 0x100);
ret = calc_sizes(ddev, info, cmd, &st, use_ifm, false,
false, use_scale);
@@ -472,11 +664,18 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
return ret;
break;
case NPU_OP_ELEMENTWISE:
- use_scale = ethosu_is_u65(edev) ?
+ if (!ethosu_is_u65(edev) &&
+ !cmd_state_reg_is_set(&st, NPU_SET_IFM_BROADCAST))
+ return -EINVAL;
+ use_ifm2 = (param != 5) && (param != 6) &&
+ (param != 7) && (param != 0x24);
+ if (use_ifm2 &&
+ !cmd_state_reg_is_set(&st, NPU_SET_IFM2_BROADCAST))
+ return -EINVAL;
+ use_scale = use_ifm2 && (ethosu_is_u65(edev) ?
(st.ifm2.broadcast & 0x80) :
- (st.ifm2.broadcast == 8);
- use_ifm2 = !(use_scale || (param == 5) ||
- (param == 6) || (param == 7) || (param == 0x24));
+ (st.ifm2.broadcast == 8));
+ use_ifm2 = use_ifm2 && !use_scale;
use_ifm = st.ifm.broadcast != 8;
ret = calc_sizes_elemwise(ddev, info, cmd, &st, use_ifm, use_ifm2);
if (ret)
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 09/22] accel: ethosu: Factor buffer bounds checks
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (7 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 08/22] accel: ethosu: Track command stream register setup Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 10/22] accel: ethosu: Fix NHCWB16 bounds calculation Rob Herring (Arm)
` (12 subsequent siblings)
21 siblings, 0 replies; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
Move the repeated command-stream buffer range validation into a
helper in preparation for validating all weight and scale streams.
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- Adjust due to previous patch
---
drivers/accel/ethosu/ethosu_gem.c | 37 +++++++++++++++++++++++++------------
1 file changed, 25 insertions(+), 12 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 1e9ffacbc394..2707b7df5dbe 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -433,6 +433,25 @@ static u64 feat_matrix_length(struct ethosu_device *edev,
return addr;
}
+static int buffer_size(struct ethosu_validated_cmdstream_info *info,
+ struct cmd_state *st, struct buffer *buf, s8 region,
+ u16 region_cmd, u16 base_cmd, u16 length_cmd)
+{
+ u64 end;
+
+ if (region < 0 || !cmd_state_reg_is_set(st, region_cmd) ||
+ !cmd_state_reg_is_set(st, base_cmd) ||
+ !cmd_state_reg_is_set(st, length_cmd))
+ return -EINVAL;
+
+ if (check_add_overflow(buf->base, (u64)buf->length, &end))
+ return -EINVAL;
+
+ info->region_size[region] = max(info->region_size[region], end);
+
+ return 0;
+}
+
static int calc_sizes(struct drm_device *ddev,
struct ethosu_validated_cmdstream_info *info,
u16 op, struct cmd_state *st,
@@ -485,26 +504,20 @@ static int calc_sizes(struct drm_device *ddev,
dev_dbg(ddev->dev, "op %d: W:%d:0x%llx-0x%llx\n",
op, st->weight[0].region, st->weight[0].base,
st->weight[0].base + st->weight[0].length - 1);
- if (!cmd_state_reg_is_set(st, NPU_SET_WEIGHT_REGION) ||
- !cmd_state_reg_is_set(st, NPU_SET_WEIGHT_BASE) ||
- !cmd_state_reg_is_set(st, NPU_SET_WEIGHT_LENGTH))
+ if (buffer_size(info, st, &st->weight[0], st->weight[0].region,
+ NPU_SET_WEIGHT_REGION, NPU_SET_WEIGHT_BASE,
+ NPU_SET_WEIGHT_LENGTH))
return -EINVAL;
- info->region_size[st->weight[0].region] =
- max(info->region_size[st->weight[0].region],
- st->weight[0].base + st->weight[0].length);
}
if (scale) {
dev_dbg(ddev->dev, "op %d: S:%d:0x%llx-0x%llx\n",
op, st->scale[0].region, st->scale[0].base,
st->scale[0].base + st->scale[0].length - 1);
- if (!cmd_state_reg_is_set(st, NPU_SET_SCALE_REGION) ||
- !cmd_state_reg_is_set(st, NPU_SET_SCALE_BASE) ||
- !cmd_state_reg_is_set(st, NPU_SET_SCALE_LENGTH))
+ if (buffer_size(info, st, &st->scale[0], st->scale[0].region,
+ NPU_SET_SCALE_REGION, NPU_SET_SCALE_BASE,
+ NPU_SET_SCALE_LENGTH))
return -EINVAL;
- info->region_size[st->scale[0].region] =
- max(info->region_size[st->scale[0].region],
- st->scale[0].base + st->scale[0].length);
}
len = feat_matrix_length(edev, info, st, &st->ofm, FEAT_MATRIX_OFM,
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 10/22] accel: ethosu: Fix NHCWB16 bounds calculation
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (8 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 09/22] accel: ethosu: Factor buffer bounds checks Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:19 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 11/22] accel: ethosu: Validate secondary streams Rob Herring (Arm)
` (11 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel, sashiko-bot
The Ethos-U NPU inherently processes NHCWB16 data in 16-channel bricks.
The NHCWB16 address calculation uses the final channel of the last
accessed brick. If the channel depth modulo 16 is less than 15, the
hardware will still read or write the full 16-element brick.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Reported-by: sashiko-bot@kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v3:
- new patch
---
drivers/accel/ethosu/ethosu_gem.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 2707b7df5dbe..6f6712f4efd1 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -415,9 +415,10 @@ static u64 feat_matrix_length(struct ethosu_device *edev,
case 1: //nhcwb16:
element_size = BIT((fm->precision >> (ofm ? 1 : 2)) & 0x3);
+ /* NHCWB16 accesses the complete final channel brick. */
if (check_mul_overflow(c / 16, (u64)fm->stride_c, &offset) ||
check_add_overflow(addr, offset, &addr) ||
- check_mul_overflow(16 * x + (c & 0xf), element_size, &offset) ||
+ check_mul_overflow(16 * x + 15, element_size, &offset) ||
check_add_overflow(addr, offset, &addr))
return U64_MAX;
break;
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 10/22] accel: ethosu: Fix NHCWB16 bounds calculation
2026-09-08 22:04 ` [PATCH v3 10/22] accel: ethosu: Fix NHCWB16 bounds calculation Rob Herring (Arm)
@ 2026-09-08 22:19 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:19 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing endianness conversion for NPU command stream.
--
commit 0751ca432d602d192378042d9197a6392196efe4
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Fix NHCWB16 bounds calculation
This commit fixes the NHCWB16 bounds calculation for the Ethos-U NPU driver.
It adjusts the address calculation to account for the NPU accessing data in
complete 16-channel bricks.
> diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
> index 2707b7df5dbe6..6f6712f4efd1f 100644
> --- a/drivers/accel/ethosu/ethosu_gem.c
> +++ b/drivers/accel/ethosu/ethosu_gem.c
[ ... ]
While reviewing this file, I noticed a pre-existing issue in
ethosu_gem_cmdstream_copy_and_validate():
drivers/accel/ethosu/ethosu_gem.c:ethosu_gem_cmdstream_copy_and_validate()
...
if (get_user(cmds[0], ucmds++))
return -EFAULT;
bocmds[i] = cmds[0];
cmd = cmds[0];
param = cmds[0] >> 16;
if (cmd & 0x4000) {
if (get_user(cmds[1], ucmds++))
return -EFAULT;
i++;
if (i >= size / 4)
return -EINVAL;
bocmds[i] = cmds[1];
...
[Severity: High]
This is a pre-existing issue, but should the NPU command stream be converted
for endianness here?
When reading from the user command stream on Big Endian hosts, the
Vela-compiled (Little Endian) commands will be read natively by get_user()
and copied directly to the DMA coherent buffer.
This causes the command opcode and parameter parsing to fail against the
expected command opcodes, returning -EINVAL.
This will cause the driver to reject valid jobs and render the driver
unusable on Big Endian architectures.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=10
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 11/22] accel: ethosu: Validate secondary streams
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (9 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 10/22] accel: ethosu: Fix NHCWB16 bounds calculation Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:22 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 12/22] accel: ethosu: Reject unsupported commands Rob Herring (Arm)
` (10 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The command-stream validator records the additional U65 scale and
weight stream addresses and the U85 weight decoder addresses, but only
checked stream 0 against its region buffer.
Check every configured secondary stream against the matching weight or
scale region before accepting a kernel operation.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- Adjust for register state tracking
---
drivers/accel/ethosu/ethosu_gem.c | 33 ++++++++++++++++++++++++++++-----
1 file changed, 28 insertions(+), 5 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 6f6712f4efd1..abfb173e1008 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -436,13 +436,17 @@ static u64 feat_matrix_length(struct ethosu_device *edev,
static int buffer_size(struct ethosu_validated_cmdstream_info *info,
struct cmd_state *st, struct buffer *buf, s8 region,
- u16 region_cmd, u16 base_cmd, u16 length_cmd)
+ u16 region_cmd, u16 base_cmd, u16 length_cmd, bool optional)
{
u64 end;
+ bool base_set = cmd_state_reg_is_set(st, base_cmd);
+ bool length_set = cmd_state_reg_is_set(st, length_cmd);
+
+ if (optional && !base_set && !length_set)
+ return 0;
if (region < 0 || !cmd_state_reg_is_set(st, region_cmd) ||
- !cmd_state_reg_is_set(st, base_cmd) ||
- !cmd_state_reg_is_set(st, length_cmd))
+ !base_set || !length_set)
return -EINVAL;
if (check_add_overflow(buf->base, (u64)buf->length, &end))
@@ -507,7 +511,20 @@ static int calc_sizes(struct drm_device *ddev,
st->weight[0].base + st->weight[0].length - 1);
if (buffer_size(info, st, &st->weight[0], st->weight[0].region,
NPU_SET_WEIGHT_REGION, NPU_SET_WEIGHT_BASE,
- NPU_SET_WEIGHT_LENGTH))
+ NPU_SET_WEIGHT_LENGTH, false))
+ return -EINVAL;
+
+ if (buffer_size(info, st, &st->weight[1], st->weight[0].region,
+ NPU_SET_WEIGHT_REGION, NPU_SET_WEIGHT1_BASE,
+ NPU_SET_WEIGHT1_LENGTH, true) ||
+ buffer_size(info, st, &st->weight[3], st->weight[0].region,
+ NPU_SET_WEIGHT_REGION, NPU_SET_WEIGHT3_BASE,
+ NPU_SET_WEIGHT3_LENGTH, true))
+ return -EINVAL;
+ if (!ethosu_is_u65(edev) &&
+ buffer_size(info, st, &st->weight[2], st->weight[0].region,
+ NPU_SET_WEIGHT_REGION, NPU_SET_WEIGHT2_BASE,
+ NPU_SET_WEIGHT2_LENGTH, true))
return -EINVAL;
}
@@ -517,7 +534,13 @@ static int calc_sizes(struct drm_device *ddev,
st->scale[0].base + st->scale[0].length - 1);
if (buffer_size(info, st, &st->scale[0], st->scale[0].region,
NPU_SET_SCALE_REGION, NPU_SET_SCALE_BASE,
- NPU_SET_SCALE_LENGTH))
+ NPU_SET_SCALE_LENGTH, false))
+ return -EINVAL;
+
+ if (ethosu_is_u65(edev) &&
+ buffer_size(info, st, &st->scale[1], st->scale[0].region,
+ NPU_SET_SCALE_REGION, NPU_SET_SCALE1_BASE,
+ NPU_SET_SCALE1_LENGTH, true))
return -EINVAL;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 11/22] accel: ethosu: Validate secondary streams
2026-09-08 22:04 ` [PATCH v3 11/22] accel: ethosu: Validate secondary streams Rob Herring (Arm)
@ 2026-09-08 22:22 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:22 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Critical] Bypass of bounds validation for secondary weight and scale streams due to 'optional' logic, allowing use of stale register values.
Pre-existing issues:
- [Critical] Missing bounds validation for the scale stream in elementwise operations.
--
commit 097571887d553c034763bdb8261594ca466ee7e8
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Validate secondary streams
The command-stream validator records the additional U65 scale and weight
stream addresses and the U85 weight decoder addresses, but only checked
stream 0 against its region buffer. This commit checks every configured
secondary stream against the matching weight or scale region before
accepting a kernel operation.
> diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
> index 6f6712f4efd1f..abfb173e10080 100644
> --- a/drivers/accel/ethosu/ethosu_gem.c
> +++ b/drivers/accel/ethosu/ethosu_gem.c
> @@ -436,13 +436,17 @@ static u64 feat_matrix_length(struct ethosu_device *edev,
>
> static int buffer_size(struct ethosu_validated_cmdstream_info *info,
> struct cmd_state *st, struct buffer *buf, s8 region,
> - u16 region_cmd, u16 base_cmd, u16 length_cmd)
> + u16 region_cmd, u16 base_cmd, u16 length_cmd, bool optional)
> {
> u64 end;
> + bool base_set = cmd_state_reg_is_set(st, base_cmd);
> + bool length_set = cmd_state_reg_is_set(st, length_cmd);
> +
> + if (optional && !base_set && !length_set)
> + return 0;
[Severity: Critical]
Does this logic allow omitted streams to bypass bounds validation while
the hardware might still use them?
If a userspace application omits the base and length commands for secondary
streams that are inherently required by an operation (such as a multi-core
convolution), this check returns 0 and skips bounds validation.
Since Ethos-U NPUs retain register state between jobs, the hardware might
execute the operation using stale offsets from a previous job. Could this
lead to an out-of-bounds memory access by the NPU if the current job's
regions are smaller than those of the previous job?
[ ... ]
[Severity: Critical]
This is a pre-existing issue, but does calc_sizes_elemwise() miss bounds
validation for the scale stream?
In ethosu_gem_cmdstream_copy_and_validate(), a use_scale variable is
computed but completely ignored when calling calc_sizes_elemwise():
drivers/accel/ethosu/ethosu_gem.c:ethosu_gem_cmdstream_copy_and_validate() {
...
ret = calc_sizes_elemwise(ddev, info, cmd, &st, use_ifm, use_ifm2);
...
}
And calc_sizes_elemwise() entirely lacks logic to validate the st.scale
buffers.
If an unprivileged userspace application provides arbitrary, out-of-bounds
NPU_SET_SCALE_BASE and LENGTH values in the command stream for an
NPU_OP_ELEMENTWISE operation, the hardware will read from the unvalidated
offset. Can this allow an attacker to access arbitrary NPU-mapped memory?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=11
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 12/22] accel: ethosu: Reject unsupported commands
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (10 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 11/22] accel: ethosu: Validate secondary streams Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 13/22] accel: ethosu: Validate all feature map tiles Rob Herring (Arm)
` (9 subsequent siblings)
21 siblings, 0 replies; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The command-stream validator does not model U85 branches, indexed DMA,
or OFM transposes. A branch can bypass the linear validation state,
indexed DMA accesses an unchecked index buffer, and a transpose changes
the feature-map address calculation.
Reject those commands and configurations, as well as the reserved DMA
stride mode and feature-map formats. Reject command-stream IRQs because
they can signal job completion before later commands finish.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- no changes
---
drivers/accel/ethosu/ethosu_device.h | 4 ++++
drivers/accel/ethosu/ethosu_gem.c | 19 +++++++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/drivers/accel/ethosu/ethosu_device.h b/drivers/accel/ethosu/ethosu_device.h
index 1eca8590e68d..c330048dbcca 100644
--- a/drivers/accel/ethosu/ethosu_device.h
+++ b/drivers/accel/ethosu/ethosu_device.h
@@ -86,14 +86,18 @@ struct gen_pool;
#define PMU_EV_TYPE_CYCLES 0x11
#define PMU_EV_TYPE_IDLE 0x20
+#define NPU_DMA_REGION_INDEX_MODE BIT(11)
+
enum ethosu_cmds {
NPU_OP_STOP = 0x0,
+ NPU_OP_IRQ = 0x1,
NPU_OP_CONV = 0x2,
NPU_OP_DEPTHWISE = 0x3,
NPU_OP_POOL = 0x5,
NPU_OP_ELEMENTWISE = 0x6,
NPU_OP_RESIZE = 0x7, // U85 only
NPU_OP_DMA_START = 0x10,
+ NPU_OP_BRANCH = 0x4100, // U85 only
NPU_SET_IFM_PAD_TOP = 0x100,
NPU_SET_IFM_PAD_LEFT = 0x101,
NPU_SET_IFM_PAD_RIGHT = 0x102,
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index abfb173e1008..5d4e89783139 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -652,6 +652,9 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
cmd_state_set_reg(&st, cmd);
switch (cmd) {
+ case NPU_OP_BRANCH:
+ case NPU_OP_IRQ:
+ return -EINVAL;
case NPU_OP_STOP:
if (i != size / 4 - 1)
return -EINVAL;
@@ -745,6 +748,8 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
st.ifm.depth = param;
break;
case NPU_SET_IFM_PRECISION:
+ if (((param >> 6) & 0x3) > 1)
+ return -EINVAL;
st.ifm.precision = param;
break;
case NPU_SET_IFM_BROADCAST:
@@ -788,6 +793,10 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
st.ofm.depth = param;
break;
case NPU_SET_OFM_PRECISION:
+ if (((param >> 6) & 0x3) > 1)
+ return -EINVAL;
+ if (!ethosu_is_u65(edev) && (param & GENMASK(13, 11)))
+ return -EINVAL;
st.ofm.precision = param;
break;
case NPU_SET_OFM_REGION:
@@ -822,6 +831,8 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
st.ifm2.broadcast = param;
break;
case NPU_SET_IFM2_PRECISION:
+ if (((param >> 6) & 0x3) > 1)
+ return -EINVAL;
st.ifm2.precision = param;
break;
case NPU_SET_IFM2_REGION:
@@ -896,18 +907,26 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
break;
case NPU_SET_DMA0_SRC_REGION:
+ if (param & NPU_DMA_REGION_INDEX_MODE)
+ return -EINVAL;
if (param & 0x100)
st.dma.src.region = -1;
else
st.dma.src.region = param & 0x7;
st.dma.src.mode = (param >> 9) & 0x3;
+ if (st.dma.src.mode == 3)
+ return -EINVAL;
break;
case NPU_SET_DMA0_DST_REGION:
+ if (param & NPU_DMA_REGION_INDEX_MODE)
+ return -EINVAL;
if (param & 0x100)
st.dma.dst.region = -1;
else
st.dma.dst.region = param & 0x7;
st.dma.dst.mode = (param >> 9) & 0x3;
+ if (st.dma.dst.mode == 3)
+ return -EINVAL;
break;
case NPU_SET_DMA0_SIZE0:
st.dma.size0 = param;
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 13/22] accel: ethosu: Validate all feature map tiles
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (11 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 12/22] accel: ethosu: Reject unsupported commands Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:14 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 14/22] accel: ethosu: Account for feature map element size Rob Herring (Arm)
` (8 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The command-stream validator checked only the final feature-map
coordinate. For tiled tensors, this can leave an earlier tile base
address unchecked even though the operation accesses it.
Check the final coordinate of every tile touched by an operation. Also
treat U65 feature maps as 2x2 tiled: its precision rounding bits are not
the U85 storage encoding.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v3:
- Fix for storage mode 0 intermediate tile calculations
v2:
- no changes
---
drivers/accel/ethosu/ethosu_gem.c | 145 ++++++++++++++++++++++++++++++--------
1 file changed, 117 insertions(+), 28 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 5d4e89783139..11aa3f4dd0e7 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -434,6 +434,94 @@ static u64 feat_matrix_length(struct ethosu_device *edev,
return addr;
}
+static int feat_matrix_check_location(struct ethosu_device *edev,
+ struct ethosu_validated_cmdstream_info *info,
+ struct cmd_state *st, struct feat_matrix *fm,
+ enum feat_matrix_type type, u32 x, u32 y,
+ u32 c, bool ofm, u64 *max_len)
+{
+ u64 len;
+
+ len = feat_matrix_length(edev, info, st, fm, type, x, y, c, ofm);
+ if (len == U64_MAX)
+ return -EINVAL;
+
+ *max_len = max(*max_len, len);
+ return 0;
+}
+
+static int feat_matrix_size(struct ethosu_device *edev,
+ struct ethosu_validated_cmdstream_info *info,
+ struct cmd_state *st, struct feat_matrix *fm,
+ enum feat_matrix_type type,
+ u32 x, u32 y, u32 c, bool ofm, u64 *max_len)
+{
+ u32 storage = ethosu_is_u65(edev) ? 0 : fm->precision >> 14;
+ int ret;
+
+ *max_len = 0;
+
+ if (storage == 0) {
+ ret = feat_matrix_check_location(edev, info, st, fm, type, 0, 0,
+ c, ofm, max_len);
+ if (ret)
+ return ret;
+
+ ret = feat_matrix_check_location(edev, info, st, fm, type,
+ min(x, (u32)fm->width0),
+ min(y, (u32)fm->height[0]), c, ofm,
+ max_len);
+ if (ret)
+ return ret;
+
+ if (fm->width0 < x) {
+ ret = feat_matrix_check_location(edev, info, st, fm, type, x,
+ min(y, (u32)fm->height[1]), c,
+ ofm, max_len);
+ if (ret)
+ return ret;
+ }
+
+ if (fm->height[0] < y) {
+ ret = feat_matrix_check_location(edev, info, st, fm, type,
+ min(x, (u32)fm->width0), y, c,
+ ofm, max_len);
+ if (ret)
+ return ret;
+ }
+
+ if (fm->width0 < x && fm->height[1] < y)
+ return feat_matrix_check_location(edev, info, st, fm, type,
+ x, y, c, ofm, max_len);
+
+ return 0;
+ }
+
+ if (storage == 1) {
+ ret = feat_matrix_check_location(edev, info, st, fm, type, x, 0, c,
+ ofm, max_len);
+ if (ret)
+ return ret;
+ if (fm->height[0] < fm->height[1] && fm->height[1] <= y) {
+ ret = feat_matrix_check_location(edev, info, st, fm, type, x,
+ fm->height[1], c, ofm,
+ max_len);
+ if (ret)
+ return ret;
+ }
+ if (fm->height[1] < y) {
+ ret = feat_matrix_check_location(edev, info, st, fm, type, x,
+ fm->height[1] + 1, c, ofm,
+ max_len);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return feat_matrix_check_location(edev, info, st, fm, type, x, y, c, ofm,
+ max_len);
+}
+
static int buffer_size(struct ethosu_validated_cmdstream_info *info,
struct cmd_state *st, struct buffer *buf, s8 region,
u16 region_cmd, u16 base_cmd, u16 length_cmd, bool optional)
@@ -464,6 +552,7 @@ static int calc_sizes(struct drm_device *ddev,
{
struct ethosu_device *edev = to_ethosu_device(ddev);
u64 len;
+ int ret;
if (ifm) {
if (!cmd_state_reg_is_set(st, NPU_SET_KERNEL_WIDTH_M1) ||
@@ -486,23 +575,22 @@ static int calc_sizes(struct drm_device *ddev,
if (ifm_height < 0 || ifm_width < 0)
return -EINVAL;
- len = feat_matrix_length(edev, info, st, &st->ifm,
- FEAT_MATRIX_IFM, ifm_width, ifm_height,
- st->ifm.depth, false);
+ ret = feat_matrix_size(edev, info, st, &st->ifm, FEAT_MATRIX_IFM,
+ ifm_width, ifm_height, st->ifm.depth, false,
+ &len);
dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n",
op, st->ifm.region, st->ifm.base[0], len);
- if (len == U64_MAX)
- return -EINVAL;
+ if (ret)
+ return ret;
}
if (ifm2) {
- len = feat_matrix_length(edev, info, st, &st->ifm2,
- FEAT_MATRIX_IFM2, st->ifm.depth, 0,
- st->ofm.depth, false);
+ ret = feat_matrix_size(edev, info, st, &st->ifm2, FEAT_MATRIX_IFM2,
+ st->ifm.depth, 0, st->ofm.depth, false, &len);
dev_dbg(ddev->dev, "op %d: IFM2:%d:0x%llx-0x%llx\n",
op, st->ifm2.region, st->ifm2.base[0], len);
- if (len == U64_MAX)
- return -EINVAL;
+ if (ret)
+ return ret;
}
if (weight) {
@@ -544,13 +632,13 @@ static int calc_sizes(struct drm_device *ddev,
return -EINVAL;
}
- len = feat_matrix_length(edev, info, st, &st->ofm, FEAT_MATRIX_OFM,
- st->ofm.width, st->ofm.height[2], st->ofm.depth,
- true);
+ ret = feat_matrix_size(edev, info, st, &st->ofm, FEAT_MATRIX_OFM,
+ st->ofm.width, st->ofm.height[2], st->ofm.depth,
+ true, &len);
dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n",
op, st->ofm.region, st->ofm.base[0], len);
- if (len == U64_MAX)
- return -EINVAL;
+ if (ret)
+ return ret;
if (!feat_matrix_chained(edev, &st->ofm))
info->output_region[st->ofm.region] = true;
@@ -565,18 +653,19 @@ static int calc_sizes_elemwise(struct drm_device *ddev,
struct ethosu_device *edev = to_ethosu_device(ddev);
u32 height, width, depth;
u64 len;
+ int ret;
if (ifm) {
height = st->ifm.broadcast & 0x1 ? 0 : st->ofm.height[2];
width = st->ifm.broadcast & 0x2 ? 0 : st->ofm.width;
depth = st->ifm.broadcast & 0x4 ? 0 : st->ofm.depth;
- len = feat_matrix_length(edev, info, st, &st->ifm,
- FEAT_MATRIX_IFM, width, height, depth, false);
+ ret = feat_matrix_size(edev, info, st, &st->ifm, FEAT_MATRIX_IFM,
+ width, height, depth, false, &len);
dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n",
op, st->ifm.region, st->ifm.base[0], len);
- if (len == U64_MAX)
- return -EINVAL;
+ if (ret)
+ return ret;
}
if (ifm2) {
@@ -584,21 +673,21 @@ static int calc_sizes_elemwise(struct drm_device *ddev,
width = st->ifm2.broadcast & 0x2 ? 0 : st->ofm.width;
depth = st->ifm2.broadcast & 0x4 ? 0 : st->ofm.depth;
- len = feat_matrix_length(edev, info, st, &st->ifm2,
- FEAT_MATRIX_IFM2, width, height, depth, false);
+ ret = feat_matrix_size(edev, info, st, &st->ifm2, FEAT_MATRIX_IFM2,
+ width, height, depth, false, &len);
dev_dbg(ddev->dev, "op %d: IFM2:%d:0x%llx-0x%llx\n",
op, st->ifm2.region, st->ifm2.base[0], len);
- if (len == U64_MAX)
- return -EINVAL;
+ if (ret)
+ return ret;
}
- len = feat_matrix_length(edev, info, st, &st->ofm, FEAT_MATRIX_OFM,
- st->ofm.width, st->ofm.height[2], st->ofm.depth,
- true);
+ ret = feat_matrix_size(edev, info, st, &st->ofm, FEAT_MATRIX_OFM,
+ st->ofm.width, st->ofm.height[2], st->ofm.depth,
+ true, &len);
dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n",
op, st->ofm.region, st->ofm.base[0], len);
- if (len == U64_MAX)
- return -EINVAL;
+ if (ret)
+ return ret;
if (!feat_matrix_chained(edev, &st->ofm))
info->output_region[st->ofm.region] = true;
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 13/22] accel: ethosu: Validate all feature map tiles
2026-09-08 22:04 ` [PATCH v3 13/22] accel: ethosu: Validate all feature map tiles Rob Herring (Arm)
@ 2026-09-08 22:14 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:14 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] In `feat_matrix_size()`, the bounds validation for `storage == 1` (NHCWB16) fails to check the maximum `y` coordinate accessed within Tile 0, allowing arbitrary out-of-bounds NPU memory access.
--
commit d083f33a7d240704ac9b7187779f0ac15b49cf73
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Validate all feature map tiles
The command-stream validator previously checked only the final feature-map
coordinate, which could leave earlier tile base addresses unchecked for
tiled tensors. This patch checks the final coordinate of every tile touched
by an operation and treats U65 feature maps as 2x2 tiled.
> diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
> index 5d4e897831396..11aa3f4dd0e7d 100644
> --- a/drivers/accel/ethosu/ethosu_gem.c
> +++ b/drivers/accel/ethosu/ethosu_gem.c
[ ... ]
> @@ -434,6 +434,94 @@ static u64 feat_matrix_length(struct ethosu_device *edev,
> +static int feat_matrix_size(struct ethosu_device *edev,
> + struct ethosu_validated_cmdstream_info *info,
> + struct cmd_state *st, struct feat_matrix *fm,
> + enum feat_matrix_type type,
> + u32 x, u32 y, u32 c, bool ofm, u64 *max_len)
> +{
[ ... ]
> + if (storage == 1) {
> + ret = feat_matrix_check_location(edev, info, st, fm, type, x, 0, c,
> + ofm, max_len);
> + if (ret)
> + return ret;
> + if (fm->height[0] < fm->height[1] && fm->height[1] <= y) {
[Severity: High]
This isn't a bug introduced by this patch, but this patch attempts to fix
tile validation and appears to still miss the maximum coordinate of Tile 0
for storage == 1 (NHCWB16).
Does this code fail to validate the upper memory bounds of Tile 0?
When userspace submits a command stream with storage == 1, fm->height[0] > 0,
and an operation maximum y > fm->height[0], the bounds validation evaluates
Tile 0 only at its base (y = 0). It never evaluates min(y, fm->height[0]).
Because the validator calculates the required size for Tile 0 based only on
its first row, it misses the memory offset for subsequent rows up to
fm->height[0]. By supplying an undersized GEM buffer for Tile 0, could an
unprivileged user bypass memory validation and corrupt NPU-accessible memory?
> + ret = feat_matrix_check_location(edev, info, st, fm, type, x,
> + fm->height[1], c, ofm,
> + max_len);
> + if (ret)
> + return ret;
> + }
> + if (fm->height[1] < y) {
> + ret = feat_matrix_check_location(edev, info, st, fm, type, x,
> + fm->height[1] + 1, c, ofm,
> + max_len);
> + if (ret)
> + return ret;
> + }
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=13
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 14/22] accel: ethosu: Account for feature map element size
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (12 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 13/22] accel: ethosu: Validate all feature map tiles Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:20 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 15/22] accel: ethosu: Validate convolution parameter Rob Herring (Arm)
` (7 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The feature map bounds calculation currently accounts for only one byte
of the final element. This can leave the tail of B16, B32, and B64
elements outside the validated buffer.
Use the feature map element size when calculating the final byte.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_gem.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 11aa3f4dd0e7..974cf562ef49 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -426,7 +426,7 @@ static u64 feat_matrix_length(struct ethosu_device *edev,
return U64_MAX;
}
- if (check_add_overflow(addr, 1ULL, &offset))
+ if (check_add_overflow(addr, (u64)element_size, &offset))
return U64_MAX;
info->region_size[fm->region] = max(info->region_size[fm->region], offset);
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 14/22] accel: ethosu: Account for feature map element size
2026-09-08 22:04 ` [PATCH v3 14/22] accel: ethosu: Account for feature map element size Rob Herring (Arm)
@ 2026-09-08 22:20 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:20 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Missing max coordinate check for tile 0 in feat_matrix_size when storage == 1 allows out-of-bounds NPU memory accesses.
--
commit 8f5e0f4e084740b6432179e0d2b5e40f78355e1e
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Account for feature map element size
The feature map bounds calculation currently accounts for only one byte
of the final element. This can leave the tail of B16, B32, and B64
elements outside the validated buffer.
Use the feature map element size when calculating the final byte.
> diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
> index 11aa3f4dd0e7d..974cf562ef495 100644
> --- a/drivers/accel/ethosu/ethosu_gem.c
> +++ b/drivers/accel/ethosu/ethosu_gem.c
> @@ -426,7 +426,7 @@ static u64 feat_matrix_length(struct ethosu_device *edev,
> return U64_MAX;
> }
>
> - if (check_add_overflow(addr, 1ULL, &offset))
> + if (check_add_overflow(addr, (u64)element_size, &offset))
> return U64_MAX;
>
> info->region_size[fm->region] = max(info->region_size[fm->region], offset);
[Severity: Critical]
This is a pre-existing issue, but while we are looking at feature map
bounds calculations, I noticed a potential out-of-bounds regression in
feat_matrix_size() introduced earlier in the series.
When storage == 1 (plan 1 vertical tiling) and the operation spans into
tile 1 or tile 2 (where y > fm->height[0]), the validation only checks y=0
for tile 0:
drivers/accel/ethosu/ethosu_gem.c:feat_matrix_size() {
...
if (storage == 1) {
ret = feat_matrix_check_location(edev, info, st, fm, type, x, 0, c,
ofm, max_len);
...
}
...
Since base[0] and base[1] are independently programmed by the user-space
command stream, base[0] + fm->height[0] * stride_y can be the true global
maximum address of the feature map.
Because the check omits the maximum boundary of tile 0 (which would be
min(y, fm->height[0])), could this severely underestimate the required
buffer size and allow unprivileged NPU command streams to access memory
outside the validated GEM buffer?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=14
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 15/22] accel: ethosu: Validate convolution parameter
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (13 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 14/22] accel: ethosu: Account for feature map element size Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 16/22] accel: ethosu: Account for kernel dilation in IFM size Rob Herring (Arm)
` (6 subsequent siblings)
21 siblings, 0 replies; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The U65 NPU_OP_CONV command has no parameter fields, but the
validator interpreted bit zero as the U85 weights_ifm2 field. A crafted
U65 stream could consequently make validation skip the weight buffer
that hardware accesses.
Require a zero parameter on U65 and reject the reserved U85 parameter
bits.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_device.h | 1 +
drivers/accel/ethosu/ethosu_gem.c | 5 ++++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/drivers/accel/ethosu/ethosu_device.h b/drivers/accel/ethosu/ethosu_device.h
index c330048dbcca..d7e1e3c8ca12 100644
--- a/drivers/accel/ethosu/ethosu_device.h
+++ b/drivers/accel/ethosu/ethosu_device.h
@@ -87,6 +87,7 @@ struct gen_pool;
#define PMU_EV_TYPE_IDLE 0x20
#define NPU_DMA_REGION_INDEX_MODE BIT(11)
+#define NPU_OP_CONV_WEIGHTS_IFM2 BIT(0)
enum ethosu_cmds {
NPU_OP_STOP = 0x0,
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 974cf562ef49..9c86f9062f55 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -764,7 +764,10 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
st.dma.dst.region, st.dma.dst.offset, dstlen);
break;
case NPU_OP_CONV:
- use_ifm2 = param & 0x1; // weights_ifm2
+ if ((ethosu_is_u65(edev) && param) || (param & ~NPU_OP_CONV_WEIGHTS_IFM2))
+ return -EINVAL;
+
+ use_ifm2 = param & NPU_OP_CONV_WEIGHTS_IFM2;
if (!cmd_state_reg_is_set(&st, NPU_SET_OFM_PRECISION))
return -EINVAL;
use_scale = !(st.ofm.precision & 0x100);
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 16/22] accel: ethosu: Account for kernel dilation in IFM size
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (14 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 15/22] accel: ethosu: Validate convolution parameter Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:14 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 17/22] accel: ethosu: Reject reserved command encodings Rob Herring (Arm)
` (5 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
Kernel dilation increases the input feature-map area accessed by a
kernel operation. Include the x and y dilation settings when
calculating the required IFM bounds.
Without this, a command stream using x2 dilation can access IFM
memory past the range validated by the driver.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_device.h | 2 ++
drivers/accel/ethosu/ethosu_gem.c | 10 ++++++++--
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_device.h b/drivers/accel/ethosu/ethosu_device.h
index d7e1e3c8ca12..8e23fdbf7f8a 100644
--- a/drivers/accel/ethosu/ethosu_device.h
+++ b/drivers/accel/ethosu/ethosu_device.h
@@ -88,6 +88,8 @@ struct gen_pool;
#define NPU_DMA_REGION_INDEX_MODE BIT(11)
#define NPU_OP_CONV_WEIGHTS_IFM2 BIT(0)
+#define NPU_KERNEL_DILATION_X BIT(3)
+#define NPU_KERNEL_DILATION_Y BIT(4)
enum ethosu_cmds {
NPU_OP_STOP = 0x0,
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 9c86f9062f55..578d1b5b955c 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -567,10 +567,16 @@ static int calc_sizes(struct drm_device *ddev,
((st->ifm.stride_kernel >> 1) & 0x1) + 1;
u32 stride_x = ((st->ifm.stride_kernel >> 5) & 0x2) +
(st->ifm.stride_kernel & 0x1) + 1;
+ u32 dilation_y = 1 + !!(st->ifm.stride_kernel &
+ NPU_KERNEL_DILATION_Y);
+ u32 dilation_x = 1 + !!(st->ifm.stride_kernel &
+ NPU_KERNEL_DILATION_X);
s32 ifm_height = st->ofm.height[2] * stride_y +
- st->ifm.height[2] - (st->ifm.pad_top + st->ifm.pad_bottom);
+ st->ifm.height[2] * dilation_y -
+ (st->ifm.pad_top + st->ifm.pad_bottom);
s32 ifm_width = st->ofm.width * stride_x +
- st->ifm.width - (st->ifm.pad_left + st->ifm.pad_right);
+ st->ifm.width * dilation_x -
+ (st->ifm.pad_left + st->ifm.pad_right);
if (ifm_height < 0 || ifm_width < 0)
return -EINVAL;
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 16/22] accel: ethosu: Account for kernel dilation in IFM size
2026-09-08 22:04 ` [PATCH v3 16/22] accel: ethosu: Account for kernel dilation in IFM size Rob Herring (Arm)
@ 2026-09-08 22:14 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:14 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The driver under-estimates the required IFM memory bounds by improperly subtracting padding (such as pad_right and pad_bottom), allowing a maliciously crafted command stream to read out of bounds.
--
commit 07909a86c51957cbbafa4aac05851fba1d3dae6d
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Account for kernel dilation in IFM size
The commit updates calc_sizes() in the Arm Ethos-U NPU driver to account
for kernel dilation settings when calculating the required input feature-map
bounds. It extracts x and y dilation from the kernel stride and multiplies
them with the IFM dimensions to prevent a command stream using dilation
from accessing IFM memory past the driver-validated range.
> diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
> index 9c86f9062f55e..578d1b5b955cf 100644
> --- a/drivers/accel/ethosu/ethosu_gem.c
> +++ b/drivers/accel/ethosu/ethosu_gem.c
> @@ -567,10 +567,16 @@ static int calc_sizes(struct drm_device *ddev,
[ ... ]
> s32 ifm_height = st->ofm.height[2] * stride_y +
> - st->ifm.height[2] - (st->ifm.pad_top + st->ifm.pad_bottom);
> + st->ifm.height[2] * dilation_y -
> + (st->ifm.pad_top + st->ifm.pad_bottom);
> s32 ifm_width = st->ofm.width * stride_x +
> - st->ifm.width - (st->ifm.pad_left + st->ifm.pad_right);
> + st->ifm.width * dilation_x -
> + (st->ifm.pad_left + st->ifm.pad_right);
[Severity: High]
This is a pre-existing issue, but does this calculation under-estimate the
required IFM memory bounds by improperly subtracting padding parameters?
Since pad_top, pad_bottom, pad_left, and pad_right are user-provided
values (potentially up to 255), subtracting them here artificially reduces
ifm_height and ifm_width.
When feat_matrix_size() later validates the memory up to
min(ifm_width, fm->width0), this smaller ifm_width could allow a maliciously
crafted command stream to pass validation on a small buffer.
Meanwhile, the NPU hardware might access out of bounds up to the true
coordinate bounded by fm->width0. Could this lead to NPU crashes, page
faults, or information leaks from reading outside the allocated buffer?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=16
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 17/22] accel: ethosu: Reject reserved command encodings
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (15 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 16/22] accel: ethosu: Account for kernel dilation in IFM size Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 18/22] accel: ethosu: Validate accumulator input Rob Herring (Arm)
` (4 subsequent siblings)
21 siblings, 0 replies; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The command stream contains a 10-bit opcode and a two-bit command
control field. Reject reserved opcode and control encodings in the
switch default case so they cannot be interpreted differently by the
validator and hardware.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_device.h | 3 +++
drivers/accel/ethosu/ethosu_gem.c | 12 +++++++-----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_device.h b/drivers/accel/ethosu/ethosu_device.h
index 8e23fdbf7f8a..68e2969b6f79 100644
--- a/drivers/accel/ethosu/ethosu_device.h
+++ b/drivers/accel/ethosu/ethosu_device.h
@@ -91,6 +91,9 @@ struct gen_pool;
#define NPU_KERNEL_DILATION_X BIT(3)
#define NPU_KERNEL_DILATION_Y BIT(4)
+#define NPU_CMD_CTRL_CMD1 BIT(14)
+#define NPU_CMD_RESERVED_MASK (BIT(15) | GENMASK(13, 10))
+
enum ethosu_cmds {
NPU_OP_STOP = 0x0,
NPU_OP_IRQ = 0x1,
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 578d1b5b955c..f4bd31018e56 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -170,9 +170,9 @@ static void cmd_state_init(struct cmd_state *st)
static void cmd_state_set_reg(struct cmd_state *st, u16 cmd)
{
- u16 reg = cmd & ~BIT(14);
+ u16 reg = cmd & ~NPU_CMD_CTRL_CMD1;
- if (cmd & BIT(14)) {
+ if (cmd & NPU_CMD_CTRL_CMD1) {
if (reg < NPU_CMD1_REGS)
__set_bit(reg, st->cmd1);
} else if (reg < NPU_CMD0_REGS) {
@@ -182,9 +182,9 @@ static void cmd_state_set_reg(struct cmd_state *st, u16 cmd)
static bool cmd_state_reg_is_set(struct cmd_state *st, u16 cmd)
{
- u16 reg = cmd & ~BIT(14);
+ u16 reg = cmd & ~NPU_CMD_CTRL_CMD1;
- if (cmd & BIT(14))
+ if (cmd & NPU_CMD_CTRL_CMD1)
return reg < NPU_CMD1_REGS && test_bit(reg, st->cmd1);
return reg < NPU_CMD0_REGS && test_bit(reg, st->cmd0);
@@ -733,7 +733,7 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
cmd = cmds[0];
param = cmds[0] >> 16;
- if (cmd & 0x4000) {
+ if (cmd & NPU_CMD_CTRL_CMD1) {
if (get_user(cmds[1], ucmds++))
return -EFAULT;
@@ -1054,6 +1054,8 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
st.dma.src.len = st.dma.dst.len = addr;
break;
default:
+ if (cmd & NPU_CMD_RESERVED_MASK)
+ return -EINVAL;
break;
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 18/22] accel: ethosu: Validate accumulator input
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (16 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 17/22] accel: ethosu: Reject reserved command encodings Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:23 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 19/22] accel: ethosu: Restrict dynamic IFM2 weights Rob Herring (Arm)
` (3 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The U85 ACC_FORMAT command can select IFM2 as the accumulator
input. This is used by null-pool operations and can also be used
by convolution. Track this selection and validate the IFM2 feature
map against the OFM extent before submitting the operation.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_device.h | 4 ++++
drivers/accel/ethosu/ethosu_gem.c | 42 ++++++++++++++++++++++++++++++++++++
2 files changed, 46 insertions(+)
diff --git a/drivers/accel/ethosu/ethosu_device.h b/drivers/accel/ethosu/ethosu_device.h
index 68e2969b6f79..6b9d093d73e6 100644
--- a/drivers/accel/ethosu/ethosu_device.h
+++ b/drivers/accel/ethosu/ethosu_device.h
@@ -126,6 +126,7 @@ enum ethosu_cmds {
NPU_SET_KERNEL_WIDTH_M1 = 0x120,
NPU_SET_KERNEL_HEIGHT_M1 = 0x121,
NPU_SET_KERNEL_STRIDE = 0x122,
+ NPU_SET_ACC_FORMAT = 0x124,
NPU_SET_WEIGHT_REGION = 0x128,
NPU_SET_SCALE_REGION = 0x129,
NPU_SET_DMA0_SRC_REGION = 0x130,
@@ -180,6 +181,9 @@ enum ethosu_cmds {
NPU_SET_WEIGHT3_LENGTH = 0x4095,
};
+#define NPU_ACC_FORMAT_INPUT_MASK GENMASK(5, 4)
+#define NPU_ACC_INPUT_IFM2 2
+
#define ETHOSU_SRAM_REGION 2 /* Matching Vela compiler */
struct ethosu_perfmon;
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index f4bd31018e56..632a2352491a 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -155,6 +155,7 @@ struct feat_matrix {
struct cmd_state {
DECLARE_BITMAP(cmd0, NPU_CMD0_REGS);
DECLARE_BITMAP(cmd1, NPU_CMD1_REGS);
+ bool acc_input_ifm2;
struct dma_state dma;
struct buffer scale[2];
struct buffer weight[4];
@@ -522,6 +523,32 @@ static int feat_matrix_size(struct ethosu_device *edev,
max_len);
}
+static int
+calc_acc_input_size(struct drm_device *ddev,
+ struct ethosu_validated_cmdstream_info *info,
+ struct cmd_state *st)
+{
+ struct ethosu_device *edev = to_ethosu_device(ddev);
+ u64 len;
+ int ret;
+
+ if (!ethosu_is_u65(edev) &&
+ !cmd_state_reg_is_set(st, NPU_SET_ACC_FORMAT))
+ return -EINVAL;
+
+ if (!st->acc_input_ifm2)
+ return 0;
+
+ /* The accumulator has one input value for each OFM element. */
+ ret = feat_matrix_size(edev, info, st, &st->ifm2,
+ FEAT_MATRIX_IFM2, st->ofm.width,
+ st->ofm.height[2], st->ofm.depth, false, &len);
+ dev_dbg(ddev->dev, "ACC IFM2:%d:0x%llx-0x%llx\n",
+ st->ifm2.region, st->ifm2.base[0], len);
+
+ return ret;
+}
+
static int buffer_size(struct ethosu_validated_cmdstream_info *info,
struct cmd_state *st, struct buffer *buf, s8 region,
u16 region_cmd, u16 base_cmd, u16 length_cmd, bool optional)
@@ -643,6 +670,9 @@ static int calc_sizes(struct drm_device *ddev,
true, &len);
dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n",
op, st->ofm.region, st->ofm.base[0], len);
+ if (ret)
+ return ret;
+ ret = calc_acc_input_size(ddev, info, st);
if (ret)
return ret;
if (!feat_matrix_chained(edev, &st->ofm))
@@ -692,6 +722,9 @@ static int calc_sizes_elemwise(struct drm_device *ddev,
true, &len);
dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n",
op, st->ofm.region, st->ofm.base[0], len);
+ if (ret)
+ return ret;
+ ret = calc_acc_input_size(ddev, info, st);
if (ret)
return ret;
if (!feat_matrix_chained(edev, &st->ofm))
@@ -830,6 +863,15 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
case NPU_SET_KERNEL_STRIDE:
st.ifm.stride_kernel = param;
break;
+ case NPU_SET_ACC_FORMAT:
+ if (!ethosu_is_u65(edev)) {
+ u32 acc_input = FIELD_GET(NPU_ACC_FORMAT_INPUT_MASK, param);
+
+ if (acc_input > NPU_ACC_INPUT_IFM2)
+ return -EINVAL;
+ st.acc_input_ifm2 = acc_input == NPU_ACC_INPUT_IFM2;
+ }
+ break;
case NPU_SET_IFM_PAD_TOP:
st.ifm.pad_top = param & 0x7f;
break;
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 18/22] accel: ethosu: Validate accumulator input
2026-09-08 22:04 ` [PATCH v3 18/22] accel: ethosu: Validate accumulator input Rob Herring (Arm)
@ 2026-09-08 22:23 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:23 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Persistent accumulator state causes spurious validation failures for subsequent operations.
- [Medium] Unconditional accumulator format validation rejects valid command streams on U85.
--
commit 1aba4b3d50bf7ffb97c4f8a708b27672c76cbb73
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Validate accumulator input
The U85 ACC_FORMAT command can select IFM2 as the accumulator
input. This is used by null-pool operations and can also be used
by convolution. Track this selection and validate the IFM2 feature
map against the OFM extent before submitting the operation.
> diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
> index f4bd31018e564..632a2352491a0 100644
> --- a/drivers/accel/ethosu/ethosu_gem.c
> +++ b/drivers/accel/ethosu/ethosu_gem.c
[ ... ]
> @@ -522,6 +523,32 @@ static int feat_matrix_size(struct ethosu_device *edev,
> max_len);
> }
>
> +static int
> +calc_acc_input_size(struct drm_device *ddev,
> + struct ethosu_validated_cmdstream_info *info,
> + struct cmd_state *st)
> +{
> + struct ethosu_device *edev = to_ethosu_device(ddev);
> + u64 len;
> + int ret;
> +
> + if (!ethosu_is_u65(edev) &&
> + !cmd_state_reg_is_set(st, NPU_SET_ACC_FORMAT))
> + return -EINVAL;
[Severity: Medium]
Does this unconditional check reject valid command streams on U85?
If a user submits a U85 command stream containing only operations that do not
use the accumulator (such as pure elementwise or resize operations), the
compiler may optimize away the unnecessary NPU_SET_ACC_FORMAT register. By
requiring it unconditionally here, the driver will reject these valid command
streams with -EINVAL, preventing userspace from executing certain models.
> +
> + if (!st->acc_input_ifm2)
> + return 0;
> +
> + /* The accumulator has one input value for each OFM element. */
> + ret = feat_matrix_size(edev, info, st, &st->ifm2,
> + FEAT_MATRIX_IFM2, st->ofm.width,
> + st->ofm.height[2], st->ofm.depth, false, &len);
[Severity: High]
Can this persistent state cause spurious validation failures for subsequent
operations?
When a user submits a command stream where an operation (like POOL) sets
NPU_SET_ACC_FORMAT to use IFM2, st->acc_input_ifm2 becomes true. If a
subsequent operation (like ELEMENTWISE or RESIZE) ignores the accumulator, the
compiler does not emit a new format register, leaving st->acc_input_ifm2 as
true.
This will validate the previous operation's IFM2 state against the new
operation's OFM dimensions in calc_acc_input_size(), leading to a dimension
mismatch and artificially inflated memory requirements or an -EINVAL rejection.
> + dev_dbg(ddev->dev, "ACC IFM2:%d:0x%llx-0x%llx\n",
> + st->ifm2.region, st->ifm2.base[0], len);
> +
> + return ret;
> +}
> +
[ ... ]
> @@ -692,6 +722,9 @@ static int calc_sizes_elemwise(struct drm_device *ddev,
> true, &len);
> dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n",
> op, st->ofm.region, st->ofm.base[0], len);
> + if (ret)
> + return ret;
> + ret = calc_acc_input_size(ddev, info, st);
[Severity: High]
Since elementwise operations do not use the accumulator, does calling
calc_acc_input_size() here inadvertently apply stale accumulator state from
previous operations?
> if (ret)
> return ret;
> if (!feat_matrix_chained(edev, &st->ofm))
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=18
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 19/22] accel: ethosu: Restrict dynamic IFM2 weights
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (17 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 18/22] accel: ethosu: Validate accumulator input Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:24 ` sashiko-bot
2026-09-08 22:04 ` [PATCH v3 20/22] accel: ethosu: Split U65 and U85 DMA length validation Rob Herring (Arm)
` (2 subsequent siblings)
21 siblings, 1 reply; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
The validator bounds dynamic IFM2 weights as a 1x1 weight matrix, but
did not enforce the corresponding kernel shape. A larger crafted kernel
could make the NPU access beyond the validated IFM2 feature map.
Reject dynamic-weight convolutions whose kernel is not 1x1.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_gem.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 632a2352491a..df26ccd50a96 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -590,6 +590,9 @@ static int calc_sizes(struct drm_device *ddev,
!cmd_state_reg_is_set(st, NPU_SET_IFM_PAD_RIGHT) ||
!cmd_state_reg_is_set(st, NPU_SET_IFM_PAD_BOTTOM))
return -EINVAL;
+ /* Dynamic IFM2 weights are only supported for 1x1 convolutions. */
+ if (ifm2 && (st->ifm.width || st->ifm.height[2]))
+ return -EINVAL;
u32 stride_y = ((st->ifm.stride_kernel >> 8) & 0x2) +
((st->ifm.stride_kernel >> 1) & 0x1) + 1;
u32 stride_x = ((st->ifm.stride_kernel >> 5) & 0x2) +
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH v3 19/22] accel: ethosu: Restrict dynamic IFM2 weights
2026-09-08 22:04 ` [PATCH v3 19/22] accel: ethosu: Restrict dynamic IFM2 weights Rob Herring (Arm)
@ 2026-09-08 22:24 ` sashiko-bot
0 siblings, 0 replies; 35+ messages in thread
From: sashiko-bot @ 2026-09-08 22:24 UTC (permalink / raw)
To: Rob Herring (Arm); +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Missing bounds validation for Tile 0's maximum Y extent when checking NHCWB16 (storage == 1) feature matrices.
--
commit 55c57d446ca04d4d5fc6125acd2d154b7839c8a4
Author: Rob Herring (Arm) <robh@kernel.org>
accel: ethosu: Restrict dynamic IFM2 weights
This commit adds validation to reject dynamic-weight convolutions where the
kernel is not 1x1. This prevents a crafted kernel from causing the NPU to
access memory beyond the validated IFM2 feature map.
Cc: stable@vger.kernel.org
> diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
> index 632a2352491a0..df26ccd50a96a 100644
> --- a/drivers/accel/ethosu/ethosu_gem.c
> +++ b/drivers/accel/ethosu/ethosu_gem.c
[ ... ]
While reviewing the memory accesses in this file, I noticed a pre-existing
issue in feat_matrix_size():
drivers/accel/ethosu/ethosu_gem.c:feat_matrix_size() {
...
if (storage == 1) {
ret = feat_matrix_check_location(edev, info, st, fm, type, x, 0, c,
ofm, max_len);
if (ret)
return ret;
...
}
[Severity: Critical]
This is a pre-existing issue, but does this NHCWB16 format (storage == 1)
validation correctly check the maximum Y coordinate accessed in Tile 0?
For storage == 0, the code correctly checks min(y, fm->height[0]). However,
for storage == 1, it only probes Tile 0 at y = 0.
If a userspace application submits a command stream where the overall
operation height y > fm->height[0] and fm->stride_y is a very large value,
could the hardware access memory up to base[0] + fm->height[0] * stride_y?
Since the validator skips checking y = fm->height[0] for Tile 0, the
computed info->region_size appears to be drastically underestimated. Could
this underestimation bypass the GEM buffer bounds check, allowing the NPU
to read or write out-of-bounds physical memory via DMA?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260908-ethosu-fixes-v3-0-490fe215286f@kernel.org?part=19
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH v3 20/22] accel: ethosu: Split U65 and U85 DMA length validation
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (18 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 19/22] accel: ethosu: Restrict dynamic IFM2 weights Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 21/22] accel: ethosu: Validate OFM transpose Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 22/22] accel: ethosu: Validate resize operations Rob Herring (Arm)
21 siblings, 0 replies; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
Ethos-U65 and Ethos-U85 have slightly different DMA programming models.
The U65 has skip values added on to the size whereas U85 has signed
stride values. The U65 shares the skip values for source and destination
whereas the U85 has independent settings for source and destination.
The current validation only correctly handles U65 constraints and only
for source 2D/3D mode. Split the shared DMA length calculation into U65
and U85 specific versions adding U65 destination 2D/3D mode and U85
constraints.
Fixes: 5a5e9c0228e6 ("accel: Add Arm Ethos-U NPU driver")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v3:
- Fix U65 destination stride/skip handling
v2:
- new patch
---
drivers/accel/ethosu/ethosu_gem.c | 114 ++++++++++++++++++++++++++++++--------
1 file changed, 91 insertions(+), 23 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index df26ccd50a96..82699050a670 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -235,40 +235,92 @@ static bool dma_params_valid(struct ethosu_device *edev, struct cmd_state *st,
return true;
}
-static u64 dma_length(struct ethosu_device *edev,
- struct ethosu_validated_cmdstream_info *info,
- struct cmd_state *st, struct dma_state *dma_st,
- struct dma *dma, u16 region_cmd, u16 addr_cmd)
+static u64 dma_length_finish(struct ethosu_validated_cmdstream_info *info,
+ const struct dma *dma, u64 len)
+{
+ if (dma->region >= 0) {
+ u64 end;
+
+ if (check_add_overflow(len, dma->offset, &end))
+ return U64_MAX;
+ info->region_size[dma->region] =
+ max(info->region_size[dma->region], end);
+ }
+
+ return len;
+}
+
+static u64 dma_length_u65(struct ethosu_validated_cmdstream_info *info,
+ struct dma_state *dma_st,
+ struct dma *dma)
{
s8 mode = dma->mode;
u64 len = dma->len;
- if (!dma_params_valid(edev, st, dma_st, dma, region_cmd, addr_cmd))
- return U64_MAX;
+ if (mode >= 1) {
+ if (check_add_overflow(len, (u64)dma->stride[0], &len) ||
+ check_mul_overflow(len, (u64)dma_st->size0, &len))
+ return U64_MAX;
+ }
+ if (mode == 2) {
+ if (check_add_overflow(len, (u64)dma->stride[1], &len) ||
+ check_mul_overflow(len, (u64)dma_st->size1, &len))
+ return U64_MAX;
+ }
+
+ return dma_length_finish(info, dma, len);
+}
+
+static u64 dma_length_u85(struct ethosu_validated_cmdstream_info *info,
+ struct dma_state *dma_st,
+ struct dma *dma)
+{
+ s8 mode = dma->mode;
+ s64 min = 0;
+ u64 max = dma->len;
+ s64 stride;
if (mode >= 1) {
- if (dma->stride[0] < 0 && (u64)(-dma->stride[0]) > len)
+ if (check_mul_overflow(dma->stride[0],
+ (s64)dma_st->size0, &stride))
return U64_MAX;
- len += dma->stride[0];
- if (check_mul_overflow(len, (u64)dma_st->size0, &len))
+ if (stride < 0) {
+ if (check_add_overflow(min, stride, &min))
+ return U64_MAX;
+ } else if (check_add_overflow(max, (u64)stride, &max)) {
return U64_MAX;
+ }
}
if (mode == 2) {
- if (dma->stride[1] < 0 && (u64)(-dma->stride[1]) > len)
+ if (check_mul_overflow(dma->stride[1],
+ (s64)dma_st->size1, &stride))
return U64_MAX;
- len += dma->stride[1];
- if (check_mul_overflow(len, (u64)dma_st->size1, &len))
+ if (stride < 0) {
+ if (check_add_overflow(min, stride, &min))
+ return U64_MAX;
+ } else if (check_add_overflow(max, (u64)stride, &max)) {
return U64_MAX;
+ }
}
- if (dma->region >= 0) {
- u64 end;
- if (check_add_overflow(len, dma->offset, &end))
- return U64_MAX;
- info->region_size[dma->region] = max(info->region_size[dma->region], end);
- }
+ if (min < 0 && -(u64)min > dma->offset)
+ return U64_MAX;
- return len;
+ return dma_length_finish(info, dma, max);
+}
+
+static u64 dma_length(struct ethosu_device *edev,
+ struct ethosu_validated_cmdstream_info *info,
+ struct cmd_state *st, struct dma_state *dma_st,
+ struct dma *dma, u16 region_cmd, u16 addr_cmd)
+{
+ if (!dma_params_valid(edev, st, dma_st, dma, region_cmd, addr_cmd))
+ return U64_MAX;
+
+ if (ethosu_is_u65(edev))
+ return dma_length_u65(info, dma_st, dma);
+
+ return dma_length_u85(info, dma_st, dma);
}
static bool feat_matrix_chained(struct ethosu_device *edev, struct feat_matrix *fm)
@@ -1078,16 +1130,32 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
st.dma.size1 = param;
break;
case NPU_SET_DMA0_SRC_STRIDE0:
- st.dma.src.stride[0] = ((s64)addr << 24) >> 24;
+ if (ethosu_is_u65(edev)) {
+ st.dma.src.stride[0] = addr;
+ st.dma.dst.stride[0] = addr;
+ } else {
+ st.dma.src.stride[0] = sign_extend64(addr, 39);
+ }
break;
case NPU_SET_DMA0_SRC_STRIDE1:
- st.dma.src.stride[1] = ((s64)addr << 24) >> 24;
+ if (ethosu_is_u65(edev)) {
+ st.dma.src.stride[1] = addr;
+ st.dma.dst.stride[1] = addr;
+ } else {
+ st.dma.src.stride[1] = sign_extend64(addr, 39);
+ }
break;
case NPU_SET_DMA0_DST_STRIDE0:
- st.dma.dst.stride[0] = ((s64)addr << 24) >> 24;
+ if (!ethosu_is_u65(edev))
+ st.dma.dst.stride[0] = sign_extend64(addr, 39);
+ else
+ return -EINVAL;
break;
case NPU_SET_DMA0_DST_STRIDE1:
- st.dma.dst.stride[1] = ((s64)addr << 24) >> 24;
+ if (!ethosu_is_u65(edev))
+ st.dma.dst.stride[1] = sign_extend64(addr, 39);
+ else
+ return -EINVAL;
break;
case NPU_SET_DMA0_SRC:
st.dma.src.offset = addr;
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 21/22] accel: ethosu: Validate OFM transpose
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (19 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 20/22] accel: ethosu: Split U65 and U85 DMA length validation Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
2026-09-08 22:04 ` [PATCH v3 22/22] accel: ethosu: Validate resize operations Rob Herring (Arm)
21 siblings, 0 replies; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
U85 OFM dimensions are specified before transposition, while
tile bases and strides address the transposed feature map. Permute
the output endpoint before validating its tile and stride accesses.
Allow the defined U85 transpose encodings and reject the two
reserved encodings.
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v2:
- no change
---
drivers/accel/ethosu/ethosu_device.h | 1 +
drivers/accel/ethosu/ethosu_gem.c | 58 ++++++++++++++++++++++++++++++++++--
2 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_device.h b/drivers/accel/ethosu/ethosu_device.h
index 6b9d093d73e6..3f1fa0a36bd9 100644
--- a/drivers/accel/ethosu/ethosu_device.h
+++ b/drivers/accel/ethosu/ethosu_device.h
@@ -90,6 +90,7 @@ struct gen_pool;
#define NPU_OP_CONV_WEIGHTS_IFM2 BIT(0)
#define NPU_KERNEL_DILATION_X BIT(3)
#define NPU_KERNEL_DILATION_Y BIT(4)
+#define NPU_OFM_TRANSPOSE_MASK GENMASK(13, 11)
#define NPU_CMD_CTRL_CMD1 BIT(14)
#define NPU_CMD_RESERVED_MASK (BIT(15) | GENMASK(13, 10))
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 82699050a670..1c64e27a0d99 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -407,6 +407,52 @@ static int feat_matrix_validate(struct ethosu_device *edev,
return 0;
}
+
+static int feat_matrix_permute(struct ethosu_device *edev,
+ struct feat_matrix *fm, u32 *x, u32 *y,
+ u32 *c, bool ofm)
+{
+ u32 width = *x;
+ u32 height = *y;
+ u32 depth = *c;
+ u32 transpose;
+
+ if (ethosu_is_u65(edev) || !ofm)
+ return 0;
+
+ transpose = FIELD_GET(NPU_OFM_TRANSPOSE_MASK, fm->precision);
+
+ switch (transpose) {
+ case 0: /* HWC */
+ break;
+ case 1: /* WHC */
+ *x = height;
+ *y = width;
+ break;
+ case 2: /* HCW */
+ *x = depth;
+ *c = width;
+ break;
+ case 3: /* WCH */
+ *x = depth;
+ *y = width;
+ *c = height;
+ break;
+ case 6: /* CHW */
+ *x = height;
+ *y = depth;
+ *c = width;
+ break;
+ case 7: /* CWH */
+ *y = depth;
+ *c = height;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
static u64 feat_matrix_length(struct ethosu_device *edev,
struct ethosu_validated_cmdstream_info *info,
struct cmd_state *st, struct feat_matrix *fm,
@@ -513,6 +559,9 @@ static int feat_matrix_size(struct ethosu_device *edev,
int ret;
*max_len = 0;
+ ret = feat_matrix_permute(edev, fm, &x, &y, &c, ofm);
+ if (ret)
+ return ret;
if (storage == 0) {
ret = feat_matrix_check_location(edev, info, st, fm, type, 0, 0,
@@ -990,8 +1039,13 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
case NPU_SET_OFM_PRECISION:
if (((param >> 6) & 0x3) > 1)
return -EINVAL;
- if (!ethosu_is_u65(edev) && (param & GENMASK(13, 11)))
- return -EINVAL;
+ if (!ethosu_is_u65(edev)) {
+ switch (FIELD_GET(NPU_OFM_TRANSPOSE_MASK, param)) {
+ case 4:
+ case 5:
+ return -EINVAL;
+ }
+ }
st.ofm.precision = param;
break;
case NPU_SET_OFM_REGION:
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* [PATCH v3 22/22] accel: ethosu: Validate resize operations
2026-09-08 22:04 [PATCH v3 00/22] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (20 preceding siblings ...)
2026-09-08 22:04 ` [PATCH v3 21/22] accel: ethosu: Validate OFM transpose Rob Herring (Arm)
@ 2026-09-08 22:04 ` Rob Herring (Arm)
21 siblings, 0 replies; 35+ messages in thread
From: Rob Herring (Arm) @ 2026-09-08 22:04 UTC (permalink / raw)
To: Tomeu Vizoso, Oded Gabbay, Frank Li, Thomas Zimmermann
Cc: dri-devel, linux-kernel
Resize input coordinates are controlled by the scale, offset, and step
registers. Require those values to be explicitly programmed, validate
the scale and step relationships, and use a conservative coordinate
bound when validating the input feature map.
This prevents retained or malformed resize state from accessing beyond
the validated input feature-map buffer.
Assisted-by: LLM
Signed-off-by: Rob Herring (Arm) <robh@kernel.org>
---
v3:
- Fix width and height calculations to account for kernel size reported
by sashiko
v2:
- new patch
---
drivers/accel/ethosu/ethosu_device.h | 8 ++
drivers/accel/ethosu/ethosu_gem.c | 161 ++++++++++++++++++++++++++++++++++-
2 files changed, 168 insertions(+), 1 deletion(-)
diff --git a/drivers/accel/ethosu/ethosu_device.h b/drivers/accel/ethosu/ethosu_device.h
index 3f1fa0a36bd9..2974173bbc40 100644
--- a/drivers/accel/ethosu/ethosu_device.h
+++ b/drivers/accel/ethosu/ethosu_device.h
@@ -120,6 +120,8 @@ enum ethosu_cmds {
NPU_SET_OFM_HEIGHT_M1 = 0x112,
NPU_SET_OFM_DEPTH_M1 = 0x113,
NPU_SET_OFM_PRECISION = 0x114,
+ NPU_SET_OFM_BLK_WIDTH_M1 = 0x115,
+ NPU_SET_OFM_BLK_HEIGHT_M1 = 0x116,
NPU_SET_OFM_WIDTH0_M1 = 0x11a,
NPU_SET_OFM_HEIGHT0_M1 = 0x11b,
NPU_SET_OFM_HEIGHT1_M1 = 0x11c,
@@ -130,6 +132,10 @@ enum ethosu_cmds {
NPU_SET_ACC_FORMAT = 0x124,
NPU_SET_WEIGHT_REGION = 0x128,
NPU_SET_SCALE_REGION = 0x129,
+ NPU_SET_RESIZE_X_SCALE_N_M1 = 0x12a,
+ NPU_SET_RESIZE_Y_SCALE_N_M1 = 0x12b,
+ NPU_SET_RESIZE_X_OFFSET = 0x12c,
+ NPU_SET_RESIZE_Y_OFFSET = 0x12d,
NPU_SET_DMA0_SRC_REGION = 0x130,
NPU_SET_DMA0_DST_REGION = 0x131,
NPU_SET_DMA0_SIZE0 = 0x132,
@@ -180,6 +186,8 @@ enum ethosu_cmds {
NPU_SET_WEIGHT2_LENGTH = 0x4093,
NPU_SET_WEIGHT3_BASE = 0x4094,
NPU_SET_WEIGHT3_LENGTH = 0x4095,
+ NPU_SET_RESIZE_X = 0x4096,
+ NPU_SET_RESIZE_Y = 0x4097,
};
#define NPU_ACC_FORMAT_INPUT_MASK GENMASK(5, 4)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index 1c64e27a0d99..5c824668f493 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -149,6 +149,15 @@ struct feat_matrix {
u8 pad_right;
};
+struct resize_axis {
+ u16 scale_n;
+ s16 offset;
+ u16 one_step_int;
+ u16 one_step_mod;
+ u16 blk_step_int;
+ u16 blk_step_mod;
+};
+
#define NPU_CMD0_REGS 0x200
#define NPU_CMD1_REGS 0x100
@@ -162,6 +171,9 @@ struct cmd_state {
struct feat_matrix ofm;
struct feat_matrix ifm;
struct feat_matrix ifm2;
+ u16 ofm_blk_width;
+ u16 ofm_blk_height;
+ struct resize_axis resize[2];
};
static void cmd_state_init(struct cmd_state *st)
@@ -650,6 +662,98 @@ calc_acc_input_size(struct drm_device *ddev,
return ret;
}
+static int resize_axis_size(struct cmd_state *st, int axis, u16 ofm_size,
+ u16 ofm_blk_size, u32 *size)
+{
+ struct resize_axis *resize = &st->resize[axis];
+ u64 one_step, blk_step, coord;
+
+ if (resize->offset < -(s16)resize->scale_n ||
+ resize->offset >= resize->scale_n ||
+ resize->one_step_mod >= resize->scale_n ||
+ resize->blk_step_mod >= resize->scale_n)
+ return -EINVAL;
+
+ one_step = resize->one_step_int * resize->scale_n +
+ resize->one_step_mod;
+ blk_step = resize->blk_step_int * resize->scale_n +
+ resize->blk_step_mod;
+ if (check_mul_overflow((u64)ofm_blk_size, one_step, &coord) ||
+ blk_step != coord)
+ return -EINVAL;
+
+ if (check_mul_overflow((u64)ofm_size, one_step, &coord) ||
+ check_add_overflow(coord, (u64)resize->scale_n - 1, &coord))
+ return -EINVAL;
+
+ coord = div_u64(coord, resize->scale_n);
+ if (coord >= U32_MAX)
+ return -EINVAL;
+
+ *size = coord + 1;
+ return 0;
+}
+
+
+static int calc_sizes_resize(struct drm_device *ddev,
+ struct ethosu_validated_cmdstream_info *info,
+ struct cmd_state *st)
+{
+ struct ethosu_device *edev = to_ethosu_device(ddev);
+ u32 ifm_width, ifm_height;
+ u64 len;
+ int ret;
+
+ if (!cmd_state_reg_is_set(st, NPU_SET_KERNEL_WIDTH_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_KERNEL_HEIGHT_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_OFM_BLK_WIDTH_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_OFM_BLK_HEIGHT_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_RESIZE_X_SCALE_N_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_RESIZE_Y_SCALE_N_M1) ||
+ !cmd_state_reg_is_set(st, NPU_SET_RESIZE_X_OFFSET) ||
+ !cmd_state_reg_is_set(st, NPU_SET_RESIZE_Y_OFFSET) ||
+ !cmd_state_reg_is_set(st, NPU_SET_RESIZE_X) ||
+ !cmd_state_reg_is_set(st, NPU_SET_RESIZE_Y))
+ return -EINVAL;
+
+ ret = resize_axis_size(st, 0, st->ofm.width, st->ofm_blk_width,
+ &ifm_width);
+ if (ret)
+ return ret;
+ ret = resize_axis_size(st, 1, st->ofm.height[2], st->ofm_blk_height,
+ &ifm_height);
+ if (ret)
+ return ret;
+
+ if (check_add_overflow(ifm_width, (u32)st->ifm.width, &ifm_width) ||
+ check_add_overflow(ifm_height, (u32)st->ifm.height[2], &ifm_height))
+ return -EINVAL;
+
+ ret = feat_matrix_size(edev, info, st, &st->ifm, FEAT_MATRIX_IFM,
+ ifm_width, ifm_height, st->ifm.depth, false, &len);
+ dev_dbg(ddev->dev, "op %d: IFM:%d:0x%llx-0x%llx\n", NPU_OP_RESIZE,
+ st->ifm.region, st->ifm.base[0], len);
+ if (ret)
+ return ret;
+
+ ret = feat_matrix_size(edev, info, st, &st->ofm, FEAT_MATRIX_OFM,
+ st->ofm.width, st->ofm.height[2], st->ofm.depth,
+ true, &len);
+ dev_dbg(ddev->dev, "op %d: OFM:%d:0x%llx-0x%llx\n", NPU_OP_RESIZE,
+ st->ofm.region, st->ofm.base[0], len);
+ if (ret)
+ return ret;
+
+ ret = calc_acc_input_size(ddev, info, st);
+ if (ret)
+ return ret;
+
+ if (!feat_matrix_chained(edev, &st->ofm))
+ info->output_region[st->ofm.region] = true;
+
+ return 0;
+}
+
static int buffer_size(struct ethosu_validated_cmdstream_info *info,
struct cmd_state *st, struct buffer *buf, s8 region,
u16 region_cmd, u16 base_cmd, u16 length_cmd, bool optional)
@@ -957,7 +1061,12 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
return ret;
break;
case NPU_OP_RESIZE: // U85 only
- return -EINVAL;
+ if (ethosu_is_u65(edev) || param > 2)
+ return -EINVAL;
+ ret = calc_sizes_resize(ddev, info, &st);
+ if (ret)
+ return ret;
+ break;
case NPU_SET_KERNEL_WIDTH_M1:
st.ifm.width = param;
break;
@@ -1048,6 +1157,12 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
}
st.ofm.precision = param;
break;
+ case NPU_SET_OFM_BLK_WIDTH_M1:
+ st.ofm_blk_width = param & 0x7f;
+ break;
+ case NPU_SET_OFM_BLK_HEIGHT_M1:
+ st.ofm_blk_height = param & 0x7f;
+ break;
case NPU_SET_OFM_REGION:
st.ofm.region = param & 0x7;
break;
@@ -1118,6 +1233,34 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
case NPU_SET_SCALE_REGION:
st.scale[0].region = param & 0x7;
break;
+ case NPU_SET_RESIZE_X_SCALE_N_M1:
+ if (ethosu_is_u65(edev))
+ break;
+ if (param & GENMASK(15, 11))
+ return -EINVAL;
+ st.resize[0].scale_n = param + 1;
+ break;
+ case NPU_SET_RESIZE_Y_SCALE_N_M1:
+ if (ethosu_is_u65(edev))
+ break;
+ if (param & GENMASK(15, 11))
+ return -EINVAL;
+ st.resize[1].scale_n = param + 1;
+ break;
+ case NPU_SET_RESIZE_X_OFFSET:
+ if (ethosu_is_u65(edev))
+ break;
+ if (param & GENMASK(15, 12))
+ return -EINVAL;
+ st.resize[0].offset = sign_extend32(param, 11);
+ break;
+ case NPU_SET_RESIZE_Y_OFFSET:
+ if (ethosu_is_u65(edev))
+ break;
+ if (param & GENMASK(15, 12))
+ return -EINVAL;
+ st.resize[1].offset = sign_extend32(param, 11);
+ break;
case NPU_SET_WEIGHT_BASE:
st.weight[0].base = addr;
break;
@@ -1154,6 +1297,22 @@ static int ethosu_gem_cmdstream_copy_and_validate(struct drm_device *ddev,
case NPU_SET_WEIGHT3_LENGTH:
st.weight[3].length = cmds[1];
break;
+ case NPU_SET_RESIZE_X:
+ case NPU_SET_RESIZE_Y:
+ if (ethosu_is_u65(edev))
+ break;
+ if ((cmds[0] & BIT(31)) ||
+ (cmds[1] & (GENMASK(31, 27) | GENMASK(15, 11))))
+ return -EINVAL;
+ st.resize[cmd - NPU_SET_RESIZE_X].one_step_int =
+ FIELD_GET(GENMASK(19, 16), cmds[0]);
+ st.resize[cmd - NPU_SET_RESIZE_X].blk_step_int =
+ FIELD_GET(GENMASK(30, 20), cmds[0]);
+ st.resize[cmd - NPU_SET_RESIZE_X].one_step_mod =
+ FIELD_GET(GENMASK(10, 0), cmds[1]);
+ st.resize[cmd - NPU_SET_RESIZE_X].blk_step_mod =
+ FIELD_GET(GENMASK(26, 16), cmds[1]);
+ break;
case NPU_SET_DMA0_SRC_REGION:
if (param & NPU_DMA_REGION_INDEX_MODE)
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread