sysfs GPIO vs libgpiod: When to Use Which
Linux userspace GPIO access usually comes down to two paths: the legacy sysfs interface under /sys/class/gpio, and the GPIO character device (/dev/gpiochipX) wrapped by libgpiod.
Kernel docs mark the sysfs userspace API obsolete and point replacements at the GPIO Character Device Userspace API. The libgpiod project docs say the same: character device + libgpiod replaces legacy sysfs.
This post is the selection axis only. Line-request / C API procedures belong in the libgpiod overview; rising/falling options and debounce belong in the gpiomon-edge post. No invented board pin diaries.
Grounded in Sysfs Interface, Obsolete GPIO interfaces, GPIO character device, and libgpiod.
When is sysfs appropriate?
One-line answer: Not the default for new work. Consider it only while maintaining or migrating legacy scripts/BSP paths already tied to sysfs, or when the target image still lacks character devices / libgpiod. The kernel keeps sysfs for a migration window and states that new features land only on the new API.
sysfs shape (/sys/class/gpio/):
| Entry | Role |
|---|---|
export / unexport | Export or reclaim a global GPIO number for userspace |
gpioN/direction, gpioN/value | Direction and value |
gpioN/edge, gpioN/active_low | Edge for poll (if supported) and active polarity |
gpiochipN/base, label, ngpio | Controller range (read-only) |
Realistic selection conditions:
- Legacy contract — factory/image scripts are locked to
echo N > exportand replacement cost dominates. - Documented global numbers — the BSP still speaks only “GPIO #23 = …” and you are not remapping to chip+offset yet. (Kernel docs warn numbers can shift with board/card stacks.)
- Missing tools — no
/dev/gpiochip*or libgpiod package; shell file I/O is all you have. - Kernel-exported debug nodes — a driver exposed nodes via
gpiod_export()as a documented BSP interface (still not “new feature development”).
Do not choose sysfs for:
- Default GPIO path in new apps/services — contradicts the kernel warning.
- Bitbashing hardware that already has a proper kernel driver — banned for both sysfs and chardev (“Do NOT abuse…”).
- Replacing the gpiomon-edge article — edge wait/options live elsewhere.
What are the benefits of libgpiod?
One-line answer: libgpiod wraps the GPIO character device (ioctl) in a C library, bindings, and CLI. Closing the device FD frees allocated resources; reliable events, multi-line get/set, and open-drain/open-source-style configs live on the character-device side—not on obsolete sysfs.
Selection-axis benefits (why pick it, not a howto):
| Axis | sysfs | character device + libgpiod |
|---|---|---|
| Identity | Global GPIO number | Chip (/dev/gpiochipX) + line offset (or line name) |
| Lifetime | Exported node / file state | Line Request; resources freed when the device FD closes (libgpiod/kernel) |
| Events | poll on value, etc. | Edge events on a line request (tools/API → other posts) |
| Multi-line | Repeat per file | Get/set multiple values in one request |
| Electrical config | Limited | Open-drain/open-source, bias, etc. (uAPI/library) |
| New features | None (obsolete) | New features only here |
Default to libgpiod (or the same chardev uAPI) when:
- New userspace — the documented recommendation.
- Stable chip + offset (or line name) — avoid shaky global numbers.
- Lifetime tied to process FDs — cleanup on exit.
- Multi-line, edge, drive/bias configs — do not paper over with sysfs.
This post does not explain gpiodetect / gpioget / gpioset / gpiomon flags. See the libgpiod overview and gpiomon-edge posts for how.
What about permissions?
One-line answer: Different interfaces, different surfaces. sysfs fails when export / gpioN attribute files under /sys/class/gpio are not writable; libgpiod usually needs to open /dev/gpiochip*. If the distro defaults to root-only, fix with udev GROUP/MODE + group membership.
| sysfs | libgpiod / chardev | |
|---|---|---|
| Object | export/unexport, gpioN/* | /dev/gpiochipX (then request FD) |
| Typical failure | Permission denied on export/value | Permission denied opening gpiochip |
| Non-root practice | Board/image udev chgrp/chmod on sysfs nodes (often a gpio group) | Rules like SUBSYSTEM=="gpio", KERNEL=="gpiochip*", GROUP="gpio", MODE="0660" + add the user to gpio |
Example (adjust to your image; group name varies):
# /etc/udev/rules.d/90-gpio.rules (example)
SUBSYSTEM=="gpio", KERNEL=="gpiochip[0-9]*", GROUP="gpio", MODE="0660"
sudo groupadd -f gpio
sudo usermod -aG gpio "$USER"
# re-login, then
ls -l /dev/gpiochip*
# expect e.g. crw-rw---- root gpio
Checks:
- Freeze the path first — legacy sysfs → sysfs node perms; new work → gpiochip node perms.
- Do not validate only under sudo — the service user/group must match runtime.
- udev reload/trigger, then
ls -l— rules without applied group leave Permission denied. - Do not drive the same line on both interfaces “temporarily” — ownership/request clashes. Migration means converging on one path.
FAQ
May a new project still use sysfs?
The kernel tells new development to use the character device API and encourages migrating existing code. Treat sysfs as migration/legacy maintenance.
Can I use /dev/gpiochip without libgpiod?
Yes—talk the kernel uAPI (ioctl) yourself. libgpiod wraps those ioctls. The new-path selection is the character device.
How does this differ from the libgpiod overview and gpiomon-edge posts?
Overview = Chip/Line Request and get/set flow. gpiomon-edge = edge options vs polling. This post only chooses sysfs vs libgpiod (chardev), plus benefits and permissions.
What about chardev v1?
Obsolete docs also list Character Device Userspace API (v1). Current docs describe v2 (kernel 5.10+). Match libgpiod major version to the uAPI your kernel provides.
Sources
- GPIO Sysfs Interface — obsolete warning, export/value/edge, gpiochip sysfs
- Obsolete GPIO Userspace APIs — sysfs and chardev v1
- GPIO Character Device Userspace API — Chip, Line Request,
/dev/gpiochipX - libgpiod documentation — sysfs replacement, FD release, events, multi-value, open-drain/source