uniLoader
|
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/iomemof 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/iomemof downstream) with enough space to hold Linux kernel.
CONFIG_RAMDISK_ENTRY
Memory address where ramdisk is loaded into.
How to calculate:
- Take
linux,initrd-startproperty in downstream DTS. - Pick an arbitrary non-reserved memory address (inspect
/proc/iomemof 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.