Skip to content

rp2: Disable DMA IRQ before clearing handler function. - #18914

Merged
dpgeorge merged 1 commit into
micropython:masterfrom
projectgus:bugfix/rp2_dma_soft_reset
Mar 18, 2026
Merged

dpgeorge merged 1 commit into
micropython:masterfrom
projectgus:bugfix/rp2_dma_soft_reset

Conversation

@projectgus

@projectgus projectgus commented Mar 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

WIthout this fix, soft reset will remove the IRQ handler before the finaliser for the DMA object(s) run and trigger IRQs if the channel is still active.

Closes #18765

This work was funded through GitHub Sponsors.

Testing

Ran the test case supplied with the issue and verified it crashes on RPI_PICO without this fix, and doesn't crash with the fix. I wasn't able to reproduce the problem on RPI_PICO2_W, so possibly it's RP2040-only (was reported on RP2040).

The stack trace shows the unhandled IRQ was to blame:

Program received signal SIGTRAP, Trace/breakpoint trap.
unhandled_user_irq_num_in_r0 () at /home/gus/ry/george/micropython/lib/pico-sdk/src/rp2_common/pico_crt0/crt0.S:327
327         bkpt #0
(gdb) bt
#0  unhandled_user_irq_num_in_r0 () at /home/gus/ry/george/micropython/lib/pico-sdk/src/rp2_common/pico_crt0/crt0.S:327
#1  <signal handler called>
#2  dma_channel_unclaim (channel=0) at /home/gus/ry/george/micropython/lib/pico-sdk/src/rp2_common/hardware_dma/dma.c:36
#3  0x10030f30 in rp2_dma_close (self_in=0x20005160) at /home/gus/ry/george/micropython/ports/rp2/rp2_dma.c:446
#4  0x100116fe in fun_builtin_1_call (self_in=0x1004b3c4 <rp2_dma_close_obj>, n_args=<optimized out>, n_kw=<optimized out>, 
    args=0x20041f4c) at /home/gus/ry/george/micropython/py/objfun.c:70
#5  0x1001a1c4 in mp_call_function_n_kw (fun_in=0x1004b3c4 <rp2_dma_close_obj>, n_args=1, n_kw=0, args=0x20041f4c)
    at /home/gus/ry/george/micropython/py/runtime.c:712
#6  0x1001a25a in mp_call_function_1 (fun=fun@entry=0x1004b3c4 <rp2_dma_close_obj>, arg=<optimized out>, arg@entry=0x20005160)
    at /home/gus/ry/george/micropython/py/runtime.c:690
#7  0x1001b0d6 in mp_call_function_1_protected (fun=0x1004b3c4 <rp2_dma_close_obj>, arg=0x20005160)
    at /home/gus/ry/george/micropython/py/runtime_utils.c:33
#8  0x10009428 in gc_sweep_run_finalisers () at /home/gus/ry/george/micropython/py/gc.c:606

I have not added a regression test for this as I think there's no "clean" way to trigger a soft reset inside a test, the test would need to rely on the rp2 crashing after the test was "done" which seems a bit iffy.

Trade-offs and Alternatives

As there's a finite number of channels, we could conceivably track them statically and clean them up from rp2_deinit() instead of waiting for finalisers to come and clean them up. However, I think relying on finalisers is OK now that the interrupt is disabled before we reach this point.

Generative AI

I did not use generative AI tools when creating this PR.

@projectgus

projectgus commented Mar 11, 2026 •

Copy link
Copy Markdown
Contributor Author

@pdw-mb @agatti Thanks for the very clear bug report and the stack trace, made this very easy to fix.

@agatti One slight niggle, your stack trace in the issue shows a crash happening earlier than mine did - and without the tell-tale unhandled_user_irq_num_in_r0 in the trace. If you have time, could you please try and reproduce the crash you saw with this fix?

@github-actions

github-actions Bot commented Mar 12, 2026 •

Copy link
Copy Markdown

Code size report:

Reference:  README: Add a section describing MicroPython's values. [4625f97]
Comparison: rp2: Disable DMA IRQ before clearing handler function. [merge of 08ffbd3]
  mpy-cross:    +0 +0.000% 
   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
      esp32:    +0 +0.000% ESP32_GENERIC
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +8 +0.001% RPI_PICO_W
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

@projectgus

Copy link
Copy Markdown
Contributor Author

FWIW I'm not exactly sure why the DMA IRQ triggers at this point, because dma_channel_set_irq0_enabled(channel, false); is called before dma_channel_unclaim(channel); so you'd think no IRQ should trigger from this channel... but it does appear to, anyway.

@dpgeorge

Copy link
Copy Markdown
Member

Thanks for looking into this.

I verified the fix in this PR works on a RPI_PICO_W. And the fix here does seem like a very sensible thing to do even regardless of it fixing a known bug.

I also found another patch which seems to fix it:

--- a/ports/rp2/rp2_dma.c
+++ b/ports/rp2/rp2_dma.c
@@ -427,6 +427,8 @@ static mp_obj_t rp2_dma_close(mp_obj_t self_in) {
     uint8_t channel = self->channel;

     if (channel != CHANNEL_CLOSED) {
+        dma_channel_set_irq0_enabled(channel, false);
+
         // Reset this channel's registers to their default values (zeros).
         dma_channel_config config = { .ctrl = 0 };
         dma_channel_configure(channel, &config, NULL, NULL, 0, false);
@@ -441,7 +443,6 @@ static mp_obj_t rp2_dma_close(mp_obj_t self_in) {
         if (irq) {
             irq->parent = MP_OBJ_NULL;
             irq->handler = MP_OBJ_NULL;
-            dma_channel_set_irq0_enabled(channel, false);
         }
         dma_channel_unclaim(channel);
         self->channel = CHANNEL_CLOSED;

and that patch also arguably makes sense regardless of it fixing any known bugs (ie disable the IRQ before resetting and aborting the DMA).

FWIW I'm not exactly sure why the DMA IRQ triggers at this point, because dma_channel_set_irq0_enabled(channel, false); is called before dma_channel_unclaim(channel); so you'd think no IRQ should trigger from this channel... but it does appear to, anyway.

The patch I show just above maybe gives a hint here: the call to dma_channel_abort() may be triggering an IRQ. I think in @agatti 's stack trace that function is the one which crashes/hangs.

Yet another idea I had was to run rp2_dma_deinit() after gc_sweep_all() (because you probably want to shut down the subsystems after running finalisers). That also seems to fix the original bug report.

What do you think is the best way to fix this? Maybe applying this PR and my patch above?

@projectgus
projectgus force-pushed the bugfix/rp2_dma_soft_reset branch from 8082e16 to 08ffbd3 Compare March 12, 2026 03:57
@projectgus

Copy link
Copy Markdown
Contributor Author

What do you think is the best way to fix this? Maybe applying this PR and my patch above?

Sounds good. I've done this and re-ran the rp2_dma.py unit test on a RPI_PICO board.

@agatti

agatti commented Mar 12, 2026

Copy link
Copy Markdown
Contributor

@agatti One slight niggle, your stack trace in the issue shows a crash happening earlier than mine did - and without the tell-tale unhandled_user_irq_num_in_r0 in the trace. If you have time, could you please try and reproduce the crash you saw with this fix?

For what it's worth, your earlier patch did fix the issue in the same scenario (debug build, swd debugger attached) :)

Both the overall IRQ line and the per-channel IRQ, for good measure.

Otherwise, soft reset will remove the handler before the finaliser for the
DMA object(s) run and trigger IRQs if the channel is still active.

Closes micropython#18765

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <[email protected]>
@dpgeorge
dpgeorge force-pushed the bugfix/rp2_dma_soft_reset branch from 08ffbd3 to d2cda57 Compare March 18, 2026 05:59
@dpgeorge
dpgeorge merged commit d2cda57 into micropython:master Mar 18, 2026
8 checks passed
@projectgus
projectgus deleted the bugfix/rp2_dma_soft_reset branch March 18, 2026 22:38
@robert-hh

robert-hh commented May 15, 2026 via email

Copy link
Copy Markdown
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rp2: crash on soft reboot with DMA IRQ enabled and DMA active

4 participants