How to Debug When gpiochip Numbers Shift
If an app always opens /dev/gpiochip0, a later kernel, module order, or extra expander can make that same index mean a different controller. Line requests then fail—or worse, poke the wrong pins. gpiochip renumber debug is not “memorize a new N”; it is see where the number comes from, then select by label/name (or a stable path).
This post covers only where chip numbers come from · how to pin by label/name · break symptoms and check order. Choice of sysfs vs libgpiod (sysfs-gpio-vs-libgpiod), request/CLI deep-dive (libgpiod), and edges (gpiomon-edge) live elsewhere. No invented board timings, pin-map reviews, affiliates, or sample code dumps.
Grounding: GPIO character device (gpiochip_info name/label/lines), gpiodetect (chips by number, name, or path), and /sys/bus/gpio/.
Where do chip numbers come from?
One-line answer: The X in /dev/gpiochipX is a character-device enumeration index. Kernel gpiochip_info.name is the chip’s kernel name; label is a functional name (may be empty). Lines are offsets in [0, lines) on that chip. X can move with boot, modules, or hotplug—so “chip0 is always the SoC GPIO” is not a safe contract.
| Identifier | Meaning | Stability |
|---|---|---|
X in /dev/gpiochipX | Chardev node index | Depends on enumeration—extra chips or load order shifts it |
gpiochip_info.name | Kernel chip name | Often mirrors the node name (gpiochip0, …); does not by itself prove which hardware |
gpiochip_info.label | Functional label | Hint for which controller; may be empty |
| Line offset | Local index on that chip | Stable for the same chip; wrong chip → same offset, different pin |
Line name | Board/chip line name | Prefer when present; may be empty |
Under /sys/bus/gpio/devices/ you typically see gpiochipN entries with attributes such as label and ngpio. Legacy /sys/class/gpio/gpiochipN historically keyed off global base-style IDs—do not assume that N equals /dev/gpiochipN. Debug new userspace against character devices + libgpiod, cross-checking /sys/bus/gpio/.
Common reasons numbers move (not board stopwatch claims):
- I2C/SPI/USB GPIO expanders probe late and slot before/after the SoC chip.
- Module load or DT overlay order differs across images/kernels.
- Chip count changes (new expander, disabled controller).
- Apps ship with a hard-coded
/dev/gpiochip0+ offset and never re-resolve.
Bottom line: the number is an enumeration result; controller identity is name/label (plus udev/path when needed).
How do you pin by label or name?
One-line answer: libgpiod accepts chips by number, name, or path. Use gpiodetect for labels and line counts; prefer the chip whose label matches, or a stable symlink to /dev/gpiochip*, and pick lines by name (if any) then offset on that chip.
Practical pinning order:
- List —
gpiodetectprintsgpiochipN [label] (N lines); omit args to list all. - Match label — record which N currently carries the expected controller label (or name). Do not memoize N alone.
- Inspect lines —
gpioinfo <chip>for offset, name, consumer, in-use. Prefer line names in apps when present. - Open policy — config should store a chip path/name or resolve-by-label, not a bare
0. libgpiod may treat0,gpiochip0, and/dev/gpiochip0as the same specifier—but which hardware that specifier opens can still change across boots. - udev (optional) — match parent/attrs and create a fixed symlink to the right
/dev/gpiochip*; apps open the link only. - sysfs cross-check — compare
/sys/bus/gpio/devices/gpiochipN/label(and related attrs) withgpiodetect.
gpiodetect
gpioinfo gpiochip0
# or: gpioinfo /dev/gpiochip0
ls /sys/bus/gpio/devices/
cat /sys/bus/gpio/devices/gpiochip0/label 2>/dev/null || true
Do not document invented constants like “our board is always chip2.” If label is empty, use name, parent device, or udev—do not fabricate a label string.
What are break symptoms and the check order?
One-line answer: Failures look like open errors, request rejects (in use / bad offset), “works” but wrong pin, or only after reboot. Narrow with list → label match → line info → path the app opened → permissions.
Symptom patterns
| Symptom | Suspect |
|---|---|
open /dev/gpiochipN fails | N gone (fewer chips) or permissions |
| open OK, request/get/set fails | offset on the wrong chip, line USED, consumer clash |
| values change but hardware silent / other device reacts | same N, different controller |
| only some images/kernels break | module/overlay order → renumber |
legacy sysfs export global numbers also wrong | mixing obsolete global numbers with chardev N |
Recommended check order
gpiodetect— chip count, labels, lines; compare to expected labels.- Map expected label → path/name — refresh the table; stop treating N as identity.
gpioinfoon that chip — line name/offset/USED/consumer. If a kernel driver already owns the line, do not bitbang from userspace (chardev docs: “Do NOT abuse…”).- App config — what string was opened; log path and resolved label.
/sys/bus/gpio/devices/— cross-check label/ngpio; do not mix class-gpio base IDs.- Permissions —
/dev/gpiochip*group/mode via udev before calling it “renumber.” gpiodetectbefore/after reboot or module reload — did N move while labels walked?
Debug order:
1) gpiodetect → map N ↔ label
2) match expected label → choose path/name (not bare N in config)
3) gpioinfo <that chip> → line name / offset / consumer
4) app open() target → log path + resolved label
5) /sys/bus/gpio/devices/…/label → cross-check
6) permissions on /dev/gpiochip* → if open fails
7) before/after boot detect diff → confirm renumber vs wrong offset
Takeaway: Most breaks are treating an enumeration index as a hardware contract. Select by label (or stable link) + line name/offset, and debug in detect → info → app path order.
FAQ
Is /dev/gpiochip0 the same as /sys/class/gpio/gpiochip0?
Do not assume yes. Chardev N is an enumeration index; legacy class gpiochip* often follows a different (base-oriented) axis. Prefer /dev/gpiochip* + libgpiod with /sys/bus/gpio/ cross-checks.
What if label is empty?
The kernel allows empty labels. Use name, parent attributes, a udev symlink, or an explicit name/path map captured at provision time—never invent a label.
Can I just change the line offset?
If N slid to another controller, offsets mean something else. Fix chip identity first, then offset/line name.
Can we keep sysfs export global numbers?
That path is obsolete, and global numbers can also shift by board/card mix. This post is about chardev renumber debug; see sysfs-gpio-vs-libgpiod for the choice axis.
Sources
- GPIO Character Device Userspace API —
/dev/gpiochipX,gpiochip_info, offsets and line names - libgpiod — GPIO tools —
gpiodetect/gpioinfo, chip id by number/name/path /sys/bus/gpio/— sysfs cross-check for gpiochip devices- Related: libgpiod, sysfs-gpio-vs-libgpiod, gpiomon-edge