← index #15163PR #18976
Related · high · value 3.188
QUERY · ISSUE

PICO rp2.PIO(1),remove_program() makes all instruction memory available for python PIO and therefor allows overwrite of CYW43 PIO program

openby wodropened 2024-05-29updated 2026-03-24
bugport-rp2

Checks

  • I agree to follow the MicroPython Code of Conduct to ensure a safe and respectful space for everyone.

  • I've searched for existing issues matching this bug, and didn't find any.

Port, board and/or hardware

PICO W

MicroPython version

MicroPython v1.22.2 on 2024-02-22; Raspberry Pi Pico W with RP2040

Problem

rp2.PIO(1).remove_program() removes all programs even the cyw43 program.

If after this statement a program is added to PIO(1) it is written from the TOP address 1F.
This cause the PICO to hang until unplugged.

CAUSE

If no program is given in rp2_pio_remove_program() then a program of lenth 32 and offset 0 is removed:

see rp2_pio_remove_program.c(314):

    uint8_t length = 32;
    uint offset = 0;
...
    struct pio_program pio_program = { NULL, length, -1 };
    rp2_pio_remove_managed_program(self->pio, &pio_program, offset);

This will also free the cyw43 pio program in the pico sdk, which keeps track of used memory.

SOLUTION

If no program is given in rp2.PIO(1).remove_program() the function should use

   rp2_pio_remove_all_managed_programs()

like it is used in rp2_pio_deinit().

This will only free instruction memory, that was allocated by python PIO. At least I hope. Didn't check this.

Steps to reproduce

# just some nop() instructions
program = [array('H', [41026, 41026, 41026, 41026, 41026]), -1, -1, 16384, 0, None, None, None]
print(program) 
rp2.StateMachine(7,program)
print(program)   # program is at address 21, because pio_add_program knows about used instruction memory
rp2.PIO(1).remove_program()
program[2] = -1 # not allocated yet, you could also use a new program
rp2.StateMachine(7,program)
print(program)   # now has offet 27 and overwrite cyw43 

this output is:

[array('H', [41026, 41026, 41026, 41026, 41026]), -1, -1, 16384, 0, None, None, None]
[array('H', [41026, 41026, 41026, 41026, 41026]), -1, 21, 16384, 0, None, None, None]
[array('H', [41026, 41026, 41026, 41026, 41026]), -1, 27, 16384, 0, None, None, None]

The program is, when added the second time, loaded at offset 27and overwrites the cyw43 program.

Workaround

instead of using remove_program() of PIO(1) use this code to free all memory except the cyw43 memory:
This assumes that cyw43 is loaded at address 1A to 1F (as it is in V1.22.2)

    nops = [array('H', [40993] * 26), -1, 0, 16384, 0, None, None, None] # mov(x,x)
    rp2.PIO(1).remove_program(nops)
    nops[2] = 0

Debug

To list the current instruction in pio memory, you can do this:

def Dump():
    from machine import mem32
    _SIZE_SM = const(0xe0 - 0xC8)
    smId = 2    
    end = 32
    start = 0 
    pio  = 1
    sm = rp2.PIO(1).state_machine(smId)        
    pioBase = 0x50200000+ pio * 0x100000
    
    smId = smId & 0x3
    for i in range(start,end,1):
        
        sm.exec(f"set(x,{i})")
        sm.exec(f"mov(pc,x)")
        addr = mem32[pioBase+smId*_SIZE_SM+ 0x0d4]
        instr = mem32[pioBase+smId*_SIZE_SM+0xD8]

        print(f"  {i:02X} {addr:04x}: {instr:04X} ")

this prints:

  00 0000: B598 
  01 0001: BBFB 
  02 0002: 304B 
  03 0003: AF57 
  04 0004: FA06 
  05 0005: 2FFD 
  06 0006: 3AF0 
  07 0007: CF3A 
  08 0008: CA27 
  09 0009: 54D9 
  0A 000a: ADA1 
  0B 000b: 8BA7 
  0C 000c: CEBE 
  0D 000d: FD26 
  0E 000e: EC7E 
  0F 000f: CBDF 
  10 0010: B147 
  11 0011: BA79 
  12 0012: 8ABF 
  13 0013: EF59 
  14 0014: 5D9A 
  15 0015: D7FA 
  16 0016: 6F9B 
  17 0017: EE48 
  18 0018: FF2A 
  19 0019: D58B 
  1A 001a: 6001
  1B 001b: 105A 
  1C 001c: E080 
  1D 001d: B042 
  1E 001e: 4001 
  1F 001f: 109E 

Address 1A to 1F is the CYW43 program

Cheers,
Dirk

Reproduction

just some nop() instructions
program = [array('H', [41026, 41026, 41026, 41026, 41026]), -1, -1, 16384, 0, None, None, None]
print(program)
rp2.StateMachine(7,program)
print(program) # program is at address 21, because pio_add_program knows about used instruction memory
rp2.PIO(1).remove_program()
program[2] = -1 # not allocated yet, you could also use a new program
rp2.StateMachine(7,program)
print(program) # now has offet 27 and overwrites cyw43

Expected behaviour

Don't overwrite cyw43 instructions.

Observed behaviour

cyw43 instructions are overwritten

CANDIDATE · PULL REQUEST

rp2: Fix stale program offset cache after remove_program().

openby bikeNomadopened 2026-03-21updated 2026-03-22
port-rp2

PIO.remove_program() without arguments clears SDK instruction-memory tracking but did not reset cached offsets in program objects. A subsequent StateMachine init using such a program would see the non-negative offset, assume the program was still loaded, and skip re-writing the instructions to PIO instruction memory.

Fix by validating the cached offset against the internal usage mask before trusting it. If the bit is clear the program is not present, so invalidate the stale offset and reload.

Summary

I discovered this issue when writing the PIO test suite in #18974; it caused a hang in test_restart.

Testing

I tested this on both RPI_PICO and RPI_PICO2 boards using the test suite from #18974 (and new PIO assembler support from #18975).

Generative AI

I used generative AI tools when creating this PR, but a human has checked the
code and is responsible for the code and the description above.

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied