megi's PinePhone Development Log RSS

Surgenons in Gaza Surgeons in Gaza

2022–02–22: Adding LibreELEC.tv to an existing Pinephone multi-distro image

I wanted to try LibreELEC.tv on Pinephone, just for fun, and to see if it will make Pinephone into a nice portable TV. :)

LibreELEC.tv has support for Pine64 A64 SBC, so the system should work fine. The image can be downloaded here: https://libreelec.tv/downloads/allwinner/

None of the images are for Pinephone, though. If you download the image, and extract it, you'll find that it contains kernel build and the DTB for the target board only. :(

Anyway, that will not stop us from trying. Coincidentally, I wanted to write about how easy it is to modify existing multi-boot image, and this is a very good opportunity to demonstrate that.

Adding a new distro to existing multi-boot image

My Pinephone multi-boot image already includes a kernel that works on Pinephone. The only thing we'll have to achieve is to extract root filesystem from the LibreELEC.tv image for Pine64 SBC (see above), and copy the files from it to multi-boot image. Then we'll have to somehow add a boot entry for the new distro, and that will be it.

Getting LibreELEC.tv rootfs contents from the image

Let's start by extracting the rootfs from LibreELEC.tv image. Get the image first:

wget 'https://releases.libreelec.tv/LibreELEC-A64.arm-10.0.1-pine64.img.gz'

It's gzipped, so ungzip it:

gzip -d LibreELEC-A64.arm-10.0.1-pine64.img.gz

The image is partitioned as you can see using sfdisk for example:

sfdisk --dump LibreELEC-A64.arm-10.0.1-pine64.img

Prints:

label: dos
label-id: 0x179a0c97
device: LibreELEC-A64.arm-10.0.1-pine64.img
unit: sectors
sector-size: 512

LibreELEC-A64.arm-10.0.1-pine64.img1 : start=        8192, size=     1048576, type=c, bootable
LibreELEC-A64.arm-10.0.1-pine64.img2 : start=     1056768, size=       65536, type=83

Let's see what's on the first partition…

dd if=LibreELEC-A64.arm-10.0.1-pine64.img of=LibreELEC-A64.arm-10.0.1-pine64.img1 skip=8192 count=1048576
7z x -obootfs LibreELEC-A64.arm-10.0.1-pine64.img1
tree bootfs

Tree will print us:

bootfs
├── extlinux
│   └── extlinux.conf
├── KERNEL
├── KERNEL.md5
├── overlays
│   ├── sun50i-a64-ir.dtbo
│   ├── sun50i-a64-pine64-audio-board.dtbo
│   ├── sun50i-a64-pine64-wifi-bt.dtbo
│   └── sun50i-a64-spdif.dtbo
├── sun50i-a64-pine64.dtb
├── SYSTEM
└── SYSTEM.md5

2 directories, 10 files

Looks like some typical kernel/DTB files and booltoader configuration file,… aaand some suspect SYSTEM file. Check it out:

file bootfs/SYSTEM

And it looks like it's SquashFS image:

bootfs/SYSTEM: Squashfs filesystem, little endian, version 4.0, zstd compressed, \
  111496993 bytes, 9715 inodes, blocksize: 1048576 bytes, created: Fri Oct 29 20:07:52 2021

If we extract it, we will get what we were for: root filesystem contents. Let's leave that for later.

Add new boot entry and the distribution subvolume to the multi-boot image

Multi-boot image has two partitions. The first one contains the boot filesystem with the kernel and other files needed by the bootloader, and the second one contains BTRFS filesystem with one subvolume per distro. During boot, the same kernel is used for all boot options, and which subvolume is used is deterimned by the rootflags=subvol=name kernel boot argument. With BTRFS, you can either mount the toplevel subvolume, or any other subvolume by name if you specify a subvol=name mount option. And this is what the kernel argument above does.

That means that in order to add a new distro to multi-boot image, all we have to do is:

Let's step through that in detail. First we need to get access to the multi-distro image somehow. This can be done from the phone itself, from the PC via uSD card reader, or directly on the downloaded and image that was not flashed yet. In all these cases we will want to have the image represented as a partitioned block device. In all cases except the last one, this is already done by the kernel automatically. To modify the unflashed image, you'd need to use loop device to turn the downloaded and extracted image into a block device (for example via losetup --find --show --partscan image.img).

To find the multi-distro block devices, use sudo blkid | grep 12345678. For me this outputs (among other things):

/dev/mmcblk2p2: UUID="dfe75f46-40a7-4593-902c-1659f6c05f2d" UUID_SUB="6b4a8f9f-d8d3-4f5b-9d66-6d3dcfe9e6c0" BLOCK_SIZE="4096" TYPE="btrfs" PARTUUID="12345678-02"
/dev/mmcblk2p1: PARTUUID="12345678-01"

(In my case I'm modifying an existing multi-distro installation on an eMMC directly on Pinephone, so this found devices /dev/mmcblk2p1 with the boot filesystem and /dev/mmcblk2p2 with the BTRFS. Names of devices will vary depending on your situation.)

Now we will need to mount the BTRFS filesystem, and add LibreELEC.tv root filesystem to it.

mkdir m
sudo mount /dev/mmcblk2p2 m
sudo subvolume create m/electv

And now extract the root filesystem (SYSTEM file) to the new subvolume (remember, it's in SquashFS format):

sudo unsquashfs -f -dest m/electv bootfs/SYSTEM

That's it for the root filesystem. We can unmount it now, and move on:

sudo umount m

Modifying the p-boot's boot filesystem

Now we need to add a boot entry. p-boot uses a custom boot filesystem format that is not understood by the Linux kernel. So we'll need some special tools to modify it. The steps to modify the p-boot's bootfs are in general:

Let's get the necessary tools first. I keep the latest versions statically pre-compiled in p-boot's repository. We will download the two tools needed to do our job from there. You can also compile them yourself, if you're so inclined.

wget https://megous.com/git/p-boot/plain/dist/p-boot-conf
wget https://megous.com/git/p-boot/plain/dist/p-boot-unconf
chmod +x p-boot-{un,}conf

Now that we have all the tools, extract the boot filesystem (remember, /dev/mmcblk2p1 is the p-boot's bootfs partition as we determined earlier):

./p-boot-unconf pboot /dev/mmcblk2p1
tree pboot

Tree will show us the structure of files extracted from the boot filesystem:

pboot
|-- atf-0.img
|-- boot.conf
|-- dtb-0.img
|-- dtb2-0.img
|-- files
|   |-- arch-dreemurrs.argb
|   |-- arch.argb
|   |-- fedora.argb
|   |-- jumpdrive.argb
|   |-- lune.argb
|   |-- maemo.argb
|   |-- manjaro-phosh.argb
|   |-- manjaro-plasma.argb
|   |-- mobian.argb
|   |-- neon.argb
|   |-- off.argb
|   |-- pboot.argb
|   |-- pboot2.argb
|   |-- pmos-dplasma.argb
|   |-- pmos-fbkeyboard.argb
|   |-- pmos-gnome.argb
|   |-- pmos-mplasma.argb
|   |-- pmos-phosh.argb
|   |-- pureos.argb
|   |-- sailfish.argb
|   |-- sxmo.argb
|   |-- ut.argb
|   `-- xnux.argb
|-- initramfs-16.img
|-- linux-0.img
`-- splash
    |-- splash-0.img
    |-- splash-1.img
    |-- splash-10.img
    |-- splash-11.img
    |-- splash-12.img
    |-- splash-13.img
    |-- splash-14.img
    |-- splash-15.img
    |-- splash-16.img
    |-- splash-2.img
    |-- splash-3.img
    |-- splash-4.img
    |-- splash-5.img
    |-- splash-6.img
    |-- splash-7.img
    |-- splash-8.img
    `-- splash-9.img

2 directories, 46 files

We're only interested in boot.conf at this time. It contains the configuration for all the boot options:

device_id = Distro Demo Image 2020-11-23

no = 0
        name        = Arch Linux ARM 2020-11-21
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=arch
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-0.img

no = 1
        name        = Arch Linux ARM / dreemurrs 20201112
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=arch-dreemurrs
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-1.img

no = 2
        name        = Lune OS 2020-11-23
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=lune bootmode=normal LUNEOS_NO_OUTPUT_REDIRECT
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-2.img

no = 3
        name        = Maemo Leste 20201101
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=maemo fbcon=rotate:1
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-3.img

no = 4
        name        = Manjaro / Phosh beta2-20201119
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=manjaro-phosh
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-4.img

no = 5
        name        = Manjaro / Plasma 201122
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=manjaro-plasma
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-5.img

no = 6
        name        = Mobian 20201121
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=mobian splash plymouth.ignore-serial-consoles vt.global_cursor_default=0
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-6.img

no = 7
        name        = KDE Neon 20201123-084050
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=neon splash
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-7.img

no = 8
        name        = pmOS / Plasma Desktop 2020-11-21
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=pmos-dplasma init=/sbin/init PMOS_NO_OUTPUT_REDIRECT
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-8.img

no = 9
        name        = pmOS / fbkeyboard 2020-11-21
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=pmos-fbkeyboard init=/sbin/init PMOS_NO_OUTPUT_REDIRECT
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-9.img

no = 10
        name        = pmOS / GNOME 2020-11-21
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=pmos-gnome init=/sbin/init PMOS_NO_OUTPUT_REDIRECT
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-10.img

no = 11
        name        = pmOS / Plasma Mobile 2020-11-21
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=pmos-mplasma init=/sbin/init PMOS_NO_OUTPUT_REDIRECT
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-11.img

no = 12
        name        = pmOS / Phosh 2020-11-21
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=pmos-phosh init=/sbin/init PMOS_NO_OUTPUT_REDIRECT
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-12.img

no = 13
        name        = Sailfish 1.1-3.3.0.16-devel-20201101
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=sailfish
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-13.img

no = 14
        name        = pmOS / sxmo nightly-202011090018
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=sxmo init=/sbin/init PMOS_NO_OUTPUT_REDIRECT
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-14.img

no = 15
        name        = Ubuntu Touch 2020-11-19
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 quiet loglevel=0 systemd.show_status=false cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=compress-force=zstd,nodatacow,subvol=ut logo.nologo vt.global_cursor_default=0
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        splash      = splash/splash-15.img

no = 16
        name        = Jumpdrive 0.6
        bootargs    = loglevel=0 silent console=tty0 vt.global_cursor_default=0
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
        initramfs   = initramfs-16.img
        splash      = splash/splash-16.img

That's quite a lot of boot options! You can modify this list to your liking. For me, I'll simply add a 17'th boot option for LibreELEC.tv:

cat <<EOF >> pboot/boot.conf
no = 17
        name        = LibreELEC.tv
        bootargs    = console=ttyS0,115200 earlycon=ns16550a,mmio32,0x01c28000 cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=subvol=electv
        atf         = atf-0.img
        dtb         = dtb-0.img
        dtb2        = dtb2-0.img
        linux       = linux-0.img
EOF

I've removed all the kernel silencing options, because when playing with new distro, we usually want all the debugging aids we can get. The most important kernel argument is rootflags=subvol=electv that one determines what subvolume this p-boot menu option will use. Don't forget to change no option to an unique boot option index, either. In my case, this is 17.

To write the modifications to boot partition, we will run:

./p-boot-conf pboot /dev/mmcblk2p1

The tool will print a bynch of detailed information about what it did, and we can now reboot to test the new distribution. :)

Boot testing

Here's a first video of the boot test:

Pretty good! :)

And it looks like LibreELEC.tv also has a touch interface:

The only problem is that Pinephone LCD panel has native resolution 720×1440, which is not exactly great for watching videos, or using LibreELEC.tv's UI for that matter (as you can see from the videos). One would have to figure out some option for rotating the display.

Using LibreELEC.tv with external monitor

Pinephone Convergence Dock to the rescue. :) LibreELEC.tv doesn't support hotplugging the external monitor on Pinephone. It keeps displaying UI on the primary LCD. We need to nudge it a bit, to do what we want. The easiest way to do it is to disable the internal display using a kernel argument (video=DSI-1:d).

For that we can modify bootargs line in boot.conf and re-run p-boot-conf exactly as before. We don't need to run p-boot-unconf anymore from now on, as long as we don't delete the pboot directory.

The final line will be:

bootargs    = quiet loglevel=0 cma=256M console=tty1 consoleblank=0 panic=3 rw rootwait root=PARTUUID=12345678-02 rootfstype=btrfs rootflags=subvol=electv video=DSI-1:d

And let's try rebooting again, with dock connected to the phone and to the external monitor:

Pretty good! :)

Now all I need to do is connect a keyboard/mouse and play with it a bit. It looks quite snappy and the video playback should be accelerated on Pinephone!

With p-boot, you can have your Pinephone serve as a mobile OS and TV box at the same time. :) You can simply add LibreELEC.tv as one of the boot options, and reboot your phone to it wherever you need to have a TV box. :)

It looks like LibreELEC.tv is comming soon to next multi-distro demo image, after I do more testing! If you want to have it sooner than that. This article shall be your guide. ;-) You can add any distro you like, this way.