Adventures with my Pinephone - A Matter of Time (Part 1)

Posted on 22 April 2021 by vkraven

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;

	timer = timerfd_create(CLOCK_REALTIME_ALARM, TFD_CLOEXEC);
	printf("timerfd_create returned %d\n", timer);

	if (timer != -1) {
		close(timer);
	}

	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)