-

Trixie / libgpiod 2.x issues

From Geekworm Wiki
Jump to navigation Jump to search

The Trixie-branch xPWR.sh and xSoft.sh updated for libgpiod 2.2.x have a behavioral bug that prevents them from holding GPIO12 and GPIO26 as intended. The scripts run without errors, but the GPIO lines they appear to set are not actually held — the consequences range from the X728 never seeing a "Pi is alive" signal on GPIO12, to GPIO26 being left in an undefined state at shutdown. This post documents the root cause and the corrected scripts.

Root cause. Both scripts use gpioset -c $GPIOCHIP -t0 $PIN=$VALUE to set a line. Under libgpiod 2.x, -t0 means "toggle with period 0," which causes gpioset to set the value and exit immediately. When gpioset exits, the kernel releases the line as part of normal file-descriptor cleanup — that's a property of the GPIO character-device interface, not a libgpiod policy. The line is no longer driven by anything once the process is gone.

Under earlier setups (Bullseye / Bookworm with libgpiod 1.x and the Raspberry Pi 0–4 pinctrl persistence patch), a released output line retained its last commanded level at the pad, so "set and exit" produced a persistent state. That platform behavior is what the original scripts implicitly relied on. On Trixie that no longer holds reliably, so the scripts need to keep a process alive owning the line for as long as the line should be driven.

Fix for xPWR.sh. GPIO12 (boot/alive signal): replace the single-shot gpioset -t0 $BOOT=1 with a backgrounded gpioset whose PID is tracked and released on exit. In libgpiod 2.x, gpioset with no -t flag blocks and holds the line until the process exits, which is exactly the behavior needed:

    # Hold GPIO12 high for the lifetime of this daemon. The X728 MCU watches
    # GPIO12 as the "Pi is alive" signal.
    gpioset -c "$GPIOCHIP" "$BOOT"=1 &
    BOOT_PID=$!
    trap 'kill "$BOOT_PID" 2>/dev/null' EXIT
    trap 'kill "$BOOT_PID" 2>/dev/null; exit 0' TERM INT

A sanity check after a short sleep that kill -0 "$BOOT_PID" still succeeds will catch the case where the line was already owned by something else.

Fix for xSoft.sh. The same -t0 problem applies to xSoft.sh's gpioset -t0 $BUTTON=1 and gpioset -t0 $BUTTON=0, with worse consequences: GPIO26 is left in an undefined state at shutdown, which the X728 MCU latches as a stuck button. The comment in the original xSoft.sh describes the symptom this produces ("you will have to press the onboard button twice to turn on the device, and the same applies to the AUTO ON function").

The userspace approach can be made to work — backgrounded gpioset calls with explicit kills — but a userspace process is fundamentally limited: it cannot drive a GPIO during or after the OS halt, because by definition no userspace exists at that point. The cleaner solution is to let the kernel own GPIO26 via the gpio-poweroff device-tree overlay. Add to /boot/firmware/config.txt:

    dtoverlay=gpio-poweroff,gpiopin=26,timeout_ms=6000

With that overlay loaded, the kernel holds GPIO26 inactive (low) for the entire time the Pi runs and drives it high at the moment of poweroff, held through the halt by the kernel until the X728 cuts power. timeout_ms=6000 gives margin over the ~4s the MCU needs; the 3s default is marginal. Default polarity (no active_low) is correct because high = simulated button press.

With the overlay in place, xSoft.sh no longer needs to drive GPIO26 — it can't, because the overlay owns the line, and it shouldn't need to. Software shutdown becomes sudo shutdown -h now; the kernel poweroff path triggers gpio-poweroff automatically. xSoft.sh can be removed entirely (the X728-script wiki's warning not to use shutdown applies to the no-overlay setup; with gpio-poweroff loaded, shutdown is the correct command).

Verification. After rebooting with the overlay added: gpioinfo -c 0 26 should show output consumer="power_ctrl". While the daemon is running: gpioinfo -c 0 12 should show output consumer="gpioset". Both being present is the prerequisite for any further testing of X728 behavior.

Battery reader. The sample x728-v2.x-bat.py (and asd.py) uses import RPi.GPIO and GPIO.setup(26, GPIO.OUT) despite reading the fuel gauge entirely over I2C — the GPIO26 setup is vestigial. With the gpio-poweroff overlay in place, RPi.GPIO poking GPIO26 will clash with the kernel and throw an error. The fix is to delete the RPi.GPIO imports and calls; the reader is then pure I2C and has no GPIO involvement at all. The same applies to any script that drives GPIO26 to trigger shutdown (plsd.py, plsd-gpiod.py, the low-voltage shutdown branch in bat.py/asd.py): with the overlay, GPIO26 is owned by the kernel and these scripts should call shutdown -h now directly instead. The plsd-gpiod.py sample additionally uses the libgpiod 1.x Python API (gpiod.Chip(), chip.get_line(), LINE_REQ_EV_BOTH_EDGES, LineEvent), which does not exist in libgpiod 2.x — it needs to be rewritten against the 2.x API or replaced with gpiomon in shell.