Exploring a Customized ESP32 Bootloader · Daniel Mangum
I just lately acquired an
ESP32-C3-DevKitC-02
module, and, as I are inclined to do, jumped proper into studying about how the system
boots and the way the (fairly good!) tooling Espressif
provides works. Now we have sometimes used QEMU within the
RISC-V Bytes sequence, however
getting our fingers on bodily {hardware} begins to make issues really feel a bit extra
actual. On this first publish on the ESP32, we’ll do some fundamental setup and take a look at a
easy customized bootloader.
The system boots when energy is equipped. Due to this, to watch the logs,
you’ll doubtless have to concern a reset after opening the serial port with a software
equivalent to minicom
. With the system related to your machine, try to be ready
to search out the serial port underneath /dev/ttyUSB*
. On my machine it was
/dev/ttyUSB0
. The baud charge is 115200
, which is the default for minicom
.
$ minicom -D /dev/ttyUSB0
If we then press the RST
button, we must always see output. Earlier than overwriting, a
Rainmaker
demo was pre-programmed on my module, and it printed some ASCII artwork over the
serial port.
Espressif offers tooling that makes constructing tasks for any ESP32 module
a lot easier. Although definitely not strictly required, Espressif extremely
recommends using the esp-idf
(ESP IOT Development
Framework). Within the root of the
repository, you’ll discover directions to get began with one of many set up
scripts. As traditional, I’m on a Linux machine, so I used the install.sh
script.
$ ./set up.sh
Detecting the Python interpreter
Checking "python3" ...
Python 3.8.10
"python3" has been detected
Checking Python compatibility
Putting in ESP-IDF instruments
Present system platform: linux-amd64
Chosen targets are: esp32c2, esp32c6, esp32s2, esp32c3, esp32, esp32s3, esp32h4, esp32h2
Putting in instruments: xtensa-esp-elf-gdb, riscv32-esp-elf-gdb, xtensa-esp32-elf, xtensa-esp32s2-elf, xtensa-esp32s3-elf, riscv32-esp-elf, esp32ulp-elf, openocd-esp32, esp-rom-elfs
Skipping xtensa-esp-elf-gdb@12.1_20221002 (already put in)
Skipping riscv32-esp-elf-gdb@12.1_20221002 (already put in)
Skipping xtensa-esp32-elf@esp-12.2.0_20230208 (already put in)
Skipping xtensa-esp32s2-elf@esp-12.2.0_20230208 (already put in)
Skipping xtensa-esp32s3-elf@esp-12.2.0_20230208 (already put in)
Skipping riscv32-esp-elf@esp-12.2.0_20230208 (already put in)
Skipping esp32ulp-elf@2.35_20220830 (already put in)
Skipping openocd-esp32@v0.11.0-esp32-20221026 (already put in)
Skipping esp-rom-elfs@20230113 (already put in)
Putting in Python setting and packages
...
Putting in collected packages: esp-idf-monitor
Making an attempt uninstall: esp-idf-monitor
Discovered current set up: esp-idf-monitor 1.0.1
Uninstalling esp-idf-monitor-1.0.1:
Efficiently uninstalled esp-idf-monitor-1.0.1
Efficiently put in esp-idf-monitor-1.0.0
All accomplished! Now you can run:
. ./export.sh
By default, it will set up the toolchains for all targets (or skip in the event that they
are already current as proven above). It is going to additionally set up supporting instruments, such
as idf.py
and esptool
. You may check out all the pieces put in in
~/.espressif
.
$ ls ~/.espressif/instruments/
esp32ulp-elf/ esp-rom-elfs/ openocd-esp32/ riscv32-esp-elf/ riscv32-esp-elf-gdb/ xtensa-esp32-elf/ xtensa-esp32s2-elf/ xtensa-esp32s3-elf/ xtensa-esp-elf-gdb/
$ ls ~/.espressif/python_env/idf5.1_py3.8_env/bin/
activate allmodconfig doesitcache esptool.py menuconfig pip pyserial-miniterm savedefconfig
activate.csh allnoconfig esp-coredump futurize normalizer pip3 pyserial-ports setconfig
activate.fish allyesconfig espefuse.py genconfig oldconfig pip3.8 python tqdm
Activate.ps1 compote esp_rfc2217_server.py guiconfig olddefconfig __pycache__/ python3 west
alldefconfig defconfig espsecure.py listnewconfig pasteurize pykwalify readelf.py
So as to add the required instruments to your $PATH
, the corresponding export script can
be sourced.
$ . ./export.sh
Detecting the Python interpreter
Checking "python3" ...
Python 3.8.10
"python3" has been detected
Checking Python compatibility
Checking different ESP-IDF model.
Utilizing a supported model of software cmake present in PATH: 3.16.3.
Nevertheless the advisable model is 3.24.0.
Including ESP-IDF instruments to PATH...
Utilizing a supported model of software cmake present in PATH: 3.16.3.
Nevertheless the advisable model is 3.24.0.
Checking if Python packages are updated...
Constraint file: /house/dan/.espressif/espidf.constraints.v5.1.txt
Requirement information:
- /house/dan/code/github.com/espressif/esp-idf/instruments/necessities/necessities.core.txt
Python being checked: /house/dan/.espressif/python_env/idf5.1_py3.8_env/bin/python
Python necessities are happy.
Added the next directories to PATH:
/house/dan/code/github.com/espressif/esp-idf/parts/esptool_py/esptool
/house/dan/code/github.com/espressif/esp-idf/parts/espcoredump
/house/dan/code/github.com/espressif/esp-idf/parts/partition_table
/house/dan/code/github.com/espressif/esp-idf/parts/app_update
Finished! Now you can compile ESP-IDF tasks.
Go to the venture listing and run:
idf.py construct
As indicated within the output, we are actually prepared to begin constructing!
The boot course of is described intimately within the ESP32-C3 API
Guide.
The principle takeaway is that booting is a two stage course of, the place the primary stage
bootloader, which is saved in ROM and can’t be modified, masses the second
stage one. The second stage bootloader lives in flash reminiscence at offset 0x0
,
however is loaded into RAM by the primary stage bootloader.
Earlier than diving deeper, it’s helpful to know the varied parts of the
ESP32-C3-DevKitC-02. The ESP32-C3 is the system-on-chip (SoC), however lives inside
of the ESP32-C3-WROOM-02 module. The module surrounds the SoC with
peripherals, equivalent to SPI flash and a PCB Antenna, enabling the mixed unit to
make the most of WiFi, Bluetooth LE, and extra. The module itself is surrounded by different
peripherals on the event equipment PCB, such because the micro-USB port and
USB-to-UART bridge, making it a lot simpler to work together with from our host
machine.
With a view to overwrite the second stage bootloader in flash, we’ll have to
talk with the ESP32-C3, which can then discuss over the SPI bus to the
flash that lives beside it within the WROOM module.
One of many instruments put in throughout setup was esptool.py
. Although a lot of the
documentation makes use of idf.py
, lots of the instructions it provides are simply wrapping
esptool.py
and passing mandatory flags. The ESP32-C3 will be configured to
boot in “serial
mode”,
which implements a serial
protocol
with help for a wide range of instructions that enable for operations equivalent to studying
and writing to flash. Curiously, by default esptool.py
will load a stub
bootloader
that implements the identical protocol, however has some optimizations and extra
options. You may select to skip loading the stub bootloader by passing the
--no-stub
argument to any command.
The serial protocol relies on the Serial Line Internet
Protocol. The
documentation
offers the complete specification for packet format, however the normal construction for
instructions and responses are reproduced under.
Command Packets
Byte | Identify | Remark |
---|---|---|
0 | Course | All the time 0x00 for requests |
1 | Command | Command identifier (see Instructions). |
2-3 | Dimension | Size of Knowledge discipline, in bytes. |
4-7 | Checksum | Easy checksum of a part of the info discipline (solely used for some instructions, see Checksum). |
8..n | Knowledge | Variable size knowledge payload (0-65535 bytes, as indicated by Dimension parameter). Utilization is dependent upon particular command. |
Response Packets
Byte | Identify | Remark |
---|---|---|
0 | Course | All the time 0x01 for responses |
1 | Command | Identical worth as Command identifier within the request packet that triggered the response |
2-3 | Dimension | Dimension of knowledge discipline. A minimum of the size of the Standing Bytes (2 or 4 bytes, see under). |
4-7 | Worth | Response worth utilized by READ_REG command (see under). Zero in any other case. |
8..n | Knowledge | Variable size knowledge payload. Size indicated by “Dimension” discipline. |
Command sequences used for writing
data
comply with an analogous sample of a single start command, adopted by some variety of
knowledge instructions, then a single finish command. This sample is used to each load
the stub bootloader and, subsequently, load the second stage bootloader. The
former is written to RAM, whereas the latter is written to flash. The instructions for
writing to RAM are:
MEM_BEGIN
(0x05
)MEM_DATA
(0x07
)MEM_END
(0x06
)
The MEM_END
command helps supplying an execute flag and an deal with in RAM
(every 32-bit phrases) that the chip will start executing directions at if
execute flag is ready. The instructions for writing to flash are:
FLASH_BEGIN
(0x02
)FLASH_DATA
(0x03
)FLASH_END
(0x04
)
The FLASH_END
command can provide a single 32-bit phrase and if the worth is 0
the chip will reboot.
Overriding the Second Stage Bootloader
Link to heading
Luckily, the esp-idf
repo has an
example
of how the second stage bootloader will be overridden. It’s fairly much like the
default second stage
bootloader,
nevertheless it allows for
customizing
an extra message that’s printed on startup.
Previous to operating the instructions within the README.md
, we have to specify which ESP32
SoC we’re concentrating on.
$ idf.py set-target esp32c3
Including "set-target"'s dependency "fullclean" to record of instructions with default set of choices.
Executing motion: fullclean
Executing motion: set-target
Set Goal to: esp32c3, new sdkconfig will probably be created.
...
-- Configuring accomplished
-- Producing accomplished
-- Construct information have been written to: /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct
This can make sure that now we have the required toolchain parts and setup the
correct configuration (sdkconfig
). It is going to additionally setup the construct
listing
equipment, which we are able to then train.
$ idf.py construct
Executing motion: all (aliases: construct)
Working cmake in listing /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct
Executing "cmake -G Ninja -DPYTHON_DEPS_CHECKED=1 -DPYTHON=/house/dan/.espressif/python_env/idf5.1_py3.8_env/bin/python -DESP_PLATFORM=1 -DCCACHE_ENABLE=0 /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override"...
-- IDF_TARGET will not be set, guessed 'esp32c3' from sdkconfig '/house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/sdkconfig'
...
[848/849] Producing binary picture from constructed executable
esptool.py v4.5.1
Creating esp32c3 picture...
Merged 1 ELF part
Efficiently created esp32c3 picture.
Generated /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct/primary.bin
[849/849] cd /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct/esp-idf/esptool_py && /house/dan/.espressif/python_env/idf5.1_py3.8_env/bin/python /house/dan/code/github.com/espressif/esp-idf/parts/partition_table/check_sizes.py --offset 0x8000 partition --type app /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct/partition_table/partition-table.bin /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct/primary.bin
primary.bin binary dimension 0x28e00 bytes. Smallest app partition is 0x100000 bytes. 0xd7200 bytes (84%) free.
Challenge construct full. To flash, run this command:
/house/dan/.espressif/python_env/idf5.1_py3.8_env/bin/python ../../../parts/esptool_py/esptool/esptool.py -p (PORT) -b 460800 --before default_reset --after hard_reset --chip esp32c3 write_flash --flash_mode dio --flash_size 2MB --flash_freq 80m 0x0 construct/bootloader/bootloader.bin 0x8000 construct/partition_table/partition-table.bin 0x10000 construct/primary.bin
or run 'idf.py -p (PORT) flash'
We are able to see that it routinely defaulted to the esp32c3
goal, constructed
artifacts, and offered the command for use to flash the system. The 2
command choices reveal how idf.py
wraps esptool
, which can write three
completely different binaries to numerous places in flash with the write_flash
command.
construct/bootloader/bootloader.bin
will probably be written to0x0
construct/partition_table/partition-table.bin
will probably be written to0x8000
construct/primary.bin
will probably be written to0x10000
We’ll dig slightly deeper into what the binaries are, in addition to why they’re
being written to these offsets in flash within the subsequent part, however first let’s see
it in motion!
NOTE: we are able to depart off
-p /dev/ttyUSB0
as it’s the default.
$ idf.py flash
Executing motion: flash
Serial port /dev/ttyUSB0
Connecting....
Detecting chip sort... ESP32-C3
Working ninja in listing /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct
Executing "ninja flash"...
[1/5] cd /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct/esp-idf/esptool_py && /house/dan/.espressif/python_env/idf5.1_py3.8_env/bin/python /house/dan/code/github.com/espressif/esp-idf/parts/partition_table/check_sizes.py --offset 0x8000 partition --type app /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct/partition_table/partition-table.bin /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct/primary.bin
primary.bin binary dimension 0x28e00 bytes. Smallest app partition is 0x100000 bytes. 0xd7200 bytes (84%) free.
[2/5] Performing construct step for 'bootloader'
[1/1] cd /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct/bootloader/esp-idf/esptool_py && /house/dan/.espressif/python_env/idf5.1_py3.8_env/bin/python /house/dan/code/github.com/espressif/esp-idf/parts/partition_table/check_sizes.py --offset 0x8000 bootloader 0x0 /house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct/bootloader/bootloader.bin
Bootloader binary dimension 0x5080 bytes. 0x2f80 bytes (37%) free.
[2/3] cd /house/dan/code/github.com/espressif/esp-idf/parts/esptool_py && /usr/bin/cmake -D IDF_PATH=/house/dan/code/github.com/espressif/esp-idf -D "SERIAL_TOOL=/house/dan/.espressif/python_env/idf5.1_py3.8_env/bin/python;;/house/dan/code/github.com/espressif/esp-idf/parts/esptool_py/esptool/esptool.py;--chip;esp32c3" -D "SERIAL_TOOL_ARGS=--before=default_reset;--after=hard_reset;write_flash;@flash_args" -D WORKING_DIRECTORY=/house/dan/code/github.com/espressif/esp-idf/examples/custom_bootloader/bootloader_override/construct -P /house/dan/code/github.com/espressif/esp-idf/parts/esptool_py/run_serial_tool.cmake
esptool esp32c3 -p /dev/ttyUSB0 -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 80m --flash_size 2MB 0x0 bootloader/bootloader.bin 0x10000 primary.bin 0x8000 partition_table/partition-table.bin
esptool.py v4.5.1
Serial port /dev/ttyUSB0
Connecting....
Chip is ESP32-C3 (revision v0.3)
Options: WiFi, BLE
Crystal is 40MHz
MAC: 58:cf:79:16:7d:a0
Importing stub...
Working stub...
Stub operating...
Altering baud charge to 460800
Modified.
Configuring flash dimension...
Flash will probably be erased from 0x00000000 to 0x00005fff...
Flash will probably be erased from 0x00010000 to 0x00038fff...
Flash will probably be erased from 0x00008000 to 0x00008fff...
Compressed 20608 bytes to 12655...
Writing at 0x00000000... (100 %)
Wrote 20608 bytes (12655 compressed) at 0x00000000 in 0.7 seconds (efficient 244.4 kbit/s)...
Hash of knowledge verified.
Compressed 167424 bytes to 88511...
Writing at 0x00010000... (16 %)
Writing at 0x0001a51f... (33 %)
Writing at 0x0002104c... (50 %)
Writing at 0x00028442... (66 %)
Writing at 0x0002ec04... (83 %)
Writing at 0x00035e5c... (100 %)
Wrote 167424 bytes (88511 compressed) at 0x00010000 in 2.8 seconds (efficient 477.3 kbit/s)...
Hash of knowledge verified.
Compressed 3072 bytes to 103...
Writing at 0x00008000... (100 %)
Wrote 3072 bytes (103 compressed) at 0x00008000 in 0.1 seconds (efficient 295.2 kbit/s)...
Hash of knowledge verified.
Leaving...
Laborious resetting by way of RTS pin...
Finished
A lot of the logic on this step will be discovered within the
source
for the write_flash
command, however at a excessive stage the steps are as follows:
- Load stub bootloader.
...
Importing stub...
Working stub...
Stub operating...
...
- Erase flash sections.
...
Configuring flash dimension...
Flash will probably be erased from 0x00000000 to 0x00005fff...
Flash will probably be erased from 0x00010000 to 0x00038fff...
Flash will probably be erased from 0x00008000 to 0x00008fff...
...
- Write second stage bootloader to flash.
...
Compressed 20608 bytes to 12655...
Writing at 0x00000000... (100 %)
Wrote 20608 bytes (12655 compressed) at 0x00000000 in 0.7 seconds (efficient 244.4 kbit/s)...
Hash of knowledge verified.
...
- Write utility to flash.
...
Compressed 167424 bytes to 88511...
Writing at 0x00010000... (16 %)
Writing at 0x0001a51f... (33 %)
Writing at 0x0002104c... (50 %)
Writing at 0x00028442... (66 %)
Writing at 0x0002ec04... (83 %)
Writing at 0x00035e5c... (100 %)
Wrote 167424 bytes (88511 compressed) at 0x00010000 in 2.8 seconds (efficient 477.3 kbit/s)...
Hash of knowledge verified.
...
- Write partition desk to flash.
...
Compressed 3072 bytes to 103...
Writing at 0x00008000... (100 %)
Wrote 3072 bytes (103 compressed) at 0x00008000 in 0.1 seconds (efficient 295.2 kbit/s)...
Hash of knowledge verified.
...
- Reset chip.
...
Leaving...
Laborious resetting by way of RTS pin...
Finished
With a view to see what occurs on boot now, we are able to as soon as once more join by way of
minicom
and press the RST
button.
$ minicom -D /dev/ttyUSB0
ESP-ROM:esp32c3-api1-20210207
Construct:Feb 7 2021
rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd5820,len:0x1754
load:0x403cc710,len:0x970
load:0x403ce710,len:0x2f68
entry 0x403cc710
I (30) boot: ESP-IDF 4f0769d2ed 2nd stage bootloader
I (30) boot: compile time Apr 8 2023 18:51:23
I (30) boot: chip revision: v0.3
I (34) boot.esp32c3: SPI Pace : 80MHz
I (38) boot.esp32c3: SPI Mode : DIO
I (43) boot.esp32c3: SPI Flash Dimension : 2MB
I (48) boot: Enabling RNG early entropy supply...
I (53) boot: Partition Desk:
I (57) boot: ## Label Utilization Sort ST Offset Size
I (64) boot: 0 nvs WiFi knowledge 01 02 00009000 00006000
I (71) boot: 1 phy_init RF knowledge 01 01 0000f000 00001000
I (79) boot: 2 manufacturing unit manufacturing unit app 00 00 00010000 00100000
I (86) boot: Finish of partition desk
[boot] Customized bootloader message outlined within the KConfig file.
I (96) esp_image: section 0: paddr=00010020 vaddr=3c020020 dimension=08480h ( 33920) map
I (110) esp_image: section 1: paddr=000184a8 vaddr=3fc8aa00 dimension=01110h ( 4368) load
I (114) esp_image: section 2: paddr=000195c0 vaddr=40380000 dimension=06a58h ( 27224) load
I (126) esp_image: section 3: paddr=00020020 vaddr=42000020 dimension=15018h ( 86040) map
I (143) esp_image: section 4: paddr=00035040 vaddr=40386a58 dimension=03d9ch ( 15772) load
I (149) boot: Loaded app from partition at offset 0x10000
I (149) boot: Disabling RNG early entropy supply...
I (163) cpu_start: Professional cpu up.
I (172) cpu_start: Professional cpu begin consumer code
I (172) cpu_start: cpu freq: 160000000 Hz
I (172) cpu_start: Utility data:
I (175) cpu_start: Challenge title: primary
I (180) cpu_start: App model: 4f0769d2ed
I (185) cpu_start: Compile time: Apr 8 2023 18:51:16
I (191) cpu_start: ELF file SHA256: 3916cd87115c6efe...
I (197) cpu_start: ESP-IDF: 4f0769d2ed
I (203) cpu_start: Min chip rev: v0.3
I (207) cpu_start: Max chip rev: v0.99
I (212) cpu_start: Chip rev: v0.3
I (217) heap_init: Initializing. RAM accessible for dynamic allocation:
I (224) heap_init: At 3FC8C940 len 0004FDD0 (319 KiB): DRAM
I (230) heap_init: At 3FCDC710 len 00002950 (10 KiB): STACK/DRAM
I (237) heap_init: At 50000020 len 00001FE0 (7 KiB): RTCRAM
I (244) spi_flash: detected chip: generic
I (248) spi_flash: flash io: dio
W (252) spi_flash: Detected dimension(4096k) bigger than the dimensions within the binary picture header(2048k). Utilizing the dimensions within the binary picture header.
I (265) sleep: Configure to isolate all GPIO pins in sleep state
I (272) sleep: Allow computerized switching of GPIO sleep configuration
I (279) app_start: Beginning scheduler on CPU0
I (284) main_task: Began on CPU0
I (284) main_task: Calling app_main()
Utility began!
I (294) main_task: Returned from app_main()
The second stage bootloader is loaded and executed efficiently, as evidenced by
the customized message.
[boot] Customized bootloader message outlined within the KConfig file.
We are able to additionally see the message from the straightforward utility that the bootloader
jumps to.
Now that we’ve run the instance customized bootloader, let’s discover the binary to
get a way of what’s taking place behind the scenes. The entrypoint to the second
stage bootloader is
call_start_cpu0(void)
.
examples/custom_bootloader/bootloader_override/bootloader_components/primary/bootloader_start.c
/*
* We arrive right here after the ROM bootloader completed loading this second stage bootloader from flash.
* The {hardware} is generally uninitialized, flash cache is down and the app CPU is in reset.
* We do have a stack, so we are able to do the initialization in C.
*/
void __attribute__((noreturn)) call_start_cpu0(void)
{
// 1. {Hardware} initialization
if (bootloader_init() != ESP_OK) {
bootloader_reset();
}
#ifdef CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP
// If this boot is a get up from the deep sleep then go to the quick method,
// attempt to load the applying which labored earlier than deep sleep.
// It skips a whole lot of checks attributable to it was accomplished earlier than (whereas first boot).
bootloader_utility_load_boot_image_from_deep_sleep();
// If it's not profitable attempt to load an utility as traditional.
#endif
// 2. Choose the variety of boot partition
bootloader_state_t bs = {0};
int boot_index = select_partition_number(&bs);
if (boot_index == INVALID_INDEX) {
bootloader_reset();
}
// 2.1 Print a customized message!
esp_rom_printf("[%s] %sn", TAG, CONFIG_EXAMPLE_BOOTLOADER_WELCOME_MESSAGE);
// 3. Load the app picture for booting
bootloader_utility_load_boot_image(&bs, boot_index);
}
The offered feedback are useful to know what is going on, and we’ll
discover them in a second, however how does the primary stage bootloader know to leap
right here? Our customized bootloader is reusing the same linker
script
because the default second stage bootloader, which defines the entrypoint on the
location of the call_start_cpu0
operate.
parts/bootloader/subproject/primary/ld/esp32c3/bootloader.ld
/* Default entry level: */
ENTRY(call_start_cpu0);
It additionally defines a few memory
regions
for instruction reminiscence (IRAM
) and knowledge reminiscence (DRAM
).
parts/bootloader/subproject/primary/ld/esp32c3/bootloader.ld
MEMORY
{
iram_seg (RWX) : org = bootloader_iram_seg_start, len = bootloader_iram_seg_len
iram_loader_seg (RWX) : org = bootloader_iram_loader_seg_start, len = bootloader_iram_loader_seg_len
dram_seg (RW) : org = bootloader_dram_seg_start, len = bootloader_dram_seg_len
}
The entrypoint is loaded at the start of th IRAM
section.
parts/bootloader/subproject/primary/ld/esp32c3/bootloader.ld
.iram.textual content :
{
. = ALIGN (16);
*(.entry.textual content)
*(.init.literal)
*(.init)
} > iram_seg
We are able to see this structure in impact by inspecting the
ELF file we
constructed for the bootloader picture. For those who efficiently added all put in instruments to
your path, you need to be capable to use riscv32-esp-elf-objdump
.
$ riscv32-esp-elf-objdump -h construct/bootloader/bootloader.elf
construct/bootloader/bootloader.elf: file format elf32-littleriscv
Sections:
Idx Identify Dimension VMA LMA File off Algn
0 .iram_loader.textual content 00002f66 403ce710 403ce710 00003710 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .iram.textual content 00000000 403cc710 403cc710 00006676 2**0
CONTENTS
2 .dram0.bss 00000110 3fcd5710 3fcd5710 00000710 2**2
ALLOC
3 .dram0.knowledge 00000004 3fcd5820 3fcd5820 00000820 2**2
CONTENTS, ALLOC, LOAD, DATA
4 .dram0.rodata 00001750 3fcd5824 3fcd5824 00000824 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .iram.textual content 0000096e 403cc710 403cc710 00002710 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
6 .debug_info 000229fa 00000000 00000000 00006676 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
7 .debug_abbrev 000048b6 00000000 00000000 00029070 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
8 .debug_loc 000075d6 00000000 00000000 0002d926 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
9 .debug_aranges 00000808 00000000 00000000 00034efc 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
10 .debug_ranges 000015b8 00000000 00000000 00035704 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
11 .debug_line 00010e82 00000000 00000000 00036cbc 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
12 .debug_str 0000a1e0 00000000 00000000 00047b3e 2**0
CONTENTS, READONLY, DEBUGGING, OCTETS
13 .remark 0000002f 00000000 00000000 00051d1e 2**0
CONTENTS, READONLY
14 .riscv.attributes 0000003f 00000000 00000000 00051d4d 2**0
CONTENTS, READONLY
15 .debug_frame 000014bc 00000000 00000000 00051d8c 2**2
CONTENTS, READONLY, DEBUGGING, OCTETS
NOTE: the
-h
flag signifies that we would like simply the part headers.
We see two .iram.textual content
sections, which look equivalent aside from the Dimension
and
Algn
(alignment). As a result of the entire .iram.textual content
symbols are 16-bit aligned
(ALIGN (16)
), the 1-byte aligned part (2**0
or 2^0 = 1
) is empty, and
the entire knowledge is within the 2-byte aligned (2**0
or 2^1 = 2
) part. Each
sections have the identical digital reminiscence deal with (VMA
), which is sensible given
that the dimensions of the primary part is 0
. We are able to soar to the .iram.textual content
part to see if our call_start_cpu0
operate is certainly current.
$ riscv32-esp-elf-objdump -D -j .iram.textual content construct/bootloader/bootloader.elf | head -46
construct/bootloader/bootloader.elf: file format elf32-littleriscv
Disassembly of part .iram.textual content:
403cc710 <call_start_cpu0>:
403cc710: 7171 addi sp,sp,-176
403cc712: d706 sw ra,172(sp)
403cc714: d522 sw s0,168(sp)
403cc716: d326 sw s1,164(sp)
403cc718: 2895 jal 403cc78c <bootloader_init>
403cc71a: c119 beqz a0,403cc720 <call_start_cpu0+0x10>
403cc71c: 55a030ef jal ra,403cfc76 <bootloader_reset>
403cc720: 0a000613 li a2,160
403cc724: 4581 li a1,0
403cc726: 850a mv a0,sp
403cc728: ffc34097 auipc ra,0xffc34
403cc72c: c2c080e7 jalr -980(ra) # 40000354 <memset>
403cc730: 850a mv a0,sp
403cc732: 19a030ef jal ra,403cf8cc <bootloader_utility_load_partition_table>
403cc736: 3fcd64b7 lui s1,0x3fcd6
403cc73a: ed19 bnez a0,403cc758 <call_start_cpu0+0x48>
403cc73c: 688020ef jal ra,403cedc4 <esp_log_early_timestamp>
403cc740: 85aa mv a1,a0
403cc742: 3fcd6537 lui a0,0x3fcd6
403cc746: 86c48613 addi a2,s1,-1940 # 3fcd586c <_data_end+0x48>
403cc74a: 87450513 addi a0,a0,-1932 # 3fcd5874 <_data_end+0x50>
403cc74e: ffc34097 auipc ra,0xffc34
403cc752: 8f2080e7 jalr -1806(ra) # 40000040 <esp_rom_printf>
403cc756: b7d9 j 403cc71c <call_start_cpu0+0xc>
403cc758: 850a mv a0,sp
403cc75a: 3a0030ef jal ra,403cfafa <bootloader_utility_get_selected_boot_partition>
403cc75e: f9d00793 li a5,-99
403cc762: 842a mv s0,a0
403cc764: faf50ce3 beq a0,a5,403cc71c <call_start_cpu0+0xc>
403cc768: 3fcd6637 lui a2,0x3fcd6
403cc76c: 3fcd6537 lui a0,0x3fcd6
403cc770: 86c48593 addi a1,s1,-1940
403cc774: 8a860613 addi a2,a2,-1880 # 3fcd58a8 <_data_end+0x84>
403cc778: 8e050513 addi a0,a0,-1824 # 3fcd58e0 <_data_end+0xbc>
403cc77c: ffc34097 auipc ra,0xffc34
403cc780: 8c4080e7 jalr -1852(ra) # 40000040 <esp_rom_printf>
403cc784: 85a2 mv a1,s0
403cc786: 850a mv a0,sp
403cc788: 50a030ef jal ra,403cfc92 <bootloader_utility_load_boot_image>
NOTE: the
-D
flag signifies that we wish to disassemble all part
contents. The-j .iram.textual content
signifies that we solely need the contents of the
.iram.textual content
part.
Right here we see the acquainted operate prologue of rising our stack (addi sp,sp,-176
) and storing our callee-saved
registers
(s0
, s1
) on it, earlier than progressing by the varied calls to bootloader
utility capabilities. The bootloader_init()
implementation varies per SoC, however
the high-level steps are pretty related. The esp32c3
implementation
is proven under.
parts/bootloader_support/src/esp32c3/bootloader_esp32c3.c
esp_err_t bootloader_init(void)
{
esp_err_t ret = ESP_OK;
bootloader_hardware_init();
bootloader_ana_reset_config();
bootloader_super_wdt_auto_feed();
// In RAM_APP, reminiscence will probably be initialized in `call_start_cpu0`
#if !CONFIG_APP_BUILD_TYPE_RAM
// shield reminiscence area
bootloader_init_mem();
/* examine that static RAM is after the stack */
assert(&_bss_start <= &_bss_end);
assert(&_data_start <= &_data_end);
// clear bss part
bootloader_clear_bss_section();
#endif // !CONFIG_APP_BUILD_TYPE_RAM
// init eFuse digital mode (learn eFuses to RAM)
#ifdef CONFIG_EFUSE_VIRTUAL
ESP_LOGW(TAG, "eFuse digital mode is enabled. If Safe boot or Flash encryption is enabled then it doesn't present any safety. FOR TESTING ONLY!");
#ifndef CONFIG_EFUSE_VIRTUAL_KEEP_IN_FLASH
esp_efuse_init_virtual_mode_in_ram();
#endif
#endif
// config clock
bootloader_clock_configure();
// initialize console, any longer, we are able to use esp_log
bootloader_console_init();
/* print 2nd bootloader banner */
bootloader_print_banner();
#if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP
//init cache hal
cache_hal_init();
//init mmu
mmu_hal_init();
// replace flash ID
bootloader_flash_update_id();
// Examine and run XMC startup circulate
if ((ret = bootloader_flash_xmc_startup()) != ESP_OK) {
ESP_LOGE(TAG, "failed when operating XMC startup circulate, reboot!");
return ret;
}
#if !CONFIG_APP_BUILD_TYPE_RAM
// learn bootloader header
if ((ret = bootloader_read_bootloader_header()) != ESP_OK) {
return ret;
}
// learn chip revision and examine if it is appropriate to bootloader
if ((ret = bootloader_check_bootloader_validity()) != ESP_OK) {
return ret;
}
#endif //#if !CONFIG_APP_BUILD_TYPE_RAM
// initialize spi flash
if ((ret = bootloader_init_spi_flash()) != ESP_OK) {
return ret;
}
#endif // !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP
// examine whether or not a WDT reset happend
bootloader_check_wdt_reset();
// config WDT
bootloader_config_wdt();
// allow RNG early entropy supply
bootloader_enable_random();
return ret;
}
If all initialization is profitable (i.e. bootloader_init()
returns 0
), we
soar over the decision to bootloader_reset()
with beqz
and proceed to loading
the applying.
403cc71a: c119 beqz a0,403cc720 <call_start_cpu0+0x10>
403cc71c: 55a030ef jal ra,403cfc76 <bootloader_reset>
403cc720: 0a000613 li a2,160
Along with the bootloader and utility binaries, we additionally noticed {that a}
partition
table
was constructed and loaded into flash (it was the final merchandise and had dimension of solely
3072
bytes). The partition desk informs the bootloader the place varied knowledge is
positioned in flash. We noticed a visible illustration of its contents within the
bootloader logs once we reset the CPU.
I (53) boot: Partition Desk:
I (57) boot: ## Label Utilization Sort ST Offset Size
I (64) boot: 0 nvs WiFi knowledge 01 02 00009000 00006000
I (71) boot: 1 phy_init RF knowledge 01 01 0000f000 00001000
I (79) boot: 2 manufacturing unit manufacturing unit app 00 00 00010000 00100000
I (86) boot: Finish of partition desk
On this case, we’re loading the manufacturing unit
partition, which is the place our
utility was written to flash (0x00010000
). There are a variety of steps in
this course of, however the sequence maps to the next operate calls.
void bootloader_utility_load_boot_image(const bootloader_state_t *bs, int start_index)
:
determines the place the specified picture exists and, if it can’t be discovered, goes
by a sequence of fallback choices.static void load_image(const esp_image_metadata_t *image_data)
:
copies loaded segments to RAM and units up caches for mapped segments.static void unpack_load_app(const esp_image_metadata_t *data)
:
configures mappings for MMU.
The ultimate step is to really begin the applying, which is achieved by
defining an entry
image on the entry_addr
, then calling it.
parts/bootloader_support/src/bootloader_utility.c
static void set_cache_and_start_app(
uint32_t drom_addr,
uint32_t drom_load_addr,
uint32_t drom_size,
uint32_t irom_addr,
uint32_t irom_load_addr,
uint32_t irom_size,
uint32_t entry_addr)
{
...
ESP_LOGD(TAG, "begin: 0xpercent08"PRIx32, entry_addr);
bootloader_atexit();
typedef void (*entry_t)(void) __attribute__((noreturn));
entry_t entry = ((entry_t) entry_addr);
// TODO: now we have used fairly a little bit of stack at this level.
// use "movsp" instruction to reset stack again to the place ROM stack begins.
(*entry)();
}
Bootloaders are an incredible place to look whenever you wish to perceive how software program
and {hardware} talk. Whilst you could by no means want to change the bootloader
your self, information of the way it works is beneficial for conceptualizing how adjustments
made at increased ranges utlimately translate to the machine.
As all the time, these posts are supposed to function a helpful useful resource for folk who’re
enthusiastic about studying extra about RISC-V and low-level software program basically. If I
can do a greater job of reaching that objective, or you could have any questions or
feedback, please be happy to ship me a message
@hasheddan on Twitter or
@hasheddan@types.pl on Mastodon!