Jump to content

uniLoader

From postmarketOS Wiki
uniLoader
uniLoader screen example
uniLoader screen example
Name uniLoader
1st stage
Unavailable
2nd stage
Works
Resources source code

uniLoader is a secondary bootloader that is capable of loading Linux for Android and iOS based devices. Such bootloader embeds ramdisk, upstream kernel and DTB into its own binary image (named uniLoader, which is then copied into RAM) and finally, after relocating upstream kernel, it jumps to the latter.

Porting

Use uniLoader commit history for porting examples (e.g. this one). Generally you'd need to edit/create these files:

  • board/Kconfig
  • board/Makefile
  • board/<vendor>/board-<your_boards_codename>.c
  • configs/<your_boards_codename>_defconfig

board/Kconfig example

config NOTHING_SPACEWAR
		bool "Support for Nothing Phone (1)"
		default n
		depends on SM7325
		help
		  Say Y if you want to include support for Nothing Phone (1)

board/Makefile example

lib-$(CONFIG_NOTHING_SPACEWAR) += nothing/board-spacewar.o

board/<your_vendor>/board-<your_boards_codename>.c example

// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2026 Ryo "evilMyQueen" Yamada <evilMyQueen@mainlining.org>
 */

#include <board.h>
#include <util.h>
#include <drivers/framework.h>
#include <lib/simplefb.h>

static struct video_info spacewar_fb = {
	.format = FB_FORMAT_ARGB8888,
	.width = 1080,
	.height = 2400,
	.stride = 4,
	.scale = 2,
	.address = (void *)0xe1000000
};

static const struct device spacewar_devices[] = {
	{ "simplefb", &spacewar_fb, "fb" },
};

struct board_data board_ops = {
	.name = "nothing-spacewar",
	.ops = {
	},
	.devices = spacewar_devices,
	.num_devices = ARRAY_SIZE(spacewar_devices),
	.quirks = 0
};

configs/<your_boards_codename>_defconfig example

CONFIG_POSITION_INDEPENDENT=y
CONFIG_LINUX_KRNL_HEADER_IMG=y
CONFIG_SM7325=y
CONFIG_NOTHING_SPACEWAR=y
CONFIG_TEXT_BASE=0x83600000
CONFIG_RAMDISK_ENTRY=0xadb6c000
CONFIG_PAYLOAD_ENTRY=0xc5100000

Finding base address

Find your base address, i.e. address in RAM where stock bootloader loads Linux (or uniLoader).

How to find it:

  • Look at bootloader logs (e.g. Booting Linux at 0xf00ba0).
  • Use this method from "U-Boot porting" page.

You need to boot your device a couple of times and see if the address is static or changes between boots. This will inform your actions later.

Defconfig options

CONFIG_POSITION_INDEPENDENT

Setting this to =y informs uniLoader that its base address may change between boots. Technically it makes the uniLoader binary use position-independent code (PIC). Additionally, it makes uniLoader copy itself to the location defined in CONFIG_TEXT_BASE during runtime. You have to use =y if your base address changes between boots. If it doesn't, you can use =n, although =y might also work if other options are provided correctly.

CONFIG_TEXT_BASE

Start address of uniLoader in RAM.
How to calculate (non-PIC):

  • Use your base address without changes.

How to calculate (PIC):

  • Pick an arbitrary non-reserved memory address (inspect /proc/iomem of downstream) with enough space to hold uniLoader binary.

CONFIG_PAYLOAD_ENTRY

Memory address where the Linux kernel is loaded into. This becomes your new base address after uniLoader does its job.

How to calculate:

  • Pick an arbitrary non-reserved memory address (inspect /proc/iomem of downstream) with enough space to hold Linux kernel.

CONFIG_RAMDISK_ENTRY

Memory address where ramdisk is loaded into.

How to calculate:

  • Take linux,initrd-start property in downstream DTS.
  • Pick an arbitrary non-reserved memory address (inspect /proc/iomem of downstream) with enough space to hold ramdisk.

CONFIG_LINUX_KRNL_HEADER_IMG

Adds Linux kernel header to uniLoader executable. Makes your bootloader think it's dealing with Linux kernel instead of some arbitrary binary.

Building uniLoader image

Install the cross-compiler

Debian/Ubuntu-based:

# apt install gcc-aarch64-linux-gnu

RHEL/Fedora-based:

# dnf install gcc-aarch64-linux-gnu

Arch Linux-based:

# pacman -S aarch64-linux-gnu-gcc

Clone the repository and enter its directory

$ git clone https://github.com/ivoszbg/uniLoader
$ cd uniLoader

Prepare the required files

Copy the kernel image, DTB, and ramdisk into the blob directory:

$ cp $HOME/$USER/linux/arch/arm64/boot/Image blob/Image
$ cp $HOME/$USER/linux/arch/arm64/boot/dts/<soc>/<soc>-<your_boards_codename>.dtb blob/dtb
$ cp $HOME/$USER/ramdisk.gz blob/ramdisk

Run the build

$ make ARCH=aarch64 CROSS_COMPILE=aarch64-linux-gnu- <your_boards_codename>_defconfig
$ make ARCH=aarch64 CROSS_COMPILE=aarch64-linux-gnu-

Packing into boot.img

Run this on stock boot.img to get stock mkbootimg command example:

$ unpack_bootimg --boot_img boot.img --out unpack --format=mkbootimg

Example output:

--header_version 1 --kernel unpack/kernel --ramdisk unpack/ramdisk --pagesize 0x00000800 --base 0x00000000 --kernel_offset 0x10008000 --ramdisk_offset 0x00000000 --second_offset 0x00000000 --tags_offset 0x10000100 --cmdline 'androidboot.selinux=permissive'

Now replace value passed to --kernel argument with uniLoader/uniLoader.gz. Add --out argument along with the desired output image name. Use the result with mkbootimg:

$ mkbootimg --header_version 1 --kernel uniLoader --ramdisk unpack/ramdisk --pagesize 0x00000800 --base 0x00000000 --kernel_offset 0x10008000 --ramdisk_offset 0x00000000 --second_offset 0x00000000 --tags_offset 0x10000100 --cmdline 'androidboot.selinux=permissive' --out boot-uniLoader.img

If you're constrained on space, an empty ramdisk instead of the stock one should work:

$ touch empty_ramdisk
$ # use via --ramdisk empty_ramdisk

uniLoader or uniLoader.gz?

You'd want to follow stock boot.img for compression choice. You can check an unpacked kernel you got above:

$ file unpack/kernel

Example output for uniLoader choice:

unpack/kernel: Linux kernel ARM64 boot executable Image, little-endian, 4K pages

Example output for uniLoader.gz choice:

unpack/kernel: gzip compressed data, max compression, from Unix, original size modulo 2^32 7630437

Even if you have a compressed kernel in mkbootimg, uniLoader's blob directory should always contain an uncompressed kernel.

Links