bootloader: override the 2nd stage bootloader

Add the possibility to have user bootloader components. This is performed
from an application/project, by creating bootloader components. To do so,
it is required to create a `bootloader_component` directory containing
the custom modules to be compiled with the bootloader.

Thanks to this, two solutions are available to override the bootloader now:
- Using hooks within a user bootloader component
- Using a user defined `main` bootloader component to totally override the
  old implementation

Please check the two new examples in `examples/custom_bootloader`

* Closes https://github.com/espressif/esp-idf/issues/7043
This commit is contained in:
Omar Chebib
2021-04-15 10:31:33 +08:00
parent 82a5f9c4b7
commit a79acb413e
27 changed files with 435 additions and 5 deletions

View File

@@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(main)

View File

@@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides
# in is a project subdirectory.
#
PROJECT_NAME := bootloader_hooks
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,57 @@
# Bootloader hooks
(See the README.md file in the upper level for more information about bootloader examples.)
The purpose of this example is to show how to add hooks to the 2nd stage bootloader.
## Usage of this example:
Simply compile it:
```
idf.py build
```
Then flash it and open the monitor with the following command:
```
idf.py flash monitor
```
If everything went well, the bootloader output should be as followed:
```
I (24) HOOK: This hook is called BEFORE bootloader initialization
I (37) boot: [...]
[...]
I (60) HOOK: This hook is called AFTER bootloader initialization
```
And finally the application will start and show the message:
```
User application is loaded and running.
```
## Organisation of this example
This project contains an application, in the `main` directory that represents a user program.
It also contains a `bootloader_components` that, as it name states, contains a component compiled and linked with the bootloader.
Below is a short explanation of files in the project folder.
```
├── CMakeLists.txt
├── main
│   ├── CMakeLists.txt
│   └── main.c User application
├── bootloader_components
│   └── my_boot_hooks
│   ├── CMakeLists.txt
│   └── hooks.c Implementation of the hooks to execute on boot
└── README.md This is the file you are currently reading
```
Bootloader hooks are **not** supported in legacy `make` build system. They are only supported with `CMake` build system.
## Note about including weak symbols
The components in ESP-IDF are compiled as static libraries. Moreover, the bootloaders' hooks are declared as `weak`. Thus, when
defining hooks for the bootloader, we **must** tell the compiler to always include our library (`my_boot_hooks`) in the link process.
To achieve this, we need to define an extra symbol: `bootloader_hooks_include`. In our case, this symbol is a function defined in
`bootloader_components/my_boot_hooks/hooks.c`. This will make the linker include all the symbols contained in that file.

View File

@@ -0,0 +1,8 @@
idf_component_register(SRCS "hooks.c")
# We need to force GCC to integrate this static library into the
# bootloader link. Indeed, by default, as the hooks in the bootloader are weak,
# the linker would just ignore the symbols in the extra. (i.e. not strictly
# required)
# To do so, we need to define the symbol (function) `bootloader_hooks_include`
# within hooks.c source file.

View File

@@ -0,0 +1,19 @@
#include "esp_log.h"
/* Function used to tell the linker to include this file
* with all its symbols.
*/
void bootloader_hooks_include(void){
}
void bootloader_before_init(void) {
/* Keep in my mind that a lot of functions cannot be called from here
* as system initialization has not been performed yet, including
* BSS, SPI flash, or memory protection. */
ESP_LOGI("HOOK", "This hook is called BEFORE bootloader initialization");
}
void bootloader_after_init(void) {
ESP_LOGI("HOOK", "This hook is called AFTER bootloader initialization");
}

View File

@@ -0,0 +1,18 @@
from __future__ import print_function
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC', target=['esp32', 'esp32s2', 'esp32c3'])
def test_custom_bootloader_hooks_example(env, _): # type: ignore
# Test with default build configurations
dut = env.get_dut('main', 'examples/custom_bootloader/bootloader_hooks')
dut.start_app()
# Expect to read both hooks messages
dut.expect('This hook is called BEFORE bootloader initialization')
dut.expect('This hook is called AFTER bootloader initialization')
if __name__ == '__main__':
test_custom_bootloader_hooks_example()

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "bootloader_hooks_example_main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,11 @@
#include <stdio.h>
void app_main(void)
{
/**
* Nothing special is done here, everything interesting in this example
* is done in the custom bootloader code, located in:
* `bootloader_components/my_boot_hooks/hooks.c`
*/
printf("User application is loaded and running.\n");
}

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)