August 17, 2023

Raspberry Pi Pico - Get linux and udev to help

Steps need to be taken to make code development on the Pico civilized. The very first step is to add a reset button. This is trivial in the electronic sense -- just add a button that pulls "RUN" to ground. But doing it in a way that doesn't involve some featherweight board flapping around and sliding off your desk is another. You can look at what I did here, with various comments:

The rain dance

So, I edit code, type make, and end up with an xyz.uf2 file I would like to set to running on the Pico. For me, the game goes like this. What I would like to happen would be for my linux machine to detect when a mass storage device appears. Just doing the reset should be all I have to do. When /dev/sdd1 shows up (and I confirm that it is RPI-RP2) I want to mount it and perform the copy and not have any extraneous filesystem navigation windows or other rubbish. This ought to be possible, and will probably involve some adventures with udev rules (and getting whatever it is that currently manages removable devices to keep its hands off).

Existing work on these lines

It turns out I am not the first to have ideas of this sort. In fact just doing some searches on udev and removable storage led me to posts discussing how to do this: Some comments discuss stupid issues involving systemd. For some reason, mount does not work as it should (or as you expect) when you run it from udev. Systemd and udev work inside of some kind of sandbox that doesn't affect the real system. Or something of that sort.

Give it a try

The first post above (dated April 16, 2022) gives a "recipe" to do exactly what I want, so I will just monkey see, monkey do, and do as he recommends, as follows (on my Fedora 38 linux system):
su
cd /etc/udev/rules.d
vi 80-rp2040.upload.rules
I create that file with the following contents:
SUBSYSTEM=="block", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", ACTION=="add", SYMLINK+="rp2040upl%n"
SUBSYSTEM=="block", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", ACTION=="add", RUN+="/usr/local/bin/rp2040_upload.sh"

Now, decide on a name for a mount folder. I like the idea /pico (why not?) so:

su
mkdir /pico

Now I am thinking on my feet. I will (as user tom) do this:

cd
mkdir pico

So the directory /home/tom/pico will be the place for the script we are about to introduce to pick up the ".uf2" file and copy it to the pico.

Create the script:

cd /usr/local/bin
vi rp2040_upload.sh
chmod a+x rp2040_upload.sh
That file will have the following contents.
#!/usr/bin/bash

MOUNT_PATH=/pico
UF2_PATH=/home/tom/pico

mount /dev/rp2040upl1 $MOUNT_PATH
sleep 2
cp $UF2_PATH/*.uf2 $MOUNT_PATH
Note:. This all looks lovely, but as you will read below, the mount command is crippled within udev scripts and this won't work. I wonder how the fellow who posted this nice idea managed to get it to work? He posted in April of 2022. Maybe he is working on a nice linux system without the nasty systemd.

Finally, tell the linux system to reload the udev rules.

su
udevadm control --reload

How I will use this

In my Makefiles, I will place an entry like so:
install:
	rm -f /home/tom/pico/*.uf2
	cp xyz.uf2 /home/tom/pico
Then what I do is to type "make install", then do the two button game on the pico itself.

It doesn't work

It all sounds and looks great, but when I reset my pico while holding BOOTSEL, I get the familiar desktop icon and my system sets up /dev/sdd for this device. My guess is that this has to do with some list of udev rules and the rule to set up a mounted disk is taking priority over this fancy new rule I just installed. In the logs, I see:
(udev-worker)[10145]: sdd: Process '/usr/local/bin/rp2040_upload.sh' failed with exit code 1.
Amazingly enough, I see this:
[root@trona tom]# ls -l /dev/rp*
lrwxrwxrwx 1 root root 3 Aug 17 15:52 /dev/rp2040upl -> sdd
lrwxrwxrwx 1 root root 4 Aug 17 15:52 /dev/rp2040upl1 -> sdd1
And when I run this by hand, it works just fine (and when the mount is done, the icon vanishes from my desktop):
su
mount /dev/rp2040upl1 /pico
But the script failed to do the mount. I add some logging to the script and find that mount is giving the error:
mount /dev/rp2040upl1 /pico
mount: /pico: permission denied.
       dmesg(1) may have more information after failed mount system call.
I find this absolutely baffling. I check and selinux is disabled. I can copy and paste the mount command into a window and the mount works. There is no interesting or useful information in dmesg output.

Systemd brain damage

The root cause is some ill conceived policy of sandboxing the mount command by systemd for udev. Udev works in its own magical world of deception and folly, but the mount command is limited or useless.

One possibility is using "systemd-mount" which operates in some weird way in this world of mirrors and confusion. It is supposed to mount things in /run/media. I experimented with it and found it to be erratic -- and when the mount succeeded it would be /run/media/system/RPI-RP2, but restricted to root.

Note that the default situation with fedora is that it knows it should mount the device as /run/media/tom/RPI-RP2, but doesn't do it until you click on the icon. And then it brings up this filemanager I hate and don't want. If I could persuade it to go ahead with the mount and skip the filemanager, this would be a workable situation.

People call this idiotic business a systemd "feature". Ha ha -- the joke is on us. But they say there is a way to bypass all of this

The game is to set up an override file for "systemd-udevd". You can do this:
su
systemctl edit systemd-udevd
Heaven only knows that editor this launches. The file seems to be:
/usr/lib/systemd/system/systemd-udevd.service
I used vim to edit this and added the following (there already was a "Service" section, so I just added the MountFlags line
[Service]
MountFlags=shared
After this, do:
systemctl daemon-reload
#service systemd-udevd --full-restart
service systemd-udevd restart
(I tried the --full-restart, but my version of "service" would not take it.)

After this, I still get the permission denied error, so very nice, but no cigar.

Conclusions

I would like to undulge myself by spewing out all kinds of venom against systemd. All I can say is "here we are again", trying to do something reasonable and having systemd standing in the say like a big nanny. Enough said.

What can we do to achieve our end purpose. One idea is to write a script that watches the /dev directory and when it sees /dev/rp2040upl appear, it would mount and copy the file to it. There would be permission issues to solve.

Another idea is to find out what part of the udev rules does the current thing that puts up the icon labeled RPI-RP2 and see if it is possible to make changes. Have it mount the device (if it is the Pico) with writeable permissions (as gets done when you click on the icon) and have it not bring up the file manager (Thunar).

Or forget the whole mess and look into Picotool.

Picotool

Picotool is a topic for a whole page of its own.

Picotool can write directly to the Pico without mounting it! The following udev rule allows this to be done by an ordinary user:

# /etc/udev/rules.d/99-pico.rules

# Make an RP2040 in BOOTSEL mode writable by all users, so you can `picotool`
# without `sudo`.
SUBSYSTEM=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", MODE="0666"

# Symlink an RP2040 running MicroPython from /dev/pico.
#
# Then you can `mpr connect $(realpath /dev/pico)`.
SUBSYSTEM=="tty", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0005", SYMLINK+="pico"


Have any comments? Questions? Drop me a line!

Tom's electronics pages / tom@mmto.org