[RFC PATCH v2 6/8] accel/rocket: add RK3576 NPU (RKNN) support
Jiaxing Hu
gahing at gahingwoo.com
Sat Jul 25 05:16:01 PDT 2026
Hi Alexey,
> This whole polled-interrupt part looks very suspicious. Does the
> vendor stack do the same thing?
No, it doesn't, and you were right on both counts. Thanks for pushing
on this -- it turned into the most useful lead we've had in months.
The vendor rknpu driver is fully interrupt-driven on RK3576:
drivers/rknpu/rknpu_drv.c:
static const struct rknpu_irqs_data rk3576_npu_irqs[] = {
{ "npu0_irq", rknpu_core0_irq_handler },
{ "npu1_irq", rknpu_core1_irq_handler }
};
devm_request_irq() + wait_event_timeout(job_done_wq), int_mask 0x300
(DPU_0|DPU_1), on GIC_SPI 247/248 -- the same lines we use. So the
justification in this patch is wrong and I'll fix it in v3.
I added a probe to find out what was actually happening. Per power
session the line fires exactly once:
IRQ entry #1 raw=0x30000155 active=0x0155 mask=0x80000300
0x155 = DPU_0|CORE_0|CNA_CSC_0|CNA_WEIGHT_0|CNA_FEATURE_0, i.e. the
whole group-0 pipeline reporting done -- exactly the completion the
vendor waits for. INTERRUPT_MASK 0x300 is writable and sticks. So it
is routable; my "read-only, cannot be routed to the GIC" note only
ever applied to bits 28-29 (PC_DONE) and I over-generalised it.
> One theory could be that the interrupt bits are not "read-only" per
> se, but rather gated inactive by some internal hardware state which
> must be cleared before jobs are submitted [...] If so, this same
> hardware state (or something related to it) may well be the cause of
> your observed "first (forced) job completes, others don't".
That is what it is. Bit 31 of INTERRUPT_MASK is set by hardware when
that interrupt asserts (visible before our handler runs, and it
survives the handler writing INTERRUPT_MASK=0). While it is set, the
line never fires again. Nothing in software clears it -- I swept
INTERRUPT_CLEAR 0x1ffff / 0x80000000 / 0xffffffff and INTERRUPT_MASK
0x80000000 / 0 / 0x300, singly and combined, at two points in the
submit path. A full NPU reset does clear it.
So I requested a reset (via the driver's own recovery path, so it
lands on a detached IOMMU) before every op. In one power session,
with no power cycling anywhere:
90 ops, 90 resets, 87 completion interrupts
against exactly one interrupt per session before. And the data path
comes back with it -- per-layer input and weight fetch at the graph's
real shapes, and DPU write-back:
wt_rd in {96, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536}
dt_rd in {1568, 3136, 4704, 6272, 9408, 12544, 25088}
core dt_wr nonzero on ~56 of the 90 ops
What still doesn't work is the multiply-accumulate: the DPU faithfully
writes out a zero-point surface (output distinct=1..10). So your
gating state is real and it gates compute as well as interrupts, but
clearing it isn't sufficient -- something below it stays dead. That's
a much sharper localisation than we had, and it moved the search off
the dispatch/arm theories we'd been stuck on.
Two more things this turned up, both mine to fix:
- INTERRUPT_RAW_STATUS bits 28-29 are permanently latched (they
survive INTERRUPT_CLEAR=0xffffffff and are high at every submit,
including the first). The hrtimer completion poll waits on exactly
those bits, so its condition is already true before the hardware has
done anything. Completion detection needs redoing regardless.
- The v2 hw_submit posted here arms a DMA-error-only mask while the
tree I actually test arms the vendor's 0x300, so anyone reproducing
from v2 would not see what I see. Sorry about that.
For v3 I'll drop the wrong justification, use the interrupt like the
vendor does, and fix the mask divergence. The per-op reset is not a
workaround, to be clear -- after 90 of them the NPU power domain can't
reach idle at power-off and that times out the shared regulator path
(-110) and takes the box down. It's a diagnostic, not a fix.
Thanks again,
Jiaxing
More information about the Linux-rockchip
mailing list