This project replaces U-Boot with a simple single-stage bootloader.
Normally U-Boot's First Stage Bootloader (SPL) loads the Second Stage Bootloader (U-Boot proper), which in turn loads the application. When using this bootloader, it loads the application immediately.
Why re-invent the wheel? Primarily for educational purposes.
Secondarily, for a particular project I required fast booting with a somewhat unorthodox boot algorithm, and had no need for all the features of U-Boot (command line, USB gadget, etc).
U-Boot is excellent and solid, it should be your first choice for a project,
especially when developing. U-Boot excels at being portable (the "U" is for
Universal), which means you can port to another platform easily. However, the
complexity means debugging and customizing can be difficult, requiring a
combination of searching through and modifying KConfig files, device trees
(DTS), C code, and sometimes even linker outputs (ll_entry_start()
etc).
A bit of background/review: on power-up, the STM32MP15x BOOTROM attempts to read a 256-byte header from whatever interface the BOOT0/1/2 pins select (NOR Flash, SDMMC, etc). If the header has the right signature, then it will load the entire image into SYSRAM at a designed address, 0x2FFC2400. Then it jumps to start executing code at 0x2FFC2500. Details about the boot procedure can be found on the st wiki.
The linker script for this project links the binary to the correct addresses in
SYSRAM, and pads 256 bytes for the header. A separate python script (taken from
here) generates the header. The resulting
file is fsbl.stm32
. We copy this image file to the SD Card using dd
like we
do for the other example projects. The image gets copied to the sectors that
the BOOTROM will be looking: LBA0 (address 0), and LBA512 (address 0x40000).
Once loaded, MP1-Boot does the minimal tasks required to load an application into RAM and then boot into it:
- Initialize the PLL clocks for the MPU and DDR RAM
- Initialize security settings (allowing the application full access)
- Configure the external PMIC chip, if present, to power the DDR RAM
- Initialize the DDR RAM peripheral
- Detect the boot method (NOR Flash, SD Card, EMMC, etc.)
- Verify the application image header, and load the image from the boot medium into DDR RAM
- Jump into the application
- Indicate an error on failure
Also there is one step that is not strictly necessary, but useful:
- Initialize the UART to allow for printing messages to the console
The python script that adds the stm32 header is taken from here.
The DDR init code ported from U-Boot ram drivers. I also studied the port by @ua1arn in the hftrx project here.
The GPT and CRC32 code (for determining the block number on an SD Card based on the partition number), is also ported from U-Boot disk drivers. The vast majority of code was removed, but the basic GPT structure and how to read it remains.
The SDMCC driver is the STM32 HAL driver. The other drivers (QSPI NOR Flash, I2C, RCC, GPIO, UART) are mine. Some leverage the register access macros ST provides in the LL drivers.
Summary
Boot time varies widely depending on the board and the boot medium (SD Card or NOR Flash), and even the particular brand/speed of SD Card.
With NOR Flash (requires a custom board), it takes about 65ms from power-on until a 1MByte program is loaded and running. This breaks down to about 36ms from power-on to app loading. Then the app is loaded at about 44MBytes/sec, so a 1MB program loads in about 24ms.
With an SD Card on the OSD32-BRK, it takes about 500ms from power-on until a 1MB program is running. The times vary depending the brand/type of SD Card. It takes around 380ms to until the the start of copying the app from the SD Card, and another 100ms or so to load a 1MByte program.
Details
The main reason for the large difference in boot time is that when using an SD Card, there is a ~300-350ms pause shortly after boot. Probing the SDMMC data/cmd/clk lines, you can see that the BOOTROM makes contact with the SD Card at about 11ms after power-on. It communicates with a clock speed of 175kHz using only the CMD and CLK lines. Then after a long pause (300-350ms) communication is resumed at 16MHz, using the D0-D3 lines as well. I presume this pause is due the SD Card preparing itself, though I'm not certain. In any case, with NOR Flash, this pause is not present. Also, when booting from NOR Flash, the BOOTROM uses a clock speed of 32MHz, according to ST's site, but I measure 21MHz.
The MP1-Boot image is about 30kB. It takes the BOOTROM about 20-30ms to load it from the SD Card. When using NOR Flash it takes about 11ms.
Once loaded, MP1-Boot takes about 13ms to run. If there is no PMIC or if the UART is disabled, then it's a few ms faster.
The remaining boot time is spent copying the app image from the boot media into DDR RAM. The duration of that depends on the size of the image and the speed of the transfer. Since we are no longer dependent on BOOTROM's hard-coded speeds and bus widths, we can set our own transfer protocol rates. NOR Flash can run with an 88MHz clock, 4-bit wide data (Quad mode) takes about 25ms to transfer a 1MB application image. It probably could be pushed further, depending on the limits of the Flash chip, but I've found this value to be reliable and fast enough.
MP1-Boot works with booting from an SD Card or NOR Flash (QSPI). It will work with a PMIC system or a discrete LDO system.
TODO:
- Faster SDMMC loading (Debug OSD32-BRK speed limit of 16MHz)
- Add extensive RAM tests (run optionally)
- Add a driver for EMMC (SDMMC2)
- Add a driver for UART booting
- Reduce binary size by using our own SDMMC config instead of HAL (10kB)
A simple config header defines such things as the console UART pins, whether or not the board has a PMIC, etc. These are the same config files for OSD32-BRK and Discovery boards used in other projects. See examples of these files here and here. Make sure you select the right board namespace at the top of main.cc.
Unlike the other projects in this repository, you do not need to have U-boot installed on the SD Card or NOR flash.
In this directory run one of these commands:
make load # To be prompted for the SD card device
make load SD_DISK_DEV=/dev/diskX # To use /dev/diskX
If you don't specify SD_DISK_DEV
, it will prompt you for the disk device name
of the SD Card. Make sure a partitioned SD Card is inserted into your computer,
and enter the device name (such as /dev/disk4
).
(Alternatively, you can load the image yourself by running make
followed by
dd
commands to load build/fsbl.stm32
onto partitions 1 and 2 of the SD
Card.)
The card must be partitioned the same way it's done in the other example
projects. Use the scripts/partition-sdcard.sh
script to do this.
Finally, copy the application uimg file to the 3rd partition like this:
sudo dd if=../../appfolder/build/a7-main.uimg of=/dev/sdXX3
Reboot your board with a UART-to-USB cable connected, and watch the startup messages scroll by in a terminal.
You should see:
MP1-Boot
MPU clock: 800000000 Hz
Initializing RAM
Testing RAM.
Booted from NOR
Loading app image...
Jumping to app
...followed by the application running.