From b319d62052d6b553b135837ff8c35ee0c8d475bf Mon Sep 17 00:00:00 2001 From: Sherif A Abdou Date: Mon, 3 Feb 2025 10:22:32 -0500 Subject: Added in_flash RuntimeBuilder constructor Applications linked through this builder can be placed in a flash reservation. You'll need some other software to launch these programs, since they lack the boot header required by the NXP boot ROM. --- CHANGELOG.md | 3 + board/Cargo.toml | 1 + board/build.rs | 25 +++--- src/host.rs | 103 ++++++++++++++++++++---- src/host/imxrt-boot-header-1180.x | 3 + src/host/imxrt-boot-header.x | 3 + src/host/imxrt-link.x | 2 - tests/inspect_elf.rs | 161 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 273 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2920663..810cb36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +Introduce `RuntimeBuilder::in_flash` for creating images that can be launched +by your own software, instead of NXP's boot ROM. + Place sections starting with `.xip` into the same load region of `.text`. Unlike `.text`, the contents in `.xip` will not be relocated. diff --git a/board/Cargo.toml b/board/Cargo.toml index 9072274..9afdfb0 100644 --- a/board/Cargo.toml +++ b/board/Cargo.toml @@ -26,6 +26,7 @@ rtt-target = { version = "0.3", optional = true, features = ["cortex-m"] } panic-rtt-target = { version = "0.1", optional = true, features = ["cortex-m"] } [features] +nonboot = [] rtic = [] # Begin board features. teensy4 = [ diff --git a/board/build.rs b/board/build.rs index 8474e85..4531667 100644 --- a/board/build.rs +++ b/board/build.rs @@ -7,6 +7,16 @@ fn extract_features() -> HashSet { .collect() } +/// Creates a runtime for a particular family, adjusting whether the image is expected to be boot +/// based on provided features. +fn create_runtime(family: imxrt_rt::Family, flash_size: usize) -> imxrt_rt::RuntimeBuilder { + if cfg!(feature = "nonboot") { + imxrt_rt::RuntimeBuilder::in_flash(family, flash_size, 16 * 1024) + } else { + imxrt_rt::RuntimeBuilder::from_flexspi(family, flash_size) + } +} + /// Configures the runtime for a variety of boards. /// /// Note that some automated tests may check these runtimes. Feel free to change @@ -44,15 +54,12 @@ fn main() { .heap_size_env_override("BOARD_HEAP") .build() .unwrap(), - "imxrt1170evk_cm7" => imxrt_rt::RuntimeBuilder::from_flexspi( - imxrt_rt::Family::Imxrt1170, - 16 * 1024 * 1024, - ) - .rodata(imxrt_rt::Memory::Dtcm) - .stack_size_env_override("BOARD_STACK") - .heap_size_env_override("BOARD_HEAP") - .build() - .unwrap(), + "imxrt1170evk_cm7" => create_runtime(imxrt_rt::Family::Imxrt1170, 8 * 1024 * 1024) + .rodata(imxrt_rt::Memory::Dtcm) + .stack_size_env_override("BOARD_STACK") + .heap_size_env_override("BOARD_HEAP") + .build() + .unwrap(), _ => continue, } break; diff --git a/src/host.rs b/src/host.rs index eb10b22..e8325a7 100644 --- a/src/host.rs +++ b/src/host.rs @@ -127,9 +127,26 @@ fn region_alias(output: &mut dyn Write, name: &str, placement: Memory) -> io::Re #[derive(Debug, Clone, PartialEq, Eq)] struct FlashOpts { size: usize, + offset: u32, flexspi: FlexSpi, } +impl FlashOpts { + /// Produce the flash address of the image within + /// FlexSPI memory. + fn flash_origin(&self, family: Family) -> Option { + self.flexspi + .start_address(family) + .map(|start_address| start_address + self.offset) + } + + /// A bootable image (with the boot header) isn't offset + /// in flash. + fn is_boot_image(&self) -> bool { + self.offset == 0 + } +} + #[derive(Debug, Clone, PartialEq, Eq)] struct EnvOverride { default: usize, @@ -342,6 +359,43 @@ impl RuntimeBuilder { heap_size: EnvOverride::new(0), flash_opts: Some(FlashOpts { size: flash_size, + offset: 0, + flexspi: FlexSpi::family_default(family), + }), + linker_script_name: DEFAULT_LINKER_SCRIPT_NAME.into(), + } + } + + /// Allocate a flash partition for this program to be booted by your software. + /// + /// `partition_size` is the size of the flash allocation, in bytes, for this + /// program. `partition_offset` describes the byte offset where the partition + /// starts. The offset is from the start of the FlexSPI memory region. + /// + /// The program constructed at this flash location cannot be booted by NXP's boot + /// ROM. You should bring your own software to execute this program. Note that + /// [the runtime behaviors](RuntimeBuilder) ensure that the vector table is placed + /// in flash at the given `partition_offset`. + /// + /// To compute a partition offset from two absolute flash addresses, use + /// [`Family::flexspi_start_addr`] to learn the FlexSPI starting address. + pub fn in_flash(family: Family, partition_size: usize, partition_offset: u32) -> Self { + Self { + family, + flexram_banks: family.default_flexram_banks(), + text: Memory::Itcm, + rodata: Memory::Ocram, + data: Memory::Ocram, + vectors: Memory::Dtcm, + bss: Memory::Ocram, + uninit: Memory::Ocram, + stack: Memory::Dtcm, + stack_size: EnvOverride::new(8 * 1024), + heap: Memory::Dtcm, + heap_size: EnvOverride::new(0), + flash_opts: Some(FlashOpts { + size: partition_size, + offset: partition_offset, flexspi: FlexSpi::family_default(family), }), linker_script_name: DEFAULT_LINKER_SCRIPT_NAME.into(), @@ -508,19 +562,21 @@ impl RuntimeBuilder { if let Some(flash_opts) = &self.flash_opts { write_flash_memory_map(writer, self.family, flash_opts, &self.flexram_banks)?; - let boot_header_x = match self.family { - Family::Imxrt1010 - | Family::Imxrt1015 - | Family::Imxrt1020 - | Family::Imxrt1040 - | Family::Imxrt1050 - | Family::Imxrt1060 - | Family::Imxrt1064 - | Family::Imxrt1160 - | Family::Imxrt1170 => include_bytes!("host/imxrt-boot-header.x").as_slice(), - Family::Imxrt1180 => include_bytes!("host/imxrt-boot-header-1180.x").as_slice(), - }; - writer.write_all(boot_header_x)?; + if flash_opts.is_boot_image() { + let boot_header_x = match self.family { + Family::Imxrt1010 + | Family::Imxrt1015 + | Family::Imxrt1020 + | Family::Imxrt1040 + | Family::Imxrt1050 + | Family::Imxrt1060 + | Family::Imxrt1064 + | Family::Imxrt1160 + | Family::Imxrt1170 => include_bytes!("host/imxrt-boot-header.x").as_slice(), + Family::Imxrt1180 => include_bytes!("host/imxrt-boot-header-1180.x").as_slice(), + }; + writer.write_all(boot_header_x)?; + } } else { write_ram_memory_map(writer, self.family, &self.flexram_banks)?; } @@ -695,10 +751,7 @@ fn write_flash_memory_map( writeln!( output, "FLASH (RX) : ORIGIN = {:#X}, LENGTH = {:#X}", - flash_opts - .flexspi - .start_address(family) - .expect("Already checked"), + flash_opts.flash_origin(family).expect("Already checked"), flash_opts.size )?; write_flexram_memories(output, family, flexram_banks)?; @@ -901,6 +954,22 @@ impl Family { }, } } + + /// Returns the starting address for the given `flexspi` instance. + /// + /// If a FlexSPI instance isn't available for this family, the return + /// is `None`. Otherwise, the return is the starting address in the + /// MCU's memory map. + /// + /// ``` + /// use imxrt_rt::{Family::*, FlexSpi::*}; + /// + /// assert_eq!(Imxrt1060.flexspi_start_addr(FlexSpi1), Some(0x6000_0000)); + /// assert!(Imxrt1010.flexspi_start_addr(FlexSpi2).is_none()); + /// ``` + pub fn flexspi_start_addr(self, flexspi: FlexSpi) -> Option { + flexspi.start_address(self) + } } /// FlexRAM bank allocations. diff --git a/src/host/imxrt-boot-header-1180.x b/src/host/imxrt-boot-header-1180.x index 101725a..cf93e7c 100644 --- a/src/host/imxrt-boot-header-1180.x +++ b/src/host/imxrt-boot-header-1180.x @@ -101,4 +101,7 @@ SECTIONS } > FLASH } +ASSERT((__dcd_end - __dcd_start) % 4 == 0, " +ERROR(imxrt-rt): .dcd (Device Configuration Data) size must be a multiple of 4 bytes."); + /* ===--- End imxrt-boot-header-1180.x ---=== */ diff --git a/src/host/imxrt-boot-header.x b/src/host/imxrt-boot-header.x index 3f33603..165e858 100644 --- a/src/host/imxrt-boot-header.x +++ b/src/host/imxrt-boot-header.x @@ -78,4 +78,7 @@ SECTIONS } > FLASH } +ASSERT((__dcd_end - __dcd_start) % 4 == 0, " +ERROR(imxrt-rt): .dcd (Device Configuration Data) size must be a multiple of 4 bytes."); + /* ===--- End imxrt-boot-header.x ---=== */ diff --git a/src/host/imxrt-link.x b/src/host/imxrt-link.x index dfe7355..7fcd76c 100644 --- a/src/host/imxrt-link.x +++ b/src/host/imxrt-link.x @@ -203,8 +203,6 @@ Dynamic relocations are not supported. If you are linking to C code compiled usi the 'cc' crate then modify your build script to compile the C code _without_ the -fPIC flag. See the documentation of the `cc::Build.pic` method for details."); -ASSERT((__dcd_end - __dcd_start) % 4 == 0, " -ERROR(imxrt-rt): .dcd (Device Configuration Data) size must be a multiple of 4 bytes."); /* Do not exceed this mark in the error messages above | */ /* ===--- End imxrt-link.x ---=== */ diff --git a/tests/inspect_elf.rs b/tests/inspect_elf.rs index e9211eb..3576385 100644 --- a/tests/inspect_elf.rs +++ b/tests/inspect_elf.rs @@ -39,6 +39,36 @@ fn cargo_build_with_envs(board: &str, envs: &[(&str, &str)]) -> Result Ok(path) } +fn cargo_build_nonboot(board: &str) -> Result { + let status = Command::new("cargo") + .arg("build") + .arg("--example=blink-rtic") + .arg(format!( + "--features=board/{},board/rtic,board/nonboot", + board + )) + .arg("--target=thumbv7em-none-eabihf") + .arg(format!("--target-dir=target/{}-nonboot", board)) + .arg("--quiet") + .spawn()? + .wait()?; + + // TODO(summivox): `ExitStatus::exit_ok()` stabilization (can be chained after the `.wait()?) + if !status.success() { + return Err(format!( + "Building board '{}' failed: process returned {:?}", + board, status, + ) + .into()); + } + + let path = PathBuf::from(format!( + "target/{}-nonboot/thumbv7em-none-eabihf/debug/examples/blink-rtic", + board + )); + Ok(path) +} + /// Build an example, returning a path to the ELF. fn cargo_build(board: &str) -> Result { cargo_build_with_envs(board, &[]) @@ -644,3 +674,134 @@ fn imxrt1170evk_cm7() { "increment_data is not XiP" ); } + +#[test] +#[ignore = "building an example can take time"] +fn imxrt1170evk_cm7_nonboot() { + const IMAGE_OFFSET: u64 = 16 * 1024; + let path = cargo_build_nonboot("imxrt1170evk-cm7").expect("Unable to build example"); + let contents = fs::read(path).expect("Could not read ELF file"); + let elf = Elf::parse(&contents).expect("Could not parse ELF"); + + let binary = ImxrtBinary::new(&elf, &contents); + assert_eq!(binary.symbol_value("__dcd_start"), None); + assert_eq!(binary.symbol_value("__dcd_end"), None); + assert_eq!(binary.symbol_value("__dcd"), None); + assert!(binary.fcb().is_err()); + assert_eq!( + binary.flexram_config().unwrap(), + 0b1111111111111111_1010101010101010 + ); + + assert!( + binary.ivt().is_err(), + "Non boot image still contains boot IVT" + ); + assert!( + binary.section(".boot").is_err(), + "Boot section is included in a non boot image" + ); + + let stack = binary.section(".stack").unwrap(); + assert_eq!( + Section { + address: DTCM, + size: 8 * 1024 + }, + stack, + "stack not at ORIGIN(DTCM), or not 8 KiB large" + ); + assert_eq!(binary.section_lma(".stack"), stack.address); + + let vector_table = binary.section(".vector_table").unwrap(); + assert_eq!( + Section { + address: stack.address + stack.size, + size: 16 * 4 + 240 * 4 + }, + vector_table, + "vector table not at expected VMA behind the stack" + ); + assert!( + vector_table.address % 1024 == 0, + "vector table is not 1024-byte aligned" + ); + assert_eq!( + binary.section_lma(".vector_table"), + 0x3000_0000 + IMAGE_OFFSET + ); + + let xip = binary.section(".xip").unwrap(); + // xip's lma==vma + assert_eq!( + xip.address, + 0x3000_0000 + IMAGE_OFFSET + vector_table.size, + "xip" + ); + assert_eq!( + binary.section_lma(".xip"), + 0x3000_0000 + IMAGE_OFFSET + vector_table.size, + "text VMA expected behind vector table" + ); + + let text = binary.section(".text").unwrap(); + assert_eq!(text.address, ITCM, "text"); + assert_eq!( + binary.section_lma(".text"), + 0x3000_0000 + IMAGE_OFFSET + aligned(xip.size, 4) + vector_table.size, + "text VMA expected behind vector table" + ); + + let rodata = binary.section(".rodata").unwrap(); + assert_eq!( + rodata.address, + vector_table.address + vector_table.size, + "rodata moved to DTCM behind vector table" + ); + assert!( + binary.section_lma(".rodata") >= 0x3000_2000 + vector_table.size + aligned(text.size, 4), + ); + + let data = binary.section(".data").unwrap(); + assert_eq!(data.address, 0x2024_0000, "data VMA in OCRAM"); + assert_eq!( + data.size, 4, + "blink-rtic expected to have a single static mut u32" + ); + assert_eq!( + binary.section_lma(".data"), + binary.section_lma(".rodata") + aligned(rodata.size, 4), + "data LMA starts behind rodata" + ); + + let bss = binary.section(".bss").unwrap(); + assert_eq!( + bss.address, + data.address + aligned(data.size, 4), + "bss in OCRAM behind data" + ); + assert_eq!(binary.section_lma(".bss"), bss.address, "bss is NOLOAD"); + + let uninit = binary.section(".uninit").unwrap(); + assert_eq!( + uninit.address, + bss.address + aligned(bss.size, 4), + "uninit in OCRAM behind bss" + ); + assert_eq!( + binary.section_lma(".uninit"), + uninit.address, + "uninit is NOLOAD" + ); + + let heap = binary.section(".heap").unwrap(); + assert_eq!( + Section { + address: rodata.address + aligned(rodata.size, 4), + size: 0, + }, + heap, + "0 byte heap in DTCM behind rodata table" + ); + assert_eq!(binary.section_lma(".heap"), heap.address, "Heap is NOLOAD"); +} -- cgit v1.2.3