For some time I've heard about the new GPIO character device API, sysfs deprecated, etc. Since I recently upgraded one of my RPi to bullseye, I decided to try this new API - or at least the "user tools" in the package named gpiod. I wanted a GPIO interface I could use in shell scripts, and from the cli.

I connected an LED and a 330𝝮 resistor in series between GPIO 25 and GND.

The material I read before deciding to try libgpiod/gpiod made use of the gpiod tools look very straightforward: 1, 2, 3, 4. So far, my experience has been anything but straightforward - at least with the gpioset tool.

Here's the issue:

$ gpioset pinctrl-bcm2835 25=1 # LED not illuminated 
$ gpioset gpiochip0 25=1       # LED not illuminated 
$ gpioset 0 25=1               # LED not illuminated
# gpioget issued immediately after gpioset returned 0 ?!

After "poking around", it seems gpioset ... is not persistent. man gpioset & gpioset -h describe 4 Modes:: I was able to illuminate the LED using the time & wait modes, but these modes are not generally well-suited for many situations. The exit mode seems to be the default, but it apparently toggles the GPIO so quickly that there is no trace of light - useless for an LED iow. And finally the signal mode described as follows: set values and wait for SIGINT or SIGTERM. Hmmm - interesting technique for switching GPIO output state.

I've found a few unofficial sources online that seem to confirm what I'm seeing; i.e. that gpioset is bizarrely non-persistent. But there don't seem to be any solutions offered - other than this ha project code.

Can anyone here confirm this, or offer a solution to get persistent GPIO control using gpioset or another tool based on libgpiod?

5 Answers 5


@Milliways is correct to say the gpiochip interface currently sets the GPIO back to defaults when the GPIO are released (explicitly by the program or by Linux when a user program terminates without releasing the GPIO).

Whether or not the GPIO should be reset upon release was a gpiochip designer decision. It would have been more flexible to leave this up to the user.

My opinion. Frankly the gpiochip interface is a mess. The designers have made fundamentally bad decisions. gpiochip should be able to control ALL GPIO modes, not just INPUT or OUTPUT. gpiochip should allow the user program to switch between modes without having to release and (hopefully) reclaim the GPIO.

If you want persistence you will need to use the lg daemon.


  • 1
    You have to successfully request/claim a GPIO from a gpiochip before you can use it. You have to free/release a GPIO when you have finished with it (if your program ends without freeing/releasing all GPIO they are automatically freed). Have a look at beyondlogic.org/… for what seems like a reasonable overview.
    – joan
    Commented Mar 7, 2022 at 21:44
  • 2
    "My opinion. Frankly the gpiochip interface is a mess." I agree! After trying to use it (despite the woeful documentation) I have given up. It seems a Linux kernel purists tool for Linux kernel purists - not designed for use by normal users. /sys/kernel/debug/pinctrl/ does show mode, but useless unless I/O.
    – Milliways
    Commented Mar 8, 2022 at 0:26
  • 2
    gpiochip seems to have been designed by people who think GPIO can only be used for switches and LEDs, i.e. basic input and output. The API is very poor. There was no reason to break it between API 1 and API 2. I can see them breaking it again in the future. I originally thought inexperience, I'm afraid I concluded incompetence.
    – joan
    Commented Mar 8, 2022 at 8:42
  • 1
    @joan is incorrect on many things. Within the kernel the GPIO subsystem only deals with lines as GPIO - there are other subsystems for other functions such as PWM, SPI and I2C. The GPIO uAPI cannot address those as they are beyond its scope. Wrt the uAPI v1 and v2, the v1 structs were not possible to extend for new features such as debounce. Each new feature required a new ioctl(). This was obviously broken. It also had an odd object model for the same reason. The uAPI v2 consolidates the object model and uses structs which can be extended for new features in the future. Commented Dec 31, 2023 at 2:25
  • 1
    @Seamus Wrt the I2C ref, user-space users tend to prefer sysfs interfaces so they can easily drive it from shell. No other reason - the other interfaces are equally valid. Commented Dec 31, 2023 at 8:43

It is only possible to guarantee the state of an output line if that line is actively requested, i.e. the fd returned by the GPIO_V2_GET_LINE_IOCTL is still open. In libgpiod terms that means you still hold the request returned by gpiod_chip_request_lines(), or gpioset is still running.

What happens when the line is released depends on the underlying device driver and is outside the control of libgpiod. It is possible to get behaviour akin to the old sysfs behaviour, but only if your device driver plays nice. As it happens, the RPi device driver was recently modified so it does. But it completely depends on the device driver in the kernel that you are running, so YMMV.

In more detail...

libgpiod wraps the GPIO chardev uAPI (aka cdev) which itself exposes the functionality of the kernel GPIO subsystem to userspace.

Note that the Linux GPIO subsystem only deals with basic setting and getting of lines, detection of edge events on input lines, and the directly related configuration. Other subsystems deal with other potential line modes, such as SPI, I2C, PWM, and those have their own userspace APIs. The cdev uAPI and libgpiod do not provide access to those modes, nor the ability to switch between them, as that is completely outside their scope. If you want a library, like say pigpio, that spans multiple modes and provides a unified user experience then go ahead and write one that wraps all the mode uAPIs. But I digress...

The GPIO subsystem requests lines from the appropriate gpiochip device driver and the line becomes owned by that consumer request.

A line can only have one owner, and only that owner can access the physical line state. A line not explicitly requested (i.e. consumed) is effectively owned by the device driver itself. In the case of cdev, the request lifetime is tied to the fd returned by the GPIO_V2_GET_LINE_IOCTL. Closing the fd releases the requested line(s). That may be explicitly by the process, or implicitly when the process exits and all its open files are closed. When using libgpiod, the request returned by gpiod_chip_request_lines() contains the request fd, and calling gpiod_line_request_release() closes it.

When a consumer releases a line, the GPIO subsystem releases it back to the device driver, and the state of the line becomes undefined from the perspective of that consumer as the line state may now be modified without its knowledge. Note that neither gpioset, libgpiod nor the GPIO subsystem change the state of a line when it is released, but the ownership is passed back to the device driver which may change it, or not, as it sees fit. It may be changed back to its default state or it may be left alone - it depends on the driver. And subsequently another consumer may request it...

Historically, the RPi device driver switched lines back to their default state, i.e. GPIO input mode, when they were released. As mentioned earlier, the RPi driver has recently been modified so that when the GPIO subsystem releases it the device driver will not alter its state if it is in GPIO mode. This effectively reproduces the sysfs-like behaviour for cdev.

This can be demonstrated using the libgpiod v2 tools, in this case running on a RPi4 with Bullseye kernel 6.1.21-v8+:

# Check currently an input
$ gpioinfo GPIO27
gpiochip0 27    "GPIO27"            input
# Set output high
$ gpioset -t0 GPIO27=1
$ gpioinfo GPIO27
gpiochip0 27    "GPIO27"            output
$ gpioget -a GPIO27
# Set output low
$ gpioset -t0 GPIO27=0
$ gpioinfo GPIO27
gpiochip0 27    "GPIO27"            output
$ gpioget -a GPIO27
# Back to an input again
$ gpioget GPIO27
$ gpioinfo GPIO27
gpiochip0 27    "GPIO27"            input

The gpioinfo checks demonstrate that the line remains an output when released by gpioset.

The gpioget returns the state of the output line - the -a option prevents the line being requested as an input, instead opening it as-is, so an output. Those values where also physically tested to be correct.

The tools have been reworked for libgpiod v2, and hopefully are less confusing, including:

  • the --mode option was removed from gpioset, as the meaning of "mode" is overloaded in this context.

  • to emphasise that the line state is only guaranteed while gpioset is running, the libgpiod v2 gpioset does not exit by default. The -t0 parameter is used here to emulate the old behaviour and have it exit immediately.

  • support identifying lines by name, as shown in the examples above, though explicit identification by chip and offset is still supported, e.g. gpioinfo -c0 27, if you prefer that form or want to avoid the name lookup overhead.

There are more changes, but not particularly relevant here.

I've referred to libgpiod v2 throughout this answer as it is the current release, but the same still applies to v1 with appropriate ioctl and function name changes. There were no changes made that would impact the "persistence" behaviour.

Btw, if you have issues with libgpiod, the appropriate forum to ask questions and contact the developers is the linux-gpio mailing list, as described in the libgpiod README:

Contributions are welcome - please send patches and bug reports to [email protected] (add the [libgpiod] prefix to the e-mail subject line) and stick to the linux kernel coding style when submitting new code.

though I would also note the mailing list spam filters quietly drop HTML emails so be sure to send plain text.

Worst case you could even pop us a direct email.

  • Thank you for the detailed answer! - I wondered why I was seeing persistence. For anyone using a recent AVRDUDE with linuxspi, you will need to do a gpioset to bring the reset line high again - as the AVRDUUE code assumes the old behaviour (non persistence). Commented Aug 7, 2023 at 3:33
  • 1
    The answer was late as this is not the core forum for libgpiod questions, so I only found it by accident. The deprecated tag indicates that all new features go into v2, not v1, and in the future v1 will be removed. The deprecated structs are still used by uAPI v1, which libgpiod v1 uses, while libgpiod v2 uses the uAPI v2. The v1 structs remain to allow users time to migrate to the new API, and from libgpiod v1 to v2. In the meantime, the kernel can support both uAPI versions at once, and should currently be built with both. Commented Dec 29, 2023 at 0:48
  • In your 'demonstration', you use the -t option (aka --toggle) with each instance of gpioset. Using gpioset without the -t option (option-less) fails to return the the bash prompt. You explain this by stating that "line state is only guaranteed while gpioset is running". But you also say that "ownership is passed back to the device driver" after gpioset exits. That raises a question: "Why doesn't the option-less gpioset just exit, and leave the state to the driver?" I'm sure you recognize that an option-less gpioset is - at best - awkward to use in a script?
    – Seamus
    Commented Jan 1 at 18:19
  • So don't use it option-less?? In v1 it did, and that caused confusion, as seen here and elsewhere, when the driver would revert the line to its default state. That results in "gpioset doesn't work" bug reports, and we got tired of that. The decision was to make it block by default to make it clearer that you lose control over the line when it exits. And the point of the demonstration was persistence, so I needed it to exit, hence -t0. Commented Jan 2 at 0:07
  • And if you want to control a line dynamically from shell, without relying on persistence, you can launch gpioset in interactive mode in a coproc and send commands to it from your script. The test suite for the tools does exactly that. Commented Jan 2 at 0:41

This is the whole point of these tools.

They are not intended to be used this way and clear any settings on completion. Whatever you do persists only for the duration of the code.

If you enclose in a script they should work but still restore state on exit.

Joan's library is more usable but fundamentally is the same.

If I just wanted to do something simple like set pin 25 HIGH I would use raspi-gpio set 25 op pn dh. No need to fiddle with code - of course libgpiod knows nothing about this.

  • 1
    "If you enclose in a script they should work but still restore state on exit." I cannot reconcile this statement with anything I've seen in my trials. If you had a script example to share, perhaps that would clear things up?
    – Seamus
    Commented Mar 8, 2022 at 0:32
  • 1
    @Seamus I did manage to get gpioset to do something by using the various modes. After wasting far too much time on these I gave up and wrote my own code to access registers.
    – Milliways
    Commented Mar 8, 2022 at 0:40

On Raspberry Pi 3 Bullseye Linux rpi 5.15.32-v7+ and gpioinfo (libgpiod) v1.6.2 the command line seems to be persistent. I have a relay connected to gpio17 and the commands:

gpioset 0 17=1 or gpioset 0 17=0

are persistent, as the relay maintain the last state.

Well here is my full history on libgpiod:

With Buster OS updated in July 2021 the libgpiod was persistent. By the end of 2021 I updated again the Buster OS and libgpiod lost persistence.

At the beginning of 2022 I moved to Bullseye and the library was still non persistent. Recently I updated again Bullseye an the library was again persistent.

As I see, it is something to do with the OS and if we want to be up to date with OS we can't rely on persistence as clearly stated by the author of the lib. Let us hope he will change his mind!

  • I've not tried that yet, but if true I guess there was a change to the kernel code that took care of this. uname -a on my 3B+ system reports Linux raspberrypi3b 5.15.32-v7+ also; tied up now, but will follow up asap. And thanks for the god news! :)
    – Seamus
    Commented May 2, 2022 at 6:42

In addition to the alternative solution offered by @joan, I'd like to add another alternative:

WiringPi has had some ups and downs since Gordon (the original author) resigned. The wiringpi package was dropped from the bullseye distribution for RPi, but has made a comeback, and is now available - as a deb package at least. Nothing at all against other APIs; in fact my preference for wiringpi is motivated in large part because it includes gpio - a user tool that can be used from the CLI, or in scripts. Note that gpio commands do have persistence... it feels silly having to say that... but this is in the post-libgpiod world!!

For now at least you can find the latest version of wiringpi here. You can download directly to your RPi by clicking one of the links from above.

Installation is accomplished as follows:

$ sudo dpkg -i <choose-your-file>.deb
  • As you are well aware, persistence, or not, is up to the device driver, not libgpiod. lgpio would behave the same. And the WiringPi page says it still doesn't work with Pi5 - you might want to mention that. Commented May 6 at 23:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.