initfs Refresher

In this short post I’ll cover some material about initfs, which could be helpful during (or, more likely, not knowing it will be detrimental to) early mainlining.

Background

Linux boot process

  1. bootloader starts linux kernel, passing along initfs*
  2. kernel does some bookkeeping and then enters initfs environment
  3. initfs job is to prepare the actual boot medium
  4. as a final step initfs chroots to the actual filesystem and runs init process (PID 0), like systemd

The reason for this is to have a more modular approach that supports more systems. The kernel (without modules) has to be able to boot, and reach initfs, then initfs can detect and load various kernel modules (for different file systems, block devices etc), which setup the root partition.

initfs generation on Linux

  • generally initfs is generated by a bunch of scripts lying around in /etc and elsewhere
  • normally, after a new kernel is built (or installed via package), the scripts have to be rerun, because they package some important kernel modules in initfs (which are tied to new kernel)
  • script is called mkinitfs (pmos) or mkinitcpio (Arch), or something of this sort

PostmarketOS notes

  • on postmarketos the process is very similar to what happens on an actual Linux box, with the caveat that the root system is accessible via chroot (pmbootstrap chroot -r to enter the rootfs chroot)
  • you can tweak the experience via pmbootstrap initfs to some extend, like adding telnet support
  • if you need to go lower level, you can look/change the files under /etc/postmarketos-mkinitfs/ and /usr/share/postmarketos-mkinitfs, most notably the init script: /usr/share/postmarketos-mkinitfs/init.sh.in (keep in mind these are relative to the rootfs chroot)
  • then just run mkinitfs -o /boot/initramfs-hack-01 flavor and use generated file

mkinitfs is actually a small bash script, so you can inspect that as well (it does a bit more than just initfs). Newer versions are actually a compiled go executable, so if you want to look around, you have to locate the source first.

Inspecting an initfs

$ # assuming path/to/initfs contains a valid initfs
$ mkdir initfs-expand && cd initfs-expand
initfs-expand$ zcat path/to/initfs | \
               sudo cpio -i --make-directories
initfs-expand$ # look around

Repackage initfs

$ # assuming you extracted a working initfs and did tweaks
initfs-expand$ find . -print0 | \
               cpio --quiet -o -0 -H newc | \
               gzip -1 > ../initfs-hack

I do suggest to always start from a “working” initfs (I mean if you’re changing it this way, I’ll bet it’s not working very well), and tweak something small and re-package it. There are weird device files in this folder, so if you want to build it truly from scratch maybe edit the pmos build script. For example I put a simple arm binary in place of init that returned an exit code, because I wasn’t sure the sh interpreter could be started (yep, I got paranoid).

IRL hacks

  • during early mainlining, it is sometimes not clear whether the kernel hung up, whether init was executed or not, or whether init was responsible. Thankfully, if you exit init with a non-zero exit code OnePlus (at least mine), shows a screen with the exit code, so you can spray your init code with exit with various values and see which one pops up
  • I also packaged a fuller version of busybox at one point (wasn’t sure dynamic linking worked). Thankfully busybox has pre-build statically linked binaries
  • for some reason the mount_partitions step in pmos’s init always ends up making a hole in my foot, so keep an eye on it, you might want to disable it
  • the setup_log step redirects all futher init messages to a log file, which is great when stuff works, but if stuff hangs up on you it’s not so great. Consider commenting it out
  • related to mount_partitions, this step can take considerable time, not really sure what is going on, but on my device it caused the delay of usb networking (telnet) setup by around 430secs (7 minutes). So before smashing your device into pieces in a moment of frustration, just give it some time, and if networking eventually comes up, you know the likely culprit
  • not really initfs related, but just run sudo dmesg --follow on your PC and you can see when the device (i.e USB ethernet gadget) it plugged in, so you know when your phone is ready for telnet
  • the way I iterate is:
    * I boot kernel (using fastboot boot) into initfs
    * telnet 172.16.42.2 from PC to phone
    * run (on PC): nc 172.16.42.2 > 01.dmesg
    * run (on phone) dmesg | nc 172.16.42.2 3001

    So I copy over the dmesg to my pc for further reading and logging. I also keep a log for each dmesg (what did I change, what happened, what I saw, what I expected to see), this way I can go back and investigate, you don’t have to remember every crash, and you can search for clues in old. I’m on number 65 right now (for this device).
  • while in initfs you can copy stuff over (using nc hack above), for example copy over kernel modules and load them. Sometimes a piece of hardware would hang the kernel (or at least prevent it from going forward), so in some cases it helps if I can FIRST connect to the phone, then load it, because there is a chance the kernel won’t hang and I can extract some dmesg, and generally you can prepare better for the hang (like record with another phone or sth). Also if it was a hidden dependency it would just work… so it’s pretty useful
  • using initfs for mainlining is actually easier than packaging a rootfs and flashing that. Not only is rootfs much bigger (even if it’s bare bones), but it also “boots faster” (because it boots only half way), and flashes faster, and writes less to internal/sd storage. Initfs should be used before you get usb, sd/ufs (and all dependencies), and can still be useful afterwards for general display, touch, gpu, wifi.

Updates

  • 2022.07.25 @caleb noted that recent versions of pmos ship with go compiled mkinitfs, not a bash script