diff options
| author | Ian McIntyre <ianpmcintyre@gmail.com> | 2023-02-11 16:35:32 -0500 |
|---|---|---|
| committer | Ian McIntyre <ianpmcintyre@gmail.com> | 2023-03-25 14:51:08 -0400 |
| commit | e1fddd5b3b678c43ee59835d7b57bbc063a22d28 (patch) | |
| tree | 405cbe80197d44234fbdced32698c3f849a9408f /src | |
| parent | 83a3ff31a0c7078fb090f9e767e0a3b050f74542 (diff) | |
Generate one linker script; add unit tests
We can still maintain individual linker script components, then write
them into one, larger linker script. We're effectively implementing the
same behavior as INCLUDE while disallowing overrides of the linker
search path to find the INCLUDEd files.
Once we have one linker script, we can refactor for easier unit testing.
This commit adds simple unit tests for the default builder, and some of
the expected errors.
Diffstat (limited to 'src')
| -rw-r--r-- | src/host.rs | 170 | ||||
| -rw-r--r-- | src/host/imxrt-boot-header.x | 5 | ||||
| -rw-r--r-- | src/host/imxrt-link.x | 10 |
3 files changed, 136 insertions, 49 deletions
diff --git a/src/host.rs b/src/host.rs index 2e47e50..131457a 100644 --- a/src/host.rs +++ b/src/host.rs @@ -11,7 +11,7 @@ use std::{ env, fmt::Display, - fs::{self, File}, + fs, io::{self, Write}, path::PathBuf, }; @@ -301,6 +301,9 @@ impl RuntimeBuilder { /// Commit the runtime configuration. /// + /// `build()` ensures that the generated linker script is available to the + /// linker. + /// /// # Errors /// /// The implementation ensures that your chip can support the FlexRAM bank @@ -321,8 +324,6 @@ impl RuntimeBuilder { /// the linker. No matter the error path, the implementation ensures that there /// will be an error. pub fn build(&self) -> Result<(), Box<dyn std::error::Error>> { - self.check_configurations()?; - // Since `build` is called from a build script, the output directory // represents the path to the _user's_ crate. let out_dir = PathBuf::from(env::var("OUT_DIR")?); @@ -331,69 +332,87 @@ impl RuntimeBuilder { // The main linker script expects to INCLUDE this file. This file // uses region aliases to associate region names to actual memory // regions (see the Memory enum). - let mut memory_x = File::create(out_dir.join("imxrt-memory.x"))?; + let mut in_memory = Vec::new(); + self.write_linker_script(&mut in_memory)?; + fs::write(out_dir.join(&self.linker_script_name), &in_memory)?; + Ok(()) + } + + /// Write the generated linker script into the provided writer. + /// + /// Use this if you want more control over where the generated linker script + /// ends up. Otherwise, you should prefer [`build()`](Self::build) for an + /// easier experience. + /// + /// Unlike `build()`, this method does not ensure that the linker script is + /// available to the linker. Additionally, this method does not consider + /// the value set by [`linker_script_name`](Self::linker_script_name). + /// + /// # Errors + /// + /// See [`build()`](Self::build) to understand the possible errors. + fn write_linker_script( + &self, + writer: &mut dyn Write, + ) -> Result<(), Box<dyn std::error::Error>> { + self.check_configurations()?; if let Some(flash_opts) = &self.flash_opts { - write_flash_memory_map(&mut memory_x, self.family, flash_opts, &self.flexram_banks)?; + write_flash_memory_map(writer, self.family, flash_opts, &self.flexram_banks)?; + + let boot_header_x = include_bytes!("host/imxrt-boot-header.x"); + writer.write_all(boot_header_x)?; } else { - write_ram_memory_map(&mut memory_x, self.family, &self.flexram_banks)?; + write_ram_memory_map(writer, self.family, &self.flexram_banks)?; } #[cfg(feature = "device")] - writeln!(&mut memory_x, "INCLUDE device.x")?; + writeln!(writer, "INCLUDE device.x")?; // Keep these alias names in sync with the primary linker script. // The main linker script uses these region aliases for placing // sections. Then, the user specifies the actual placement through // the builder. This saves us the step of actually generating SECTION // commands. - region_alias(&mut memory_x, "TEXT", self.text)?; - region_alias(&mut memory_x, "VTABLE", self.vectors)?; - region_alias(&mut memory_x, "RODATA", self.rodata)?; - region_alias(&mut memory_x, "DATA", self.data)?; - region_alias(&mut memory_x, "BSS", self.bss)?; - region_alias(&mut memory_x, "UNINIT", self.uninit)?; - - region_alias(&mut memory_x, "STACK", self.stack)?; - region_alias(&mut memory_x, "HEAP", self.heap)?; + region_alias(writer, "TEXT", self.text)?; + region_alias(writer, "VTABLE", self.vectors)?; + region_alias(writer, "RODATA", self.rodata)?; + region_alias(writer, "DATA", self.data)?; + region_alias(writer, "BSS", self.bss)?; + region_alias(writer, "UNINIT", self.uninit)?; + + region_alias(writer, "STACK", self.stack)?; + region_alias(writer, "HEAP", self.heap)?; // Used in the linker script and / or target code. - writeln!(&mut memory_x, "__stack_size = {:#010X};", self.stack_size)?; - writeln!(&mut memory_x, "__heap_size = {:#010X};", self.heap_size)?; + writeln!(writer, "__stack_size = {:#010X};", self.stack_size)?; + writeln!(writer, "__heap_size = {:#010X};", self.heap_size)?; if self.flash_opts.is_some() { // Runtime will see different VMA and LMA, and copy the sections. - region_alias(&mut memory_x, "LOAD_VTABLE", Memory::Flash)?; - region_alias(&mut memory_x, "LOAD_TEXT", Memory::Flash)?; - region_alias(&mut memory_x, "LOAD_RODATA", Memory::Flash)?; - region_alias(&mut memory_x, "LOAD_DATA", Memory::Flash)?; + region_alias(writer, "LOAD_VTABLE", Memory::Flash)?; + region_alias(writer, "LOAD_TEXT", Memory::Flash)?; + region_alias(writer, "LOAD_RODATA", Memory::Flash)?; + region_alias(writer, "LOAD_DATA", Memory::Flash)?; } else { // When the VMA and LMA are equal, the runtime performs no copies. - region_alias(&mut memory_x, "LOAD_VTABLE", self.vectors)?; - region_alias(&mut memory_x, "LOAD_TEXT", self.text)?; - region_alias(&mut memory_x, "LOAD_RODATA", self.rodata)?; - region_alias(&mut memory_x, "LOAD_DATA", self.data)?; + region_alias(writer, "LOAD_VTABLE", self.vectors)?; + region_alias(writer, "LOAD_TEXT", self.text)?; + region_alias(writer, "LOAD_RODATA", self.rodata)?; + region_alias(writer, "LOAD_DATA", self.data)?; } // Referenced in target code. writeln!( - &mut memory_x, + writer, "__flexram_config = {:#010X};", self.flexram_banks.config() )?; // The target runtime looks at this value to predicate some pre-init instructions. // Could be helpful for binary identification, but it's an undocumented feature. - writeln!(&mut memory_x, "__imxrt_family = {};", self.family.id(),)?; + writeln!(writer, "__imxrt_family = {};", self.family.id(),)?; - // Place the primary linker script in the user's output directory. Name may be decided - // by the user. let link_x = include_bytes!("host/imxrt-link.x"); - fs::write(out_dir.join(&self.linker_script_name), link_x)?; - - // Also place the boot header in the search path. Do this unconditionally (even if - // the user is booting from RAM). Name matters, since it's INCLUDEd in our linker - // scripts. - let boot_header_x = include_bytes!("host/imxrt-boot-header.x"); - fs::write(out_dir.join("imxrt-boot-header.x"), boot_header_x)?; + writer.write_all(link_x)?; Ok(()) } @@ -490,9 +509,6 @@ fn write_flexram_memories( } /// Generate a linker script MEMORY command that includes a FLASH block. -/// -/// If called, the linker script includes the boot header, which is also -/// expressed as a linker script. fn write_flash_memory_map( output: &mut dyn Write, family: Family, @@ -517,7 +533,6 @@ fn write_flash_memory_map( write_flexram_memories(output, family, flexram_banks)?; writeln!(output, "}}")?; writeln!(output, "__fcb_offset = {:#X};", family.fcb_offset())?; - writeln!(output, "INCLUDE imxrt-boot-header.x")?; Ok(()) } @@ -739,7 +754,21 @@ impl FlexRamBanks { #[cfg(test)] mod tests { - use super::FlexRamBanks; + use crate::Memory; + + use super::{Family, FlexRamBanks, RuntimeBuilder}; + use std::{error, io}; + + const ALL_FAMILIES: &[Family] = &[ + Family::Imxrt1010, + Family::Imxrt1015, + Family::Imxrt1020, + Family::Imxrt1050, + Family::Imxrt1060, + Family::Imxrt1064, + Family::Imxrt1170, + ]; + type Error = Box<dyn error::Error>; #[test] fn flexram_config() { @@ -836,4 +865,61 @@ mod tests { ); } } + + #[test] + fn runtime_builder_default_from_flexspi() -> Result<(), Error> { + for family in ALL_FAMILIES { + RuntimeBuilder::from_flexspi(*family, 16 * 1024 * 1024) + .write_linker_script(&mut io::sink())?; + } + Ok(()) + } + + /// Strange but currently allowed. + #[test] + fn runtime_builder_from_flexspi_no_flash() -> Result<(), Error> { + RuntimeBuilder::from_flexspi(Family::Imxrt1060, 0).write_linker_script(&mut io::sink()) + } + + #[test] + fn runtime_builder_too_many_flexram_banks() { + let banks = FlexRamBanks { + itcm: 32, + dtcm: 32, + ocram: 32, + }; + for family in ALL_FAMILIES { + let res = RuntimeBuilder::from_flexspi(*family, 16 * 1024) + .flexram_banks(banks) + .write_linker_script(&mut io::sink()); + assert!(res.is_err(), "{family:?}"); + } + } + + #[test] + fn runtime_builder_invalid_flash_section() { + type Placer = fn(&mut RuntimeBuilder) -> &mut RuntimeBuilder; + macro_rules! placement { + ($section:ident) => { + (|bldr| bldr.$section(Memory::Flash), stringify!($section)) + }; + } + let placements: &[(Placer, &'static str)] = &[ + placement!(data), + placement!(vectors), + placement!(bss), + placement!(uninit), + placement!(stack), + placement!(heap), + ]; + + for family in ALL_FAMILIES { + for placement in placements { + let mut bldr = RuntimeBuilder::from_flexspi(*family, 16 * 1024); + placement.0(&mut bldr); + let res = bldr.write_linker_script(&mut io::sink()); + assert!(res.is_err(), "{:?}, section: {}", family, placement.1); + } + } + } } diff --git a/src/host/imxrt-boot-header.x b/src/host/imxrt-boot-header.x index 67355a2..296eff5 100644 --- a/src/host/imxrt-boot-header.x +++ b/src/host/imxrt-boot-header.x @@ -1,4 +1,5 @@ -/* This extra file is injected into imxrt-memory.x depending on the +/* ===--- Begin imxrt-boot-header.x ---=== + * This extra content is injected into the linker script depending on the * runtime configuration. */ @@ -71,3 +72,5 @@ SECTIONS . = ORIGIN(FLASH) + 0x2000; /* Reserve the remaining 8K as a convenience for a non-XIP boot. */ } > FLASH } + +/* ===--- End imxrt-boot-header.x ---=== */ diff --git a/src/host/imxrt-link.x b/src/host/imxrt-link.x index 272ffd8..d02a170 100644 --- a/src/host/imxrt-link.x +++ b/src/host/imxrt-link.x @@ -1,12 +1,8 @@ -/* - * This linker script is a fork of the default linker script provided by +/* ===--- Begin imxrt-link.x ---=== + * This section of the linker script is a fork of the default linker script provided by * imxrt-rt, version 0.7.1. It's modified to support the needs of imxrt-rt. */ -/* Provides information about the memory layout of the device */ -/* This will be provided by the build script that uses imxrt-rt. */ -INCLUDE imxrt-memory.x - /* # Entry point = reset vector */ EXTERN(__RESET_VECTOR); EXTERN(Reset); @@ -196,3 +192,5 @@ 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."); /* Do not exceed this mark in the error messages above | */ + +/* ===--- End imxrt-link.x ---=== */ |
