Skip to content

Commit

Permalink
Merge pull request #49 from gustavonihei/feature/c3_mcuboot
Browse files Browse the repository at this point in the history
ESP32-C3: Add support for booting from MCUboot bootloader
  • Loading branch information
jessebraham authored Oct 24, 2022
2 parents ca4f295 + 1ced513 commit a8e879b
Show file tree
Hide file tree
Showing 11 changed files with 767 additions and 97 deletions.
5 changes: 1 addition & 4 deletions esp-hal-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,8 @@ mod critical_section_impl {
unsafe impl critical_section::Impl for super::CriticalSection {
unsafe fn acquire() -> critical_section::RawRestoreState {
let mut mstatus = 0u32;
unsafe {
core::arch::asm!("csrrci {0}, mstatus, 8", inout(reg) mstatus);
}
core::arch::asm!("csrrci {0}, mstatus, 8", inout(reg) mstatus);
let interrupts_active = (mstatus & 0b1000) != 0;

#[cfg(multi_core)]
{
let guard = multicore::MULTICORE_LOCK.lock();
Expand Down
5 changes: 5 additions & 0 deletions esp32c3-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ categories = [
]

[dependencies]
cfg-if = "1.0"
embedded-hal = { version = "0.2.7", features = ["unproven"] }
embedded-hal-1 = { version = "=1.0.0-alpha.9", optional = true, package = "embedded-hal" }
embedded-hal-nb = { version = "=1.0.0-alpha.1", optional = true }
Expand All @@ -43,6 +44,7 @@ ssd1306 = "0.7.1"

[features]
default = ["rt", "vectored"]
mcu-boot = []
direct-boot = []
eh1 = ["esp-hal-common/eh1", "dep:embedded-hal-1", "dep:embedded-hal-nb"]
rt = ["riscv-rt"]
Expand All @@ -62,3 +64,6 @@ required-features = ["eh1"]
[[example]]
name = "spi_eh1_device_loopback"
required-features = ["eh1"]

[profile.dev]
opt-level = 1
124 changes: 124 additions & 0 deletions esp32c3-hal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,130 @@ The compilation target for this device is officially supported via the `stable`
$ rustup target add riscv32imc-unknown-none-elf
```

### Supported boot methods

#### IDF Bootloader

The [IDF second stage bootloader](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/startup.html#second-stage-bootloader) is the default bootloader solution.

By default, [espflash](https://github.com/esp-rs/espflash) fetches the required binaries (Bootloader and Partition Table) and flashes them onto the target device together with the Rust-based application firmware image.

#### MCUboot Secure Bootloader

[MCUboot](https://github.com/mcu-tools/mcuboot) is a secure bootloader solution feature-wise equivalent to the [IDF Bootloader](#idf-bootloader).
You may find more information on the documentation pages for MCUboot and the Espressif port:
- https://docs.mcuboot.com/
- https://docs.mcuboot.com/readme-espressif.html

##### Requirements

Booting from MCUboot secure bootloader requires the Rust application image to be built in a [MCUboot-specific image format](https://docs.mcuboot.com/design.html#image-format). You need to install the following dependencies:

```shell
# Required for generating the object file in Intel HEX format
cargo install cargo-binutils
rustup component add llvm-tools-preview

# MCUboot's tool for image signing and key management
pip install imgtool
```

Currently, MCUboot is still not supported as a booting option in [espflash](https://github.com/esp-rs/espflash/issues/267), so you'll need to use the [esptool](https://github.com/espressif/esptool) utility for flashing both the MCUboot bootloader and the Rust application binaries:

```shell
# Serial flasher utility for Espressif chips
pip install esptool
```

Download a prebuilt MCUboot bootloader image for the target device:

```shell
# Prebuilt MCUboot bootloader binary
curl -LO https://github.com/espressif/esp-nuttx-bootloader/releases/download/latest/mcuboot-esp32c3.bin
```

##### Booting the Hello World example from MCUboot

Build the Hello World example with MCUboot support:

```shell
cargo build --release --example hello_world --features mcu-boot
```
Then proceed to generating the application binary and flashing it onto the target device:

```shell
# Generate the object file in Intel HEX format
rust-objcopy -O ihex target/riscv32imc-unknown-none-elf/release/examples/hello_world app.hex

# Generate the application firmware image binary file in MCUboot-format
imgtool sign --pad --align 4 -v 0 -s auto -H 32 --pad-header -S 0x100000 app.hex app.bin

# Flash the application firmware image binary onto the target device
esptool.py -c esp32c3 -p /dev/ttyUSB0 -b 921600 --after no_reset write_flash -fs 4MB -fm dio -ff 40m 0x0 ./mcuboot-esp32c3.bin 0x110000 ./app.bin
```
Once the device is flashed, you may monitor the serial interface (e.g. with `picocom`):

```shell
picocom -b 115200 /dev/ttyUSB0 --imap lfcrlf
```

Reset the board and MCUboot should load the Hello World example:
```shell
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fcd8598,len:0x10cc
load:0x403c8000,len:0x2b90
load:0x403d0000,len:0x1364
entry 0x403c804a
[esp32c3] [INF] Enabling RNG early entropy source...
[esp32c3] [INF] *** Booting MCUboot build v1.8.0-86-g14763b1 ***
[esp32c3] [INF] Primary image: magic=good, swap_type=0x2, copy_done=0x1, image_ok=0x3
[esp32c3] [INF] Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
[esp32c3] [INF] Boot source: none
[esp32c3] [INF] Swap type: test
[esp32c3] [INF] Disabling RNG early entropy source...
[esp32c3] [INF] br_image_off = 0x10000
[esp32c3] [INF] ih_hdr_size = 0x20
[esp32c3] [INF] DRAM segment: start=0x3fcd0000, size=0x0, vaddr=0x3fcd0000
[esp32c3] [INF] IRAM segment: start=0x1d00, size=0x170c, vaddr=0x40380000
[esp32c3] [INF] start=0x40380004
Hello world!
Hello world!
Hello world!
```

#### Direct Boot

[Direct Boot](https://github.com/espressif/esp32c3-direct-boot-example#direct-boot-in-esp32-c3) allows an application stored in the External Flash to be executed directly, without being copied into Internal RAM.

##### Booting the Hello World example using Direct Boot

Build the Hello World example with support for Direct Boot:

```shell
cargo build --release --example hello_world --features direct-boot
```

Then proceed to generating the application binary and flashing it onto the target device:

```shell
cargo espflash --release --format direct-boot --features direct-boot --example hello_world --monitor
```

The ROM Bootloader will identify the firmware image built with Direct Boot support and load it appropriately from the External Flash:

```shell
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
Hello world!
Hello world!
Hello world!
```

## License

Licensed under either of:
Expand Down
60 changes: 59 additions & 1 deletion esp32c3-hal/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
use std::{env, fs::File, io::Write, path::PathBuf, process::exit};

// Thanks to kennytm and TheDan64 for the assert_used_features macro.
// Source:
// https://github.com/TheDan64/inkwell/blob/36c3b106e61b1b45295a35f94023d93d9328c76f/src/lib.rs#L81-L110
macro_rules! assert_unique_features {
() => {};
($first:tt $(,$rest:tt)*) => {
$(
#[cfg(all(feature = $first, feature = $rest))]
compile_error!(concat!("Features \"", $first, "\" and \"", $rest, "\" cannot be used together"));
)*
assert_unique_features!($($rest),*);
}
}

assert_unique_features! {"mcu-boot", "direct-boot"}

#[cfg(feature = "direct-boot")]
fn main() {
check_opt_level();
Expand Down Expand Up @@ -36,7 +52,7 @@ fn main() {
add_defaults();
}

#[cfg(not(feature = "direct-boot"))]
#[cfg(not(any(feature = "mcu-boot",feature = "direct-boot")))]
fn main() {
check_opt_level();

Expand Down Expand Up @@ -66,13 +82,55 @@ fn main() {
add_defaults();
}

#[cfg(feature = "mcu-boot")]
fn main() {
check_opt_level();

// Put the linker script somewhere the linker can find it
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());

File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("ld/mb-esp32c3-memory.x"))
.unwrap();

File::create(out.join("esp32c3-link.x"))
.unwrap()
.write_all(include_bytes!("ld/mb-esp32c3-link.x"))
.unwrap();

File::create(out.join("riscv-link.x"))
.unwrap()
.write_all(include_bytes!("ld/mb-riscv-link.x"))
.unwrap();

File::create(out.join("linkall.x"))
.unwrap()
.write_all(include_bytes!("ld/mb-linkall.x"))
.unwrap();

println!("cargo:rustc-link-search={}", out.display());

// Only re-run the build script when memory.x is changed,
// instead of when any part of the source code changes.
println!("cargo:rerun-if-changed=ld/memory.x");

add_defaults();
}

fn add_defaults() {
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());

File::create(out.join("hal-defaults.x"))
.unwrap()
.write_all(include_bytes!("ld/hal-defaults.x"))
.unwrap();

File::create(out.join("rom-functions.x"))
.unwrap()
.write_all(include_bytes!("ld/rom-functions.x"))
.unwrap();

println!("cargo:rustc-link-search={}", out.display());
}

Expand Down
1 change: 0 additions & 1 deletion esp32c3-hal/ld/db-esp32c3-memory.x
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ MEMORY
RTC_FAST : ORIGIN = 0x50000000, LENGTH = 0x2000 /*- ESP_BOOTLOADER_RESERVE_RTC*/
}


REGION_ALIAS("REGION_TEXT", IROM);
REGION_ALIAS("REGION_RODATA", DROM);

Expand Down
45 changes: 45 additions & 0 deletions esp32c3-hal/ld/mb-esp32c3-link.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
INCLUDE memory.x

SECTIONS
{
.metadata :
{
/* Magic for load header */

LONG(0xace637d3)

/* Application entry point address */

KEEP(*(.entry_addr))

/* IRAM metadata:
* - Destination address (VMA) for IRAM region
* - Flash offset (LMA) for start of IRAM region
* - Size of IRAM region
*/

LONG(ADDR(.rwtext))
LONG(LOADADDR(.rwtext))
LONG(SIZEOF(.rwtext))

/* DRAM metadata:
* - Destination address (VMA) for DRAM region
* - Flash offset (LMA) for start of DRAM region
* - Size of DRAM region
*/

LONG(ADDR(.data))
LONG(LOADADDR(.data))
LONG(SIZEOF(.data))
} > metadata
}

INCLUDE riscv-link.x

_image_drom_vma = ADDR(.rodata);
_image_drom_lma = LOADADDR(.rodata);
_image_drom_size = LOADADDR(.rodata) + SIZEOF(.rodata) - _image_drom_lma;

_image_irom_vma = ADDR(.text);
_image_irom_lma = LOADADDR(.text);
_image_irom_size = LOADADDR(.text) + SIZEOF(.text) - _image_irom_lma;
66 changes: 66 additions & 0 deletions esp32c3-hal/ld/mb-esp32c3-memory.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
MEMORY
{
/*
https://github.com/espressif/esptool/blob/ed64d20b051d05f3f522bacc6a786098b562d4b8/esptool/targets/esp32c3.py#L78-L90
MEMORY_MAP = [[0x00000000, 0x00010000, "PADDING"],
[0x3C000000, 0x3C800000, "DROM"],
[0x3FC80000, 0x3FCE0000, "DRAM"],
[0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"],
[0x3FF00000, 0x3FF20000, "DROM_MASK"],
[0x40000000, 0x40060000, "IROM_MASK"],
[0x42000000, 0x42800000, "IROM"],
[0x4037C000, 0x403E0000, "IRAM"],
[0x50000000, 0x50002000, "RTC_IRAM"],
[0x50000000, 0x50002000, "RTC_DRAM"],
[0x600FE000, 0x60100000, "MEM_INTERNAL2"]]
*/

/* The origin values for "metadata" and "ROM" memory regions are the actual
* load addresses.
*
* NOTE: The memory region starting from 0x0 with 0x20 length is reserved
* for the MCUboot header, which will be prepended to the binary file by
* the "imgtool" during the signing of firmware image.
*/
metadata : ORIGIN = 0x20, LENGTH = 0x20
ROM : ORIGIN = 0x40, LENGTH = 0x400000 - 0x40

/* 400K of on soc RAM, 16K reserved for cache */
ICACHE : ORIGIN = 0x4037C000, LENGTH = 0x4000
/* Instruction RAM */
IRAM : ORIGIN = 0x4037C000 + 0x4000, LENGTH = 400K - 0x4000
/* Data RAM */
DRAM : ORIGIN = 0x3FC80000, LENGTH = 0x50000

/* External flash */
/* Instruction ROM */
IROM : ORIGIN = 0x42000000, LENGTH = 0x400000
/* Data ROM */
/* The DROM segment origin is offset by 0x40 for mirroring the actual ROM
* image layout:
* 0x0 - 0x1F : MCUboot header
* 0x20 - 0x3F : Application image metadata section
* 0x40 onwards: ROM code and data
* This is required to meet the following constraint from the external
* flash MMU:
* VMA % 64KB == LMA % 64KB
* i.e. the lower 16 bits of both the virtual address (address seen by the
* CPU) and the load address (physical address of the external flash) must
* be equal.
*/
DROM : ORIGIN = 0x3C000000 + 0x40, LENGTH = 0x400000 - 0x40

/* RTC fast memory (executable). Persists over deep sleep. */
RTC_FAST : ORIGIN = 0x50000000, LENGTH = 0x2000 /*- ESP_BOOTLOADER_RESERVE_RTC*/
}

REGION_ALIAS("REGION_TEXT", IROM);
REGION_ALIAS("REGION_RODATA", DROM);

REGION_ALIAS("REGION_DATA", DRAM);
REGION_ALIAS("REGION_BSS", DRAM);
REGION_ALIAS("REGION_HEAP", DRAM);
REGION_ALIAS("REGION_STACK", DRAM);

REGION_ALIAS("REGION_RWTEXT", IRAM);
REGION_ALIAS("REGION_RTC_FAST", RTC_FAST);
3 changes: 3 additions & 0 deletions esp32c3-hal/ld/mb-linkall.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
INCLUDE "esp32c3-link.x"
INCLUDE "hal-defaults.x"
INCLUDE "rom-functions.x"
Loading

0 comments on commit a8e879b

Please sign in to comment.