After power-up, the A10/A20 boots from an integrated, non-replaceable 32 KiB ROM chip (Boot ROM or BROM). This could be considered the primary program-loader. The SoC starts to fetch instructions from address 0xffff0000 which is where the BROM is located at. The BROM split up into two parts: The first part (at 0xffff0000) is the FEL mode and the second is the eGON.BRM (located at 0xffff4000).
The reset vector is located at the very begining of FEL mode: at address 0xffff0000. On reset, it jumps to 0xffff0028 where it loads 0xffff4000 (eGON.BRM) into the program counter to be executed next.
The eGON Boot ROM performs a few tasks:
- does some co-processor setup (c15, (virtual) System Control Coprocessor).
- disables the WatchDog Timer
- setups CPU, AXI, AHB and APB0 clocks
- enables AHB Gating
- enables APB0 Gating
- sets the Stack Pointer to 32K
- then it jumps to 'boot' which immediately jumps to check_uboot
- check_uboot setups up some registers, then checks the status pin (often called FEL pin, BSP pin or uboot)
- if the pin is low (connected to GND) executes FEL mode at 0xffff0020.
- If the pin is high it continues trying to boot from the following media and on failure continues to the next in order.
As can be seen, the A10/A20 has several ways to boot and a lot would need to go wrong or 'fail' before entering FEL mode. This is especially important if there is a valid header in the NAND flash. Obviously this can be abused, by corrupting the header and thus forcing failure. If no other boot options are available, then FEL mode should be the final result. As a bypass mechanism, the A10 has the so called Boot Select Pin (BSP). This pin is normally internally pulled up by a 50KΩ resistor. If the pin is pulled low to GND, the A10 will try to boot into FEL mode. Otherwise the above boot-order will be tried.
Source code for boot0 and boot1
As of March 2015 Allwinner has also published bootloader code on GitHub.
Instead of falling through boot options, the A31 boots slightly differently. One fel pin, which is the same as A10/A20. Two boot select pins, boot sel0 and boot sel1, which decide where to boot. The fel key priority is higher than the boot select key. The boot-flow process is explained briefly below.
boot-> check fel key pressed (yes)-> check if sd0 bootable(no) -->go to fel mode \ \ \ (yes)\__ boot from sd0 \ \ _____ boot from nand flash (go to fel mode if failed) (no) \ /_____ boot from sd2 (go to fel mode if failed) \ / \___check boot sel[0:1]------------------- boot from emmc2 (go to fel mode if failed) \ \______ boot from spi (go to fel mode if failed) by hipboi
boot-> check fel key pressed (yes)--> FEL mode (boot from USB OTG) \ (no) \ \-------> 1) try to boot from SMHC2 (eMMC) 2) try to boot from SMHC0 (SD card) 3) try to boot from SPI0 (SPI NOR Flash) 4) FEL mode (boot from USB OTG)
FIXME: The A64 manual says that booting is supported from "NAND Flash", "SD/TF card", "eMMC" and "Nor Flash", but does not provide any details. "NAND Flash" is not listed above because we have not seen such devices yet.
U-Boot SPL limitations
In order to be recognized by the BROM, the SPL needs to written to a certain location on the SD card (see SD Card Layout) and have a special header with a correct checksum. Such special header can be added to a binary file using the Mksunxiboot tool. The size of the SPL must be a multiple of 8 KiB in NAND and a multiple of 512 bytes on the SD card (see the "sunxi/nand: change BLOCK_SIZE in mksunxiboot to match NAND block size" commit in U-Boot).
|SoC name||SPL size limit on MMC||SPL size limit on NAND||SPL load address||Initial stack pointer value (SP register)||Notes|
|Allwinner A10||24 KiB||0x00000||sp=0x07FF8||There is an artificial 24 KiB limit and anything larger is rejected by the BROM|
|Allwinner A13||0x7E00||0x00000||sp=0x07FF8||Sizes larger than 0x7E00 bytes are rejected by the BROM. Exactly 0x7E00 is fine, as verified by writing a special pattern at the end of the SPL file and checking it in the SRAM. Note that the top of the SPL stack needs to be changed in U-Boot sources from 0x8000 to something like 0x7000 in order to make it a clean experiment.|
|Allwinner A20||24 KiB||0x00000||sp=0x07FF8||There is an artificial 24 KiB limit and anything larger is rejected by the BROM|
|Allwinner A31s||32 KiB||0x00000||sp=0x27FD8||Sizes larger than 32 KiB are rejected by the BROM. Exactly 32 KiB is fine, as verified by writing a special pattern at the end of the SPL and checking it in the SRAM.|
|Allwinner A64||32 KiB||0x10000||sp=0x47FE0||Sizes larger than 32 KiB are rejected by the BROM. Exactly 32 KiB is fine, as verified by writing a special pattern at the end of the SPL and checking it in the SRAM.|
|Allwinner H3||32 KiB||0x00000||sp=0x0F7DC||Sizes larger than 32 KiB are rejected by the BROM. Exactly 32 KiB is fine, as verified by writing a special pattern at the end of the SPL and checking it in the SRAM.|
Early SoC variants (A10 and A20) used to have a somewhat artificial 24 KiB restriction of the SPL size. Then A13 tried to increase this limit to almost 32 KiB. And finally all recent SoC variants (A31 / H3 / A64) have 32 KiB size limit for the SPL. The 32 KiB limit is still artificial. For example, A64 has 192 KiB of contiguous SRAM space, starting at 0x10000.
It is possible to increase the practical size limit to some extent by making use of runtime decompression (via LZO or UCL). As an additional bonus, compression should eliminate repeatable patterns, which are supposedly bad for MLC NAND and maybe (?) help the NAND hardware randomizer to some extent. And while we are at it, the runtime decompressor code could also take care of applying relocations, allowing to use the same unified SPL binary even on devices with different SPL load addresses.