* [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement
@ 2026-08-31 8:19 Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 01/14] accel/rocket: request the core clocks by name Jiaxing Hu
` (13 more replies)
0 siblings, 14 replies; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu
1/14 is Igor Paunovic's "[PATCH v2] accel/rocket: request the core clocks
by name", unchanged and carried inside the series rather than named as a
prerequisite outside it. It keeps his authorship and its four tags.
Tomeu asked for it on v10:
"Sashiko is not reviewing this series because it doesn't not understand
yet prerequisite-patch-id. Can you please resubmit the series with
Igor's (presumably) patches bundled in it?"
So it applies to a plain next-20260814, with no prerequisite-patch-id
left to follow. Tested on a Radxa ROCK 4D.
This adds the RK3576 NPU to accel/rocket, which today supports RK3588
only. The RK3576 carries two cores of the same RKNN block, wired up
differently. Two extra convolution buffer clocks, two power domains per
core, one reset rather than two, no NPU SRAM rail, and a PC_TASK_CON
that packs the task number into sixteen bits rather than twelve.
What changed since v10
No code, again. Igor's patch joins the series as 1/14 and everything that
was 1/13 through 13/13 in v10 is now 2/14 through 14/14, same diffs.
The git note that was on 5/13 is gone rather than renumbered: it recorded
a dependency outside the series, and there is not one now.
The tags:
3/14 Tested-by: Igor Paunovic # RK3588, three cores, induced
# reset, differential base,
# JOB_TIMEOUT_MS=2
4/14 Tested-by: Igor Paunovic # RK3588, three cores, induced
# reset, JOB_TIMEOUT_MS=2
7/14 Acked-by: Conor Dooley
8/14 Acked-by: Conor Dooley
9/14 Reviewed-by: Abel Vesa
10/14 Reviewed-by: Abel Vesa
The bindings are unchanged since v9, where dt_binding_check was clean on
all three with dtschema 2026.6 and yamllint 1.38.0, and CHECK_DTBS was
clean on all 13 rk3576 and all 48 rk3588 dtbs.
Two things I raised in v9 and would still rather hear about than guess
at. 9/14 does three things: it adds the settle delay, renames a macro,
and gives RK3576_PD_NPU a regulator, which also makes every RK3576 board
force that domain off at probe. I asked whether it wants splitting;
Abel Vesa's Reviewed-by may be the answer, but nobody has said so, and I
would rather split it than have it merged on my silence. And 13/14 gives
each core both NPU domains, which is the description that has been
tested here rather than the topology; if it should be one domain per
core, 6/14's minItems has to change with it. That one has had no reply
at all.
Nothing else moved.
Igor Paunovic (1):
accel/rocket: request the core clocks by name
Jiaxing Hu (13):
accel/rocket: take the completion register writes under job_lock
accel/rocket: wait for a running IRQ handler before resetting a core
accel/rocket: let the core suspend after a reset
accel/rocket: factor the completion tail out of the IRQ handler
dt-bindings: npu: rockchip: add rockchip,rk3576-rknn-core
dt-bindings: power: rockchip: allow resets in a power domain node
dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU
pmdomain/rockchip: add optional per-domain power-on settle delay
pmdomain/rockchip: cycle optional power-domain resets on power-on
accel/rocket: select the per-core clock and reset counts from match
data
accel/rocket: add RK3576 NPU (RKNN) support
arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes
arm64: dts: rockchip: rk3576-rock-4d: enable NPU
.../bindings/iommu/rockchip,iommu.yaml | 28 +++++
.../npu/rockchip,rk3588-rknn-core.yaml | 47 ++++++-
.../power/rockchip,power-controller.yaml | 8 ++
.../boot/dts/rockchip/rk3576-rock-4d.dts | 13 ++
arch/arm64/boot/dts/rockchip/rk3576.dtsi | 82 +++++++++++-
drivers/accel/rocket/rocket_core.c | 32 ++++-
drivers/accel/rocket/rocket_core.h | 11 +-
drivers/accel/rocket/rocket_device.c | 7 +-
drivers/accel/rocket/rocket_drv.c | 28 ++++-
drivers/accel/rocket/rocket_drv.h | 2 +
drivers/accel/rocket/rocket_job.c | 119 ++++++++++++++----
drivers/pmdomain/rockchip/pm-domains.c | 75 +++++++----
12 files changed, 389 insertions(+), 63 deletions(-)
base-commit: 4477a78374a57c3809b172ad30cceabda48c47c6
--
2.43.0
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v11 01/14] accel/rocket: request the core clocks by name
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 8:36 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 02/14] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
` (12 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Sidong Yang, Sebastian Reichel,
Jiaxing Hu
From: Igor Paunovic <royalnet026@gmail.com>
rocket_core_init() hands core->clks to devm_clk_bulk_get() without ever
setting the .id members. The rocket_core array is allocated with
devm_kcalloc() in rocket_device_init(), and rocket_probe() only fills in
.rdev, .dev and .index, so all four clk_bulk_data entries are requested
with a NULL con_id (unlike core->resets, whose ids are set a few lines
above).
clk_get(dev, NULL) ends up in of_clk_get_hw(np, 0, NULL), and
of_parse_clkspec() only consults "clock-names" when a name was passed,
so the index stays 0 for all four entries. Every entry therefore ends up
holding a handle to the *first* clock of the DT "clocks" property, i.e.
ACLK_NPUn. Nothing fails: probe succeeds and the driver believes it owns
four different clocks.
The consequence is that rocket_device_runtime_resume() prepares and
enables the AXI clock four times, while hclk, pclk and - most
importantly - the NPU compute clock ("npu", SCMI_CLK_NPU on RK3588) are
never prepared or enabled by this driver at all. The NPU still works
only because the Rockchip power-domain driver sets GENPD_FLAG_PM_CLK and
its attach_dev() callback walks the device node with of_clk_get() and
adds every clock to the pm_clk list, so genpd happens to keep the
remaining clocks running. The bug is therefore latent today, but it
means the driver holds no reference to the clock that actually feeds the
NPU, which stands in the way of any future frequency scaling
(OPP/devfreq) work.
Found on an Orange Pi 5 Plus (RK3588) by reading the live clock tree:
/sys/kernel/debug/clk/clk_summary shows four "fdab0000.npu" consumer
handles on aclk_npu0 (and likewise on aclk_npu1/aclk_npu2 for the other
two cores), while hclk_npu0, pclk_npu_root and scmi_clk_npu have no
"fdab0000.npu" consumer at all - their only consumers are the
"npu@fdab0000" handles created by the power-domain driver via
of_clk_get().
Set the ids explicitly, in the order mandated by the binding
(Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml):
aclk, hclk, npu, pclk. After the change the driver holds one handle per
distinct clock and clk_bulk_prepare_enable() covers all four.
Note that this is a user-visible tightening for out-of-tree DTs: the
old NULL-id requests resolved by index and succeeded no matter what
"clock-names" contained, while the named requests fail probe with
-ENOENT when one of the four names is missing. That is the right
outcome for in-tree users - the binding requires exactly these four
clock-names and rk3588-base.dtsi carries them on all three cores - but
a DT that relied on the permissive lookup goes from silently running on
the wrong clock handles to not probing at all, so record the change
here where git log will find it.
Fixes: ed98261b4168 ("accel/rocket: Add a new driver for Rockchip's NPU")
Signed-off-by: Igor Paunovic <royalnet026@gmail.com>
Tested-by: Sidong Yang <sidong.yang@furiosa.ai>
Tested-by: Diederik de Haas <diederik@cknow-tech.com> # NanoPC-T6 LTS, NanoPC-T6 Plus
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Reviewed-by: Jiaxing Hu <gahing@gahingwoo.com>
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
drivers/accel/rocket/rocket_core.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/accel/rocket/rocket_core.c b/drivers/accel/rocket/rocket_core.c
index b3b2fa9ba..5dd260bac 100644
--- a/drivers/accel/rocket/rocket_core.c
+++ b/drivers/accel/rocket/rocket_core.c
@@ -28,6 +28,10 @@ int rocket_core_init(struct rocket_core *core)
if (err)
return dev_err_probe(dev, err, "failed to get resets for core %d\n", core->index);
+ core->clks[0].id = "aclk";
+ core->clks[1].id = "hclk";
+ core->clks[2].id = "npu";
+ core->clks[3].id = "pclk";
err = devm_clk_bulk_get(dev, ARRAY_SIZE(core->clks), core->clks);
if (err)
return dev_err_probe(dev, err, "failed to get clocks for core %d\n", core->index);
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 02/14] accel/rocket: take the completion register writes under job_lock
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 01/14] accel/rocket: request the core clocks by name Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 8:36 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 03/14] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
` (11 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu
rocket_job_handle_irq() writes OPERATION_ENABLE and INTERRUPT_CLEAR before
taking job_lock, while rocket_job_hw_submit() writes OPERATION_ENABLE from
inside it. The two can therefore race: a completion being handled on one core
can write its zero after a submit on the same core has written its one, and
stop a task that has only just started.
Nothing in tree hits this often, because the interrupt is the only completion
path and it does not overlap its own submit, but the ordering is wrong on its
own terms.
Move both writes inside the existing scoped_guard() rather than adding a second
critical section, so stopping the block and deciding what to start next are one
atomic step.
Fixes: 0810d5ad88a1 ("accel/rocket: Add job submission IOCTL")
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three cores
---
drivers/accel/rocket/rocket_job.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index 3141f210f..5f0f9682e 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -345,10 +345,15 @@ static void rocket_job_handle_irq(struct rocket_core *core)
{
pm_runtime_mark_last_busy(core->dev);
- rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
- rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
+ scoped_guard(mutex, &core->job_lock) {
+ /*
+ * Stopping the block belongs under the lock. hw_submit() writes
+ * OPERATION_ENABLE too, and outside the lock this zero can land
+ * after that one and stop a task that has only just started.
+ */
+ rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
+ rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
- scoped_guard(mutex, &core->job_lock)
if (core->in_flight_job) {
if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
rocket_job_hw_submit(core, core->in_flight_job);
@@ -360,6 +365,7 @@ static void rocket_job_handle_irq(struct rocket_core *core)
pm_runtime_put_autosuspend(core->dev);
core->in_flight_job = NULL;
}
+ }
}
static void
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 03/14] accel/rocket: wait for a running IRQ handler before resetting a core
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 01/14] accel/rocket: request the core clocks by name Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 02/14] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 8:37 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 04/14] accel/rocket: let the core suspend after a reset Jiaxing Hu
` (10 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu
rocket_reset() calls drm_sched_stop(), which stops the scheduler and
returns. It does not wait for a threaded handler that is already
running, so the comment that follows, "Remaining interrupts have been
handled", states an assumption rather than something the code arranges.
Call synchronize_irq(core->irq) after drm_sched_stop() and reword the
comment to say what holds afterwards.
It has to go before the scoped_guard(mutex, &core->job_lock) rather than
inside it. rocket_job_handle_irq() takes job_lock, so waiting for the
handler while holding that lock would be waiting for a handler that is
waiting for us. Nothing is held at that point, and both callers,
rocket_job_timedout() and rocket_reset_work(), run in process context,
so sleeping there is allowed.
This does not stop a handler that has already read in_flight_job from
finishing its work on the job the reset is about to drop. That window
needs the check and the register writes to be one step under the lock,
which is what the previous patch does; the two are complementary.
Mask the block before the sync as well. INTERRUPT_MASK is armed by
hw_submit() on every submit and cleared only by the hardirq, so on an
ordinary timeout it is still live and a completion can arrive after
synchronize_irq() returns. Nothing is lost by clearing it, since the next
submit arms it again.
That write is the first register access this function has ever made, and
it is guarded, because the function holds no runtime PM reference of its
own. The only reference in the window belongs to in_flight_job, and the
completion path can have put it and cleared the pointer before the
timeout worker arrives: drm_sched_stop() sits in between and can block on
cancel_work_sync() and on a dma_fence_wait(), and it subtracts every
pending job's credits, so rocket_job_is_idle() is true and
rocket_device_runtime_suspend() will not refuse. With the autosuspend
delay elapsed the clocks are off and both NPU domains are down. A
register access in that state takes an async SError on this hardware,
which is the failure two later patches in this series describe from the
power-on side.
pm_runtime_get_if_active() resumes nothing and allocates nothing; if the
core is already down there is no live interrupt to mask and the following
synchronize_irq() is all that is needed. Igor Paunovic asked the general
form of this on v8 -- whether rocket_reset() should hold a reference --
and it was deferred then because nothing in the path touched a register.
This patch is what makes it matter.
The deadlock this placement avoids would not have been reported. The wait
is on desc->wait_for_threads rather than on a lock, so lockdep does not
model it and it would have hung silently.
Suggested-by: Igor Paunovic <royalnet026@gmail.com>
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three cores, induced reset, differential base, JOB_TIMEOUT_MS=2
---
drivers/accel/rocket/rocket_job.c | 34 ++++++++++++++++++++++++++++---
1 file changed, 31 insertions(+), 3 deletions(-)
diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index 5f0f9682e..3c0ed4605 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -377,9 +377,37 @@ rocket_reset(struct rocket_core *core, struct drm_sched_job *bad)
drm_sched_stop(&core->sched, bad);
/*
- * Remaining interrupts have been handled, but we might still have
- * stuck jobs. Let's make sure the PM counters stay balanced by
- * manually calling pm_runtime_put_noidle().
+ * Mask the block before waiting. hw_submit() arms INTERRUPT_MASK on
+ * every submit and only the hardirq clears it, so on an ordinary
+ * timeout it is still live and a completion can arrive after the sync
+ * returns. The next submit re-arms it, so nothing is lost here.
+ *
+ * Only when the device is already awake, though. This function holds no
+ * runtime PM reference of its own: the only one in the window belongs to
+ * in_flight_job, and the completion path may have put it and cleared the
+ * pointer before the timeout worker got here. drm_sched_stop() above can
+ * block for a long time, and it drops every pending job's credits, so
+ * rocket_job_is_idle() is true and nothing keeps the core resumed. On
+ * this hardware a register access with the domain down takes an async
+ * SError, so a reset must not be the thing that causes one.
+ */
+ if (pm_runtime_get_if_active(core->dev) > 0) {
+ rocket_pc_writel(core, INTERRUPT_MASK, 0x0);
+ pm_runtime_put_autosuspend(core->dev);
+ }
+
+ /*
+ * drm_sched_stop() returns without waiting for a threaded handler that
+ * is already running, so wait for one here. This has to stay outside
+ * job_lock: the handler takes that lock, so waiting for it while
+ * holding it would deadlock instead of fencing anything.
+ */
+ synchronize_irq(core->irq);
+
+ /*
+ * No handler is running now, but we might still have stuck jobs. Let's
+ * make sure the PM counters stay balanced by manually calling
+ * pm_runtime_put_noidle().
*/
scoped_guard(mutex, &core->job_lock) {
if (core->in_flight_job)
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 04/14] accel/rocket: let the core suspend after a reset
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (2 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 03/14] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 8:49 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 05/14] accel/rocket: factor the completion tail out of the IRQ handler Jiaxing Hu
` (9 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu
rocket_reset() drops the in-flight job's runtime PM reference with
pm_runtime_put_noidle(), a bare decrement that requests nothing. The core
is left at usage_count 0 but still runtime-active with no idle request
pending, so it does not suspend until something else asks, and on a
platform whose power domain does work on power-on that work never happens.
On RK3576 that work is a bus interface reset the domain cycles when it
comes up. Without it the NPU's IOMMU stops answering, and the job after a
timeout returns a surface of the output zero point with rk_iommu reporting
that MMU_DTE_ADDR is not functioning.
Measured on a ROCK 4D in one boot, three runs, one variable between them.
With the bare put the core reads runtime-active with its rail still up
after the reset, the IOMMU reports the failure on the next attach and the
inference returns 0 of 128 channels. With the reference put back through
pm_runtime_put_autosuspend() the core reads suspended with the rail down,
there is no IOMMU message, and the same inference returns 128 of 128. A
third run repeating the first failed the same way.
It also matches the put in the completion path a few lines away, so the
reset path no longer leaves the device in a state the rest of the driver
never produces. The remaining put, on the error path in rocket_job_run(),
is a plain pm_runtime_put() and is left alone here: it unwinds a
get_sync() that never reached the hardware, and changing it belongs in
its own patch.
Igor Paunovic ran the differential on RK3588: 45 induced resets across
three cores, with and without the two preceding patches, and the domain
dropped every single time with no MMU message on either kernel. So this
is not rocket-wide. His conditions cross a healthy block with a lowered
timeout rather than a hung one, which he was careful to say his protocol
cannot settle, but it is what scopes the change to RK3576.
Link: https://lore.kernel.org/all/20260819073530.6087-1-royalnet026@gmail.com/
Fixes: 0810d5ad88a1 ("accel/rocket: Add job submission IOCTL")
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Tested-by: Igor Paunovic <royalnet026@gmail.com> # RK3588, three cores, induced reset, JOB_TIMEOUT_MS=2
---
drivers/accel/rocket/rocket_job.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index 3c0ed4605..a89ab49e1 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -406,12 +406,12 @@ rocket_reset(struct rocket_core *core, struct drm_sched_job *bad)
/*
* No handler is running now, but we might still have stuck jobs. Let's
- * make sure the PM counters stay balanced by manually calling
- * pm_runtime_put_noidle().
+ * make sure the PM counters stay balanced by putting the reference the
+ * job took, and request idle while doing it so the core can suspend.
*/
scoped_guard(mutex, &core->job_lock) {
if (core->in_flight_job)
- pm_runtime_put_noidle(core->dev);
+ pm_runtime_put_autosuspend(core->dev);
iommu_detach_group(NULL, core->iommu_group);
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 05/14] accel/rocket: factor the completion tail out of the IRQ handler
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (3 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 04/14] accel/rocket: let the core suspend after a reset Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 8:52 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 06/14] dt-bindings: npu: rockchip: add rockchip,rk3576-rknn-core Jiaxing Hu
` (8 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu
rocket_job_handle_irq() stops the block and then either starts the job's
next task or retires the job. The second half is a step of its own and
reads better with a name, now that taking the register writes under
job_lock has moved it a level deeper inside the scoped guard.
Move it to rocket_job_next_locked(). The early return that used to leave
the handler now leaves the helper, which is the same thing here: the
scoped guard drops job_lock either way and nothing follows it.
Doing it as its own patch keeps the locking fix at the head of the
series minimal, so a bisect that stops before this one gets that fix and
nothing else. There is one caller, and no functional change.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Reviewed-by: Igor Paunovic <royalnet026@gmail.com>
---
drivers/accel/rocket/rocket_job.c | 31 ++++++++++++++++++++-----------
1 file changed, 20 insertions(+), 11 deletions(-)
diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index a89ab49e1..69e29f40f 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -341,6 +341,25 @@ static struct dma_fence *rocket_job_run(struct drm_sched_job *sched_job)
return ERR_PTR(ret);
}
+/* Start the job's next task, or retire it. Caller holds job_lock. */
+static void rocket_job_next_locked(struct rocket_core *core)
+{
+ lockdep_assert_held(&core->job_lock);
+
+ if (!core->in_flight_job)
+ return;
+
+ if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
+ rocket_job_hw_submit(core, core->in_flight_job);
+ return;
+ }
+
+ iommu_detach_group(NULL, iommu_group_get(core->dev));
+ dma_fence_signal(core->in_flight_job->done_fence);
+ pm_runtime_put_autosuspend(core->dev);
+ core->in_flight_job = NULL;
+}
+
static void rocket_job_handle_irq(struct rocket_core *core)
{
pm_runtime_mark_last_busy(core->dev);
@@ -354,17 +373,7 @@ static void rocket_job_handle_irq(struct rocket_core *core)
rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
- if (core->in_flight_job) {
- if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
- rocket_job_hw_submit(core, core->in_flight_job);
- return;
- }
-
- iommu_detach_group(NULL, iommu_group_get(core->dev));
- dma_fence_signal(core->in_flight_job->done_fence);
- pm_runtime_put_autosuspend(core->dev);
- core->in_flight_job = NULL;
- }
+ rocket_job_next_locked(core);
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 06/14] dt-bindings: npu: rockchip: add rockchip,rk3576-rknn-core
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (4 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 05/14] accel/rocket: factor the completion tail out of the IRQ handler Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 07/14] dt-bindings: power: rockchip: allow resets in a power domain node Jiaxing Hu
` (7 subsequent siblings)
13 siblings, 0 replies; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu, Krzysztof Kozlowski
The RK3576 NPU has two cores of the same RKNN block the RK3588 binding
already describes, but it wires them up differently: two extra CBUF
clocks, two power domains per core, and a single reset instead of two.
It also has no NPU SRAM supply.
Widen the property ranges to cover both, then pin each SoC back to its
own shape in allOf so nothing loosens for RK3588, and keep sram-supply
required for rockchip,rk3588-rknn-core only.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
.../npu/rockchip,rk3588-rknn-core.yaml | 47 +++++++++++++++++--
1 file changed, 44 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml b/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml
index caca2a490..3b611b64c 100644
--- a/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml
+++ b/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml
@@ -21,6 +21,7 @@ properties:
compatible:
enum:
+ - rockchip,rk3576-rknn-core
- rockchip,rk3588-rknn-core
reg:
@@ -33,14 +34,18 @@ properties:
- const: core # Main NPU core processing unit registers
clocks:
- maxItems: 4
+ minItems: 4
+ maxItems: 6
clock-names:
+ minItems: 4
items:
- const: aclk
- const: hclk
- const: npu
- const: pclk
+ - const: aclk_cbuf
+ - const: hclk_cbuf
interrupts:
maxItems: 1
@@ -51,12 +56,15 @@ properties:
npu-supply: true
power-domains:
- maxItems: 1
+ minItems: 1
+ maxItems: 2
resets:
+ minItems: 1
maxItems: 2
reset-names:
+ minItems: 1
items:
- const: srst_a
- const: srst_h
@@ -75,7 +83,40 @@ required:
- resets
- reset-names
- npu-supply
- - sram-supply
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: rockchip,rk3588-rknn-core
+ then:
+ properties:
+ clocks:
+ maxItems: 4
+ clock-names:
+ maxItems: 4
+ power-domains:
+ maxItems: 1
+ resets:
+ minItems: 2
+ reset-names:
+ minItems: 2
+ required:
+ - sram-supply
+ else:
+ properties:
+ clocks:
+ minItems: 6
+ clock-names:
+ minItems: 6
+ power-domains:
+ minItems: 2
+ resets:
+ maxItems: 1
+ reset-names:
+ maxItems: 1
+ sram-supply: false
additionalProperties: false
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 07/14] dt-bindings: power: rockchip: allow resets in a power domain node
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (5 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 06/14] dt-bindings: npu: rockchip: add rockchip,rk3576-rknn-core Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 08/14] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU Jiaxing Hu
` (6 subsequent siblings)
13 siblings, 0 replies; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu, Conor Dooley
Some domains do not come up in a usable state on their own and need
their resets cycled once power is on. The RK3576 NPU domains are one
case: without it the first access after power-on takes an async SError.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
.../bindings/power/rockchip,power-controller.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml b/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml
index b41db576f..83741f048 100644
--- a/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml
+++ b/Documentation/devicetree/bindings/power/rockchip,power-controller.yaml
@@ -136,6 +136,13 @@ $defs:
A number of phandles to clocks that need to be enabled
while power domain switches state.
+ resets:
+ maxItems: 1
+ description:
+ A phandle to a reset that needs to be cycled once the power domain has
+ been switched on, for domains whose logic does not come up in a usable
+ state by itself.
+
domain-supply:
description: domain regulator supply.
@@ -216,6 +223,7 @@ examples:
reg = <RK3399_PD_IEP>;
clocks = <&cru ACLK_IEP>,
<&cru HCLK_IEP>;
+ resets = <&cru SRST_A_IEP>;
pm_qos = <&qos_iep>;
#power-domain-cells = <0>;
};
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 08/14] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (6 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 07/14] dt-bindings: power: rockchip: allow resets in a power domain node Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 09/14] pmdomain/rockchip: add optional per-domain power-on settle delay Jiaxing Hu
` (5 subsequent siblings)
13 siblings, 0 replies; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu, Conor Dooley
The RK3576 NPU MMUs are rk3568-iommu compatible but take five clocks
where every other Rockchip MMU takes two, the extra three being the
compute clock and the two convolution buffer clocks.
Give them a compatible of their own and pin both sides with an allOf, so
that an rk3568-iommu cannot carry five clocks and an NPU MMU cannot
carry two. Describing the extra clocks as belonging to one SoC without
saying so in the schema, which is what a comment on a description does,
leaves both of those spellings valid.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
.../bindings/iommu/rockchip,iommu.yaml | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/Documentation/devicetree/bindings/iommu/rockchip,iommu.yaml b/Documentation/devicetree/bindings/iommu/rockchip,iommu.yaml
index 6ce41d11f..83d7e7c8e 100644
--- a/Documentation/devicetree/bindings/iommu/rockchip,iommu.yaml
+++ b/Documentation/devicetree/bindings/iommu/rockchip,iommu.yaml
@@ -26,6 +26,7 @@ properties:
- items:
- enum:
- rockchip,rk3576-iommu
+ - rockchip,rk3576-npu-iommu
- rockchip,rk3588-iommu
- const: rockchip,rk3568-iommu
@@ -42,14 +43,22 @@ properties:
minItems: 1
clocks:
+ minItems: 2
items:
- description: Core clock
- description: Interface clock
+ - description: Compute clock
+ - description: Convolution buffer core clock
+ - description: Convolution buffer interface clock
clock-names:
+ minItems: 2
items:
- const: aclk
- const: iface
+ - const: npu
+ - const: aclk_cbuf
+ - const: hclk_cbuf
"#iommu-cells":
const: 0
@@ -72,6 +81,25 @@ required:
- clock-names
- "#iommu-cells"
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: rockchip,rk3576-npu-iommu
+ then:
+ properties:
+ clocks:
+ minItems: 5
+ clock-names:
+ minItems: 5
+ else:
+ properties:
+ clocks:
+ maxItems: 2
+ clock-names:
+ maxItems: 2
+
additionalProperties: false
examples:
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 09/14] pmdomain/rockchip: add optional per-domain power-on settle delay
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (7 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 08/14] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 10/14] pmdomain/rockchip: cycle optional power-domain resets on power-on Jiaxing Hu
` (4 subsequent siblings)
13 siblings, 0 replies; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu, Abel Vesa
The RK3576 NPU domains need a short settle time after the idle request
is released before the registers behind the domain answer. Without it
the QoS writes that rockchip_pmu_restore_qos() issues land while the
domain is still coming up, and the NPU throws an async SError on the
first cold power-on.
Give rockchip_domain_info an optional delay_us and wait for it between
releasing idle and restoring QoS. Rename DOMAIN_M_O_R_G to
DOMAIN_M_O_R_G_W, since the suffixes name the fields the macro sets and
this one now also carries a wakeup delay; RK3576 is its only user, so
the old spelling is not kept around.
While the macro is being rewritten, give it the regulator argument that
DOMAIN_M_O_R and DOMAIN_M_R already take. Without .need_regulator set,
rockchip_pd_regulator_enable() returns early for every RK3576 domain, so
a domain-supply in the device tree is never looked up and never enabled.
Add a DOMAIN_RK3576_R spelling that passes true and use it for
RK3576_PD_NPU, which is the one RK3576 domain with a rail of its own;
every other domain passes false and is unchanged.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
drivers/pmdomain/rockchip/pm-domains.c | 56 ++++++++++++++++----------
1 file changed, 34 insertions(+), 22 deletions(-)
diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c
index ba66ae719..39988efd8 100644
--- a/drivers/pmdomain/rockchip/pm-domains.c
+++ b/drivers/pmdomain/rockchip/pm-domains.c
@@ -18,6 +18,7 @@
#include <linux/of_address.h>
#include <linux/of_clk.h>
#include <linux/clk.h>
+#include <linux/delay.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/mfd/syscon.h>
@@ -59,6 +60,7 @@ struct rockchip_domain_info {
u32 pwr_offset;
u32 mem_offset;
u32 req_offset;
+ u32 delay_us;
};
struct rockchip_pmu_info {
@@ -185,7 +187,7 @@ struct rockchip_pmu {
.need_regulator = regulator, \
}
-#define DOMAIN_M_O_R_G(_name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, ack, g_mask, wakeup) \
+#define DOMAIN_M_O_R_G_W(_name, p_offset, pwr, status, m_offset, m_status, r_status, r_offset, req, idle, ack, g_mask, delay, wakeup, regulator) \
{ \
.name = _name, \
.pwr_offset = p_offset, \
@@ -200,8 +202,10 @@ struct rockchip_pmu {
.req_mask = (req), \
.idle_mask = (idle), \
.clk_ungate_mask = (g_mask), \
+ .delay_us = (delay), \
.ack_mask = (ack), \
.active_wakeup = wakeup, \
+ .need_regulator = regulator, \
}
#define DOMAIN_M_R(_name, pwr, status, req, idle, ack, wakeup, regulator) \
@@ -258,8 +262,11 @@ struct rockchip_pmu {
#define DOMAIN_RK3568(name, pwr, req, wakeup, regulator) \
DOMAIN_M_R(name, pwr, pwr, req, req, req, wakeup, regulator)
-#define DOMAIN_RK3576(name, p_offset, pwr, status, r_status, r_offset, req, idle, g_mask, wakeup) \
- DOMAIN_M_O_R_G(name, p_offset, pwr, status, 0, r_status, r_status, r_offset, req, idle, idle, g_mask, wakeup)
+#define DOMAIN_RK3576(name, p_offset, pwr, status, r_status, r_offset, req, idle, g_mask, delay, wakeup) \
+ DOMAIN_M_O_R_G_W(name, p_offset, pwr, status, 0, r_status, r_status, r_offset, req, idle, idle, g_mask, delay, wakeup, false)
+
+#define DOMAIN_RK3576_R(name, p_offset, pwr, status, r_status, r_offset, req, idle, g_mask, delay, wakeup) \
+ DOMAIN_M_O_R_G_W(name, p_offset, pwr, status, 0, r_status, r_status, r_offset, req, idle, idle, g_mask, delay, wakeup, true)
/*
* Dynamic Memory Controller may need to coordinate with us -- see
@@ -681,6 +688,10 @@ static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
if (ret < 0)
goto out;
+ /* Some domains need to settle before the QoS registers answer. */
+ if (pd->info->delay_us)
+ udelay(pd->info->delay_us);
+
rockchip_pmu_restore_qos(pd);
}
@@ -1300,25 +1311,26 @@ static const struct rockchip_domain_info rk3568_pm_domains[] = {
};
static const struct rockchip_domain_info rk3576_pm_domains[] = {
- [RK3576_PD_NPU] = DOMAIN_RK3576("npu", 0x0, BIT(0), BIT(0), 0, 0x0, 0, 0, 0, false),
- [RK3576_PD_NVM] = DOMAIN_RK3576("nvm", 0x0, BIT(6), 0, BIT(6), 0x4, BIT(2), BIT(18), BIT(2), false),
- [RK3576_PD_SDGMAC] = DOMAIN_RK3576("sdgmac", 0x0, BIT(7), 0, BIT(7), 0x4, BIT(1), BIT(17), 0x6, false),
- [RK3576_PD_AUDIO] = DOMAIN_RK3576("audio", 0x0, BIT(8), 0, BIT(8), 0x4, BIT(0), BIT(16), BIT(0), false),
- [RK3576_PD_PHP] = DOMAIN_RK3576("php", 0x0, BIT(9), 0, BIT(9), 0x0, BIT(15), BIT(15), BIT(15), false),
- [RK3576_PD_SUBPHP] = DOMAIN_RK3576("subphp", 0x0, BIT(10), 0, BIT(10), 0x0, 0, 0, 0, false),
- [RK3576_PD_VOP] = DOMAIN_RK3576("vop", 0x0, BIT(11), 0, BIT(11), 0x0, 0x6000, 0x6000, 0x6000, false),
- [RK3576_PD_VO1] = DOMAIN_RK3576("vo1", 0x0, BIT(14), 0, BIT(14), 0x0, BIT(12), BIT(12), 0x7000, false),
- [RK3576_PD_VO0] = DOMAIN_RK3576("vo0", 0x0, BIT(15), 0, BIT(15), 0x0, BIT(11), BIT(11), 0x6800, false),
- [RK3576_PD_USB] = DOMAIN_RK3576("usb", 0x4, BIT(0), 0, BIT(16), 0x0, BIT(10), BIT(10), 0x6400, true),
- [RK3576_PD_VI] = DOMAIN_RK3576("vi", 0x4, BIT(1), 0, BIT(17), 0x0, BIT(9), BIT(9), BIT(9), false),
- [RK3576_PD_VEPU0] = DOMAIN_RK3576("vepu0", 0x4, BIT(2), 0, BIT(18), 0x0, BIT(7), BIT(7), 0x280, false),
- [RK3576_PD_VEPU1] = DOMAIN_RK3576("vepu1", 0x4, BIT(3), 0, BIT(19), 0x0, BIT(8), BIT(8), BIT(8), false),
- [RK3576_PD_VDEC] = DOMAIN_RK3576("vdec", 0x4, BIT(4), 0, BIT(20), 0x0, BIT(6), BIT(6), BIT(6), false),
- [RK3576_PD_VPU] = DOMAIN_RK3576("vpu", 0x4, BIT(5), 0, BIT(21), 0x0, BIT(5), BIT(5), BIT(5), false),
- [RK3576_PD_NPUTOP] = DOMAIN_RK3576("nputop", 0x4, BIT(6), 0, BIT(22), 0x0, 0x18, 0x18, 0x18, false),
- [RK3576_PD_NPU0] = DOMAIN_RK3576("npu0", 0x4, BIT(7), 0, BIT(23), 0x0, BIT(1), BIT(1), 0x1a, false),
- [RK3576_PD_NPU1] = DOMAIN_RK3576("npu1", 0x4, BIT(8), 0, BIT(24), 0x0, BIT(2), BIT(2), 0x1c, false),
- [RK3576_PD_GPU] = DOMAIN_RK3576("gpu", 0x4, BIT(9), 0, BIT(25), 0x0, BIT(0), BIT(0), BIT(0), false),
+ /* name p_offset pwr status r_status r_offset req idle g_mask delay wakeup */
+ [RK3576_PD_NPU] = DOMAIN_RK3576_R("npu", 0x0, BIT(0), BIT(0), 0, 0x0, 0, 0, 0, 0, false),
+ [RK3576_PD_NVM] = DOMAIN_RK3576("nvm", 0x0, BIT(6), 0, BIT(6), 0x4, BIT(2), BIT(18), BIT(2), 0, false),
+ [RK3576_PD_SDGMAC] = DOMAIN_RK3576("sdgmac", 0x0, BIT(7), 0, BIT(7), 0x4, BIT(1), BIT(17), 0x6, 0, false),
+ [RK3576_PD_AUDIO] = DOMAIN_RK3576("audio", 0x0, BIT(8), 0, BIT(8), 0x4, BIT(0), BIT(16), BIT(0), 0, false),
+ [RK3576_PD_PHP] = DOMAIN_RK3576("php", 0x0, BIT(9), 0, BIT(9), 0x0, BIT(15), BIT(15), BIT(15), 0, false),
+ [RK3576_PD_SUBPHP] = DOMAIN_RK3576("subphp", 0x0, BIT(10), 0, BIT(10), 0x0, 0, 0, 0, 0, false),
+ [RK3576_PD_VOP] = DOMAIN_RK3576("vop", 0x0, BIT(11), 0, BIT(11), 0x0, 0x6000, 0x6000, 0x6000, 0, false),
+ [RK3576_PD_VO1] = DOMAIN_RK3576("vo1", 0x0, BIT(14), 0, BIT(14), 0x0, BIT(12), BIT(12), 0x7000, 0, false),
+ [RK3576_PD_VO0] = DOMAIN_RK3576("vo0", 0x0, BIT(15), 0, BIT(15), 0x0, BIT(11), BIT(11), 0x6800, 0, false),
+ [RK3576_PD_USB] = DOMAIN_RK3576("usb", 0x4, BIT(0), 0, BIT(16), 0x0, BIT(10), BIT(10), 0x6400, 0, true),
+ [RK3576_PD_VI] = DOMAIN_RK3576("vi", 0x4, BIT(1), 0, BIT(17), 0x0, BIT(9), BIT(9), BIT(9), 0, false),
+ [RK3576_PD_VEPU0] = DOMAIN_RK3576("vepu0", 0x4, BIT(2), 0, BIT(18), 0x0, BIT(7), BIT(7), 0x280, 0, false),
+ [RK3576_PD_VEPU1] = DOMAIN_RK3576("vepu1", 0x4, BIT(3), 0, BIT(19), 0x0, BIT(8), BIT(8), BIT(8), 0, false),
+ [RK3576_PD_VDEC] = DOMAIN_RK3576("vdec", 0x4, BIT(4), 0, BIT(20), 0x0, BIT(6), BIT(6), BIT(6), 0, false),
+ [RK3576_PD_VPU] = DOMAIN_RK3576("vpu", 0x4, BIT(5), 0, BIT(21), 0x0, BIT(5), BIT(5), BIT(5), 0, false),
+ [RK3576_PD_NPUTOP] = DOMAIN_RK3576("nputop", 0x4, BIT(6), 0, BIT(22), 0x0, 0x18, 0x18, 0x18, 15, false),
+ [RK3576_PD_NPU0] = DOMAIN_RK3576("npu0", 0x4, BIT(7), 0, BIT(23), 0x0, BIT(1), BIT(1), 0x1a, 15, false),
+ [RK3576_PD_NPU1] = DOMAIN_RK3576("npu1", 0x4, BIT(8), 0, BIT(24), 0x0, BIT(2), BIT(2), 0x1c, 15, false),
+ [RK3576_PD_GPU] = DOMAIN_RK3576("gpu", 0x4, BIT(9), 0, BIT(25), 0x0, BIT(0), BIT(0), BIT(0), 0, false),
};
static const struct rockchip_domain_info rk3588_pm_domains[] = {
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 10/14] pmdomain/rockchip: cycle optional power-domain resets on power-on
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (8 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 09/14] pmdomain/rockchip: add optional per-domain power-on settle delay Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 9:05 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 11/14] accel/rocket: select the per-core clock and reset counts from match data Jiaxing Hu
` (3 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu, Abel Vesa
Some Rockchip domains come out of power-on with their bus interface in
an undefined state. On the RK3576 NPU this shows up as a hang on the
first register access after the domain is switched on, and pulsing the
domain's resets at this point clears it.
Take the domain node's resets if it has any, and pulse them between
releasing idle and restoring QoS. The resets are optional, so domains
that do not list any are unaffected.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
---
drivers/pmdomain/rockchip/pm-domains.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c
index 39988efd8..8f2fd8a83 100644
--- a/drivers/pmdomain/rockchip/pm-domains.c
+++ b/drivers/pmdomain/rockchip/pm-domains.c
@@ -19,6 +19,7 @@
#include <linux/of_clk.h>
#include <linux/clk.h>
#include <linux/delay.h>
+#include <linux/reset.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/mfd/syscon.h>
@@ -103,6 +104,7 @@ struct rockchip_pm_domain {
struct clk_bulk_data *clks;
struct device_node *node;
struct regulator *supply;
+ struct reset_control *resets;
};
struct rockchip_pmu {
@@ -692,6 +694,13 @@ static int rockchip_pd_power(struct rockchip_pm_domain *pd, bool power_on)
if (pd->info->delay_us)
udelay(pd->info->delay_us);
+ /* Optional: some domains need their resets cycled after power-on. */
+ if (pd->resets) {
+ reset_control_assert(pd->resets);
+ usleep_range(10, 20);
+ reset_control_deassert(pd->resets);
+ }
+
rockchip_pmu_restore_qos(pd);
}
@@ -861,6 +870,14 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
if (error)
goto err_put_clocks;
+ pd->resets = of_reset_control_array_get_optional_exclusive(node);
+ if (IS_ERR(pd->resets)) {
+ error = dev_err_probe(pmu->dev, PTR_ERR(pd->resets),
+ "%pOFn: failed to get resets\n", node);
+ pd->resets = NULL;
+ goto err_unprepare_clocks;
+ }
+
pd->num_qos = of_count_phandle_with_args(node, "pm_qos",
NULL);
@@ -931,6 +948,7 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
clk_bulk_unprepare(pd->num_clks, pd->clks);
err_put_clocks:
clk_bulk_put(pd->num_clks, pd->clks);
+ reset_control_put(pd->resets);
return error;
}
@@ -949,6 +967,7 @@ static void rockchip_pm_remove_one_domain(struct rockchip_pm_domain *pd)
clk_bulk_unprepare(pd->num_clks, pd->clks);
clk_bulk_put(pd->num_clks, pd->clks);
+ reset_control_put(pd->resets);
/* protect the zeroing of pm->num_clks */
mutex_lock(&pd->pmu->mutex);
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 11/14] accel/rocket: select the per-core clock and reset counts from match data
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (9 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 10/14] pmdomain/rockchip: cycle optional power-domain resets on power-on Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 9:04 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 12/14] accel/rocket: add RK3576 NPU (RKNN) support Jiaxing Hu
` (2 subsequent siblings)
13 siblings, 1 reply; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu
The RK3576 carries the same RKNN block with a different set of clocks and
resets, so the counts cannot stay compile-time constants. Add a soc_data
struct to the of_device_id match data and take the bulk counts from it.
RK3588 keeps four clocks and two resets, so nothing changes for it, and
the arrays keep their present sizes: the SoC that needs a longer one
grows it in the patch that adds the names.
rocket_core_reset() is switched over as well. It is the same array, and
leaving it on ARRAY_SIZE() would walk entries that were never acquired
once a SoC asks for fewer.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
drivers/accel/rocket/rocket_core.c | 8 ++++----
drivers/accel/rocket/rocket_core.h | 7 +++++++
drivers/accel/rocket/rocket_drv.c | 12 +++++++++---
3 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/drivers/accel/rocket/rocket_core.c b/drivers/accel/rocket/rocket_core.c
index 5dd260bac..b202d1581 100644
--- a/drivers/accel/rocket/rocket_core.c
+++ b/drivers/accel/rocket/rocket_core.c
@@ -23,7 +23,7 @@ int rocket_core_init(struct rocket_core *core)
core->resets[0].id = "srst_a";
core->resets[1].id = "srst_h";
- err = devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(core->resets),
+ err = devm_reset_control_bulk_get_exclusive(&pdev->dev, core->soc->num_resets,
core->resets);
if (err)
return dev_err_probe(dev, err, "failed to get resets for core %d\n", core->index);
@@ -32,7 +32,7 @@ int rocket_core_init(struct rocket_core *core)
core->clks[1].id = "hclk";
core->clks[2].id = "npu";
core->clks[3].id = "pclk";
- err = devm_clk_bulk_get(dev, ARRAY_SIZE(core->clks), core->clks);
+ err = devm_clk_bulk_get(dev, core->soc->num_clks, core->clks);
if (err)
return dev_err_probe(dev, err, "failed to get clocks for core %d\n", core->index);
@@ -109,9 +109,9 @@ void rocket_core_fini(struct rocket_core *core)
void rocket_core_reset(struct rocket_core *core)
{
- reset_control_bulk_assert(ARRAY_SIZE(core->resets), core->resets);
+ reset_control_bulk_assert(core->soc->num_resets, core->resets);
udelay(10);
- reset_control_bulk_deassert(ARRAY_SIZE(core->resets), core->resets);
+ reset_control_bulk_deassert(core->soc->num_resets, core->resets);
}
diff --git a/drivers/accel/rocket/rocket_core.h b/drivers/accel/rocket/rocket_core.h
index f6d738285..ba74c5339 100644
--- a/drivers/accel/rocket/rocket_core.h
+++ b/drivers/accel/rocket/rocket_core.h
@@ -27,9 +27,16 @@
#define rocket_core_writel(core, reg, value) \
writel(value, (core)->core_iomem + (REG_CORE_##reg) - REG_CORE_S_STATUS)
+/* Per-SoC differences, selected by the of_device_id match data. */
+struct rocket_soc_data {
+ unsigned int num_clks; /* clk_bulk count */
+ unsigned int num_resets; /* reset_bulk count */
+};
+
struct rocket_core {
struct device *dev;
struct rocket_device *rdev;
+ const struct rocket_soc_data *soc;
unsigned int index;
int irq;
diff --git a/drivers/accel/rocket/rocket_drv.c b/drivers/accel/rocket/rocket_drv.c
index 8bbbce594..6e7dc91c5 100644
--- a/drivers/accel/rocket/rocket_drv.c
+++ b/drivers/accel/rocket/rocket_drv.c
@@ -176,6 +176,7 @@ static int rocket_probe(struct platform_device *pdev)
rdev->cores[core].rdev = rdev;
rdev->cores[core].dev = &pdev->dev;
+ rdev->cores[core].soc = of_device_get_match_data(&pdev->dev);
rdev->cores[core].index = core;
rdev->num_cores++;
@@ -213,8 +214,13 @@ static void rocket_remove(struct platform_device *pdev)
}
}
+static const struct rocket_soc_data rk3588_soc_data = {
+ .num_clks = 4,
+ .num_resets = 2,
+};
+
static const struct of_device_id dt_match[] = {
- { .compatible = "rockchip,rk3588-rknn-core" },
+ { .compatible = "rockchip,rk3588-rknn-core", .data = &rk3588_soc_data },
{}
};
MODULE_DEVICE_TABLE(of, dt_match);
@@ -240,7 +246,7 @@ static int rocket_device_runtime_resume(struct device *dev)
if (core < 0)
return -ENODEV;
- err = clk_bulk_prepare_enable(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
+ err = clk_bulk_prepare_enable(rdev->cores[core].soc->num_clks, rdev->cores[core].clks);
if (err) {
dev_err(dev, "failed to enable (%d) clocks for core %d\n", err, core);
return err;
@@ -260,7 +266,7 @@ static int rocket_device_runtime_suspend(struct device *dev)
if (!rocket_job_is_idle(&rdev->cores[core]))
return -EBUSY;
- clk_bulk_disable_unprepare(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
+ clk_bulk_disable_unprepare(rdev->cores[core].soc->num_clks, rdev->cores[core].clks);
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 12/14] accel/rocket: add RK3576 NPU (RKNN) support
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (10 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 11/14] accel/rocket: select the per-core clock and reset counts from match data Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 9:11 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 13/14] arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 14/14] arm64: dts: rockchip: rk3576-rock-4d: enable NPU Jiaxing Hu
13 siblings, 1 reply; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu
The RK3576 has two cores of the same RKNN block and a few platform
differences:
- the CBUF (convolution buffer) has its own clock domain, so the core
needs six clocks rather than four;
- there is no per-core hclk reset. The CRU has SRST_A_RKNN0 and
SRST_A_RKNN1 but no SRST_H_RKNN0 or SRST_H_RKNN1, so a core takes one
reset where RK3588 takes two;
- the NPU spans two power domains, and a device with more than one is
skipped by the driver-core single-domain auto-attach, so the list has
to be attached explicitly;
- PC_TASK_CON packs the task number with sixteen bits rather than
twelve, moving the three controls above it up by four.
That last one is the reason this series has been reporting, since v3,
that the block accepts exactly one task per reset. rocket_registers.h is
generated from the RK3588 description, so writing it unchanged to an
RK3576 asks for task_number 0x7001, which is 28673 tasks, and puts
TASK_COUNT_CLEAR on a bit that does nothing. The counter is then only
ever cleared by a reset.
The layout was confirmed by Chaoyi Chen of Rockchip, including a fourth
control at BIT(18), task_last_layer_clear, which belongs on every submit
alongside the count clear:
https://lore.kernel.org/all/4f300b78-d96d-4d98-8819-dc292b0c9b97@rock-chips.com/
With that written correctly a job of several tasks runs to completion,
the completion interrupt arrives, and /proc/interrupts counts up. A
convolution submitted three times with three different inputs is byte
exact against the CPU reference each time, with no reset in between and
with nothing retiring the job but the interrupt.
Counting the cores now walks the driver's own match table instead of a
second, hand-kept list of compatibles. The array sized from that count
is indexed by every core that goes on to probe, so the two lists cannot
be allowed to disagree.
All of it hangs off the soc_data added earlier, so the RK3588 path keeps
its existing counts and behaviour.
The match table moves to rocket_drv.h so rocket_device.c can walk it with
for_each_matching_node() rather than repeating a for_each_compatible_node()
loop per SoC, which also keeps num_cores in step with the table that sizes
the array it counts into. The declaration needs struct of_device_id, taken
from <linux/device-id/of.h> rather than <linux/mod_devicetable.h>, which
carries every subsystem's tables with it.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
drivers/accel/rocket/rocket_core.c | 20 +++++++++++++++
drivers/accel/rocket/rocket_core.h | 8 +++---
drivers/accel/rocket/rocket_device.c | 7 ++++-
drivers/accel/rocket/rocket_drv.c | 16 +++++++++---
drivers/accel/rocket/rocket_drv.h | 2 ++
drivers/accel/rocket/rocket_job.c | 38 +++++++++++++++++++++++++---
6 files changed, 80 insertions(+), 11 deletions(-)
diff --git a/drivers/accel/rocket/rocket_core.c b/drivers/accel/rocket/rocket_core.c
index b202d1581..91f690176 100644
--- a/drivers/accel/rocket/rocket_core.c
+++ b/drivers/accel/rocket/rocket_core.c
@@ -8,6 +8,7 @@
#include <linux/err.h>
#include <linux/iommu.h>
#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
@@ -21,6 +22,7 @@ int rocket_core_init(struct rocket_core *core)
u32 version;
int err = 0;
+ /* RK3576 has no per-core hclk reset, so it takes srst_a alone. */
core->resets[0].id = "srst_a";
core->resets[1].id = "srst_h";
err = devm_reset_control_bulk_get_exclusive(&pdev->dev, core->soc->num_resets,
@@ -32,6 +34,9 @@ int rocket_core_init(struct rocket_core *core)
core->clks[1].id = "hclk";
core->clks[2].id = "npu";
core->clks[3].id = "pclk";
+ /* RK3576 clocks the CBUF separately; the compute path stalls without these. */
+ core->clks[4].id = "aclk_cbuf";
+ core->clks[5].id = "hclk_cbuf";
err = devm_clk_bulk_get(dev, core->soc->num_clks, core->clks);
if (err)
return dev_err_probe(dev, err, "failed to get clocks for core %d\n", core->index);
@@ -60,6 +65,21 @@ int rocket_core_init(struct rocket_core *core)
if (err)
return err;
+ /*
+ * RK3576 spans two power domains, and a multi-domain device is skipped
+ * by the driver-core single-domain auto-attach, so attach the list here.
+ * This goes before the first thing that would have to be unwound, so a
+ * failure can simply return.
+ */
+ if (core->soc->multi_power_domain) {
+ struct dev_pm_domain_list *pd_list;
+
+ err = devm_pm_domain_attach_list(dev, NULL, &pd_list);
+ if (err < 0)
+ return dev_err_probe(dev, err,
+ "failed to attach NPU power domains\n");
+ }
+
core->iommu_group = iommu_group_get(dev);
err = rocket_job_init(core);
diff --git a/drivers/accel/rocket/rocket_core.h b/drivers/accel/rocket/rocket_core.h
index ba74c5339..8c8d1f453 100644
--- a/drivers/accel/rocket/rocket_core.h
+++ b/drivers/accel/rocket/rocket_core.h
@@ -29,8 +29,10 @@
/* Per-SoC differences, selected by the of_device_id match data. */
struct rocket_soc_data {
- unsigned int num_clks; /* clk_bulk count */
- unsigned int num_resets; /* reset_bulk count */
+ unsigned int num_clks; /* clk_bulk count: 4 base, 6 with CBUF */
+ unsigned int num_resets; /* reset_bulk count: 2 base, 1 on RK3576 */
+ bool multi_power_domain; /* device spans more than one PM domain */
+ bool task_con_16bit; /* PC_TASK_CON uses the 16-bit task number */
};
struct rocket_core {
@@ -43,7 +45,7 @@ struct rocket_core {
void __iomem *pc_iomem;
void __iomem *cna_iomem;
void __iomem *core_iomem;
- struct clk_bulk_data clks[4];
+ struct clk_bulk_data clks[6];
struct reset_control_bulk_data resets[2];
struct iommu_group *iommu_group;
diff --git a/drivers/accel/rocket/rocket_device.c b/drivers/accel/rocket/rocket_device.c
index 46e6ee1e7..923add5bd 100644
--- a/drivers/accel/rocket/rocket_device.c
+++ b/drivers/accel/rocket/rocket_device.c
@@ -9,6 +9,7 @@
#include <linux/of.h>
#include "rocket_device.h"
+#include "rocket_drv.h"
struct rocket_device *rocket_device_init(struct platform_device *pdev,
const struct drm_driver *rocket_drm_driver)
@@ -27,7 +28,11 @@ struct rocket_device *rocket_device_init(struct platform_device *pdev,
ddev = &rdev->ddev;
dev_set_drvdata(dev, rdev);
- for_each_compatible_node(core_node, NULL, "rockchip,rk3588-rknn-core")
+ /*
+ * Count over the same match table the platform driver binds with, so
+ * that a core added there is counted here without a second edit.
+ */
+ for_each_matching_node(core_node, rocket_dt_match)
if (of_device_is_available(core_node))
num_cores++;
diff --git a/drivers/accel/rocket/rocket_drv.c b/drivers/accel/rocket/rocket_drv.c
index 6e7dc91c5..e46962949 100644
--- a/drivers/accel/rocket/rocket_drv.c
+++ b/drivers/accel/rocket/rocket_drv.c
@@ -217,13 +217,23 @@ static void rocket_remove(struct platform_device *pdev)
static const struct rocket_soc_data rk3588_soc_data = {
.num_clks = 4,
.num_resets = 2,
+ .multi_power_domain = false,
+ .task_con_16bit = false,
};
-static const struct of_device_id dt_match[] = {
+static const struct rocket_soc_data rk3576_soc_data = {
+ .num_clks = 6,
+ .num_resets = 1,
+ .multi_power_domain = true,
+ .task_con_16bit = true,
+};
+
+const struct of_device_id rocket_dt_match[] = {
{ .compatible = "rockchip,rk3588-rknn-core", .data = &rk3588_soc_data },
+ { .compatible = "rockchip,rk3576-rknn-core", .data = &rk3576_soc_data },
{}
};
-MODULE_DEVICE_TABLE(of, dt_match);
+MODULE_DEVICE_TABLE(of, rocket_dt_match);
static int find_core_for_dev(struct device *dev)
{
@@ -282,7 +292,7 @@ static struct platform_driver rocket_driver = {
.driver = {
.name = "rocket",
.pm = pm_ptr(&rocket_pm_ops),
- .of_match_table = dt_match,
+ .of_match_table = rocket_dt_match,
},
};
diff --git a/drivers/accel/rocket/rocket_drv.h b/drivers/accel/rocket/rocket_drv.h
index 2c673bb99..0cd692a66 100644
--- a/drivers/accel/rocket/rocket_drv.h
+++ b/drivers/accel/rocket/rocket_drv.h
@@ -6,10 +6,12 @@
#include <drm/drm_mm.h>
#include <drm/gpu_scheduler.h>
+#include <linux/device-id/of.h>
#include "rocket_device.h"
extern const struct dev_pm_ops rocket_pm_ops;
+extern const struct of_device_id rocket_dt_match[];
struct rocket_iommu_domain {
struct iommu_domain *domain;
diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
index 69e29f40f..2a272c2ef 100644
--- a/drivers/accel/rocket/rocket_job.c
+++ b/drivers/accel/rocket/rocket_job.c
@@ -21,6 +21,29 @@
#define JOB_TIMEOUT_MS 500
+/*
+ * PC_TASK_CON packs the task number with three controls, and the field widths
+ * are not the same on every SoC. rocket_registers.h is generated from the
+ * RK3588 description, where the task number is twelve bits:
+ *
+ * RK3588 BIT[11:0] task_number, BIT[12] pp_en, BIT[13] count_clear
+ * RK3576 BIT[15:0] task_number, BIT[16] pp_en, BIT[17] count_clear,
+ * BIT[18] last_layer_clear
+ *
+ * The RK3576 layout was confirmed by Chaoyi Chen of Rockchip:
+ * https://lore.kernel.org/all/4f300b78-d96d-4d98-8819-dc292b0c9b97@rock-chips.com/
+ *
+ * Writing the RK3588 layout to an RK3576 therefore asks for task_number
+ * 0x7001, that is 28673 tasks, and lands the count clear on a bit that does
+ * nothing. The task counter is then only ever cleared by a reset, which is
+ * exactly the "one task per reset" behaviour this series has been reporting
+ * since v3.
+ */
+#define RK3576_PC_TASK_CON_TASK_NUMBER(n) ((n) & 0xffff)
+#define RK3576_PC_TASK_CON_PP_EN BIT(16)
+#define RK3576_PC_TASK_CON_COUNT_CLEAR BIT(17)
+#define RK3576_PC_TASK_CON_LAST_LAYER_CLEAR BIT(18)
+
static struct rocket_job *
to_rocket_job(struct drm_sched_job *sched_job)
{
@@ -142,10 +165,17 @@ static void rocket_job_hw_submit(struct rocket_core *core, struct rocket_job *jo
rocket_pc_writel(core, INTERRUPT_MASK, PC_INTERRUPT_MASK_DPU_0 | PC_INTERRUPT_MASK_DPU_1);
rocket_pc_writel(core, INTERRUPT_CLEAR, PC_INTERRUPT_CLEAR_DPU_0 | PC_INTERRUPT_CLEAR_DPU_1);
- rocket_pc_writel(core, TASK_CON, PC_TASK_CON_RESERVED_0(1) |
- PC_TASK_CON_TASK_COUNT_CLEAR(1) |
- PC_TASK_CON_TASK_NUMBER(1) |
- PC_TASK_CON_TASK_PP_EN(1));
+ if (core->soc->task_con_16bit)
+ rocket_pc_writel(core, TASK_CON,
+ RK3576_PC_TASK_CON_LAST_LAYER_CLEAR |
+ RK3576_PC_TASK_CON_COUNT_CLEAR |
+ RK3576_PC_TASK_CON_PP_EN |
+ RK3576_PC_TASK_CON_TASK_NUMBER(1));
+ else
+ rocket_pc_writel(core, TASK_CON, PC_TASK_CON_RESERVED_0(1) |
+ PC_TASK_CON_TASK_COUNT_CLEAR(1) |
+ PC_TASK_CON_TASK_NUMBER(1) |
+ PC_TASK_CON_TASK_PP_EN(1));
rocket_pc_writel(core, TASK_DMA_BASE_ADDR, PC_TASK_DMA_BASE_ADDR_DMA_BASE_ADDR(0x0));
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 13/14] arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (11 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 12/14] accel/rocket: add RK3576 NPU (RKNN) support Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
2026-08-31 9:16 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 14/14] arm64: dts: rockchip: rk3576-rock-4d: enable NPU Jiaxing Hu
13 siblings, 1 reply; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu
Add the two RKNN cores and their IOMMUs. Both cores are disabled by
default; boards enable what they wire up.
PD_NPU0 and PD_NPU1 are siblings under PD_NPUTOP and hold one core each,
but the convolution buffer and the DSU sit above them: ACLK_RKNN_CBUF,
HCLK_RKNN_CBUF and CLK_RKNN_DSU0 belong to the block rather than to
either core, and PD_NPUTOP already lists all three. Add them to both
core domains as well, so a core domain switching state has the clocks of
the path it shares running, and give each core domain the BIU reset that
the pmdomain driver now cycles once power is on.
Each core lists both core domains, its own first, so that a core in use
has the whole block powered. Whether a single core can reach the shared
path with the sibling domain off is not something this series
establishes; listing both is the description that has been tested here.
The IOMMU in front of each core lists that core's domain only.
Label the outer PD_NPU node so a board can attach the NPU rail to the
domain that gates the block.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
arch/arm64/boot/dts/rockchip/rk3576.dtsi | 82 +++++++++++++++++++++++-
1 file changed, 79 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
index b0c0d3c8b..2d0133cdf 100644
--- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
@@ -1042,7 +1042,7 @@ power: power-controller {
#address-cells = <1>;
#size-cells = <0>;
- power-domain@RK3576_PD_NPU {
+ pd_npu: power-domain@RK3576_PD_NPU {
reg = <RK3576_PD_NPU>;
#power-domain-cells = <1>;
#address-cells = <1>;
@@ -1070,14 +1070,22 @@ power-domain@RK3576_PD_NPUTOP {
power-domain@RK3576_PD_NPU0 {
reg = <RK3576_PD_NPU0>;
clocks = <&cru HCLK_RKNN_ROOT>,
- <&cru ACLK_RKNN0>;
+ <&cru ACLK_RKNN0>,
+ <&cru CLK_RKNN_DSU0>,
+ <&cru ACLK_RKNN_CBUF>,
+ <&cru HCLK_RKNN_CBUF>;
+ resets = <&cru SRST_A_RKNN0_BIU>;
pm_qos = <&qos_npu_m0>;
#power-domain-cells = <0>;
};
power-domain@RK3576_PD_NPU1 {
reg = <RK3576_PD_NPU1>;
clocks = <&cru HCLK_RKNN_ROOT>,
- <&cru ACLK_RKNN1>;
+ <&cru ACLK_RKNN1>,
+ <&cru CLK_RKNN_DSU0>,
+ <&cru ACLK_RKNN_CBUF>,
+ <&cru HCLK_RKNN_CBUF>;
+ resets = <&cru SRST_A_RKNN1_BIU>;
pm_qos = <&qos_npu_m1>;
#power-domain-cells = <0>;
};
@@ -1261,6 +1269,74 @@ power-domain@RK3576_PD_VO1 {
};
};
+ rknn_core_0: npu@27700000 {
+ compatible = "rockchip,rk3576-rknn-core";
+ reg = <0x0 0x27700000 0x0 0x1000>,
+ <0x0 0x27701000 0x0 0x1000>,
+ <0x0 0x27703000 0x0 0x1000>;
+ reg-names = "pc", "cna", "core";
+ interrupts = <GIC_SPI 247 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_RKNN0>, <&cru HCLK_RKNN_ROOT>,
+ <&cru CLK_RKNN_DSU0>, <&cru PCLK_NPUTOP_ROOT>,
+ <&cru ACLK_RKNN_CBUF>, <&cru HCLK_RKNN_CBUF>;
+ clock-names = "aclk", "hclk", "npu", "pclk",
+ "aclk_cbuf", "hclk_cbuf";
+ resets = <&cru SRST_A_RKNN0>;
+ reset-names = "srst_a";
+ power-domains = <&power RK3576_PD_NPU0>, <&power RK3576_PD_NPU1>;
+ iommus = <&rknn_mmu_0>;
+ status = "disabled";
+ };
+
+ rknn_mmu_0: iommu@27702000 {
+ compatible = "rockchip,rk3576-npu-iommu", "rockchip,rk3568-iommu";
+ reg = <0x0 0x27702000 0x0 0x100>,
+ <0x0 0x27702100 0x0 0x100>;
+ interrupts = <GIC_SPI 247 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_RKNN0>, <&cru HCLK_RKNN_ROOT>,
+ <&cru CLK_RKNN_DSU0>, <&cru ACLK_RKNN_CBUF>,
+ <&cru HCLK_RKNN_CBUF>;
+ clock-names = "aclk", "iface", "npu",
+ "aclk_cbuf", "hclk_cbuf";
+ #iommu-cells = <0>;
+ power-domains = <&power RK3576_PD_NPU0>;
+ status = "disabled";
+ };
+
+ rknn_core_1: npu@27708000 {
+ compatible = "rockchip,rk3576-rknn-core";
+ reg = <0x0 0x27708000 0x0 0x1000>,
+ <0x0 0x27709000 0x0 0x1000>,
+ <0x0 0x2770b000 0x0 0x1000>;
+ reg-names = "pc", "cna", "core";
+ interrupts = <GIC_SPI 248 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_RKNN1>, <&cru HCLK_RKNN_ROOT>,
+ <&cru CLK_RKNN_DSU0>, <&cru PCLK_NPUTOP_ROOT>,
+ <&cru ACLK_RKNN_CBUF>, <&cru HCLK_RKNN_CBUF>;
+ clock-names = "aclk", "hclk", "npu", "pclk",
+ "aclk_cbuf", "hclk_cbuf";
+ resets = <&cru SRST_A_RKNN1>;
+ reset-names = "srst_a";
+ power-domains = <&power RK3576_PD_NPU1>, <&power RK3576_PD_NPU0>;
+ iommus = <&rknn_mmu_1>;
+ status = "disabled";
+ };
+
+ rknn_mmu_1: iommu@2770a000 {
+ compatible = "rockchip,rk3576-npu-iommu", "rockchip,rk3568-iommu";
+ reg = <0x0 0x2770a000 0x0 0x100>,
+ <0x0 0x2770a100 0x0 0x100>;
+ interrupts = <GIC_SPI 248 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&cru ACLK_RKNN1>, <&cru HCLK_RKNN_ROOT>,
+ <&cru CLK_RKNN_DSU0>, <&cru ACLK_RKNN_CBUF>,
+ <&cru HCLK_RKNN_CBUF>;
+ clock-names = "aclk", "iface", "npu",
+ "aclk_cbuf", "hclk_cbuf";
+ #iommu-cells = <0>;
+ power-domains = <&power RK3576_PD_NPU1>;
+ status = "disabled";
+ };
+
gpu: gpu@27800000 {
compatible = "rockchip,rk3576-mali", "arm,mali-bifrost";
reg = <0x0 0x27800000 0x0 0x20000>;
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v11 14/14] arm64: dts: rockchip: rk3576-rock-4d: enable NPU
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
` (12 preceding siblings ...)
2026-08-31 8:19 ` [PATCH v11 13/14] arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes Jiaxing Hu
@ 2026-08-31 8:19 ` Jiaxing Hu
13 siblings, 0 replies; 24+ messages in thread
From: Jiaxing Hu @ 2026-08-31 8:19 UTC (permalink / raw)
To: tomeu, heiko, robh, krzk+dt, conor+dt, joro, will, robin.murphy,
ulfh, p.zabel, ogabbay, zhangqing
Cc: royalnet026, u.kleine-koenig, chaoyi.chen, diederik, alchark,
dri-devel, linux-rockchip, iommu, linux-pm, devicetree,
linux-arm-kernel, linux-kernel, Jiaxing Hu
Enable rknn_core_0 and its IOMMU on the Radxa ROCK 4D, and hand
vdd_npu_s0 to the NPU power domain as its domain-supply, so the rail is
switched by the domain that gates the block.
Measured on a ROCK 4D with this in place. The rail's regulator debugfs
reports open_count 1, so the domain is the consumer that took it. A
sampler running beside an inference caught use_count at 1, and three
reads at rest report use_count 0 with the rail disabled, so it follows
the domain rather than staying on. Over the same run the genpd
active_time of all four NPU domains rises by roughly 60ms per inference,
and the inferences either side of that are 128 of 128 channels against
the CPU reference.
rknn_core_0 keeps npu-supply on the same rail, which the binding
requires. Only rknn_core_0 is enabled: the driver binds one core per
node and the second core is left to whoever can test it.
Signed-off-by: Jiaxing Hu <gahing@gahingwoo.com>
---
arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts
index 272af1012..79e039bee 100644
--- a/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3576-rock-4d.dts
@@ -722,6 +722,10 @@ &pcie0 {
status = "okay";
};
+&pd_npu {
+ domain-supply = <&vdd_npu_s0>;
+};
+
&pinctrl {
hdmi {
hdmi_tx_on_h: hdmi-tx-on-h {
@@ -779,6 +783,15 @@ wifi_en_h: wifi-en-h {
};
};
+&rknn_core_0 {
+ npu-supply = <&vdd_npu_s0>;
+ status = "okay";
+};
+
+&rknn_mmu_0 {
+ status = "okay";
+};
+
&sai6 {
status = "okay";
};
--
2.43.0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* Re: [PATCH v11 01/14] accel/rocket: request the core clocks by name
2026-08-31 8:19 ` [PATCH v11 01/14] accel/rocket: request the core clocks by name Jiaxing Hu
@ 2026-08-31 8:36 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-31 8:36 UTC (permalink / raw)
To: Jiaxing Hu; +Cc: dri-devel, robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
Pre-existing issues:
- [High] rocket_remove() decrements rdev->num_cores without shifting array elements, making subsequent cores inaccessible.
- [High] IOMMU group reference count leak on every job completion in rocket_job_handle_irq().
- [High] Double free / invalid cleanup when drm_sched_init() fails.
- [High] Unbounded memory leak of rdev and rdev->cores arrays on probe deferral.
- [High] Shared IRQ handler unconditionally accesses hardware registers when clocks and power domains might be disabled.
- [High] Global rdev static variable is left containing an error pointer if rocket_device_init() fails, causing a kernel panic on subsequent probes.
- [High] Registers are accessed while the device might still be held in reset, leading to bus hangs.
--
commit 31505652560107069dc219815082a4ea6f173762
Author: Igor Paunovic <royalnet026@gmail.com>
accel/rocket: request the core clocks by name
This commit fixes a clock handling bug where devm_clk_bulk_get() was
called with uninitialized clock IDs, resulting in the driver repeatedly
requesting the first clock from the device tree and ignoring the others.
The clock IDs are now explicitly set to "aclk", "hclk", "npu", and
"pclk" before requesting them, ensuring the correct clocks are prepared
and enabled.
> diff --git a/drivers/accel/rocket/rocket_core.c b/drivers/accel/rocket/rocket_core.c
> index b3b2fa9ba645a..5dd260bacbff6 100644
> --- a/drivers/accel/rocket/rocket_core.c
> +++ b/drivers/accel/rocket/rocket_core.c
> @@ -28,6 +28,10 @@ int rocket_core_init(struct rocket_core *core)
> if (err)
> return dev_err_probe(dev, err, "failed to get resets for core %d\n", core->index);
>
> + core->clks[0].id = "aclk";
> + core->clks[1].id = "hclk";
> + core->clks[2].id = "npu";
> + core->clks[3].id = "pclk";
> err = devm_clk_bulk_get(dev, ARRAY_SIZE(core->clks), core->clks);
> if (err)
> return dev_err_probe(dev, err, "failed to get clocks for core %d\n", core->index);
[Severity: High]
This is a pre-existing issue, but later in this same function:
drivers/accel/rocket/rocket_core.c:rocket_core_init() {
...
version = rocket_pc_readl(core, VERSION);
...
}
Are these registers accessed while the device might still be held in reset?
Since devm_reset_control_bulk_get_exclusive() is called earlier, shouldn't
the resets be explicitly deasserted before this first memory-mapped read to
prevent bus hangs?
[Severity: High]
This is a pre-existing issue, but in rocket_drv.c:rocket_remove():
drivers/accel/rocket/rocket_drv.c:rocket_remove() {
...
rocket_core_fini(&rdev->cores[core]);
rdev->num_cores--;
...
}
Does decrementing rdev->num_cores without shifting array elements make
subsequent cores inaccessible? Since find_core_for_dev() loops up to
rdev->num_cores, if an earlier core is removed, wouldn't the highest
index core fall outside the loop bounds?
[Severity: High]
This is a pre-existing issue, but in rocket_job.c:rocket_job_handle_irq():
drivers/accel/rocket/rocket_job.c:rocket_job_handle_irq() {
...
iommu_detach_group(NULL, iommu_group_get(core->dev));
...
}
Does this code leak the IOMMU group reference count? iommu_group_get()
increments the reference count, but iommu_detach_group() consumes the
pointer without decrementing it, which could prevent the group from ever
being freed on driver unbind.
[Severity: High]
This is a pre-existing issue, but in rocket_job.c:rocket_job_init():
drivers/accel/rocket/rocket_job.c:rocket_job_init() {
...
ret = drm_sched_init(&core->sched, &args);
if (ret) {
dev_err(core->dev, "Failed to create scheduler: %d.", ret);
goto err_sched;
}
return 0;
err_sched:
drm_sched_fini(&core->sched);
...
}
Can this lead to a double free or NULL pointer dereference? If
drm_sched_init() fails, it internally frees its resources and returns an
error. Calling drm_sched_fini() in the error path could attempt to stop
uninitialized workqueues and free resources again.
[Severity: High]
This is a pre-existing issue, but in rocket_drv.c:rocket_probe():
drivers/accel/rocket/rocket_drv.c:rocket_probe() {
...
if (rdev == NULL) {
/* First core probing, initialize DRM device. */
rdev = rocket_device_init(drm_dev, &rocket_drm_driver);
...
}
Does this cause an unbounded memory leak on probe deferral?
rocket_device_init() allocates devres tied to the global drm_dev,
which outlives the probe failure. Since rdev is then reset to NULL
during probe teardown, would a subsequent probe retry allocate an
entirely new set of structures without freeing the old ones?
[Severity: High]
This is a pre-existing issue, but in rocket_job.c:rocket_job_init():
drivers/accel/rocket/rocket_job.c:rocket_job_init() {
...
ret = devm_request_threaded_irq(core->dev, core->irq,
rocket_job_irq_handler,
rocket_job_irq_handler_thread,
IRQF_SHARED, dev_name(core->dev),
core);
...
}
Does this shared IRQ handler unconditionally access hardware registers
when clocks and power domains might be disabled? If another device
sharing the IRQ line fires before pm_runtime_resume_and_get() is
called, could rocket_job_irq_handler() immediately execute a
memory-mapped read and cause a kernel panic?
[Severity: High]
This is a pre-existing issue, but in rocket_drv.c:rocket_probe():
drivers/accel/rocket/rocket_drv.c:rocket_probe() {
...
if (rdev == NULL) {
/* First core probing, initialize DRM device. */
rdev = rocket_device_init(drm_dev, &rocket_drm_driver);
if (IS_ERR(rdev)) {
dev_err(&pdev->dev, "failed to initialize rocket device\n");
return PTR_ERR(rdev);
}
}
unsigned int core = rdev->num_cores;
...
}
Is the global rdev static variable left containing an error pointer
if rocket_device_init() fails? On a subsequent probe retry, rdev
would no longer evaluate to NULL, leading directly to a dereference of
the error pointer when accessing rdev->num_cores.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831081956.84871-1-gahing@gahingwoo.com?part=1
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v11 02/14] accel/rocket: take the completion register writes under job_lock
2026-08-31 8:19 ` [PATCH v11 02/14] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
@ 2026-08-31 8:36 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-31 8:36 UTC (permalink / raw)
To: Jiaxing Hu; +Cc: dri-devel, devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
Pre-existing issues:
- [High] Reference count leak of the IOMMU group in rocket_job_handle_irq().
- [High] The hardirq handler accesses hardware registers without ensuring the device is active, causing invalid register reads or bus faults when a shared IRQ fires while the device is runtime-suspended.
- [High] Threaded IRQ handler accesses hardware registers without a PM reference if there is no in-flight job, causing a bus fault.
- [High] Missing `synchronize_irq()` in runtime suspend callback for a shared IRQ, risking mid-flight handler execution.
- [High] Calling drm_sched_fini() on a failed scheduler initialization causes a null pointer dereference.
--
commit 873cb013be0f4784d0270a98eec1dd55e8a02c68
Author: Jiaxing Hu <gahing@gahingwoo.com>
accel/rocket: take the completion register writes under job_lock
This commit moves the OPERATION_ENABLE and INTERRUPT_CLEAR register writes
inside the job_lock scoped_guard block in rocket_job_handle_irq(). It fixes
a race condition where a completion being handled could overwrite a newly
submitted task's enable bit.
> diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
> index 3141f210fcd1b..5f0f9682e57ca 100644
> --- a/drivers/accel/rocket/rocket_job.c
> +++ b/drivers/accel/rocket/rocket_job.c
> @@ -345,10 +345,15 @@ static void rocket_job_handle_irq(struct rocket_core *core)
> {
> pm_runtime_mark_last_busy(core->dev);
>
> - rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
> - rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
> + scoped_guard(mutex, &core->job_lock) {
> + /*
> + * Stopping the block belongs under the lock. hw_submit() writes
> + * OPERATION_ENABLE too, and outside the lock this zero can land
> + * after that one and stop a task that has only just started.
> + */
> + rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
> + rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
[Severity: High]
This is a pre-existing issue, but do these unconditional hardware writes risk
causing a bus fault?
If a shared interrupt fires while the NPU is runtime-suspended, the threaded
handler will execute with core->in_flight_job == NULL. Because no PM reference
is held in that case, the hardware is powered down, and writing to these
registers could cause an async SError.
>
> - scoped_guard(mutex, &core->job_lock)
> if (core->in_flight_job) {
> if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
> rocket_job_hw_submit(core, core->in_flight_job);
> @@ -360,6 +365,7 @@ static void rocket_job_handle_irq(struct rocket_core *core)
> pm_runtime_put_autosuspend(core->dev);
> core->in_flight_job = NULL;
> }
[Severity: High]
This isn't a bug introduced by this patch, but does this code leak a reference
to the IOMMU group?
drivers/accel/rocket/rocket_job.c:rocket_job_handle_irq() {
...
if (core->in_flight_job) {
...
iommu_detach_group(NULL, iommu_group_get(core->dev));
dma_fence_signal(core->in_flight_job->done_fence);
...
}
The call to iommu_group_get() increments the kobject reference count for the
group, but its result is leaked without a corresponding put. Over time on the
hot success path, could this overflow the refcount and prevent the IOMMU group
from ever being freed?
> + }
> }
[ ... ]
[Severity: High]
This is a pre-existing issue, but can the hardirq handler access hardware
registers while the device is runtime-suspended?
drivers/accel/rocket/rocket_job.c:rocket_job_irq_handler() {
struct rocket_core *core = data;
u32 raw_status = rocket_pc_readl(core, INTERRUPT_RAW_STATUS);
...
}
Since the driver registers a shared IRQ (IRQF_SHARED), another device on the
same IRQ line can trigger an interrupt. The handler will unconditionally read
INTERRUPT_RAW_STATUS without calling pm_runtime_get_if_active(). Could this
cause invalid register reads or a bus fault if the NPU's power domain is down?
[ ... ]
[Severity: High]
This is a pre-existing issue, but is it safe to disable the device clocks here
without synchronizing the shared IRQ line?
drivers/accel/rocket/rocket_drv.c:rocket_device_runtime_suspend() {
...
if (!rocket_job_is_idle(&rdev->cores[core]))
return -EBUSY;
clk_bulk_disable_unprepare(ARRAY_SIZE(rdev->cores[core].clks), rdev->cores[core].clks);
return 0;
}
Because the interrupt line is shared, an IRQ handler could be executing on
another CPU mid-flight. Does this need a synchronize_irq() before powering
down hardware to prevent the handler from accessing hardware whose clocks have
just been disabled?
[ ... ]
[Severity: High]
This isn't a bug introduced by this patch, but will drm_sched_fini() cause a
null pointer dereference if scheduler initialization fails?
drivers/accel/rocket/rocket_job.c:rocket_job_init() {
...
ret = drm_sched_init(&core->sched, &args);
if (ret) {
dev_err(core->dev, "Failed to create scheduler: %d.", ret);
goto err_sched;
}
return 0;
err_sched:
drm_sched_fini(&core->sched);
...
}
If drm_sched_init() fails (e.g. due to memory allocation failure), it frees
its internal structures and leaves sched->sched_rq as NULL but leaves
sched->num_rqs > 0. Would calling drm_sched_fini() on the error path attempt
to dereference the NULL sched->sched_rq array?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831081956.84871-1-gahing@gahingwoo.com?part=2
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v11 03/14] accel/rocket: wait for a running IRQ handler before resetting a core
2026-08-31 8:19 ` [PATCH v11 03/14] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
@ 2026-08-31 8:37 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-31 8:37 UTC (permalink / raw)
To: Jiaxing Hu; +Cc: robh, conor+dt, dri-devel, devicetree
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Lockless modification of INTERRUPT_MASK in rocket_reset() races with concurrent task submission in the IRQ handler, potentially leaving interrupts enabled.
- [Medium] Fails to mask interrupts when Runtime PM is disabled, incorrectly treating -EINVAL as an inactive state.
Pre-existing issues:
- [High] Missing iommu_group_put() leaks the IOMMU group reference on every completed job.
- [High] The threaded IRQ handler unconditionally accesses hardware registers without ensuring the device is active, causing an async SError on spurious or delayed interrupts.
--
commit dafaccb47666f525ef9a0018c8357108f3591b85
Author: Jiaxing Hu <gahing@gahingwoo.com>
accel/rocket: wait for a running IRQ handler before resetting a core
rocket_reset() calls drm_sched_stop(), which stops the scheduler and
returns. It does not wait for a threaded handler that is already
running, so the comment that follows, "Remaining interrupts have been
handled", states an assumption rather than something the code arranges.
[ ... ]
> diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
> index 5f0f9682e57ca..3c0ed46050669 100644
> --- a/drivers/accel/rocket/rocket_job.c
> +++ b/drivers/accel/rocket/rocket_job.c
> @@ -377,9 +377,37 @@ rocket_reset(struct rocket_core *core, struct drm_sched_job *bad)
> drm_sched_stop(&core->sched, bad);
>
> /*
> - * Remaining interrupts have been handled, but we might still have
> - * stuck jobs. Let's make sure the PM counters stay balanced by
> - * manually calling pm_runtime_put_noidle().
> + * Mask the block before waiting. hw_submit() arms INTERRUPT_MASK on
> + * every submit and only the hardirq clears it, so on an ordinary
> + * timeout it is still live and a completion can arrive after the sync
> + * returns. The next submit re-arms it, so nothing is lost here.
> + *
> + * Only when the device is already awake, though. This function holds no
> + * runtime PM reference of its own: the only one in the window belongs to
> + * in_flight_job, and the completion path may have put it and cleared the
> + * pointer before the timeout worker got here. drm_sched_stop() above can
> + * block for a long time, and it drops every pending job's credits, so
> + * rocket_job_is_idle() is true and nothing keeps the core resumed. On
> + * this hardware a register access with the domain down takes an async
> + * SError, so a reset must not be the thing that causes one.
> + */
> + if (pm_runtime_get_if_active(core->dev) > 0) {
[Severity: Medium]
Does this condition fail to account for the -EINVAL return value when
Runtime PM is disabled?
pm_runtime_get_if_active() returns -EINVAL when Runtime PM is disabled.
The condition > 0 evaluates to false, causing the interrupt masking to
be skipped. Even though the device is always awake when PM is disabled,
the mask is still required to prevent concurrent access during reset.
[Severity: High]
Does this lockless register write race with the IRQ handler's hardware
submission?
If rocket_reset() checks pm_runtime_get_if_active() and writes 0x0 to
INTERRUPT_MASK concurrently while the threaded IRQ handler calls
rocket_job_hw_submit() (holding core->job_lock) after reading
reset.pending == 0:
IRQ Handler:
rocket_job_hw_submit()
rocket_pc_writel(core, INTERRUPT_MASK, PC_INTERRUPT_MASK_DPU_0 | PC_INTERRUPT_MASK_DPU_1);
If this write occurs after rocket_reset()'s write, the interrupt is left
enabled. This could allow the IRQ handler to execute while the device is
suspended, triggering an async SError.
> + rocket_pc_writel(core, INTERRUPT_MASK, 0x0);
> + pm_runtime_put_autosuspend(core->dev);
> + }
> +
> + /*
> + * drm_sched_stop() returns without waiting for a threaded handler that
> + * is already running, so wait for one here. This has to stay outside
> + * job_lock: the handler takes that lock, so waiting for it while
> + * holding it would deadlock instead of fencing anything.
> + */
> + synchronize_irq(core->irq);
[ ... ]
[Severity: High]
This is a pre-existing issue, but does this code leak the IOMMU group reference
on every completed job?
drivers/accel/rocket/rocket_job.c:rocket_job_next_locked() {
...
iommu_detach_group(NULL, iommu_group_get(core->dev));
...
}
iommu_group_get() increments the kobject reference counter, but
iommu_detach_group() does not consume the reference. Without a matching
call to iommu_group_put(), this causes a permanent resource leak that
could prevent device unbinding.
[Severity: High]
This is a pre-existing issue, but does this unconditional register access risk
an async SError on spurious or delayed interrupts?
drivers/accel/rocket/rocket_job.c:rocket_job_handle_irq() {
...
scoped_guard(mutex, &core->job_lock) {
/*
* Stopping the block belongs under the lock. hw_submit() writes
* OPERATION_ENABLE too, and outside the lock this zero can land
* after that one and stop a task that has only just started.
*/
rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
...
}
The threaded IRQ handler unconditionally accesses hardware registers without
verifying if the device is active (e.g., using pm_runtime_get_if_active()).
If a spurious or delayed interrupt runs after the device has suspended,
writing to powered-off hardware will trigger an async SError and kernel
panic.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831081956.84871-1-gahing@gahingwoo.com?part=3
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v11 04/14] accel/rocket: let the core suspend after a reset
2026-08-31 8:19 ` [PATCH v11 04/14] accel/rocket: let the core suspend after a reset Jiaxing Hu
@ 2026-08-31 8:49 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-31 8:49 UTC (permalink / raw)
To: Jiaxing Hu; +Cc: dri-devel, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Using asynchronous pm_runtime_put_autosuspend() instead of a synchronous suspend in the reset path prevents the required power domain cycle if the next job is submitted immediately.
Pre-existing issues:
- [High] The shared IRQ handler accesses hardware registers without verifying if the device is active, and the runtime suspend callback powers down hardware without synchronizing with in-flight shared IRQs.
- [High] Reference count leak of the IOMMU group on every job completion in rocket_job_next_locked().
- [High] Detaching the IOMMU before halting the hardware during reset causes SMMU translation faults if the hardware is still active.
--
commit b4995381e41020bdb1af33ec4a432209b68f8226
Author: Jiaxing Hu <gahing@gahingwoo.com>
accel/rocket: let the core suspend after a reset
This patch modifies rocket_reset to drop the PM reference using
pm_runtime_put_autosuspend, allowing the core to suspend and trigger
a bus interface reset upon resuming.
Link: https://lore.kernel.org/all/20260819073530.6087-1-royalnet026@gmail.com/
> diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
> index 3c0ed46050669..a89ab49e17e52 100644
> --- a/drivers/accel/rocket/rocket_job.c
> +++ b/drivers/accel/rocket/rocket_job.c
> @@ -406,12 +406,12 @@ rocket_reset(struct rocket_core *core, struct drm_sched_job *bad)
>
> /*
> * No handler is running now, but we might still have stuck jobs. Let's
> - * make sure the PM counters stay balanced by manually calling
> - * pm_runtime_put_noidle().
> + * make sure the PM counters stay balanced by putting the reference the
> + * job took, and request idle while doing it so the core can suspend.
> */
> scoped_guard(mutex, &core->job_lock) {
> if (core->in_flight_job)
> - pm_runtime_put_noidle(core->dev);
> + pm_runtime_put_autosuspend(core->dev);
[Severity: High]
Does using the asynchronous pm_runtime_put_autosuspend() here prevent the
required power domain cycle?
If a queued job is submitted immediately after reset, couldn't the autosuspend
timer be canceled before the core actually suspends?
rocket_reset() finishes by restarting the scheduler:
/* Restart the scheduler */
drm_sched_start(&core->sched, 0);
If the scheduler restarts and the next job runs immediately, it will take a
PM reference and cancel the autosuspend timer before the suspend happens.
Would using a synchronous suspend instead guarantee the hardware ordering
constraints are met?
>
> iommu_detach_group(NULL, core->iommu_group);
[Severity: High]
This is a pre-existing issue, but does detaching the IOMMU here before halting
the hardware risk SMMU translation faults?
If a hung job leaves the hardware actively attempting DMA transactions,
detaching the IOMMU unmaps the device's DMA domain while the transactions are
still in flight.
The hardware is reset and halted after the IOMMU group is already detached:
iommu_detach_group(NULL, core->iommu_group);
...
/* Proceed with reset now. */
rocket_core_reset(core);
Should the hardware be halted prior to revoking its DMA mappings?
[ ... ]
[Severity: High]
This isn't a bug introduced by this patch, but does the shared IRQ handler
access hardware registers without verifying the device's PM state?
In rocket_job_irq_handler(), the handler reads a hardware register on a shared
IRQ line:
u32 raw_status = rocket_pc_readl(core, INTERRUPT_RAW_STATUS);
If an interrupt fires for another device sharing the same IRQ line while the
NPU is runtime suspended, won't this read unclocked hardware registers and
potentially cause an asynchronous SError panic?
Additionally, in rocket_device_runtime_suspend(), the hardware is powered down
without synchronizing with in-flight shared IRQs:
clk_bulk_disable_unprepare(rdev->cores[core].soc->num_clks,
rdev->cores[core].clks);
Could this crash the kernel if the handler is executing mid-flight while the
clocks are disabled?
[Severity: High]
This is a pre-existing issue, but does rocket_job_next_locked() leak an IOMMU
group reference on every normal job completion?
The group reference count is incremented but not decremented when detaching:
iommu_detach_group(NULL, iommu_group_get(core->dev));
Because jobs execute continuously, won't this eventually saturate the refcount
and permanently leak the IOMMU group object on teardown?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831081956.84871-1-gahing@gahingwoo.com?part=4
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v11 05/14] accel/rocket: factor the completion tail out of the IRQ handler
2026-08-31 8:19 ` [PATCH v11 05/14] accel/rocket: factor the completion tail out of the IRQ handler Jiaxing Hu
@ 2026-08-31 8:52 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-31 8:52 UTC (permalink / raw)
To: Jiaxing Hu; +Cc: robh, dri-devel, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] Leak of IOMMU group reference in job completion path due to missing iommu_group_put().
- [High] Shared IRQ handler accesses hardware registers without checking if the device is active, risking crashes or spurious interrupts on suspended devices.
- [High] Missing synchronize_irq() in runtime suspend callback allows races with shared interrupts.
--
commit 3949b3388a0eed37353941f6527bafda9857fb01
Author: Jiaxing Hu <gahing@gahingwoo.com>
accel/rocket: factor the completion tail out of the IRQ handler
This commit factors out the completion and task advancement logic from
rocket_job_handle_irq() into a new helper function rocket_job_next_locked().
This clarifies the code structure after previous locking changes and isolates
the logic for easier bisection. There are no intended functional changes.
> diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
> index a89ab49e17e52..69e29f40f27a0 100644
> --- a/drivers/accel/rocket/rocket_job.c
> +++ b/drivers/accel/rocket/rocket_job.c
> @@ -341,6 +341,25 @@ static struct dma_fence *rocket_job_run(struct drm_sched_job *sched_job)
> return ERR_PTR(ret);
> }
>
> +/* Start the job's next task, or retire it. Caller holds job_lock. */
> +static void rocket_job_next_locked(struct rocket_core *core)
> +{
> + lockdep_assert_held(&core->job_lock);
> +
> + if (!core->in_flight_job)
> + return;
> +
> + if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
> + rocket_job_hw_submit(core, core->in_flight_job);
> + return;
> + }
> +
> + iommu_detach_group(NULL, iommu_group_get(core->dev));
[Severity: High]
This isn't a bug introduced by this patch, but does this code permanently
leak the IOMMU group reference?
iommu_group_get() increments the reference count of the IOMMU group, but
the returned pointer is passed directly to iommu_detach_group() which does
not take ownership. I don't see a matching iommu_group_put() anywhere.
Since this happens on every job completion, won't this continuously leak
kobject references until the refcount_t saturates and warns? Could this
use core->iommu_group directly without taking a new reference?
> + dma_fence_signal(core->in_flight_job->done_fence);
> + pm_runtime_put_autosuspend(core->dev);
> + core->in_flight_job = NULL;
> +}
> +
> static void rocket_job_handle_irq(struct rocket_core *core)
> {
> pm_runtime_mark_last_busy(core->dev);
> @@ -354,17 +373,7 @@ static void rocket_job_handle_irq(struct rocket_core *core)
> rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
> rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
[Severity: High]
This is a pre-existing issue, but do these unconditional hardware register
accesses risk a system crash if the device is runtime suspended?
The interrupt handler doesn't check if the device is active before accessing
hardware:
drivers/accel/rocket/rocket_job.c:rocket_job_irq_handler() {
...
u32 raw_status = rocket_pc_readl(core, INTERRUPT_RAW_STATUS);
...
}
Because the driver registers this as a shared IRQ (IRQF_SHARED), if another
device on the line triggers an interrupt while the NPU is suspended, this read
typically returns 0xffffffff.
This satisfies the interrupt mask (e.g. PC_INTERRUPT_RAW_STATUS_DPU_0),
causing the handler to wake the thread and execute rocket_job_handle_irq()
here, which writes to powered-off registers like OPERATION_ENABLE, causing
an asynchronous SError or bus fault.
Should the hardirq handler use pm_runtime_get_if_active() to check the
power state before reading INTERRUPT_RAW_STATUS?
> - if (core->in_flight_job) {
> - if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
> - rocket_job_hw_submit(core, core->in_flight_job);
> - return;
> - }
> -
> - iommu_detach_group(NULL, iommu_group_get(core->dev));
> - dma_fence_signal(core->in_flight_job->done_fence);
> - pm_runtime_put_autosuspend(core->dev);
> - core->in_flight_job = NULL;
> - }
> + rocket_job_next_locked(core);
> }
> }
While reviewing this, I noticed another pre-existing issue related to the
shared IRQ handling in the driver's runtime PM suspend path:
drivers/accel/rocket/rocket_drv.c:rocket_device_runtime_suspend() {
...
if (!rocket_job_is_idle(&rdev->cores[core]))
return -EBUSY;
clk_bulk_disable_unprepare(ARRAY_SIZE(rdev->cores[core].clks),
rdev->cores[core].clks);
...
}
[Severity: High]
This is a pre-existing issue, but does this function pull hardware power
before ensuring all interrupt handlers have finished executing?
Subsystem rules state that drivers using IRQF_SHARED must call
synchronize_irq() in their runtime suspend callback before powering down
hardware. Since the IRQ is shared, rocket_job_irq_handler() could be
executing concurrently on another CPU when the clock is disabled, which
would cause a system crash.
Could a call to synchronize_irq() be added here before disabling clocks?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831081956.84871-1-gahing@gahingwoo.com?part=5
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v11 11/14] accel/rocket: select the per-core clock and reset counts from match data
2026-08-31 8:19 ` [PATCH v11 11/14] accel/rocket: select the per-core clock and reset counts from match data Jiaxing Hu
@ 2026-08-31 9:04 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-31 9:04 UTC (permalink / raw)
To: Jiaxing Hu; +Cc: dri-devel, conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] Missing NULL check for the return value of of_device_get_match_data(), leading to a guaranteed kernel panic on manual sysfs bind.
Pre-existing issues:
- [High] The driver tracks cores in an array using `num_cores` as both the array length and the probe index, causing resource leaks and array slot overwrites on out-of-order unbinds.
- [High] Devres memory leak of `struct rocket_device` and its children on probe deferral and manual unbind.
--
commit d68f29f9366dd1f885e0585ee67c1b358bda44b6
Author: Jiaxing Hu <gahing@gahingwoo.com>
accel/rocket: select the per-core clock and reset counts from match data
This patch introduces a soc_data structure to provide per-SoC clock and
reset counts from the device match data. This enables supporting the
RK3576, which has different counts compared to the RK3588.
> diff --git a/drivers/accel/rocket/rocket_core.c b/drivers/accel/rocket/rocket_core.c
> index 5dd260bacbff6..b202d15816a39 100644
> --- a/drivers/accel/rocket/rocket_core.c
> +++ b/drivers/accel/rocket/rocket_core.c
> @@ -23,7 +23,7 @@ int rocket_core_init(struct rocket_core *core)
>
> core->resets[0].id = "srst_a";
> core->resets[1].id = "srst_h";
> - err = devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(core->resets),
> + err = devm_reset_control_bulk_get_exclusive(&pdev->dev, core->soc->num_resets,
> core->resets);
[Severity: High]
Does this code crash with a NULL pointer dereference if a user with root
privileges force-binds the driver to a device using the sysfs bind interface?
The sysfs interface bypasses the device tree match, causing
of_device_get_match_data() (called in rocket_probe()) to return NULL, which
is then unconditionally dereferenced here as core->soc->num_resets.
[ ... ]
> diff --git a/drivers/accel/rocket/rocket_drv.c b/drivers/accel/rocket/rocket_drv.c
> index 8bbbce594883e..6e7dc91c5faac 100644
> --- a/drivers/accel/rocket/rocket_drv.c
> +++ b/drivers/accel/rocket/rocket_drv.c
> @@ -176,6 +176,7 @@ static int rocket_probe(struct platform_device *pdev)
>
> rdev->cores[core].rdev = rdev;
> rdev->cores[core].dev = &pdev->dev;
> + rdev->cores[core].soc = of_device_get_match_data(&pdev->dev);
> rdev->cores[core].index = core;
>
> rdev->num_cores++;
[Severity: High]
This is a pre-existing issue, but does decrementing rdev->num_cores during
an out-of-order unbind in rocket_remove() leak resources and corrupt memory?
If a system has multiple cores (e.g., RK3588 has 3 RKNN cores) and a user
manually unbinds a lower-indexed core via sysfs before a higher-indexed
core, rdev->num_cores is decremented.
When the higher-indexed core is later unbound, find_core_for_dev() fails to
find it because the search space core < rdev->num_cores shrank, skipping
its teardown. A subsequent bind will then overwrite the active core's slot
at rdev->cores[rdev->num_cores].
[Severity: High]
This is also a pre-existing issue, but does probe deferral or manual unbind
permanently leak the struct rocket_device and its children?
During the first core probe, rocket_device_init() allocates rdev using
devm_drm_dev_alloc() tied to the global drm_dev.
If the core probe subsequently fails (e.g., -EPROBE_DEFER) or is manually
unbound, rocket_device_fini() unregisters the DRM device and rdev is set to
NULL. However, the devres allocations remain bound to the long-lived drm_dev
and persist indefinitely, leaking memory on every cycle.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831081956.84871-1-gahing@gahingwoo.com?part=11
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v11 10/14] pmdomain/rockchip: cycle optional power-domain resets on power-on
2026-08-31 8:19 ` [PATCH v11 10/14] pmdomain/rockchip: cycle optional power-domain resets on power-on Jiaxing Hu
@ 2026-08-31 9:05 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-31 9:05 UTC (permalink / raw)
To: Jiaxing Hu; +Cc: robh, devicetree, dri-devel, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Resource leak of clock references on of_clk_get failure
- [High] Use-after-free when pm_genpd_remove fails during probe error cleanup
--
commit c4349e9db6c9eecdba71ab60966e3312c6588638
Author: Jiaxing Hu <gahing@gahingwoo.com>
pmdomain/rockchip: cycle optional power-domain resets on power-on
Some Rockchip domains come out of power-on with their bus interface in
an undefined state. On the RK3576 NPU this shows up as a hang on the
first register access after the domain is switched on, and pulsing the
domain's resets at this point clears it.
Take the domain node's resets if it has any, and pulse them between
releasing idle and restoring QoS. The resets are optional, so domains
that do not list any are unaffected.
> diff --git a/drivers/pmdomain/rockchip/pm-domains.c b/drivers/pmdomain/rockchip/pm-domains.c
> index 39988efd86aaa..8f2fd8a83e8e9 100644
> --- a/drivers/pmdomain/rockchip/pm-domains.c
> +++ b/drivers/pmdomain/rockchip/pm-domains.c
[ ... ]
> @@ -861,6 +870,14 @@ static int rockchip_pm_add_one_domain(struct rockchip_pmu *pmu,
> if (error)
> goto err_put_clocks;
[Severity: High]
This isn't a bug introduced by this patch, but does this code leak the
clocks on of_clk_get() failure?
Looking at rockchip_pm_add_one_domain(), just above this code:
for (i = 0; i < pd->num_clks; i++) {
pd->clks[i].clk = of_clk_get(node, i);
if (IS_ERR(pd->clks[i].clk)) {
error = PTR_ERR(pd->clks[i].clk);
dev_err(pmu->dev,
"%pOFn: failed to get clk at index %d: %d\n",
node, i, error);
return error;
}
}
If a PM domain has multiple clocks defined in its device tree node, and
of_clk_get() succeeds for the first clock(s) but fails for a subsequent
clock (e.g., returning -EPROBE_DEFER), the function returns the error
directly without calling clk_put() on the successfully acquired clocks.
Could this direct return bypass the err_put_clocks cleanup path and leak
the clocks acquired in previous loop iterations?
> + pd->resets = of_reset_control_array_get_optional_exclusive(node);
> + if (IS_ERR(pd->resets)) {
> + error = dev_err_probe(pmu->dev, PTR_ERR(pd->resets),
> + "%pOFn: failed to get resets\n", node);
> + pd->resets = NULL;
> + goto err_unprepare_clocks;
> + }
> +
> pd->num_qos = of_count_phandle_with_args(node, "pm_qos",
> NULL);
[ ... ]
> @@ -949,6 +967,7 @@ static void rockchip_pm_remove_one_domain(struct rockchip_pm_domain *pd)
>
> clk_bulk_unprepare(pd->num_clks, pd->clks);
> clk_bulk_put(pd->num_clks, pd->clks);
[Severity: High]
This is a pre-existing issue, but can this lead to a use-after-free when
pm_genpd_remove() fails during probe error cleanup?
In rockchip_pm_remove_one_domain(), right before this code:
ret = pm_genpd_remove(&pd->genpd);
if (ret < 0)
dev_err(pd->pmu->dev, "failed to remove domain '%s' : %d - state may be inconsistent\n",
pd->genpd.name, ret);
If rockchip_pm_add_subdomain() or pm_genpd_provider_add_of() fails,
rockchip_pm_domain_cleanup() iterates over all domains in reg ID order.
If a child domain has a smaller ID than its parent, the child is removed
first. Because its parent link still exists, pm_genpd_remove() returns
-EBUSY and fails to remove the child from the global gpd_list.
Despite the failure, rockchip_pm_remove_one_domain() returns and the
rockchip_pm_domain structure is freed by devm when the probe fails.
Does this leave a pointer to freed memory in the global gpd_list, leading
to a use-after-free when the list is next accessed (e.g., by
genpd_power_off_unused())?
> + reset_control_put(pd->resets);
>
> /* protect the zeroing of pm->num_clks */
> mutex_lock(&pd->pmu->mutex);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831081956.84871-1-gahing@gahingwoo.com?part=10
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v11 12/14] accel/rocket: add RK3576 NPU (RKNN) support
2026-08-31 8:19 ` [PATCH v11 12/14] accel/rocket: add RK3576 NPU (RKNN) support Jiaxing Hu
@ 2026-08-31 9:11 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-31 9:11 UTC (permalink / raw)
To: Jiaxing Hu; +Cc: robh, conor+dt, devicetree, dri-devel
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
Pre-existing issues:
- [High] Shared IRQ handler accesses hardware registers without checking PM state, and runtime suspend lacks synchronize_irq().
- [High] Reference leak of iommu_group in job completion path.
- [High] Array bounds mismatch and logic error in `find_core_for_dev` and core registration due to naive decrement on device removal.
- [High] Concurrent probe of multiple NPU cores causes data races in global rdev initialization and array management.
- [High] Leaking devm resources on unbound pseudo-device across rebinds or `-EPROBE_DEFER`.
- [High] A 64-bit DMA address is truncated to 32 bits when written to the hardware, leading to IOMMU faults if the address exceeds 4GB.
--
commit da7c000fc37b3b36196f66187168ddef41f790b6
Author: Jiaxing Hu <gahing@gahingwoo.com>
accel/rocket: add RK3576 NPU (RKNN) support
The RK3576 has two cores of the same RKNN block and a few platform
differences:
[ ... ]
> diff --git a/drivers/accel/rocket/rocket_device.c b/drivers/accel/rocket/rocket_device.c
> index 46e6ee1e72c5f..923add5bdc87e 100644
> --- a/drivers/accel/rocket/rocket_device.c
> +++ b/drivers/accel/rocket/rocket_device.c
> @@ -9,6 +9,7 @@
> #include <linux/of.h>
>
> #include "rocket_device.h"
> +#include "rocket_drv.h"
>
> struct rocket_device *rocket_device_init(struct platform_device *pdev,
> const struct drm_driver *rocket_drm_driver)
> @@ -27,7 +28,11 @@ struct rocket_device *rocket_device_init(struct platform_device *pdev,
> ddev = &rdev->ddev;
> dev_set_drvdata(dev, rdev);
>
[Severity: High]
This is a pre-existing issue, but does rocket_device_init() leak devm
resources if probe defers or unbinds?
It appears that devm_drm_dev_alloc() is called earlier in this function
and binds the allocation to the persistent global drm_dev instead of the
pdev being probed. If probe defers, or if the cores are unbound,
rocket_remove() simply clears the rdev pointer without freeing the memory
bound to drm_dev, leading to memory leaks on successive probe deferrals.
> - for_each_compatible_node(core_node, NULL, "rockchip,rk3588-rknn-core")
> + /*
> + * Count over the same match table the platform driver binds with, so
> + * that a core added there is counted here without a second edit.
> + */
> + for_each_matching_node(core_node, rocket_dt_match)
> if (of_device_is_available(core_node))
> num_cores++;
>
[ ... ]
> diff --git a/drivers/accel/rocket/rocket_drv.c b/drivers/accel/rocket/rocket_drv.c
> index 6e7dc91c5faac..e469629499fdb 100644
> --- a/drivers/accel/rocket/rocket_drv.c
> +++ b/drivers/accel/rocket/rocket_drv.c
> @@ -217,13 +217,23 @@ static void rocket_remove(struct platform_device *pdev)
[Severity: High]
This is a pre-existing issue, but are there concurrent probe data races here?
In rocket_probe(), the global rdev pointer is checked and assigned without
synchronization, and rdev->num_cores is used as an array index concurrently.
If multiple NPU cores are probed concurrently, could this lead to double
initialization or out-of-bounds array accesses?
Additionally, when rocket_remove() executes:
drivers/accel/rocket/rocket_drv.c:rocket_remove() {
...
rdev->num_cores--;
...
}
It decrements the active core count without compacting the array.
Could a subsequent bind overwrite active devices and truncate search
boundaries in find_core_for_dev()?
> static const struct rocket_soc_data rk3588_soc_data = {
> .num_clks = 4,
> .num_resets = 2,
> + .multi_power_domain = false,
> + .task_con_16bit = false,
> };
>
[ ... ]
> diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
> index 69e29f40f27a0..2a272c2ef6bed 100644
> --- a/drivers/accel/rocket/rocket_job.c
> +++ b/drivers/accel/rocket/rocket_job.c
[ ... ]
[Severity: High]
This is a pre-existing issue, but does this driver safely handle shared IRQs
during runtime suspend?
In rocket_job_irq_handler(), the code unconditionally reads from
INTERRUPT_RAW_STATUS:
drivers/accel/rocket/rocket_job.c:rocket_job_irq_handler() {
...
u32 raw_status = rocket_pc_readl(core, INTERRUPT_RAW_STATUS);
...
}
However, rocket_device_runtime_suspend() disables clocks without calling
synchronize_irq(). Because this uses a shared IRQ line (IRQF_SHARED), if
another device on the same line triggers an interrupt while the NPU is
suspended, could reading these unpowered hardware registers cause a system
hang or external abort?
[Severity: High]
This isn't a bug introduced by this patch, but is there a reference leak of
the IOMMU group in the job completion path?
In rocket_job_next_locked():
drivers/accel/rocket/rocket_job.c:rocket_job_next_locked() {
...
iommu_detach_group(NULL, iommu_group_get(core->dev));
...
}
This calls iommu_group_get() inline, which increments the reference count,
but there appears to be no corresponding iommu_group_put(), meaning the
reference is leaked on every job completion.
> @@ -142,10 +165,17 @@ static void rocket_job_hw_submit(struct rocket_core *core, struct rocket_job *jo
> rocket_pc_writel(core, INTERRUPT_MASK, PC_INTERRUPT_MASK_DPU_0 | PC_INTERRUPT_MASK_DPU_1);
> rocket_pc_writel(core, INTERRUPT_CLEAR, PC_INTERRUPT_CLEAR_DPU_0 | PC_INTERRUPT_CLEAR_DPU_1);
>
[Severity: High]
This is a pre-existing issue, but could the DMA address get truncated here?
The driver explicitly asks the IOMMU for up to 40-bit IOVAs via
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40)) in rocket_core_init().
However, the hardware submission path writes the command buffer address
using a 32-bit register write:
drivers/accel/rocket/rocket_job.c:rocket_job_hw_submit() {
...
rocket_pc_writel(core, BASE_ADDRESS, task->regcmd);
...
}
Since task->regcmd is written to a 32-bit hardware register, and userspace
may pass a truncated IOVA, will this cause IOMMU faults if the address exceeds
4GB?
> - rocket_pc_writel(core, TASK_CON, PC_TASK_CON_RESERVED_0(1) |
> - PC_TASK_CON_TASK_COUNT_CLEAR(1) |
> - PC_TASK_CON_TASK_NUMBER(1) |
> - PC_TASK_CON_TASK_PP_EN(1));
> + if (core->soc->task_con_16bit)
> + rocket_pc_writel(core, TASK_CON,
> + RK3576_PC_TASK_CON_LAST_LAYER_CLEAR |
> + RK3576_PC_TASK_CON_COUNT_CLEAR |
> + RK3576_PC_TASK_CON_PP_EN |
> + RK3576_PC_TASK_CON_TASK_NUMBER(1));
> + else
> + rocket_pc_writel(core, TASK_CON, PC_TASK_CON_RESERVED_0(1) |
> + PC_TASK_CON_TASK_COUNT_CLEAR(1) |
> + PC_TASK_CON_TASK_NUMBER(1) |
> + PC_TASK_CON_TASK_PP_EN(1));
>
> rocket_pc_writel(core, TASK_DMA_BASE_ADDR, PC_TASK_DMA_BASE_ADDR_DMA_BASE_ADDR(0x0));
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831081956.84871-1-gahing@gahingwoo.com?part=12
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v11 13/14] arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes
2026-08-31 8:19 ` [PATCH v11 13/14] arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes Jiaxing Hu
@ 2026-08-31 9:16 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-31 9:16 UTC (permalink / raw)
To: Jiaxing Hu; +Cc: conor+dt, robh, dri-devel, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Leaking `iommu_group` reference in `rocket_job_next_locked`.
--
commit 6d0158c8b1c151133b206dc57eb669f47d5dc64e
Author: Jiaxing Hu <gahing@gahingwoo.com>
arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes
Add the two RKNN cores and their IOMMUs. Both cores are disabled by
default; boards enable what they wire up.
[Severity: High]
This isn't a bug introduced by this patch, but is there an iommu_group
reference leak in rocket_job_next_locked()?
drivers/accel/rocket/rocket_job.c:rocket_job_next_locked() {
...
if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
rocket_job_hw_submit(core, core->in_flight_job);
return;
}
iommu_detach_group(NULL, iommu_group_get(core->dev));
dma_fence_signal(core->in_flight_job->done_fence);
...
}
The reference acquired by iommu_group_get() is passed to
iommu_detach_group() which does not consume it. Since iommu_group_put()
is never called, this reference appears to leak.
Could this code use the already acquired core->iommu_group pointer instead?
> diff --git a/arch/arm64/boot/dts/rockchip/rk3576.dtsi b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
> index b0c0d3c8b1b14..2d0133cdf9b5f 100644
> --- a/arch/arm64/boot/dts/rockchip/rk3576.dtsi
> +++ b/arch/arm64/boot/dts/rockchip/rk3576.dtsi
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260831081956.84871-1-gahing@gahingwoo.com?part=13
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-08-31 9:16 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31 8:19 [PATCH v11 00/14] accel/rocket: RK3576 NPU (RKNN) enablement Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 01/14] accel/rocket: request the core clocks by name Jiaxing Hu
2026-08-31 8:36 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 02/14] accel/rocket: take the completion register writes under job_lock Jiaxing Hu
2026-08-31 8:36 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 03/14] accel/rocket: wait for a running IRQ handler before resetting a core Jiaxing Hu
2026-08-31 8:37 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 04/14] accel/rocket: let the core suspend after a reset Jiaxing Hu
2026-08-31 8:49 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 05/14] accel/rocket: factor the completion tail out of the IRQ handler Jiaxing Hu
2026-08-31 8:52 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 06/14] dt-bindings: npu: rockchip: add rockchip,rk3576-rknn-core Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 07/14] dt-bindings: power: rockchip: allow resets in a power domain node Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 08/14] dt-bindings: iommu: rockchip: describe the RK3576 NPU MMU Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 09/14] pmdomain/rockchip: add optional per-domain power-on settle delay Jiaxing Hu
2026-08-31 8:19 ` [PATCH v11 10/14] pmdomain/rockchip: cycle optional power-domain resets on power-on Jiaxing Hu
2026-08-31 9:05 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 11/14] accel/rocket: select the per-core clock and reset counts from match data Jiaxing Hu
2026-08-31 9:04 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 12/14] accel/rocket: add RK3576 NPU (RKNN) support Jiaxing Hu
2026-08-31 9:11 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 13/14] arm64: dts: rockchip: rk3576: add NPU (RKNN) nodes Jiaxing Hu
2026-08-31 9:16 ` sashiko-bot
2026-08-31 8:19 ` [PATCH v11 14/14] arm64: dts: rockchip: rk3576-rock-4d: enable NPU Jiaxing Hu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).