Power button and embedded Linux
Not all embedded Linux systems has a power button, but for consumer electronic devices it could be a good thing to be able to turn it off. But how does it work in practice?
Physical button
It all starts with a physical button.
At the board level, the button is usually connected to a General-Purpose-Input-Output (GPIO) pin on the processor. It doesn't have to be directly connected to the processor though, all that matters is that the button press can somehow be mapped to the Linux input subsystem.
The input system reports certain key codes to a process in user space that listen for those codes and performs the corresponding action.
All possible keycodes is listed in linux-event-codes.h [1]. The ones that are relevant to us are:
- KEY_POWER
- KEY_SLEEP
- KEY_SUSPEND
- KEY_RESTART
Map the button to the input system
To map a certain GPIO to the input system we make use of the gpio-keys kernel driver [2]. Make sure you have the CONFIG_KEYBOARD_GPIO option selected in your kernel configuration.
The mapping is described in the devicetree. The key (pun intended) is to set the right key-code on the linux,code property.
See example below:
gpio-keys { compatible = "gpio-keys"; pinctrl-names = "default"; pinctrl-0 = <&button_pins>; status = "okay"; key-power { label = "Power Button"; gpios = <&gpio 7 GPIO_ACTIVE_LOW>; linux,code = <KEY_POWER>; wakeup-source; }; key-restart { label = "Restart Button"; gpios = <&gpio 8 GPIO_ACTIVE_LOW>; linux,code = <KEY_RESTART>; wakeup-source; }; key-suspend { label = "Suspend Button"; gpios = <&gpio 9 GPIO_ACTIVE_LOW>; linux,code = <KEY_SUSPEND>; wakeup-source; }; };
Hopefully you will now have the input device registered when you boot up:
1 $ dmesg | grep input
2 [ 6.886635] input: gpio-keys as /devices/platform/gpio-keys/input/input0
systemd-logind
We now have an input device that reports key-events when we press the button. The next step is to have something that read these reports and act on them on the userspace side.
On systems with systemd, we have the systemd-logind service. This service is responsible for a lot of things including:
- Keeping track of users and sessions, their processes and their idle state. This is implemented by allocating a systemd slice unit for each user below user.slice, and a scope unit below it for each concurrent session of a user. Also, a per-user service manager is started as system service instance of user@.service for each logged in user.
- Generating and managing session IDs. If auditing is available and an audit session ID is already set for a session, then this ID is reused as the session ID. Otherwise, an independent session counter is used.
- Providing polkit[1]-based access for users for operations such as system shutdown or sleep
- Implementing a shutdown/sleep inhibition logic for applications
- Handling of power/sleep hardware keys
- Multi-seat management
- Session switch management
- Device access management for users
- Automatic spawning of text logins (gettys) on virtual console activation and user runtime directory management
- Scheduled shutdown
- Sending "wall" messages
Is is of course possible to have your own listener, but as we have systemd-logind and ... I sort of started to like systemd, we will go that way.
Configure systemd-logind
systemd-logind has its configuration file [3] in /etc/systemd/logind.conf where it's possible to set an action for each event. Possible actions are:
Action | Description |
---|---|
ignore | systemd-login will never handle these keys |
poweroff | System will poweroff |
reboot | System will reboot |
halt | System will halt |
kexec | System will start kexec |
suspend | System will suspend |
hibernate | System will hybernate |
hybrid-sleep | |
suspend-then-hibernate | |
lock | All running sessions will be screen-locked |
factory-reset |
The events that are configurable are:
Configuration item | Listen on | Default action |
---|---|---|
HandlePowerKey | KEY_POWER | poweroff |
HandlePowerKeyLongPress | KEY_POWER | ignore |
HandleRebootKey | KEY_RESTART | reboot |
HandleRebootKeyLongPress | KEY_RESTART | poweroff |
HandleSuspendKey | KEY_SUSPEND | suspend |
HandleSuspendKeyLongPress | KEY_SUSPEND | hibernate |
HandleHibernateKey | KEY_SLEEP | hibernate |
HandleHibernateKeyLongPress | KEY_SLEEP | ignore |
It's not obvious how long a long press is, I could not find anything about it in the documentation. However, the code [4] tells us that it's 5 seconds.
1#define LONG_PRESS_DURATION (5 * USEC_PER_SEC)
power-switch tag
The logind service will only watch input devices with the power-switch udev tag for keycodes, so there has to be a udev-rule that sets such a tag. The default rule that comes with systemd, 70-power-switch.rules, looks as follow:
ACTION=="remove", GOTO="power_switch_end" SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_SWITCH}=="1", TAG+="power-switch" SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_KEY}=="1", TAG+="power-switch" LABEL="power_switch_end"
All input devices of the type ID_INPUT_SWITCH or ID_INPUT_KEY will then be monitored.
We can confirm that the tag is set for our devices we created earlier:
1 $ udevadm info /dev/input/event0
2P: /devices/platform/gpio-keys/input/input0/event0
3M: event0
4R: 0
5U: input
6D: c 13:64
7N: input/event0
8L: 0
9S: input/by-path/platform-gpio-keys-event
10E: DEVPATH=/devices/platform/gpio-keys/input/input0/event0
11E: DEVNAME=/dev/input/event0
12E: MAJOR=13
13E: MINOR=64
14E: SUBSYSTEM=input
15E: USEC_INITIALIZED=7790298
16E: ID_INPUT=1
17E: ID_INPUT_KEY=1
18E: ID_PATH=platform-gpio-keys
19E: ID_PATH_TAG=platform-gpio-keys
20E: DEVLINKS=/dev/input/by-path/platform-gpio-keys-event
21E: TAGS=:power-switch:
22E: CURRENT_TAGS=:power-switch:
Summary
The signal path from a physical power button up to a service handler in userspace is not that complicated. Systemd makes handling the signals and what behavior they should have both smooth and flexible.
References
[1] | https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/input-event-codes.h?h=v6.8-rc3 |
[2] | https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/input/keyboard/gpio_keys.c?h=v6.8-rc3 |
[3] | https://www.freedesktop.org/software/systemd/man/latest/logind.conf.html |
[4] | https://github.com/systemd/systemd/blob/fe0acbf7e061eb5be2708f0dbb9275084abc97af/src/login/logind-button.c#L22 |