* [PATCH v2 01/19] accel: ethosu: Suspend after initialization
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 02/19] accel: ethosu: Fix probe error cleanup Rob Herring (Arm)
` (17 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 [flat|nested] 20+ messages in thread* [PATCH v2 02/19] accel: ethosu: Fix probe error cleanup
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 01/19] accel: ethosu: Suspend after initialization Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 03/19] accel: ethosu: Disable clocks on PM setup failure Rob Herring (Arm)
` (16 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 df76253d01a6..c684e28ac787 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 [flat|nested] 20+ messages in thread* [PATCH v2 03/19] accel: ethosu: Disable clocks on PM setup failure
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 01/19] accel: ethosu: Suspend after initialization Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 02/19] accel: ethosu: Fix probe error cleanup Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 04/19] accel: ethosu: Quiesce jobs before scheduler teardown Rob Herring (Arm)
` (15 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 c684e28ac787..65b148e770e5 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 [flat|nested] 20+ messages in thread* [PATCH v2 04/19] accel: ethosu: Quiesce jobs before scheduler teardown
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (2 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 03/19] accel: ethosu: Disable clocks on PM setup failure Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 05/19] accel: ethosu: Move DMA mode to src/dst struct Rob Herring (Arm)
` (14 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_drv.c | 4 ++--
drivers/accel/ethosu/ethosu_drv.h | 2 ++
drivers/accel/ethosu/ethosu_job.c | 22 +++++++++++++++++++++-
3 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_drv.c b/drivers/accel/ethosu/ethosu_drv.c
index 65b148e770e5..0918fd9b7041 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..74a1136635f8 100644
--- a/drivers/accel/ethosu/ethosu_job.c
+++ b/drivers/accel/ethosu/ethosu_job.c
@@ -189,6 +189,16 @@ 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_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 +325,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 +374,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 [flat|nested] 20+ messages in thread* [PATCH v2 05/19] accel: ethosu: Move DMA mode to src/dst struct
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (3 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 04/19] accel: ethosu: Quiesce jobs before scheduler teardown Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 06/19] accel: ethosu: Track command stream register setup Rob Herring (Arm)
` (13 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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>
---
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 9afe2549ec84..408b93350dd7 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -100,6 +100,7 @@ int ethosu_gem_create_with_handle(struct drm_file *file,
struct dma {
s8 region;
+ s8 mode;
u64 len;
u64 offset;
s64 stride[2];
@@ -108,7 +109,6 @@ struct dma {
struct dma_state {
u16 size0;
u16 size1;
- s8 mode;
struct dma src;
struct dma dst;
};
@@ -161,7 +161,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)
@@ -654,13 +654,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 [flat|nested] 20+ messages in thread* [PATCH v2 06/19] accel: ethosu: Track command stream register setup
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (4 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 05/19] accel: ethosu: Move DMA mode to src/dst struct Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 07/19] accel: ethosu: Factor buffer bounds checks Rob Herring (Arm)
` (12 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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>
---
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 408b93350dd7..8a44a5d89bce 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>
@@ -138,7 +139,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];
@@ -149,8 +155,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)
@@ -158,13 +185,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 U64_MAX;
+ 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 U64_MAX;
+
+ 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) {
@@ -199,17 +267,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;
@@ -237,24 +386,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;
}
@@ -268,7 +432,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;
@@ -282,8 +452,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)
@@ -291,8 +462,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)
@@ -303,8 +475,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],
@@ -315,16 +488,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)
@@ -349,8 +524,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)
@@ -362,16 +537,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)
@@ -426,6 +602,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)
@@ -433,8 +611,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;
@@ -445,16 +625,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);
@@ -462,11 +654,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 [flat|nested] 20+ messages in thread* [PATCH v2 07/19] accel: ethosu: Factor buffer bounds checks
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (5 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 06/19] accel: ethosu: Track command stream register setup Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 08/19] accel: ethosu: Validate secondary streams Rob Herring (Arm)
` (11 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 8a44a5d89bce..0b1edf02d48e 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -423,6 +423,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,
@@ -475,26 +494,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 [flat|nested] 20+ messages in thread* [PATCH v2 08/19] accel: ethosu: Validate secondary streams
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (6 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 07/19] accel: ethosu: Factor buffer bounds checks Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 09/19] accel: ethosu: Reject unsupported commands Rob Herring (Arm)
` (10 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 0b1edf02d48e..ec2832eb9a07 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -425,13 +425,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))
@@ -496,7 +500,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;
}
@@ -506,7 +523,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 [flat|nested] 20+ messages in thread* [PATCH v2 09/19] accel: ethosu: Reject unsupported commands
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (7 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 08/19] accel: ethosu: Validate secondary streams Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 10/19] accel: ethosu: Validate all feature map tiles Rob Herring (Arm)
` (9 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 ec2832eb9a07..c913c95e48ae 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -641,6 +641,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;
@@ -734,6 +737,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:
@@ -777,6 +782,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:
@@ -811,6 +820,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:
@@ -885,18 +896,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 [flat|nested] 20+ messages in thread* [PATCH v2 10/19] accel: ethosu: Validate all feature map tiles
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (8 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 09/19] accel: ethosu: Reject unsupported commands Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 11/19] accel: ethosu: Account for feature map element size Rob Herring (Arm)
` (8 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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>
---
v2:
- no changes
---
drivers/accel/ethosu/ethosu_gem.c | 125 +++++++++++++++++++++++++++++---------
1 file changed, 97 insertions(+), 28 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index c913c95e48ae..c54496fa08f4 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -423,6 +423,74 @@ 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 (ethosu_is_u65(edev) || storage == 0) {
+ for (int xi = 0; xi < 2; xi++) {
+ for (int yi = 0; yi < 2; yi++) {
+ ret = feat_matrix_check_location(edev, info, st, fm, type,
+ xi ? x : 0,
+ yi ? y : 0, c, ofm,
+ max_len);
+ if (ret)
+ return ret;
+ }
+ }
+ 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);
+ }
+
+ 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)
@@ -453,6 +521,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) ||
@@ -475,23 +544,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) {
@@ -533,13 +601,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;
@@ -554,18 +622,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) {
@@ -573,21 +642,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 [flat|nested] 20+ messages in thread* [PATCH v2 11/19] accel: ethosu: Account for feature map element size
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (9 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 10/19] accel: ethosu: Validate all feature map tiles Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 12/19] accel: ethosu: Validate convolution parameter Rob Herring (Arm)
` (7 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 c54496fa08f4..bca68fab0527 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -415,7 +415,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 [flat|nested] 20+ messages in thread* [PATCH v2 12/19] accel: ethosu: Validate convolution parameter
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (10 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 11/19] accel: ethosu: Account for feature map element size Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 13/19] accel: ethosu: Account for kernel dilation in IFM size Rob Herring (Arm)
` (6 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 bca68fab0527..d60a2a453302 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -733,7 +733,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 [flat|nested] 20+ messages in thread* [PATCH v2 13/19] accel: ethosu: Account for kernel dilation in IFM size
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (11 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 12/19] accel: ethosu: Validate convolution parameter Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 14/19] accel: ethosu: Reject reserved command encodings Rob Herring (Arm)
` (5 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 d60a2a453302..63dd07791f31 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -536,10 +536,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 [flat|nested] 20+ messages in thread* [PATCH v2 14/19] accel: ethosu: Reject reserved command encodings
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (12 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 13/19] accel: ethosu: Account for kernel dilation in IFM size Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 15/19] accel: ethosu: Validate accumulator input Rob Herring (Arm)
` (4 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 63dd07791f31..d5c3a2c530dc 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -160,9 +160,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) {
@@ -172,9 +172,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);
@@ -702,7 +702,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;
@@ -1023,6 +1023,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 [flat|nested] 20+ messages in thread* [PATCH v2 15/19] accel: ethosu: Validate accumulator input
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (13 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 14/19] accel: ethosu: Reject reserved command encodings Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 16/19] accel: ethosu: Restrict dynamic IFM2 weights Rob Herring (Arm)
` (3 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 d5c3a2c530dc..05a3cc04e7d1 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -145,6 +145,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];
@@ -491,6 +492,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)
@@ -612,6 +639,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))
@@ -661,6 +691,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))
@@ -799,6 +832,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 [flat|nested] 20+ messages in thread* [PATCH v2 16/19] accel: ethosu: Restrict dynamic IFM2 weights
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (14 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 15/19] accel: ethosu: Validate accumulator input Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 17/19] accel: ethosu: Split U65 and U85 DMA length validation Rob Herring (Arm)
` (2 subsequent siblings)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 05a3cc04e7d1..c3dad3f80c34 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -559,6 +559,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 [flat|nested] 20+ messages in thread* [PATCH v2 17/19] accel: ethosu: Split U65 and U85 DMA length validation
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (15 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 16/19] accel: ethosu: Restrict dynamic IFM2 weights Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 18/19] accel: ethosu: Validate OFM transpose Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 19/19] accel: ethosu: Validate resize operations Rob Herring (Arm)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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. Split the
shared DMA length calculation into U65 and U85 specific versions.
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 | 110 ++++++++++++++++++++++++++++++--------
1 file changed, 87 insertions(+), 23 deletions(-)
diff --git a/drivers/accel/ethosu/ethosu_gem.c b/drivers/accel/ethosu/ethosu_gem.c
index c3dad3f80c34..559fbf55f12d 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -225,40 +225,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)
@@ -1047,16 +1099,28 @@ 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.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.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 [flat|nested] 20+ messages in thread* [PATCH v2 18/19] accel: ethosu: Validate OFM transpose
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (16 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 17/19] accel: ethosu: Split U65 and U85 DMA length validation Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
2026-09-05 0:43 ` [PATCH v2 19/19] accel: ethosu: Validate resize operations Rob Herring (Arm)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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 559fbf55f12d..8114447891b2 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -397,6 +397,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,
@@ -502,6 +548,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 (ethosu_is_u65(edev) || storage == 0) {
for (int xi = 0; xi < 2; xi++) {
@@ -959,8 +1008,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 [flat|nested] 20+ messages in thread* [PATCH v2 19/19] accel: ethosu: Validate resize operations
2026-09-05 0:43 [PATCH v2 00/19] accel: ethosu: Another batch of fixes Rob Herring (Arm)
` (17 preceding siblings ...)
2026-09-05 0:43 ` [PATCH v2 18/19] accel: ethosu: Validate OFM transpose Rob Herring (Arm)
@ 2026-09-05 0:43 ` Rob Herring (Arm)
18 siblings, 0 replies; 20+ messages in thread
From: Rob Herring (Arm) @ 2026-09-05 0:43 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>
---
v2:
- new patch
---
drivers/accel/ethosu/ethosu_device.h | 8 ++
drivers/accel/ethosu/ethosu_gem.c | 159 ++++++++++++++++++++++++++++++++++-
2 files changed, 166 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 8114447891b2..67bee9612934 100644
--- a/drivers/accel/ethosu/ethosu_gem.c
+++ b/drivers/accel/ethosu/ethosu_gem.c
@@ -139,6 +139,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
@@ -152,6 +161,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)
@@ -619,6 +631,96 @@ 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;
+
+ ret = feat_matrix_size(edev, info, st, &st->ifm, FEAT_MATRIX_IFM,
+ max(ifm_width, (u32)st->ifm.width),
+ max(ifm_height, (u32)st->ifm.height[2]), 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)
@@ -926,7 +1028,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;
@@ -1017,6 +1124,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;
@@ -1087,6 +1200,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;
@@ -1123,6 +1264,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 [flat|nested] 20+ messages in thread