I received my Pinephone near the end of last week, so this week was entirely spent getting oasis up and running on it.
Rather than building a system image from scratch — I should probably write
a script to do this in the long term — I started with a postmarketOS
image. I replaced the rootfs with an oasis rootfs, built a static kernel
based on megi's kernel (essentially using sed "s/=m/=y/"
on the config), and modified the postmarketOS u-boot script to suit this new
setup. To aid in debugging my broken u-boot script, I used the Pinephone's
serial interface which can be accessed by toggling the 6th DIP switch, and
connecting to the headphone jack. Unfortunately I had to sacrifice an audio
cable and do some sketchy wiring using alligator clips, but after compiling USB
serial support into my laptop's kernel, I had a functioning if fragile serial
connection to the phone. After spending some time screwing around with the
u-boot script, I had oasis booting.
At this point, I would be able to get wifi and an ssh connection going, so I
could ditch the serial interface. oasis uses perp
for managing services, so enabling ssh was as simple as toggling the sticky
bit on. For wifi, I had to write my own services for perp, but this was pretty
straightforward. The only challenge was learning some idiosyncracies of the rc
shell's syntax, which all of the oasis services are written in. I could have used
ksh
as the shell language, but I figure if I'm using oasis, I might as well
do it the oasis way.
Given that I intend for my phone interface to be graphical, a natural next
step was getting a graphical environment going, in this case velox
.
Disappointingly, it did not work out of the box. It wasn't printing error
messages either, so I resorted to annotating it with printf
s (something
that is only practical because of oasis's build system), and found that the
issue was with swc's DRM initialization. Specifically, the following in
drm_initialize
caused it to fail:
if (drmSetClientCap(swc.drm->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1) < 0) {
ERROR("Could not enable DRM universal planes\n");
goto error1;
}
This error occured when it used card0
as the primary card, but I noticed that
there were two cards in /sys/class/drm
. So I tried hardcoding it to use card1
.
It made it a little bit farther, but failed on the following lines:
drm.path = drmGetRenderDeviceNameFromFd(swc.drm->fd);
if (!drm.path) {
ERROR("Could not determine render node path\n");
goto error1;
}
After several hours of alternating between reading about DRM, banging my head
against a wall and comtemplating calling it a day (having done nothing), I found
the
information that I needed
to make sense of this. On desktop GPUs, it is the graphics card that does both
hardware accelerated rendering and display output (scan out). This explains
why on a desktop you plug monitors directly into the graphics card, rather than
the motherboard. On many ARM devices it seems, it is different. The tasks of
hardware accelerated rendering and scan out can be split into different
devices. On the Pinephone, the Mali 400 GPU does the rendering, while the
Display Engine on the A64 chip does scan out. So card0
is the GPU, and card1
is the Display Engine. This explains why card0
doesn't have the universal
planes capability, and card1
doesn't have an associated render node. I also
assume that this wasn't taken into account when swc
was built, because it
wasn't developed for this environment. To fix this, I added a check that
ensures that the primary device used by swc
has the universal planes
capability. I also made it just use a device with the path /dev/dri/renderD*
as the render node, the way the article above recommends.
With this fixed, velox
seemed to work. After enabling hotplugd
, I could
use the keyboard to spawn terminal windows.
The last thing that I worked on this week was touch support in swc
. I
implemented basic touch support based on the existing pointer
and keyboard
input devices.
Initially, libinput was not sending touch events to swc
and I couldn't figure
out why. I enabled debug output for libinput, and found that it wasn't
detecting the touchscreen as a supported device. It turns out udev
handles
detecting supported devices under normal circumstances, but oasis doesn't use
udev
. So libinput was modified to detect devices based on the events that a
device supports. Here is the relevant code with my humble addition:
if (major(st.st_rdev) == INPUT_MAJOR)
tags |= EVDEV_UDEV_TAG_INPUT;
if (libevdev_has_event_code(evdev, EV_KEY, KEY_ENTER))
tags |= EVDEV_UDEV_TAG_KEYBOARD;
if (libevdev_has_event_code(evdev, EV_REL, REL_X) &&
libevdev_has_event_code(evdev, EV_REL, REL_Y) &&
libevdev_has_event_code(evdev, EV_KEY, BTN_MOUSE))
tags |= EVDEV_UDEV_TAG_MOUSE;
if (libevdev_has_event_code(evdev, EV_ABS, ABS_X) &&
libevdev_has_event_code(evdev, EV_ABS, ABS_Y)) {
if (libevdev_has_event_code(evdev, EV_KEY, BTN_TOOL_FINGER) &&
!libevdev_has_event_code(evdev, EV_KEY, BTN_TOOL_PEN)) {
tags |= EVDEV_UDEV_TAG_TOUCHPAD;
} else if (libevdev_has_event_code(evdev, EV_KEY, BTN_MOUSE)) {
tags |= EVDEV_UDEV_TAG_MOUSE;
} else {
/* TODO account for tablets */
tags |= EVDEV_UDEV_TAG_TOUCHSCREEN;
}
}
Again, because oasis wasn't designed for phones, the touchscreen wasn't
supported out of the box. That last else clause was my addition, and it seems
to work properly, as swc
now received touch events. Using the new touch
support, I added basic window switching, but I still haven't implemented
forwarding touch events to clients. That's for this week. Here's a
demonstration of touch-based window switching:
Finally, I want to touch on my workflow. For most of the week, I have been
pulling the battery and SD card out of the phone, inserting it in a micro-to-SD
converter, inserting it into my laptop, mounting it, and doing a git pull
from the repo containing the rootfs. Then I would perform the same sequence but
backward to update the phone. Even typing it is painful. A few days ago, I set
it up so that I can update without the SD card being removed from the phone at
all. To do this, I use git daemon
to run a git server on my laptop, and added
this git server as a remote on the phone's root repository. Then all it takes
is
# cd /
# git pull laptop pinephone-root
and I can reboot into the updated system without worrying about touching the SD card. There is no other Linux system — as far as I'm aware — where this is so easy. It makes development way easier.