Note: If you don’t care enough about my blabbering and you’re just looking for the code, you can find it on Github. And who can blame you? I blabber a lot =P
This is the first part of the Adventures with my Pinephone - A Matter of Time series. Part 2 may be found at Adventures with my Pinephone - A Matter of Time (Part 2)
The Journey Begins
I don’t know about you, but one of the main functions my smartphones serve for me is being an expensive alarm clock.
It would be nice if that clock had smooth and pleasing animations, of course. And the fact that it also happens to make and receive calls is a minor plus. But I place in it the greatest of trusts - that of ensuring that I am not late for (awful) early morning meetings, amongst other things.
When I received my (KDE Community Edition) Pinephone, one of the first functions I tested was the Clock app. It worked fine enough then, but alas, I noticed that if I set a really long timer or an alarm for the next morning, the phone would not ring until after I had woken the phone up. I imagine it’s an experience all new Pinephone users undergo - to realize that when the Linux phone suspends / sleeps, it truly sleeps, just like a desktop or laptop would.
A Saviour Arrives
Nonetheless, in almost all things, there’s always someone out there more capable than I who has taken a shot at tackling the issue. I found the most wonderful wake-mobile by Kai Lüke, and through it discovered that the systemd system timer has, in fact, the capability to wake the Pinephone through the real-time clock (RTC).
With the following WakeSystem
property:
[Unit]
Description=blah blah...
...
[Timer]
WakeSystem=true
...
Systemd will wake the system up when the timer triggers. Neat!
wake-mobile
was written for a Mobian device. I submitted a tiny patch to get it working on Manjaro ARM, and used it for three months. During those blissful months, wake-mobile
never failed me once. 10/10 Highly Recommend
Notice that I mentioned the systemd system timer has the capability to wake the device from suspend.
Indeed, for the command-line junkies, there’s another simple way to schedule a wake-from-suspend: if your
kernel supports it (and most Pinephone kernels do) there should be a handy pseudo-file exposed by sysfs
at:
/sys/class/rtc/rtcX/wakealarm
With rtcX
being the identifier of your rtc
device, of course. In my case, it’s rtc0
And, as you might expect, writing to sysfs requires root privileges.
To set a wakeup for this very moment on my device, for example:
# echo 1619146342 > /sys/class/rtc/rtc0/wakealarm
Similarly, since only the systemd system timers can wake the device, wake-mobile
used an SUID binary to register and activate the systemd wakeup timer. Yes yes, SUID is so bad and scary blah blah.
I still give wake-mobile
a 10/10 Highly Recommend for actually solving a mission-critical problem.
Some Gnome (or was it Mobian?) folks actually did send a request to the Systemd team to expose the WakeAlarm timer to a regular user timer. This was exciting for me back then when
I first got my Pinephone, buuuut it’s been a few months.
Systemd seems to have passed the issue on to PAM, and the issue
sits there still open. Oh wells. Things will move when they move.
A Change in Circumstances
I comfortably used the Manjaro ARM Plasma Mobile image for a few days before getting, uh, somewhat bored by the pace of updates on the Stable / Beta branches. Knowing the risks, I switched over to the Plasma Mobile dev / Nightly images, and was delighted with how haptic feedback was enabled on the keyboard, amongst other features. One awesome effort I “lived through” was the Manjaro team successfully integrating Ofono mobile data into NetworkManager, for example. Great stuff.
Unfortunately, a number of pacman -Syu
operations did lead to PlaMo no longer fully starting up. Because I
don’t enable SSH on my phone by default, and without a working serial-to-USB (or similar) interface
at hand, I did have to re-“flash” older images every once in a while. This was fine for a time - I
did sign up for breakage by switching to the Unstable branch, after all.
It was fine - for a time. After a few months on the Unstable branch, and after yet another KDE breakage, and with the release of PostmarketOS Beta PlaMo images for the Pinephone, I decided to finally switch away from Manjaro ARM and give PostmarketOS Edge a go instead.
In PostmarketOS, I found a lightweight experience that performed surprisingly well. Animations were smooth, haptics were enabled, and almost everything in Plasma Mobile seemed to be working! This greatly pleased me, and I decided to stick with it for a while.
I began to configure my PostmarketOS Pinephone to my liking, pulling in the alpine-sdk
build tools,
git clone-ing wake-mobile
, and…
Oh.
PostmarketOS derives from Alpine, which does not use systemd.
Without systemd, wake-mobile
’s ability to wake the Pinephone (or even to register a timer at all) will
not work.
Oh dear.
Searching for a Solution
I did eventually find a solution I’m pretty pleased with! But to be fair, the original idea wasn’t mine. I was looking through Plasma Mobile’s kclock code when I saw this line:
… if the system is sleeping, it will be woken up …
How curious. It looks like PowerDevil already has the capacity to wake the phone from suspend. Indeed, it looks like Bhushan Shah had already introduced the timerfd solution to Plasma Mobile’s PowerDevil code in Jul 2020. But since the code is already in, the question remains - why wasn’t it already working in Manjaro ARM and PostmarketOS? This mystery required a bit more investigation.
The timerfd
syscalls have been in Linux since 2.6.22, and timerfd
’s ability to
wake the system from suspend has been in since 3.11. Both Manjaro and pmOS run Linux 5.11+
(with megi’s patches), so that box should be checked.
If we have quick look at timerfd
’s man page, we see the following:
… The caller must have the
CAP_WAKE_ALARM
capability in order to set a timer against this clock.
Okay. It bypasses the need for root
privileges through Linux capabilities. So either the Pinephone
kernels don’t support capabilities, or the cap_wake_alarm
capability has not been granted to the
process / user.
To find out, I ran:
$ capsh --print
Current: =
Bounding set =cap_chown,...
So my current user does not have cap_wake_alarm
. But what about Powerdevil itself?
$ getcap -v /usr/lib/libexec/org_kde_powerdevil
Nothing. PowerDevil does not have cap_wake_alarm
either. So that’s why wake-from-suspend
wasn’t working out of the box!
Enumerating Possible Solutions
If cap_wake_alarm
is all that is required to have KDE do what it’s already been designed to do,
then surely the solution simply involves granting either my user (through /etc/security/capability.conf
)
or the org_kde_powerdevil
binary the cap_wake_alarm
capability. Right?
Granting the Capability to my User (NO GOOD)
The first route I tried was granting my user the cap_wake_alarm
privilege. Doing so means adding a line
into /etc/security/capability.conf
, and ensuring that pam_cap.so
is used by a program that gets called
during bootup (and which subsequently activates org_kde_powerdevil
). After looking through the system, it
looked like autologin
(and thus /etc/pam.d/autologin
), which starts tinydm
, which then starts KDE, was
a good candidate. Just to make sure everything is where it should be, I ran:
# find / -name "pam_cap.so"
And… nothing. Okay. The package isn’t installed. I’ll just need to find the package with pam_cap.so
and
add it into pmOS.
$ apk search pam_cap.so
Wait… nothing? No package provides pam_cap.so
in Alpine?
So user-level capabilities is a bust (though it seems to make sense to me as the right solution - just like how
desktop users are allowed to shutdown / reboot the system without being root
).
On to the next option.
Granting the Capability to PowerDevil (NOPE)
Okay then. Maybe I could grant cap_wake_alarm
to the PowerDevil binary instead. Just to make sure that I was
heading in the right direction, I made a simple piece of test code:
#include <stdio.h>
#include <sys/timerfd.h>
#include <unistd.h>
int main() {
int timer;
= timerfd_create(CLOCK_REALTIME_ALARM, TFD_CLOEXEC);
timer ("timerfd_create returned %d\n", timer);
printf
if (timer != -1) {
(timer);
close}
return 0;
}
If timerfd_create
fails, we should get -1, and if it succeeds, we should get an fd.
Either way, the program will print out the result of the timerfd_create
call.
First, I ran the program without granting it cap_wake_alarm
capabilities:
$ ./testrtc
timerfd_create returned -1
Okay, good. That went as expected. Now as root
:
$ sudo ./testrtc
timerfd_create returned 3
Fantastic. That also went as expected. Now, if I grant my testrtc
binary cap_wake_alarm
capabilities, it should also return a fd without requiring root privileges.
$ sudo setcap cap_wake_alarm+ep /home/user/testrtc
Operation not supported
Huh? The fact that capsh
returned sensible values previous suggested that the kernel did
support Linux capabilities. What’s going on?
$ zcat /proc/config.gz | grep -i ext
...
# CONFIG_EXT2_FS_XATTR is not set
# CONFIG_EXT2_FS_POSIX_ACL is not set
# CONFIG_EXT2_FS_SECURITY is not set
# CONFIG_EXT3_FS_POSIX_ACL is not set
# CONFIG_EXT3_FS_SECURITY is not set
# CONFIG_EXT4_FS_POSIX_ACL is not set
# CONFIG_EXT4_FS_SECURITY is not set
...
Oh. No xattrs.
I have no idea why the pmOS Pinephone kernel is configured without xattrs. Although it seems harmless enough to me, I’m not familiar enough with the Pinephone to say if it leads to some breakage somewhere else down the line or not. If you happen to know the reason, please do share!
One quick
pmbootstrap kconfig edit linux-postmarketos-allwinner
pmbootstrap kconfig check
pmbootstrap checksum
pmbootstrap build --force linux-postmarketos-allwinner
pmbootstrap install --sdcard
later (and with a bit of help from the folks on the pmOS Matrix channel - don’t forget to
bump pkgrel
or else pmbootstrap install
will pull the upstream kernel instead of your own):
$ zcat /proc/config.gz | grep -i ext
...
CONFIG_EXT2_FS=y
CONFIG_EXT2_FS_XATTR=y
CONFIG_EXT2_FS_POSIX_ACL=y
CONFIG_EXT2_FS_SECURITY=y
CONFIG_EXT3_FS=y
CONFIG_EXT3_FS_POSIX_ACL=y
CONFIG_EXT3_FS_SECURITY=y
CONFIG_EXT4_FS=y
CONFIG_EXT4_FS_POSIX_ACL=y
CONFIG_EXT4_FS_SECURITY=y
Perfect! And now:
$ sudo setcap cap_wake_alarm+ep /home/user/testrtc
$ ./testrtc
timerfd_create returned 5
It worked! Fantastic. With the kernel able to understand the cap properties stored in the EXT4 file system’s xattrs, I should be able to grant PowerDevil the very capabilities it needs to wake the phone from sleep.
$ sudo setcap cap_wake_alarm+ep /usr/lib/libexec/org_kde_powerdevil
$ getcap -v /usr/lib/libexec/org_kde_powerdevil
/usr/lib/libexec/org_kde_powerdevil cap_wake_alarm=ep
Very nice. I rebooted the Pinephone and… nothing. The PowerDevil process did not run. When I tried to start it manually, I received the following message:
$ /usr/lib/libexec/org_kde_powerdevil
"Session bus not found\nTo circumvent this problem try the following command (with Linux and bash)\nexport $(dbus-launch)"
What? I checked OpenRC and made a few dbus queries. The session bus was definitely running! After a few moments of making sure I didn’t make a typo somewhere, I turned to Google and came across this message in the Alpine APKBUILD
Alpine actually removes the capability from PowerDevil because it “breaks dbus”. Major sigh
To be continued in Adventures with my Pinephone - A Matter of Time (Part 2)