Skip to content

Instantly share code, notes, and snippets.

@stvhay
Last active February 23, 2023 12:13
Show Gist options
  • Save stvhay/83f376ad3d9399bd55f11b016791cdcd to your computer and use it in GitHub Desktop.
Save stvhay/83f376ad3d9399bd55f11b016791cdcd to your computer and use it in GitHub Desktop.
Dissecting the Batocera Boot Image

Batocera Boot Image Dissection (Rockchip rk3588)

Image format

Partition Layout

The Batocera image is a compressed disk image (.img) file with gzip (.gz). The file is mounted to /dev/loop0 with losetup -P --show -f $image and sfdisk -d /dev/loop0 shows the following:

label: gpt
label-id: CBECB451-5B87-4EA1-851B-D727826652F4
device: /dev/loop0
unit: sectors
first-lba: 32768
last-lba: 8945670
sector-size: 512

/dev/loop0p1 : start=       32768, size=     8388608, type=EBD0A0A2-B9E5-4433-87C0-68B6B72699C7, uuid=B849C18A-E422-4875-9B09-3B908F82C469, name="vfat"
/dev/loop0p2 : start=     8421376, size=      524288, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=73E5369E-E0FA-45BE-B88F-72721ED91658, name="userdata"

The first 32768 sectors are reserved for the bootloader, including ATF and U-boot.

Partition Contents Overview

Partition 1 (vfat) contains the following files:

Filename File Type
/boot/batocera Squashfs filesystem, little endian, v4.0, zstd
/boot/rk3588-rock-5b.dtb Device Tree Blob version 17
/boot/linux Linux kernel ARM64 boot Image, little endian, 4K pages
/boot/batocera.board Board identifier string
/boot/initrd.lz4 LZ4 compressed initramfs image
/extlinux/extlinux.conf Configuration file for extlinux
/tools/btrfs_on_windows Contains btrfs tools for Windows users (directory)
/batocera-boot.conf Userland boot configuration parameters for Batocera

Parition 2 (ext4) is an empty ext4 partition.

initrd

The initrd.lz4 archive, when uncompressed unlz4 and using cpio -idv < initrd yields the initramfs.

The initramfs is Busybox and contains ash shell in the file init to bootstrap the system.

init Script Overview

do_root() is the main function of the init script and does the following:

  1. mounts /proc and /sys.
  2. reads kernel commandline for dev and label parameters
  3. mounts /dev.
  4. By calling do_mount() in a loop, which attempts to mount the root partition to /boot_root, the script waits for the root device to be available and mounted.¹
  5. Checks /boot_root/boot/batocera.update and goes through an update process if the file present. The batocera.update file is a replacement squashfs file.
  6. Populates and manages the file system overlay
  7. Mounts the squashfs at /new_root.
  8. Moves existing mounts (/sys, /proc, /dev, /boot, and /overlayinto /new_root mount point, preparing it for boot.
  9. Executes switch_root /new_root /sbin/init, launching regular init.

¹ The mount script looks for dev and label options in the kernel commandline to find a mount point.

The script follows:

Batocera init script

#!/bin/ash

do_mount() {
    if mount -o ro "${1}"           /boot_root; then return 0; fi
    return 1
}

do_root() {
    mkdir -p /boot_root /new_root /overlay_root /sys /proc || return 1
    mount -t proc  -o nodev,noexec,nosuid proc  /proc   || return 1
    mount -t sysfs -o nodev,noexec,nosuid sysfs /sys    || return 1

    # read the parameters
    read -r cmdline < /proc/cmdline
    for param in ${cmdline} ; do
        case ${param} in
            dev=*)   dev=${param#dev=};;
            label=*) label=${param#label=};;
        esac
    done

    # look for devices
    mount -t devtmpfs none /dev

    MOUNTARG=none
    test -n "${dev}"   && MOUNTARG=${dev}
    test -n "${label}" && MOUNTARG=LABEL=${label}

    while ! do_mount "${MOUNTARG}"
    do
        echo "Waiting for the root device"
        sleep 1
    done

    # update the squashfs
    if test -e /boot_root/boot/batocera.update
    then
      mount -o remount,rw /boot_root || return 1
      mv /boot_root/boot/batocera.update /boot_root/boot/batocera || return 1
      # remove the overlay when updating
      if test -e /boot_root/boot/overlay
      then
          mv /boot_root/boot/overlay /boot_root/boot/overlay.old      || return 1
      fi
      mount -o remount,ro /boot_root || return 1
    fi

    # create an overlay on memory
    mount -t tmpfs -o size=256M tmpfs /overlay_root || return 1
    mkdir /overlay_root/base /overlay_root/overlay /overlay_root/work /overlay_root/saved || return 1

    # fill the overlay with the stored one
    if test -f /boot_root/boot/overlay
    then
        # the mount can fail if the fs was open in write and not correctly closed
        if mount -o ro /boot_root/boot/overlay /overlay_root/saved
        then
            cp -pr /overlay_root/saved/* /overlay_root/overlay || return 1
            umount /overlay_root/saved                         || return 1
        fi
    fi

    # mount the squashfs
    mount /boot_root/boot/batocera /overlay_root/base || return 1

    # mount the future root in read write
    if ! mount -t overlay overlay -o rw,lowerdir=/overlay_root/base,upperdir=/overlay_root/overlay,workdir=/overlay_root/work /new_root
    then
        # mount only as squashfs, no overlay (xu4 doesn't support overlayfs)
        mount /boot_root/boot/batocera /new_root || return 1
    fi

    # moving current mounts
    mount --move /boot_root    /new_root/boot    || return 1
    mount --move /overlay_root /new_root/overlay || return 1
    mount --move /sys          /new_root/sys     || return 1
    mount --move /proc         /new_root/proc    || return 1
    mount --move /dev          /new_root/dev     || return 1

    # switch to the new root
    exec switch_root /new_root /sbin/init || return 1
}

if ! do_root
then
    echo "oooutch !"
    /bin/ash
fi

SquashFS

To create a squashfs with the same settings, use the following:

squashfs -noappend -processors 12 -b 128k -comp zstd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment