From 5b6c52c02c7a149ccdf05cafa767d8c5881bc93a Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Nihei Date: Tue, 5 Apr 2022 16:51:55 -0300 Subject: [PATCH 1/4] esp32c3: Add support for booting from MCUboot bootloader Signed-off-by: Gustavo Henrique Nihei --- esp32c3-hal/Cargo.toml | 5 + esp32c3-hal/build.rs | 60 ++++++- esp32c3-hal/ld/db-esp32c3-memory.x | 1 - esp32c3-hal/ld/mb-esp32c3-link.x | 45 ++++++ esp32c3-hal/ld/mb-esp32c3-memory.x | 66 ++++++++ esp32c3-hal/ld/mb-linkall.x | 3 + esp32c3-hal/ld/mb-riscv-link.x | 251 +++++++++++++++++++++++++++++ esp32c3-hal/ld/rom-functions.x | 7 + esp32c3-hal/src/lib.rs | 124 +++++++++++++- 9 files changed, 559 insertions(+), 3 deletions(-) create mode 100644 esp32c3-hal/ld/mb-esp32c3-link.x create mode 100644 esp32c3-hal/ld/mb-esp32c3-memory.x create mode 100644 esp32c3-hal/ld/mb-linkall.x create mode 100644 esp32c3-hal/ld/mb-riscv-link.x create mode 100644 esp32c3-hal/ld/rom-functions.x diff --git a/esp32c3-hal/Cargo.toml b/esp32c3-hal/Cargo.toml index eb6d7e50e01..bf525740f2f 100644 --- a/esp32c3-hal/Cargo.toml +++ b/esp32c3-hal/Cargo.toml @@ -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 } @@ -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"] @@ -62,3 +64,6 @@ required-features = ["eh1"] [[example]] name = "spi_eh1_device_loopback" required-features = ["eh1"] + +[profile.dev] +opt-level = 1 diff --git a/esp32c3-hal/build.rs b/esp32c3-hal/build.rs index f4f7994d54e..8bfe0d5ee00 100644 --- a/esp32c3-hal/build.rs +++ b/esp32c3-hal/build.rs @@ -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(); @@ -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(); @@ -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()); } diff --git a/esp32c3-hal/ld/db-esp32c3-memory.x b/esp32c3-hal/ld/db-esp32c3-memory.x index a3d0108689d..af43bd8183c 100644 --- a/esp32c3-hal/ld/db-esp32c3-memory.x +++ b/esp32c3-hal/ld/db-esp32c3-memory.x @@ -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); diff --git a/esp32c3-hal/ld/mb-esp32c3-link.x b/esp32c3-hal/ld/mb-esp32c3-link.x new file mode 100644 index 00000000000..cc72f821fd3 --- /dev/null +++ b/esp32c3-hal/ld/mb-esp32c3-link.x @@ -0,0 +1,45 @@ +INCLUDE memory.x + +SECTIONS +{ + .metadata : + { + /* Magic for load header */ + + LONG(0xace637d3) + + /* Application entry point address */ + + LONG(ADDR(.rwtext)) + + /* 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; diff --git a/esp32c3-hal/ld/mb-esp32c3-memory.x b/esp32c3-hal/ld/mb-esp32c3-memory.x new file mode 100644 index 00000000000..a7c59cff8ec --- /dev/null +++ b/esp32c3-hal/ld/mb-esp32c3-memory.x @@ -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); diff --git a/esp32c3-hal/ld/mb-linkall.x b/esp32c3-hal/ld/mb-linkall.x new file mode 100644 index 00000000000..02bc894cd57 --- /dev/null +++ b/esp32c3-hal/ld/mb-linkall.x @@ -0,0 +1,3 @@ +INCLUDE "esp32c3-link.x" +INCLUDE "hal-defaults.x" +INCLUDE "rom-functions.x" diff --git a/esp32c3-hal/ld/mb-riscv-link.x b/esp32c3-hal/ld/mb-riscv-link.x new file mode 100644 index 00000000000..081c0b75ff0 --- /dev/null +++ b/esp32c3-hal/ld/mb-riscv-link.x @@ -0,0 +1,251 @@ +ENTRY(_start_hal) +PROVIDE(_start_trap = _start_trap_hal); + +PROVIDE(_stext = ORIGIN(REGION_TEXT)); +PROVIDE(_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(UserSoft = DefaultHandler); +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(UserTimer = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(UserExternal = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +SECTIONS +{ + .rodata : + { + _srodata = .; + *(EXCLUDE_FILE (*libriscv-*.rlib:riscv.*) .rodata); + *(EXCLUDE_FILE (*libriscv-*.rlib:riscv.*) .rodata.*); + *(EXCLUDE_FILE (*libriscv_rt-*.rlib:riscv-rt.*) .rodata); + *(EXCLUDE_FILE (*libriscv_rt-*.rlib:riscv-rt.*) .rodata.*); + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + _erodata = .; + } > REGION_RODATA AT>ROM + + .rwtext : + { + _srwtext = .; + /* Put reset handler first in .rwtext section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + KEEP(*(.trap)); + KEEP(*(.trap.rust)); + + *libriscv-*.rlib:riscv.*(.literal .text .literal.* .text.*); + *libriscv_rt-*.rlib:riscv-rt.*(.literal .text .literal.* .text.*); + *(.rwtext); + . = ALIGN(4); + _erwtext = .; + } > REGION_RWTEXT AT>ROM + + .rwtext.dummy (NOLOAD): + { + /* This section is required to skip .rwtext area because REGION_RWTEXT + * and REGION_BSS reflect the same address space on different buses. + */ + + . = ORIGIN(REGION_BSS) + _erwtext - _srwtext; + } > REGION_BSS + + .bss (NOLOAD) : + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(4); + _ebss = .; + } > REGION_BSS + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > REGION_HEAP + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > REGION_STACK + + .data : + { + _sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + *libriscv-*.rlib:riscv.*(.rodata .rodata.*); + *libriscv_rt-*.rlib:riscv-rt.*(.rodata .rodata.*); + . = ALIGN(4); + _edata = .; + } > REGION_DATA AT>ROM + + .rtc_fast.text : + { + _srtc_fast_text = .; + *(.rtc_fast.literal .rtc_fast.text .rtc_fast.literal.* .rtc_fast.text.*) + . = ALIGN(4); + _ertc_fast_text = .; + } > REGION_RTC_FAST AT>ROM + + .rtc_fast.data : + { + _rtc_fast_data_start = ABSOLUTE(.); + *(.rtc_fast.data .rtc_fast.data.*) + . = ALIGN(4); + _rtc_fast_data_end = ABSOLUTE(.); + } > REGION_RTC_FAST AT>ROM + + .rtc_fast.bss (NOLOAD) : ALIGN(4) + { + _rtc_fast_bss_start = ABSOLUTE(.); + *(.rtc_fast.bss .rtc_fast.bss.*) + . = ALIGN(4); + _rtc_fast_bss_end = ABSOLUTE(.); + } > REGION_RTC_FAST + + .rtc_fast.noinit (NOLOAD) : ALIGN(4) + { + *(.rtc_fast.noinit .rtc_fast.noinit.*) + } > REGION_RTC_FAST + + /* The alignment of the "text" output section is forced to + * 0x00010000 (64KB) to ensure that it will be allocated at the beginning + * of the next available Flash block. + * 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. + */ + + .text.dummy (NOLOAD) : ALIGN(0x10000) + { + /* This section is required to skip .rodata area because REGION_TEXT + * and REGION_RODATA reflect the same address space on different buses. + */ + + . += SIZEOF(.rodata); + } > REGION_TEXT + + .text : ALIGN(0x10000) + { + _stext = .; + *(EXCLUDE_FILE (*libriscv-*.rlib:riscv.*) .text) + *(EXCLUDE_FILE (*libriscv-*.rlib:riscv.*) .text.*) + *(EXCLUDE_FILE (*libriscv_rt-*.rlib:riscv-rt.*) .text) + *(EXCLUDE_FILE (*libriscv_rt-*.rlib:riscv-rt.*) .text.*) + *(.text .text.*); + _etext = .; + } > REGION_TEXT AT>ROM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(_sidata = _erodata + 8); +PROVIDE(_irwtext = ORIGIN(DROM) + _text_size + _rodata_size + _data_size); +PROVIDE(_irtc_fast_text = ORIGIN(DROM) + _text_size + _rodata_size + _data_size + _rwtext_size); +PROVIDE(_irtc_fast_data = ORIGIN(DROM) + _text_size + _rodata_size + _data_size + _rwtext_size + _fast_text_size); + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_RODATA) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_RODATA must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_DATA) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_DATA must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_HEAP) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_HEAP must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_TEXT) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_TEXT must be 4-byte aligned"); + +ASSERT(ORIGIN(REGION_STACK) % 4 == 0, " +ERROR(riscv-rt): the start of the REGION_STACK must be 4-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 4 == 0 && _edata % 4 == 0, " +BUG(riscv-rt): .data is not 4-byte aligned"); + +ASSERT(_sidata % 4 == 0, " +BUG(riscv-rt): the LMA of .data is not 4-byte aligned"); + +ASSERT(_sbss % 4 == 0 && _ebss % 4 == 0, " +BUG(riscv-rt): .bss is not 4-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT), " +ERROR(riscv-rt): The .text section must be placed inside the REGION_TEXT region. +Set _stext to an address smaller than 'ORIGIN(REGION_TEXT) + LENGTH(REGION_TEXT)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ diff --git a/esp32c3-hal/ld/rom-functions.x b/esp32c3-hal/ld/rom-functions.x new file mode 100644 index 00000000000..34431f0a21b --- /dev/null +++ b/esp32c3-hal/ld/rom-functions.x @@ -0,0 +1,7 @@ +ets_printf = 0x40000040; +PROVIDE(esp_rom_printf = ets_printf); +PROVIDE(cache_invalidate_icache_all = 0x400004d8); +PROVIDE(cache_suspend_icache = 0x40000524); +PROVIDE(cache_resume_icache = 0x40000528); +PROVIDE(cache_ibus_mmu_set = 0x40000560); +PROVIDE(cache_dbus_mmu_set = 0x40000564); diff --git a/esp32c3-hal/src/lib.rs b/esp32c3-hal/src/lib.rs index 9efac024a54..76ef90959c3 100644 --- a/esp32c3-hal/src/lib.rs +++ b/esp32c3-hal/src/lib.rs @@ -2,6 +2,9 @@ use core::arch::global_asm; +#[cfg(feature = "mcu-boot")] +use core::mem::size_of; + pub use embedded_hal as ehal; pub use esp_hal_common::{ clock, @@ -31,6 +34,7 @@ pub use esp_hal_common::{ Serial, UsbSerialJtag, }; + #[cfg(feature = "direct-boot")] use riscv_rt::pre_init; @@ -45,6 +49,49 @@ pub mod analog { } extern "C" { + cfg_if::cfg_if! { + if #[cfg(feature = "mcu-boot")] { + // Functions from internal ROM + fn cache_suspend_icache() -> u32; + fn cache_resume_icache(val: u32); + fn cache_invalidate_icache_all(); + fn cache_dbus_mmu_set( + ext_ram: u32, + vaddr: u32, + paddr: u32, + psize: u32, + num: u32, + fixed: u32, + ) -> i32; + fn cache_ibus_mmu_set( + ext_ram: u32, + vaddr: u32, + paddr: u32, + psize: u32, + num: u32, + fixed: u32, + ) -> i32; + + /* IROM metadata: + * - Destination address (VMA) for IROM region + * - Flash offset (LMA) for start of IROM region + * - Size of IROM region + */ + static mut _image_irom_vma: u32; + static mut _image_irom_lma: u32; + static mut _image_irom_size: u32; + + /* DROM metadata: + * - Destination address (VMA) for DROM region + * - Flash offset (LMA) for start of DROM region + * - Size of DROM region + */ + static mut _image_drom_vma: u32; + static mut _image_drom_lma: u32; + static mut _image_drom_size: u32; + } + } + // Boundaries of the .iram section static mut _srwtext: u32; static mut _erwtext: u32; @@ -204,7 +251,7 @@ _start_hal: global_asm!( r#" -.section .text +.section .init _abs_start_hal: .option norelax @@ -306,10 +353,85 @@ unsafe fn init() { r0::init_data(&mut _srtc_fast_text, &mut _ertc_fast_text, &_irtc_fast_text); } +#[cfg(feature = "mcu-boot")] +#[link_section = ".rwtext"] +unsafe fn configure_mmu() { + const PARTITION_OFFSET: u32 = 0x10000; + let app_irom_lma = PARTITION_OFFSET + ((&_image_irom_lma as *const u32) as u32); + let app_irom_size = (&_image_irom_size as *const u32) as u32; + let app_irom_vma = (&_image_irom_vma as *const u32) as u32; + let app_drom_lma = PARTITION_OFFSET + ((&_image_drom_lma as *const u32) as u32); + let app_drom_size = (&_image_drom_size as *const u32) as u32; + let app_drom_vma = (&_image_drom_vma as *const u32) as u32; + + let autoload = cache_suspend_icache(); + cache_invalidate_icache_all(); + + /* Clear the MMU entries that are already set up, so the new app only has + * the mappings it creates. + */ + + const FLASH_MMU_TABLE: *mut u32 = 0x600c_5000 as *mut u32; + const ICACHE_MMU_SIZE: usize = 0x200; + const FLASH_MMU_TABLE_SIZE: usize = ICACHE_MMU_SIZE / size_of::(); + const MMU_TABLE_INVALID_VAL: u32 = 0x100; + + for i in 0..FLASH_MMU_TABLE_SIZE { + FLASH_MMU_TABLE.add(i).write_volatile(MMU_TABLE_INVALID_VAL); + } + + const MMU_BLOCK_SIZE: u32 = 0x0001_0000; + const MMU_FLASH_MASK: u32 = !(MMU_BLOCK_SIZE - 1); + + let calc_mmu_pages = |size, vaddr| { + (size + (vaddr - (vaddr & MMU_FLASH_MASK)) + MMU_BLOCK_SIZE - 1) / MMU_BLOCK_SIZE + }; + + let drom_lma_aligned = app_drom_lma & MMU_FLASH_MASK; + let drom_vma_aligned = app_drom_vma & MMU_FLASH_MASK; + let drom_page_count = calc_mmu_pages(app_drom_size, app_drom_vma); + cache_dbus_mmu_set( + 0, + drom_vma_aligned, + drom_lma_aligned, + 64, + drom_page_count, + 0, + ); + + let irom_lma_aligned = app_irom_lma & MMU_FLASH_MASK; + let irom_vma_aligned = app_irom_vma & MMU_FLASH_MASK; + let irom_page_count = calc_mmu_pages(app_irom_size, app_irom_vma); + cache_ibus_mmu_set( + 0, + irom_vma_aligned, + irom_lma_aligned, + 64, + irom_page_count, + 0, + ); + + let peripherals = pac::Peripherals::steal(); + peripherals.EXTMEM.icache_ctrl1.modify(|_, w| { + w.icache_shut_ibus() + .clear_bit() + .icache_shut_dbus() + .clear_bit() + }); + + cache_resume_icache(autoload); +} + #[allow(unreachable_code)] #[export_name = "_mp_hook"] #[doc(hidden)] +#[cfg_attr(feature = "mcu-boot", link_section = ".rwtext")] pub fn mp_hook() -> bool { + #[cfg(feature = "mcu-boot")] + unsafe { + configure_mmu(); + } + unsafe { r0::zero_bss(&mut _rtc_fast_bss_start, &mut _rtc_fast_bss_end); } From 5cca83ca51d579cb68aa3c45109f6f7fe2efd3c6 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Nihei Date: Thu, 7 Apr 2022 11:26:09 -0300 Subject: [PATCH 2/4] esp32c3: Set entry point address on MCUboot image header Previously it assumed that the entry point was allocated at the beginning of the ".rwtext" output section, which is not always true. Signed-off-by: Gustavo Henrique Nihei --- esp32c3-hal/ld/mb-esp32c3-link.x | 2 +- esp32c3-hal/ld/mb-riscv-link.x | 1 - esp32c3-hal/src/lib.rs | 176 +++++++++++++++---------------- 3 files changed, 86 insertions(+), 93 deletions(-) diff --git a/esp32c3-hal/ld/mb-esp32c3-link.x b/esp32c3-hal/ld/mb-esp32c3-link.x index cc72f821fd3..0b44b527f2c 100644 --- a/esp32c3-hal/ld/mb-esp32c3-link.x +++ b/esp32c3-hal/ld/mb-esp32c3-link.x @@ -10,7 +10,7 @@ SECTIONS /* Application entry point address */ - LONG(ADDR(.rwtext)) + KEEP(*(.entry_addr)) /* IRAM metadata: * - Destination address (VMA) for IRAM region diff --git a/esp32c3-hal/ld/mb-riscv-link.x b/esp32c3-hal/ld/mb-riscv-link.x index 081c0b75ff0..1190c755a47 100644 --- a/esp32c3-hal/ld/mb-riscv-link.x +++ b/esp32c3-hal/ld/mb-riscv-link.x @@ -1,7 +1,6 @@ ENTRY(_start_hal) PROVIDE(_start_trap = _start_trap_hal); -PROVIDE(_stext = ORIGIN(REGION_TEXT)); PROVIDE(_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK)); PROVIDE(_max_hart_id = 0); PROVIDE(_hart_stack_size = 2K); diff --git a/esp32c3-hal/src/lib.rs b/esp32c3-hal/src/lib.rs index 76ef90959c3..53a2b118038 100644 --- a/esp32c3-hal/src/lib.rs +++ b/esp32c3-hal/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -use core::arch::global_asm; +use core::arch::{asm, global_asm}; #[cfg(feature = "mcu-boot")] use core::mem::size_of; @@ -237,97 +237,91 @@ _start_trap_hal: "# ); -global_asm!( - r#" -.section .init, "ax" -.global _start_hal - -_start_hal: - /* Jump to the absolute address defined by the linker script. */ - lui ra, %hi(_abs_start_hal) - jr %lo(_abs_start_hal)(ra) -"# -); - -global_asm!( - r#" -.section .init - -_abs_start_hal: - .option norelax - .cfi_startproc - .cfi_undefined ra - - // unsupported on ESP32C3 - // csrw mie, 0 - // csrw mip, 0 - - li x1, 0 - li x2, 0 - li x3, 0 - li x4, 0 - li x5, 0 - li x6, 0 - li x7, 0 - li x8, 0 - li x9, 0 - li x10,0 - li x11,0 - li x12,0 - li x13,0 - li x14,0 - li x15,0 - li x16,0 - li x17,0 - li x18,0 - li x19,0 - li x20,0 - li x21,0 - li x22,0 - li x23,0 - li x24,0 - li x25,0 - li x26,0 - li x27,0 - li x28,0 - li x29,0 - li x30,0 - li x31,0 +#[cfg(feature = "mcu-boot")] +#[link_section = ".entry_addr"] +#[no_mangle] +#[used] +// Entry point address for the MCUboot image header +static ENTRY_POINT: unsafe fn() -> ! = start_hal; + +#[link_section = ".init"] +#[export_name = "_start_hal"] +unsafe fn start_hal() -> ! { + asm!( + r#" + .option norelax + + // unsupported on ESP32-C3 + // csrw mie, 0 + // csrw mip, 0 + + li x1, 0 + li x2, 0 + li x3, 0 + li x4, 0 + li x5, 0 + li x6, 0 + li x7, 0 + li x8, 0 + li x9, 0 + li x10,0 + li x11,0 + li x12,0 + li x13,0 + li x14,0 + li x15,0 + li x16,0 + li x17,0 + li x18,0 + li x19,0 + li x20,0 + li x21,0 + li x22,0 + li x23,0 + li x24,0 + li x25,0 + li x26,0 + li x27,0 + li x28,0 + li x29,0 + li x30,0 + li x31,0 + + .option push + .option norelax + la gp, __global_pointer$ + .option pop + + // Check hart id + csrr a2, mhartid + lui t0, %hi(_max_hart_id) + add t0, t0, %lo(_max_hart_id) + bgtu a2, t0, abort_hal + + // Allocate stacks + la sp, _stack_start + lui t0, %hi(_hart_stack_size) + add t0, t0, %lo(_hart_stack_size) + + beqz a2, 2f // Jump if single-hart + mv t1, a2 + mv t2, t0 + 1: + add t0, t0, t2 + addi t1, t1, -1 + bnez t1, 1b + 2: + sub sp, sp, t0 + + // Set frame pointer + add s0, sp, zero + + jal zero, _start_rust + "# + ); - .option push - .option norelax - la gp, __global_pointer$ - .option pop - - // Check hart id - csrr a2, mhartid - lui t0, %hi(_max_hart_id) - add t0, t0, %lo(_max_hart_id) - bgtu a2, t0, abort_hal - - // Allocate stacks - la sp, _stack_start - lui t0, %hi(_hart_stack_size) - add t0, t0, %lo(_hart_stack_size) - - beqz a2, 2f // Jump if single-hart - mv t1, a2 - mv t2, t0 -1: - add t0, t0, t2 - addi t1, t1, -1 - bnez t1, 1b -2: - sub sp, sp, t0 - - // Set frame pointer - add s0, sp, zero - - jal zero, _start_rust - - .cfi_endproc -"# -); + unreachable!() +} global_asm!( r#" From 3ff5da5aa78695108b62a47d0489f9f9a5527949 Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Nihei Date: Fri, 21 Oct 2022 15:22:57 -0300 Subject: [PATCH 3/4] esp-hal-common: Remove unnecessary `unsafe` block Signed-off-by: Gustavo Henrique Nihei --- esp-hal-common/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/esp-hal-common/src/lib.rs b/esp-hal-common/src/lib.rs index 289608ab7bd..ced0edfa135 100644 --- a/esp-hal-common/src/lib.rs +++ b/esp-hal-common/src/lib.rs @@ -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(); From 1ced513d969b92419901c5e4b6bb95cec5bf4a7d Mon Sep 17 00:00:00 2001 From: Gustavo Henrique Nihei Date: Fri, 21 Oct 2022 18:57:36 -0300 Subject: [PATCH 4/4] Add documentation about supported boot methods Signed-off-by: Gustavo Henrique Nihei --- esp32c3-hal/README.md | 124 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/esp32c3-hal/README.md b/esp32c3-hal/README.md index e9baa4ed8ed..2498251490a 100644 --- a/esp32c3-hal/README.md +++ b/esp32c3-hal/README.md @@ -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: