Interrupting soft reset (Ctrl-D) with Ctrl-C erases filesystem on Raspberry Pi Pico
Sending a Ctrl-D to the MicroPython REPL to initiate a soft reset and then immediately issuing a Ctrl-C causes the entire filesystem to be erased on the Raspberry Pi Pico board. This issue was observed on 2 separate Pico boards across the following nightly builds (the 4 latest as of 01 March 2023):
However, the bug was not observed in the latest stable version 1.19.1.
Steps to reproduce
These steps were tested on a Linux host (Ubuntu 22.04).
First, load one of the above nightly versions of MicroPython onto a Raspberry Pi Pico board.
Next, using mpremote, install a library on the Pico’s filesystem, for example hashlib:
mpremote mip install hashlib
Then check that the library exists on the Pico’s filesystem:
mpremote fs ls /lib
This should show the hashlib folder:
ls :/lib
0 hashlib/
Then, to send the required keystrokes (Ctrl-D followed by Ctrl-C), execute the following lines of Python on the Linux host machine to which the Pico is connected, replacing /dev/ttyACM0 with the serial port of the MicroPython REPL. This script requires pyserial.
import serial
s = serial.Serial('/dev/ttyACM0')
s.write(b'\x04') # Ctrl-D
s.write(b'\x03') # Ctrl-C
s.close()
Less precisely, the issue can also be reproduced by entering a REPL with mpremote and pressing Ctrl-D and Ctrl-C in rapid succession.
Then check the Pico’s filesystem once again:
mpremote fs ls
This should show that the lib folder and all files it contains have been removed:
ls :
This issue became evident because the visual studio code extension Pico-W-Go exhibits this behaviour when running a MicroPython file, issuing a Ctrl-D and Ctrl-C in rapid succession before running the given code. This issue has been reported on that project’s GitHub page here, suggesting it affects other nightly versions older than the 4 listed above.
rp2: Random lockups on soft reboot if core 1 was running
Running the following minimal example on the Raspberry Pi Pico:
import _thread
def burn():
while True:
pass
_thread.start_new_thread(burn, [])
... starts an endless loop on the second CPU core (aka core 1). If a soft reboot via Ctrl+D is then performed, Micropython sometimes locks up to the point where only a hardware reset of the Pico helps. (You might have to try this several times, it does not happen every soft reboot.)
I've traced this to an endless wait for the spinlock in _osal_q_lock. I think, this is because the TinyUSB task is called on both cores via MICROPY_HW_USBDEV_TASK_HOOK:
https://github.com/micropython/micropython/blob/587339022689187a1acbccc1d0e2425a67385ff7/ports/rp2/mpconfigport.h#L232
... which is called via MICROPY_VM_HOOK_LOOP in vm.c.
Therefore, I think this is what happens: During a soft reboot, core 1 is reset:
https://github.com/micropython/micropython/blob/587339022689187a1acbccc1d0e2425a67385ff7/ports/rp2/mpthreadport.c#L50
If this happens at an unfavorable moment, when core 1 holds the Tiny USB spinlock, said lock is never released and core 0 hangs indefinitely when trying to acquire the lock the next time.
At first glance, only calling the TinyUSB task on core 0:
#define MICROPY_HW_USBDEV_TASK_HOOK extern void tud_task(void); if (get_core_num() == 0) tud_task();
... seems to fix the problem, as I cannot observe any random lockups.
However, I'm not familiar enough with Micropython's internals to know if there are situations in which the task needs to run on core 1, too. Therefore, I did not propose this tentative fix as a PR.