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/):

EntryRole
export / unexportExport or reclaim a global GPIO number for userspace
gpioN/direction, gpioN/valueDirection and value
gpioN/edge, gpioN/active_lowEdge for poll (if supported) and active polarity
gpiochipN/base, label, ngpioController range (read-only)

Realistic selection conditions:

  1. Legacy contract — factory/image scripts are locked to echo N > export and replacement cost dominates.
  2. 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.)
  3. Missing tools — no /dev/gpiochip* or libgpiod package; shell file I/O is all you have.
  4. 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):

Axissysfscharacter device + libgpiod
IdentityGlobal GPIO numberChip (/dev/gpiochipX) + line offset (or line name)
LifetimeExported node / file stateLine Request; resources freed when the device FD closes (libgpiod/kernel)
Eventspoll on value, etc.Edge events on a line request (tools/API → other posts)
Multi-lineRepeat per fileGet/set multiple values in one request
Electrical configLimitedOpen-drain/open-source, bias, etc. (uAPI/library)
New featuresNone (obsolete)New features only here

Default to libgpiod (or the same chardev uAPI) when:

  1. New userspace — the documented recommendation.
  2. Stable chip + offset (or line name) — avoid shaky global numbers.
  3. Lifetime tied to process FDs — cleanup on exit.
  4. 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.

sysfslibgpiod / chardev
Objectexport/unexport, gpioN/*/dev/gpiochipX (then request FD)
Typical failurePermission denied on export/valuePermission denied opening gpiochip
Non-root practiceBoard/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:

  1. Freeze the path first — legacy sysfs → sysfs node perms; new work → gpiochip node perms.
  2. Do not validate only under sudo — the service user/group must match runtime.
  3. udev reload/trigger, then ls -l — rules without applied group leave Permission denied.
  4. 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