It's Pi all the way down...

The Pins They Are A-Changin’

Tue 20 April 2021
by Dave Jones

Some fairly major (and frankly rather overdue) changes are coming in the way that users access the GPIO pins on the Raspberry Pi. The shift will (hopefully) be fairly gradual and, for users of certain libraries, entirely invisible. However, for others they may be quite disruptive. As forewarned is forearmed, hopefully this little article may be of some use to those affected!

I really must stop writing “little” or “quick” in a desperate attempt to persuade my writing tendencies toward brevity … it never works … my humble apologies, dear reader, for the extended ramble you are about to endure.

Ancient History

Our story begins in antiquity, which is to say Linux kernel version 3.16, circa 2014. The Raspberry Pi is growing in popularity and everyone is keen to use these new (to the PC world anyway) things called “pins”.

The simple, neat theory of hardware access is that a kernel mediates all access to your hardware, and your program interacts with kernel APIs of some form to indirectly use that hardware. For the GPIO pins on the Pi, this meant the GPIO sysfs interface. This is still found today under the /sys/class/gpio path in the file-system. The two important paths under here are export and unexport.

Let’s say you have an LED on GPIO 26 that you want to control, like so:

Breadboard diagram illustrating an LED and a 330Ω resistor connected in series between GPIO 26 (on pin 37) and ground (on pin 39)

You can do so via simple file-system operations (the following assumes you have write access to this area of the file-system, which is the case for the default user on RaspiOS, but not Ubuntu so switch to root if you want to try it there):

$ cd /sys/class/gpio
$ ls
export  gpiochip0  gpiochip504  unexport
$ echo 26 > export
$ ls
export  gpio26  gpiochip0  gpiochip504  unexport
$ ls gpio26
active_low  device  direction  edge  power  subsystem  uevent  value

As we can see, writing “26” to export has magically created a new gpio26 path that has various files under it which we can use to manipulate the GPIO:

$ cat gpio26/direction
in
$ echo out > gpio26/direction
$ cat gpio26/value
0
$ echo 1 > gpio26/value

Presto! At this point, your LED will be lit.

As you may guess, you can change the GPIO back into an input by writing “in” to gpio26/direction. You can read gpio26/value to determine the value of the input. You can perform edge detection by writing values to the gpio26/edge file, and so on.

This may look simple and neat, but in practice there’s a lot of problems with it. At the root of most of those problems is the issue that the file-system is a global resource, so this API provides no concept of “ownership”.

If process A and process B both have write access to the GPIO sysfs interface, both can export GPIO 26. What’s stopping them both from accessing GPIO 26 at the same time?

Nothing.

Can this be worked around with file-system permissions? Sadly, it’s not that simple: what process sets those permissions? How do you avoid race conditions when changing permissions of multiple file-system entries?

For instance, on RaspiOS, to permit GPIO access to ordinary users, rules were added that change the ownership of these files to permit members of the “gpio” group to access them. However, that change isn’t instantaneous when the gpio26 path is created, so all GPIO libraries relying on it have hacks that look something like:

  1. Check if gpio26 exists; if it does, exit
  2. Write “26” to gpio/export
  3. Check gpio26 exists
  4. Check gpio26/direction is writable
  5. If not, wait a few milliseconds and try again
  6. If after, say, 1 second we still can’t write to it, assume we’ll never be able to and throw an error

Urgh! You can see what this looks like in gpiozero’s “native” driver under the GPIOFS.export method (full disclosure: this part of gpiozero is largely my fault).

It gets worse …

Due to that first check, if the GPIO is already exported, our only option is to go ahead and use it. Maybe it’s left over from something that crashed before it could un-export it? Maybe it’s currently in use by another process? We can’t know, so we just barge in and use it; a library could throw a warning but there’s precious little a user could do in response to that warning.

And there’s another problem: this interface is slow. Sufficiently slow that back in the Pi 1 days, it was considered effectively useless for Pulse Width Modulation (PWM). This is a technique of rapidly pulsing a pin which is commonly used to provide variable speed control on motors, position information for servos, etc.

What’s faster than sysfs?

How The Wave Was Done

If the sysfs interface was too slow, and the Linux kernel (prior to 4.8) provided no alternative … how did userspace (that is, “not kernel”) processes manage PWM back in the Pi 1 days? There were two options:

  1. Do things the Right Way™, with hardware! The Pi provides a hardware driven PWM port capable of fantastic speeds. Because it’s hardware driven it also doesn’t use any CPU time and is wonderfully stable as it’s not interrupted by things like I/O. Perfect!

    Unfortunately it’s only available on a single pin (actually on modern Pis there’s a choice of 4 pins, but there’s still only two hardware PWM channels you can use).

  2. Get hacky! Just bang on the hardware registers from userspace! If you’re using a compiled language like C, you can still go pretty damned fast, and you can use whichever and however many pins you want (provided the CPU can keep up).

Take a wild guess which one got used!

To be clear, I don’t blame anyone for this choice. The limitation of a single pin for hardware PWM was a major problem. What if you have two servos to control (pitch and yaw, say)? The “correct” answer is “go buy more hardware like an I²C servo driver” but writing code is often cheaper, and faster than waiting for the postman.

But hang on a tick … doesn’t the kernel control the hardware? How does a userspace application get to bang on hardware registers?! Simple! Get root privileges, find the relevant area for the GPIO registers in /dev/mem (a “special file” that represents all the physical memory in your machine) and write to it!

This is, shall we say, not what userspace programs are meant to do. Things got slightly more respectable along the way, of course (they had to!). Running everything as root was a problem that was solved by producing a device called /dev/gpiomem which simply gave access to the portion of memory that contained the GPIO registers and nothing else. And numerous libraries for numerous languages sprung up to paper over these horrors and present a nice user-friendly GPIO interface. Still, if all this sounds incredibly hacky … well, that’s because it was.

Or rather is.

Yes, dear reader, this is still how the vast majority of GPIO control on the Pi is done on at the time of writing. This is how wiringPi, pigpio, RPi.GPIO, and gpiozero’s “native” driver all operate (yes kids, the latter is pure Python banging directly on hardware registers; you don’t have to learn C just to abuse stuff!).

Doesn’t stomping all over the registers under the kernel’s nose cause any issues? Especially considering the kernel’s also trying to control those same pins via the sysfs interface, using those same registers?

Well … yes …

The Bleeding Edge

Almost from the start it became apparent that attempting to perform edge detection (nothing to do with images; this is simply responding to the rising or falling “edge” of a pulse on a pin) didn’t work with the registers. This was one area where the Linux kernel exerted absolute control.

You could configure pins and drive them high and low quite happily without the kernel complaining, but mess with the edge detection registers and stuff started crashing.

Ultimately this meant that most libraries wound up being rather schizophrenic. They’d “play nice” and use the sysfs interface for edge detection, but then stomp all over the hardware registers whenever they wanted to do anything else (reading the value of a GPIO, setting its direction, writing a value, etc.).

And until very recently, this has worked happily …

As Neat as a Pin

Time rolls on, the seasons change, and Linux kernel 4.8 is released.

This kernel introduced a new “gpiochip” device file (e.g. /dev/gpiochip0). Such files have a set of ioctls (“special” commands for special files) that provide the means to manipulate the GPIOs, and this is the basis of the new API for userspace control of the pins.

By far the most important change is that now there’s a concept of “ownership”. Before your process can do anything with a GPIO it has to request access to it (via an ioctl). In doing so, the kernel will track your process’ use of that GPIO, and won’t permit any other process to use it simultaneously. Should your process terminate without releasing it, the kernel will notice and free up the ownership automatically.

So far, so good.

A userspace library (libgpiod) exists to provide a friendly interface to all the ioctls for the gpiochip device, and it comes with a few nice command line utilities like gpioinfo which can be used to determine what GPIOs exist and whether they are currently in use by anything. You can get all this by installing the “gpiod” package. For example:

$ sudo apt install gpiod
... all the usual apt stuff ...
$ gpioinfo
gpiochip0 - 58 lines:
        line   0:     "ID_SDA"       unused   input  active-high
        line   1:     "ID_SCL"       unused   input  active-high
        line   2:       "SDA1"       unused   input  active-high
        line   3:       "SCL1"       unused   input  active-high
        line   4:  "GPIO_GCLK"       unused   input  active-high
        line   5:      "GPIO5"       unused   input  active-high
        line   6:      "GPIO6"       unused   input  active-high
        line   7:  "SPI_CE1_N"   "spi0 CS1"  output   active-low [used]
        line   8:  "SPI_CE0_N"   "spi0 CS0"  output   active-low [used]
        line   9:   "SPI_MISO"       unused   input  active-high
        line  10:   "SPI_MOSI"       unused   input  active-high
        line  11:   "SPI_SCLK"       unused   input  active-high
        line  12:     "GPIO12"       unused   input  active-high
        line  13:     "GPIO13"       unused  output  active-high
        line  14:       "TXD1"       unused   input  active-high
        line  15:       "RXD1"       unused   input  active-high
        line  16:     "GPIO16"       unused   input  active-high
        line  17:     "GPIO17"       unused   input  active-high
        line  18:     "GPIO18" "gpio-fan@0"  output  active-high [used]
        line  19:     "GPIO19"       unused   input  active-high
        line  20:     "GPIO20"       unused   input  active-high
        line  21:     "GPIO21"       unused   input  active-high
        line  22:     "GPIO22"       unused   input  active-high
        line  23:     "GPIO23"       unused   input  active-high
        line  24:     "GPIO24"       unused   input  active-high
        line  25:     "GPIO25"       unused   input  active-high
        line  26:     "GPIO26"      "sysfs"   input  active-high [used]
        line  27:     "GPIO27"       unused   input  active-high
        line  28: "RGMII_MDIO"       unused   input  active-high
        line  29:  "RGMIO_MDC"       unused   input  active-high
        line  30:       "CTS0"       unused   input  active-high
        line  31:       "RTS0"       unused   input  active-high
        line  32:       "TXD0"       unused   input  active-high
        line  33:       "RXD0"       unused   input  active-high
        line  34:    "SD1_CLK"       unused   input  active-high
        line  35:    "SD1_CMD"       unused   input  active-high
        line  36:  "SD1_DATA0"       unused   input  active-high
        line  37:  "SD1_DATA1"       unused   input  active-high
        line  38:  "SD1_DATA2"       unused   input  active-high
        line  39:  "SD1_DATA3"       unused   input  active-high
        line  40:  "PWM0_MISO"       unused   input  active-high
        line  41:  "PWM1_MOSI"       unused   input  active-high
        line  42: "STATUS_LED_G_CLK" "led0" output active-high [used]
        line  43: "SPIFLASH_CE_N" unused input active-high
        line  44:       "SDA0"       unused   input  active-high
        line  45:       "SCL0"       unused   input  active-high
        line  46: "RGMII_RXCLK" unused input active-high
        line  47: "RGMII_RXCTL" unused input active-high
        line  48: "RGMII_RXD0"       unused   input  active-high
        line  49: "RGMII_RXD1"       unused   input  active-high
        line  50: "RGMII_RXD2"       unused   input  active-high
        line  51: "RGMII_RXD3"       unused   input  active-high
        line  52: "RGMII_TXCLK" unused input active-high
        line  53: "RGMII_TXCTL" unused input active-high
        line  54: "RGMII_TXD0"       unused   input  active-high
        line  55: "RGMII_TXD1"       unused   input  active-high
        line  56: "RGMII_TXD2"       unused   input  active-high
        line  57: "RGMII_TXD3"       unused   input  active-high
gpiochip1 - 8 lines:
        line   0:      "BT_ON"       unused  output  active-high
        line   1:      "WL_ON"       unused  output  active-high
        line   2: "PWR_LED_OFF" "led1" output active-low [used]
        line   3: "GLOBAL_RESET" unused output active-high
        line   4: "VDD_SD_IO_SEL" "vdd-sd-io" output active-high [used]
        line   5:   "CAM_GPIO"       unused  output  active-high
        line   6:  "SD_PWR_ON" "sd_vcc_reg"  output  active-high [used]
        line   7:    "SD_OC_N"       unused   input  active-high

That’s a whole lotta output! But, hold on! If you look over the list, you’ll see that GPIO 26 is apparently “used” by “sysfs”. Yup, the old interface is still there, but now it’s implemented as something on top of the new gpiochip device(s).

This is actually good news: it means nothing breaks immediately (sort of). The bad news is: remember how things using sysfs can leave things exported? If anything does, the user needs to manually unexport them before anything using the new interface can use them. To be fair, there’s no good way around this, and I think this is probably the best compromise possible in the circumstances.

How would controlling our LED look with libgpiod under Python?

>>> import gpiod
>>> chip = gpiod.Chip('0', gpiod.Chip.OPEN_BY_NUMBER)
>>> line = chip.get_line(26)
>>> line.request('my-script')
>>> line.set_direction_output(1)

All this looks very promising; the problems with the sysfs interface are dealt with and there’s now a simple, neat solution to controlling GPIO pins from userspace in the form of libgpiod. Which begs the question: if all this came along in kernel version 4.8, circa 2016, why are the vast majority of userspace GPIO libraries on the Pi still banging on registers?

Two reasons: firstly, it’s still slower than banging on registers. No real surprise there, but it’s not much slower. However, a bigger problem is that (in the initial version of this new interface, at least), there was still something missing …

Pulling the Pin

GPIO pins are remarkably versatile things. We’ve seen them acting as an output. But how about as an input? Imagine we have a push-button connected between GPIO 13 and ground, like so:

Breadboard diagram adding a momentary push-button connected between GPIO 13 (on pin 33) and ground (on pin 39) to the earlier circuit

How do we go about detecting when this button is pressed?

  • We need the GPIO to be an input. No problem there.
  • We need to wait for a falling edge when the button connects the GPIO to ground. Again, no problem.
  • We need the GPIO to be pulled up to ensure there’s a potential to fall (or at least enough to register a difference on a GPIO which would otherwise be floating).

Go back and have a look at the sysfs interface we saw earlier. See any pseudo-file for configuring a pull?

No, and this was another big reason why all the libraries resorted to banging on the registers; the kernel interface of the time didn’t support a fairly major piece of functionality, and one that is useful in the most basic of operations like detecting when a switch closes.

Sadly, the brand new gpiochip interface, also omitted this functionality in its first incarnation (and all the way up to the version that shipped with Debian and RaspiOS “Buster”), so even if the myriad GPIO libraries had wanted to move to it as an underlying layer, they’d still have to bang on registers to implement some of their existing interface.

C’est la vie …

Pins and Needles

Allow me a brief interlude, dear reader, to explore userspace GPIO access from the perspective of the kernel.

Accessing GPIOs from userspace is considered an aberration that should never be tolerated in any end-user product. That statement may sound rather strong, but here’s a quote from the kernel’s GPIO sub-system documentation:

The userspace ABI is intended for one-off deployments. […] Do not under any circumstances abuse the GPIO userspace ABI to cut corners in any product development projects. If you use it for prototyping, then do not productify [sic] the prototype: rewrite it using proper kernel drivers. Do not under any circumstances deploy any uniform products using GPIO from userspace.

If the Kernel Gods hold my work in disfavour, I can only plead that gpiozero is a library for educators and makers, not mass-produced products.

Furthermore, I have some considerable sympathy with this view. GPIOs are a means to an end, and userspace should be concerned with the “end”, not the “means”. In fact this is a large part of gpiozero’s philosophy: that by concentrating on the things attached to pins (like LEDs and buttons) rather than the pins themselves, everything becomes much simpler (I’d love to claim credit for this stroke of genius, but it was another of Ben’s ideas).

Still, gpiozero is a userspace GPIO library, so perhaps I’m damned regardless. In the course of my background research for this article, I did come across a presentation (PDF) covering the introduction of the gpiochip interface which included “The Rules of Linux Userspace GPIO”:

  1. You do not access GPIOs from userspace
  2. YOU DO NOT ACCESS GPIOS FROM USERSPACE
  3. Read Documentation/gpio/drivers-on-gpio.txt
  4. Use the character device [/dev/gpiochipN]

And now I’m picturing Tyler Durden railing against the kids at a Raspberry Jam for breaking rule #1 :-)

Bang Up To Date

Anyhow, that brings us to the present day. Ubuntu Hirsute Hippo is releasing shortly (a couple of days away as I write this), and will ship with Linux kernel version 5.11. This has a gpiochip device (and libgpiod) which does support setting pulls, and is therefore (mostly) capable of doing everything the traditional GPIO libraries do.

Hang on … “mostly”?

Unfortunately, software PWM is still missing from this. But fret not! The creator of one of the traditional GPIO libraries (pigpio) has produced another GPIO library built upon the new gpiochip device called “lg“. This is a well and truly feature-complete library covering all the GPIO operations discussed above (including a very full-featured PWM implementation), and covering the SPI, I²C, and UART kernel devices.

This will be include in Ubuntu Hirsute with the packages:

  • liblgpio1 — the C library
  • python3-lgpio — the Python 3 bindings for liblgpio

It will also be included in RaspiOS Buster shortly (I can only apologise I haven’t sent the packaging for this yet, but last minute release debugging has swamped me! I will get to it shortly!), and is a fine interface to migrate your GPIO projects to if you’re currently using one of the traditional libraries like RPi.GPIO.

Why should you migrate?

  • The sysfs interface has been deprecated for a long time and will disappear at some point.
  • Fighting the kernel for control of the GPIO registers is not guaranteed to work.
  • There are a few more changes coming to the gpiochip interface, and we can’t guarantee that libraries that entirely bypass the kernel will keep working in the face of those changes.

Migration is always a pain, but lg has good documentation (considerably more than libgpiod does as far as I can tell), and plenty of examples. For some more lg examples, you should also head over to my colleague William’s site where he’s put together a nice lgpio tutorial post using lgpio on Hirsute (and say “hi” — he’s new to the team, but he’s been a huge help during the Hirsute cycle!).

However, if you’re using gpiozero …

Professor Farnsworth from Futurama with the caption "Good News Everyone!"

You shouldn’t need to migrate at all. The gpiozero library itself has traditionally defaulted to using RPi.GPIO as its underlying “pin driver”. In Ubuntu Hirsute (and probably RaspiOS Bullseye when it releases) that underlying implementation will default to lg instead (falling back to RPi.GPIO if lg isn’t found), so you shouldn’t need to do anything (but obviously if you do notice any issues, please open a ticket).

Finally, for those users of pigpio’s remote socket support, it’s worth noting that the “lg” project also includes:

  • rgpiod — a daemon using the lgpio library to provide a (potentially remote) socket based interface to the GPIO header
  • librgpio1 — the C client library for rgpiod (largely the same interface as lgpio but talks to an rgpiod instance instead of the local gpiochip device)
  • python3-rgpio — the Python 3 bindings for librgpio
  • rgpio-tools — includes “rgs”, a shell utility for talking to rgpiod