Orange Pi PC2 Boot Image Creation
Introduction
This article mainly describes how to create a boot image for the Orange Pi PC2, covering the sunxi H5 chip's bootrom, SPI flash image creation, and SD card image creation. Image creation is closely related to bootrom content; if you are unfamiliar, you can first read this article: OrangePIPC2—bootrom
sunxi (sunxi represents the family of ARM SoCs from Allwinner Technology) Reference: https://linux-sunxi.org/BROM
This article focuses on the H5 variant, explaining how to create NOR flash and SD card images. Since bootrom handles both NOR and SD card operations, special startup instructions are attached below. "eGON" refers to a piece of code in the bootrom.
General Boot Process
From the file uboot/board/sunxi/README.sunxi64:
Boot process
============
The on-die BROM code will try several methods to load and execute the firmware.
On a typical board like the Pine64 this will result in the following boot order:
1) Reading 32KB from sector 16 (@8K) of the microSD card to SRAM A1. If the
BROM finds the magic "eGON" header in the first bytes, it will execute that
code. If not (no SD card at all or invalid magic), it will:
2) Try to read 32KB from sector 16 (@8K) of memory connected to the MMC2
controller, typically an on-board eMMC chip. If there is no eMMC or it does
not contain a valid boot header, it will:
3) Initialize the SPI0 controller and try to access a NOR flash connected to
it (using the CS0 pin). If a flash chip is found, the BROM will load the
first 32KB (from offset 0) into SRAM A1. Now it checks for the magic eGON
header and checksum and will execute the code upon finding it. If not, it will:
4) Initialize the USB OTG controller and will wait for a host to connect to
it, speaking the Allwinner proprietary (but deciphered) "FEL" USB protocol.
To boot the Pine64 board, you can use U-Boot and any of the described methods.
- For SD cards: read 32KB starting from sector 16 (512B per sector, i.e., offset 8K) into SRAM.
- For NOR: read 32KB starting from address 0 into SRAM.
By default, U-Boot debug output is disabled. To enable it, add #define _DEBUG 1 at the top of the relevant C source file.
The SPL binary is located at uboot/spl/sunxi-spl.bin after compilation.
Bootrom Processing
The bootrom is a piece of logic hardcoded in the CPU. Sunxi has open-sourced it; you can look into it. The general flow is: on power-up, it scans the boot media in order. Once a medium is identified, it copies the SPL from that medium to SRAM and executes it. For example, for SPI NOR flash, it copies from address 0; for SD cards, it copies from offset 8K. These offsets are hardcoded in the bootrom. The bootrom knows which medium to use based on hardware, but how does SPL know where to load U-Boot into DRAM? The bootrom makes a small modification: it writes certain information in the SPL header, and SPL reads that information to determine the boot medium, then loadss U-Boot from the corresponding medium.
The SPL header information is in boot_media. In the U-Boot code, it is defined in arch/arm/include/asm/arch-sunxi/spl.h as struct boot_file_head. You can inspect the SPL file spl/sunxi-spl.bin to see the header.
For H3, the SPL file compiled directly from U-Boot works; for H5, it does not work for unknown reasons. I used a precompiled SPL from someone else.
SPL Detection Process
Note: board_init_r is in common/spl/spl.c, which is the generic SPL logic. Different platforms may have their own implementation. Sunxi uses the generic SPL logic. board_init_r in common/board_r.c is for U-Boot's initialization, so be careful to distinguish.
The call chain is: board_init_r -> board_boot_order -> spl_boot_device -> sunxi_get_boot_device, which reads the SPL header to determine the boot medium.
sunxi_get_boot_source is used in SPL code to detect and report the boot source. struct boot_file_head *egon_head = (void *)SPL_ADDR; This address is in SRAM. When the bootrom loads the SPL to this address, it also modifies the header to pass boot medium information. 
The SPL header structure is essentially the SPL binary itself. The field boot_media is modified by the bootrom to indicate the boot medium: 0 for MMC, 1 for NAND, etc. 
NOR Boot
eGON Handling of NOR
In the bootrom assembly code, the function load_from_spinor handles NOR boot. For SPI NOR flash boot, the BROM sets the SPI0 clock to 6 MHz and issues a series of Read Data Bytes (03h) commands. The first command reads 256 bytes from address 0. If a valid eGON header is recognized, it reads twice: first 2048 bytes from address 0, then the rest of the boot0 image starting from address 2048. But how does it know the size of boot0? It obtains this from the boot0 header, which is read in the first read. Then it jumps to boot0 to execute.
The details mostly match, but there is some inconsistency with the PLT information. 
Analysis
As discussed, the image read address is determined by SPL. For SPI media, the relevant file is spl_spi.c (in common/spl). In the function spl_spi_load_image, the variable payload_offs determines where to read the image.
From the code:
- The default value is
CONFIG_SYS_SPI_U_BOOT_OFFS = 0x8000. - If
OF_CONTROLis enabled andOF_PLATDATAis disabled, it reads this value from the device tree under the propertyu-boot,spl-payload-offset. If not set, you need to set it yourself. For H5, this compilation path is not taken.
Here, the default value 0x8000 is used. The process reads 0x40 bytes starting from CONFIG_SYS_SPI_U_BOOT_OFFS to detect the mkimage header (64 bytes) and to check if it contains a device tree.
Dependencies
The spl_spi.c support requires configuration changes. In the SPL Makefile:
obj-$(CONFIG_$(SPL_TPL_)MMC_SUPPORT) += spl_mmc.o
obj-$(CONFIG_$(SPL_TPL_)SPI_LOAD) += spl_spi.o
CONFIG_$(SPL_TPL_)SPI_LOAD must be enibled (though it is not the file itself).
Note: I initially thought this file was spl_spi.c, but after checking the Makefile and finding no such config, I searched for spl_spi and discovered that sunxi has its own implementation.
./arch/arm/mach-sunxi/spl_spi_sunxi.c:275:static int spl_spi_load_image(struct spl_image_info *spl_image,
./arch/arm/mach-sunxi/spl_spi_sunxi.c:312:SPL_LOAD_IMAGE_METHOD("sunxi SPI", 0, BOOT_DEVICE_SPI, spl_spi_load_image);
Makefile:
obj-$(CONFIG_SPL_SPI_SUNXI) += spl_spi_sunxi.o
So the method is:
- Check if
CONFIG_SPL_SPI_SUNXIis enabled.
A Small Question
Why does SPI flash use spl_spi_sunxi.c instead of spl_nor.c? The explanation is:
/*
* Loading of the payload to SDRAM is done with skipping of
* the mkimage header in this SPL NOR driver
*/
It does not have an mkimage header. Looking at the load code:
It first parses the header at CONFIG_SYS_OS_BASE. If the check succeeds and it is a Linux image, it runs Linux directly. If it fails, it parses the header at CONFIG_SYS_UBOOT_BASE and enters U-Boot.
Code Flow
spl_spi_load_image
if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) &&
image_get_magic(header) == FDT_MAGIC) { // Based on u-boot.its image layout, load to corresponding positions (not the U-Boot device tree, another device tree file)
struct spl_load_info load;
debug("Found FIT\n");
load.dev = flash;
load.priv = NULL;
load.filename = NULL;
load.bl_len = 1;
load.read = spl_spi_fit_read;
err = spl_load_simple_fit(spl_image, &load,
payload_offs,
header);
}
- The header read is actually the header of
boot.its, which is a device tree file, so it also has a header containing the size of this device tree file. - In
spl_load_simple_fit, the fullboot.itsis read into memory based on the actual size. - According to the parsing, the image is moved to the DRAM location specified in
boot.its.
Example u-boot.its:
/ {
description = "Configuration to load ATF before U-Boot";
#address-cells = <1>;
images {
uboot {
description = "U-Boot (64-bit)";
data = /incbin/("u-boot-nodtb.bin");
type = "standalone";
arch = "arm64";
compression = "none";
load = <0x4a000000>;
};
atf {
description = "ARM Trusted Firmware";
data = /incbin/("bl31.bin");
type = "firmware";
arch = "arm64";
compression = "none";
load = <0x44000>;
entry = <0x44000>;
};
fdt_1 {
description = "sun50i-h5-orangepi-pc2";
data = /incbin/("arch/arm/dts/sun50i-h5-orangepi-pc2.dtb");
type = "flat_dt";
compression = "none";
};
};
configurations {
default = "config_1";
config_1 {
description = "sun50i-h5-orangepi-pc2";
firmware = "uboot";
loadables = "atf";
fdt = "fdt_1";
};
};
};
Image Creation
According to the bootrom behavior, boot0 (i.e., spl.bin) must be written to flash address 0. What about other files? The address for boot1 is determined by boot0, because once bootrom loads boot0 into SRAM, boot0 takes over execution. So boot1's address is determined by boot0.
Commands:
tftp sunxi-spl.bin
sf erase 0 0x8000
sf write 0x42000000 0 0x8000
tftp u-boot.itb
sf erase 0x8000 0x80000
sf write 0x42000000 0x8000 0x80000
- Write
sunxi-spl.binto NOR flash at address 0. - Write
u-boot.itbto address 0x8000.
Boot Log
resetting ...
INFO: PSCI Affinity Map:
INFO: AffInst: Level 0, MPID 0x0, State ON
INFO: AffInst: Level 0, MPID 0x1, State OFF
INFO: AffInst: Level 0, MPID 0x2, State OFF
INFO: AffInst: Level 0, MPID 0x3, State OFF
U-Boot SPL 2018.03 (Jun 26 2022 - 20:31:13 +0800)
DRAM: 1024 MiB
>>spl:board_init_r()
Trying to boot from sunxi SPI
os :17
Jumping to U-Boot
SPL malloc() used 0x bytes (0 KB)
loaded - jumping to U-Boot...
image entry point: 0x
NOTICE: BL3-1: Running on H5 (1718) in SRAM A2 (@0x44000)
NOTICE: Configuring SPC Controller
NOTICE: BL3-1: v1.0(debug):ae787242
NOTICE: BL3-1: Built : 20:43:27, Feb 21 2018
NOTICE: DT: sun50i-h5-orangepi-pc2
NOTICE: SCPI: dummy stub handler, implementation level: 000000
INFO: BL3-1: Initializing runtime services
INFO: BL3-1: Preparing for EL3 exit to normal world
INFO: BL3-1: Next image address: 0x4a000000, SPSR: 0x3c9
U-Boot 2018.03 (Jun 26 2022 - 20:34:26 +0800) Allwinner Technology
CPU: Allwinner H5 (SUN50I)
Model: OrangePi PC 2
DRAM: 1 GiB
MMC: SUNXI SD/MMC: 0
Loading Environment from FAT... MMC: no card present
** Bad device mmc 0 **
Failed (-5)
Loading Environment from MMC... MMC: no card present
*** Warning - MMC init failed, using default environment
Failed (-5)
In: serial
Out: serial
Err: serial
Net: phy interface7
eth0: ethernet@1c30000
starting USB...
USB0: PA: set_value: error: gpio PA0 not reserved
USB EHCI 1.00
USB1: USB OHCI 1.0
scanning bus 0 for devices... 1 USB Device(s) found
scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot: 0
AK #
AK #
SD Card Boot
eGON Handling of SD Card

Image Creation
From the bootrom behavior, it checks for boot0 starting at offset 8K.
ATF and U-Boot are packaged together into u-boot.itb. This file is written to sector 0x50 * 512B = 40KB.
Operations:
spl: 8K /512 = 0x10 (16 sectors)
0x8000 /512 = 0x30
tftp sunxi-spl.bin
mmc write 0x42000000 10 40
tftp u-boot.itb
40K/512 = 0x50
mmc write 0x42000000 50 400
- Write
spl.binto address 8KB (sector 16). - Write
u-boot.itbto address 40KB (sector 80).
Boot Log
U-Boot SPL 2018.03 (Jun 26 2022 - 20:31:13 +0800)
DRAM: 1024 MiB
>>spl:board_init_r()
Trying to boot from MMC1
spl: bootmode: 1
os :17
Jumping to U-Boot
SPL malloc() used 0x bytes (408 KB)
loaded - jumping to U-Boot...
image entry point: 0x
NOTICE: BL3-1: Running on H5 (1718) in SRAM A2 (@0x44000)
NOTICE: Configuring SPC Controller
NOTICE: BL3-1: v1.0(debug):ae787242
NOTICE: BL3-1: Built : 20:43:27, Feb 21 2018
NOTICE: DT: sun50i-h5-orangepi-pc2
NOTICE: SCPI: dummy stub handler, implementation level: 000000
INFO: BL3-1: Initializing runtime services
INFO: BL3-1: Preparing for EL3 exit to normal world
INFO: BL3-1: Next image address: 0x4a000000, SPSR: 0x3c9
U-Boot 2018.03 (Jun 26 2022 - 20:34:26 +0800) Allwinner Technology
CPU: Allwinner H5 (SUN50I)
Model: OrangePi PC 2
DRAM: 1 GiB
MMC: SUNXI SD/MMC: 0
Loading Environment from FAT... Unable to use mmc 0:0... Failed (-5)
Loading Environment from MMC... *** Warning - bad CRC, using default environment
Failed (-5)
In: serial
Out: serial
Err: serial
Net: phy interface7
eth0: ethernet@1c30000
starting USB...
USB0: PA: set_value: error: gpio PA0 not reserved
USB EHCI 1.00
USB1: USB OHCI 1.0
scanning bus 0 for devices... 1 USB Device(s) found
scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot: 0
AK #
AK #
Why 40KB?
The address 40KB is determined by SPL. In SPL, depending on the boot medium, the corresponding load_image function is executed. For MMC (SD card), the call chain is:
board_init_r->boot_from_devices(determine boot source) ->spl_load_image(function pointer, different for each medium) -> SD card:spl_mmc_load_image->spl_mmc_load(usesMMCSD_MODE_RAW)
There are branches:
- If loading an OS, it goes through
mmc_load_image_raw_os. - If there is a partition (
CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_PARTITION), it goes throughmmc_load_image_raw_partition. - If
CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTORis set, it goes throughmmc_load_image_raw_sector.
Currently, the code uses mmc_load_image_raw_sector. That function:
- Checks the image header (mkimage header, 64 bytes).
- The function parameter specifies the starting MMC address to read the image. Its value is
CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_USE_SECTORplus the offset corresponding to 8K.
Here, raw_sect equals U_BOOT_USE_SECTOR = 0x40. The spl_mmc_raw_uboot_offset function returns the macro CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_DATA_PART_OFFSET (0x10), which is exactly 8K. So the total address is (0x40 + 0x10) * 512 = 0x50 * 512 = 40KB. This is why SPL reads U-Boot correct from that location.
