MP1-Boot is a fast, lightweight, and easily configured bootloader for the STM32MP15x.
When using MP1-Boot, it replaces U-Boot with a 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? MP1-Boot has very few features, which makes it fast (small binary, can boot a 1MB app in ~65ms). It's also easy to configure to new boards (take a look at a file in src/board_conf/ and compare to the number of options in U-Boot's menuconfig and DTS files).
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).
There are many, many awesome and useful features that U-Boot does, that MP1-Boot will never do. MP1-Boot is intended to fill a niche-case, not to replace U-Boot.
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:
Also there is one step that is not strictly necessary, but useful:
This is an optional feature. It lets you have two application firmware binaries present on a NOR Flash chip, and select which one to boot using a GPIO pin. The pin can be connected to a button, header, or jumper.
This feature is enabled by defining BootSelectPin
and setting UseBootConf
to
true in the board conf file.
MP1-Boot will enable the internal pull-up on the BootSelectPin. Thus, if the pin is left floating, it will be read as high. This causes MP1 boot to load the application firmware as normal (from NOR Flash address 0x80000, or from SDMMC gpt partition 3).
On the other hand, if the pin is being pulled low (because it's connected to a
button or switch that's shorting it to ground, or an external device is pulling it low),
then MP1-Boot will load firmware from NOR Flash address 0x60000 or SDMMC
gpt partition 4. These values can be configured in boot_image_def.hh
.
The firmware at 0x60000 can be a maximum of 128kB since it cannot overlap the firmware at 0x80000 (0x80000 - 0x60000 = 128kB).
The use-case I designed this for is to allowing booting a USB DFU bootloader at 0x60000, or the main application at 0x80000. The USB DFU bootloader allows a user to load new firmware over USB and write it to the NOR Flash.
Another use-case could be to load a self-test firmware at 0x60000. A user could boot with a button pressed to verify their hardware is working.
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:
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.
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 example
projects in the stm32mp1-baremetal repo.
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.