Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESP32-C3: Add support for booting from MCUboot bootloader #49

Merged
merged 4 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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