pimoroni / clean-shutdown

Python daemon to watch a GPIO pin and trigger a clean shutdown.
MIT License
124 stars 39 forks source link

Pi does not shutdown any more after upgrade to bookworm #37

Open elhennig opened 5 months ago

elhennig commented 5 months ago

Hi, I upgraded my raspberry pi 4 to bookworm and kernel 6.6 and even if the GPIO pin 17 is triggered the clean shutdown down does not respond or shutdown the raspberry pi anymore. I also changed the command from raspi-gpio to pinctrl as messages lead to the info the the command raspi-gpio is deprecated. Any idea how to debug this more or even better solve the issue?

Gadgetoid commented 5 months ago

I think a systemd service was always a bit overkill for poweroff, and no doubt has been hopelessly broken by bookworm- if not due to be shortly, since it uses sysfs GPIO.

Might be worth trying to add:

dtoverlay=gpio-poweroff,gpiopin=17,active_low=0

To /boot/firmware/config.txt

uh... I can't remember if it needs to be active low or not 😬

elhennig commented 5 months ago

Strange is, that is was working before the OS and kernel update. I will check /boot/firmware/config.txt anyway.

But I guess it is active_low. If I check the pin manually with # while true; do pinctrl get 17; sleep 1 ;done I get

17: ip pu | hi // GPIO17 = input 17: ip pu | hi // GPIO17 = input 17: ip pu | hi // GPIO17 = input 17: ip pu | hi // GPIO17 = input 17: ip pu | lo // GPIO17 = input 17: ip pu | lo // GPIO17 = input 17: ip pu | lo // GPIO17 = input 17: ip pu | lo // GPIO17 = input 17: ip pu | hi // GPIO17 = input 17: ip pu | hi // GPIO17 = input 17: ip pu | hi // GPIO17 = input 17: ip pu | hi // GPIO17 = input 17: ip pu | hi // GPIO17 = input 17: ip pu | hi // GPIO17 = input

with the lo values while pressing the button.

elhennig commented 5 months ago

I did some more testing: Shutdown works if I set the PIN 4 explicitly on the shell: raspi-gpio set 4 op dl

The clean shutdown daemon confiuration look slike this: trigger_pin=17 led_pin=17 poweroff_pin=4 hold_time=1 shutdown_delay=0 polling_rate=1 However if I press the switch the PIN 4 is not changed, only 17.

What is the correct setup: PIN 4 for shutdown and 17 for the restart trigger?

Gadgetoid commented 5 months ago

That looks right-

If raspi-gpio set 4 op dl immediately cuts power, then you'll need to add dtoverlay=gpio-poweroff,gpiopin=4,active_low=1 to /boot/firmware/config.txt.

There's also a straight dtoverlay approach for the shutdown button, but it doesn't have any long-press or LED indicator functionality: https://github.com/raspberrypi/linux/blob/rpi-6.1.y/arch/arm/boot/dts/overlays/gpio-shutdown-overlay.dts

And from the docs:

Name:   gpio-shutdown
Info:   Initiates a shutdown when GPIO pin changes. The given GPIO pin
        is configured as an input key that generates KEY_POWER events.

        This event is handled by systemd-logind by initiating a
        shutdown. Systemd versions older than 225 need an udev rule
        enable listening to the input device:

                ACTION!="REMOVE", SUBSYSTEM=="input", KERNEL=="event*", \
                        SUBSYSTEMS=="platform", DRIVERS=="gpio-keys", \
                        ATTRS{keys}=="116", TAG+="power-switch"

        Alternatively this event can be handled also on systems without
        systemd, just by traditional SysV init daemon. KEY_POWER event
        (keycode 116) needs to be mapped to KeyboardSignal on console
        and then kb::kbrequest inittab action which is triggered by
        KeyboardSignal from console can be configured to issue system
        shutdown. Steps for this configuration are:

            Add following lines to the /etc/console-setup/remap.inc file:

                # Key Power as special keypress
                keycode 116 = KeyboardSignal

            Then add following lines to /etc/inittab file:

                # Action on special keypress (Key Power)
                kb::kbrequest:/sbin/shutdown -t1 -a -h -P now

            And finally reload configuration by calling following commands:

                # dpkg-reconfigure console-setup
                # service console-setup reload
                # init q

        This overlay only handles shutdown. After shutdown, the system
        can be powered up again by driving GPIO3 low. The default
        configuration uses GPIO3 with a pullup, so if you connect a
        button between GPIO3 and GND (pin 5 and 6 on the 40-pin header),
        you get a shutdown and power-up button. Please note that
        Raspberry Pi 1 Model B rev 1 uses GPIO1 instead of GPIO3.
Load:   dtoverlay=gpio-shutdown,<param>=<val>
Params: gpio_pin                GPIO pin to trigger on (default 3)
                                For Raspberry Pi 1 Model B rev 1 set this
                                explicitly to value 1, e.g.:

                                    dtoverlay=gpio-shutdown,gpio_pin=1

        active_low              When this is 1 (active low), a falling
                                edge generates a key down event and a
                                rising edge generates a key up event.
                                When this is 0 (active high), this is
                                reversed. The default is 1 (active low).

        gpio_pull               Desired pull-up/down state (off, down, up)
                                Default is "up".

                                Note that the default pin (GPIO3) has an
                                external pullup. Same applies for GPIO1
                                on Raspberry Pi 1 Model B rev 1.

        debounce                Specify the debounce interval in milliseconds
                                (default 100)
Name:   gpio-poweroff
Info:   Drives a GPIO high or low on poweroff (including halt). Using this
        overlay interferes with the normal power-down sequence, preventing the
        kernel from resetting the SoC (a necessary step in a normal power-off
        or reboot). This also disables the ability to trigger a boot by driving
        GPIO3 low.

        The GPIO starts in an inactive state. At poweroff time it is driven
        active for 100ms, then inactive for 100ms, then active again. It is
        safe to remove the power at any point after the initial activation of
        the GPIO.

        Users of this overlay are required to provide an external mechanism to
        switch off the power supply when signalled - failure to do so results
        in a kernel BUG, increased power consumption and undefined behaviour.
Load:   dtoverlay=gpio-poweroff,<param>=<val>
Params: gpiopin                 GPIO for signalling (default 26)

        active_low              Set if the power control device requires a
                                high->low transition to trigger a power-down.
                                Note that this will require the support of a
                                custom dt-blob.bin to prevent a power-down
                                during the boot process, and that a reboot
                                will also cause the pin to go low.
        input                   Set if the gpio pin should be configured as
                                an input.
        export                  Set to export the configured pin to sysfs
        active_delay_ms         Initial GPIO active period (default 100)
        inactive_delay_ms       Subsequent GPIO inactive period (default 100)
        timeout_ms              Specify (in ms) how long the kernel waits for
                                power-down before issuing a WARN (default 3000).
elhennig commented 5 months ago

The straight power off is not really what I want. The question is then, why the shutdown daemon does not react on the PIN 17 lo signal. How can I debug the daemon script?

elhennig commented 5 months ago

I found several problems with the bash script itself, with the outputs of raspi-gpio, that seem to have changed and some number comparison errors in if statements. Still I do have an error after fixing the script: The systems shuts down but comes up again immediately (at least for the bootloader, the red LED is on, the OS not available

Gadgetoid commented 5 months ago

with the outputs of raspi-gpio

Yup Bookworm has broken a whole lot, and I'm nearly 5 months in to trying to fix it all with no end in sight. The lack of sysfs GPIO (needed IIRC for the GPIO to do anything from bash after the system has torn everything else down) means this script probably isn't (easily) fixable. (Note: I haven't touched this code for ~5 years.)

The systems shuts down but comes up again immediately (at least for the bootloader, the red LED is on, the OS not available

Which on/off / power control board are you using?

The straight power off is not really what I want

Not sure what you mean- the mentioned dtoverlays - gpio-shutdown and gpio-poweroff, respectively:

  1. Turn a GPIO pin into a proper system power off button
  2. Nominate a GPIO pin to be activated at the very last stage of power off, to trigger some external hardware to cut the power to the Pi

Combined, they pretty much make this script obsolete. And it seems if you set a high debounce it'll give you a push-and-hold-to-shutdown to avoid any accidental triggers. The only thing missing is fancy LED wrangling, which there's probably a proper driver for too.

elhennig commented 5 months ago

I am using the OnOffShim.

Do I understand right, that the dtoverlay triggers a real shutdown like the command "shutdown -h". What is the better overlay gpio-shutdown or gpio-poweroff in my case? In bullseye the pi completely shut down (red LED off). Can this be done with the overlay config?

What am I doing wrong if the pi immediately stops again after booting? Is this caused by active_low=0?

elhennig commented 5 months ago

I use dtoverlay=gpio-shutdown,gpio_pin=17,active_low=1,gpio_pull=up and it works sufficiently good. Is it right, that I cannot trigger a startup without having cycled the power before?

Gadgetoid commented 4 months ago

You need to use both gpio-shutdown which- and I may mangle terminology here- just acts like a real ACPI power button and gpio-poweroff which toggles a pin to - hopefully - trigger your external board to cut the power completely.

OnOffShim, IIRC, then can latch the power back on when you press the button again.

So maybe:

dtoverlay=gpio-shutdown,gpio_pin=17,active_low=1,gpio_pull=up
dtoverlay=gpio-poweroff,gpiopin=4,active_low=1

Then OnOffShim will cycle the power for you. (Though only if you have the power connected via OnOffShim, if you use the Pi's power connector it can't work)

elhennig commented 4 months ago

OK, will try the proposed config. Of course I use the power connector of the OnOffShim as I did before with bulleseye, too

EDIT: Works perfectly fine, now! Thanks a lot for your support.

Ramblurr commented 3 weeks ago

@elhennig could you share the working config you ended up with?

EDIT:

Ok, so I read back a bit and adding the following from @Gadgetoid

dtoverlay=gpio-shutdown,gpio_pin=17,active_low=1,gpio_pull=up
dtoverlay=gpio-poweroff,gpiopin=4,active_low=1

... works, but it causes the pi to shutdown immediately. One super nice thing about the original implementation was the use of systemd's shutdown hooks to cause a clean shutdown. In my case a clean shutdown is definitely necessary.

Execing pinctrl set 4 op dl causes power to be cut right away, so we just need to modify the scripts accordingly.

Here's my modification to the scripts to make a clean shutdown work with bookworm

/usr/lib/systemd/system-shutdown/gpio-shutoff

Don't forget to make it executable (I use 0750)

#!/usr/bin/bash
set -e
poweroff_pin="4"
if [ "$1" = "poweroff" ]; then
  /usr/bin/pinctrl set "$poweroff_pin" op dl
fi

/usr/local/bin/onoff-shim-trigger

Don't forget to make it executable (I use 0750)

#!/usr/bin/bash
set -e
trigger_pin="17"
led_pin="17"
/usr/bin/pinctrl set "$trigger_pin" ip
power=$(/usr/bin/pinctrl get "$trigger_pin" | awk '{print $5}')
if [ "$power" = "hi" ]; then
  switchtype="lo" # Momentary button
elif [ "$power" = "lo" ]; then
  switchtype="hi" # Not a momentary button
else
  echo "Failed getting trigger_pin=$trigger_pin value (got: '$power')"
  exit 1
fi
until [ "$power" = "$switchtype" ]; do
  power=$(/usr/bin/pinctrl get "$trigger_pin" | awk '{print $5}')
  sleep 1
done
/usr/bin/pinctrl set "$led_pin" op
# Blink the LED
for iteration in 1 2 3 5 6 7 8; do
  /usr/bin/pinctrl set "$led_pin" dl
  sleep 0.2
  /usr/bin/pinctrl set "$led_pin" dh
  sleep 0.2
done
echo "Powering Off!"
/usr/bin/systemctl poweroff

/etc/systemd/system/onoff-trigger.service

[Unit]
Description=OnOff SHIM daemon
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/onoff-shim-trigger
Restart=on-failure

[Install]
WantedBy=multi-user.target

Then:

systemctl daemon-reload
systemctl enable --now onoff-shim-trigger

I moved the blinking to the trigger script because in my usecase a clean shutdown can take awhile as systemd stops services, and I wanted to give feedback to the button-pusher sooner.

ikedah-beyond commented 3 days ago

Hello, excuse my abrupt writing. I had a similar problem on my Pi3 and OnOff-SHIM.

I think one of the causes of the trouble is that the number given to /sys/class/gpio was changed by bookwarm, and the other is that the format of the return value of raspi-gpio get was changed. So, it seems that the following two modifications in the code avoided the trouble.

/lib/systemd/system-shutdown/gpio-poweroff

change gpio num for bookworm

cat /sys/kernel/debug/gpio | grep GPIO$poweroff_pin |cut -f2 -d ' ' |cut -f2 -d '-'

poweroff_pin=516

cat /sys/kernel/debug/gpio | grep GPIO$led_pin |cut -f2 -d ' ' |cut -f2 -d '-'

led_pin=529

/user/bin/cleanshutd trigger_pin_low() {

raspi-gpio get $trigger_pin | grep -q "level=0 fsel=0 func=INPUT"

raspi-gpio get $trigger_pin | grep -q "level=0 func=INPUT"

}

Please refer to the following.