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.

IdentifierMeaningStability
X in /dev/gpiochipXChardev node indexDepends on enumeration—extra chips or load order shifts it
gpiochip_info.nameKernel chip nameOften mirrors the node name (gpiochip0, …); does not by itself prove which hardware
gpiochip_info.labelFunctional labelHint for which controller; may be empty
Line offsetLocal index on that chipStable for the same chip; wrong chip → same offset, different pin
Line nameBoard/chip line namePrefer 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):

  1. I2C/SPI/USB GPIO expanders probe late and slot before/after the SoC chip.
  2. Module load or DT overlay order differs across images/kernels.
  3. Chip count changes (new expander, disabled controller).
  4. 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:

  1. List — gpiodetect prints gpiochipN [label] (N lines); omit args to list all.
  2. Match label — record which N currently carries the expected controller label (or name). Do not memoize N alone.
  3. Inspect lines — gpioinfo <chip> for offset, name, consumer, in-use. Prefer line names in apps when present.
  4. Open policy — config should store a chip path/name or resolve-by-label, not a bare 0. libgpiod may treat 0, gpiochip0, and /dev/gpiochip0 as the same specifier—but which hardware that specifier opens can still change across boots.
  5. udev (optional) — match parent/attrs and create a fixed symlink to the right /dev/gpiochip*; apps open the link only.
  6. sysfs cross-check — compare /sys/bus/gpio/devices/gpiochipN/label (and related attrs) with gpiodetect.
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

SymptomSuspect
open /dev/gpiochipN failsN gone (fewer chips) or permissions
open OK, request/get/set failsoffset on the wrong chip, line USED, consumer clash
values change but hardware silent / other device reactssame N, different controller
only some images/kernels breakmodule/overlay order → renumber
legacy sysfs export global numbers also wrongmixing obsolete global numbers with chardev N
  1. gpiodetect — chip count, labels, lines; compare to expected labels.
  2. Map expected label → path/name — refresh the table; stop treating N as identity.
  3. gpioinfo on 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…”).
  4. App config — what string was opened; log path and resolved label.
  5. /sys/bus/gpio/devices/ — cross-check label/ngpio; do not mix class-gpio base IDs.
  6. Permissions — /dev/gpiochip* group/mode via udev before calling it “renumber.”
  7. gpiodetect before/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