aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIan McIntyre <ianpmcintyre@gmail.com>2022-08-02 06:21:12 -0400
committerIan McIntyre <ianpmcintyre@gmail.com>2022-12-01 20:21:05 -0500
commitc7a9b9f3d4b9e71303c7b988d2bd916c2e4df9bc (patch)
tree6d41ea7e433cac328fa165d45d1bc0cd71a1bf8f /src
First commit
Diffstat (limited to 'src')
-rw-r--r--src/host.rs822
-rw-r--r--src/host/imxrt-boot-header.x73
-rw-r--r--src/host/imxrt-link.x198
-rw-r--r--src/lib.rs167
-rw-r--r--src/target.rs147
5 files changed, 1407 insertions, 0 deletions
diff --git a/src/host.rs b/src/host.rs
new file mode 100644
index 0000000..f0fa512
--- /dev/null
+++ b/src/host.rs
@@ -0,0 +1,822 @@
+//! Host-side configurations for the target.
+//!
+//! See [`RuntimeBuilder::build`] to understand the linker script generation
+//! steps.
+
+use std::{
+ env,
+ fmt::Display,
+ fs::{self, File},
+ io::{self, Write},
+ path::PathBuf,
+};
+
+/// Memory partitions.
+///
+/// Use with [`RuntimeBuilder`] to specify the placement of sections
+/// in the final program. Note that the `RuntimeBuilder` only does limited
+/// checks on memory placements. Generally, it's OK to place data in ITCM,
+/// and instructions in DTCM; however, this isn't recommended for optimal
+/// performance.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Memory {
+ /// Place the section in (external) flash.
+ ///
+ /// Reads and writes are translated into commands on an external
+ /// bus, like FlexSPI.
+ Flash,
+ /// Place the section in data tightly coupled memory (DTCM).
+ Dtcm,
+ /// Place the section in instruction tightly coupled memory (ITCM).
+ Itcm,
+ /// Place the section in on-chip RAM (OCRAM).
+ ///
+ /// If your chip includes dedicated OCRAM memory, the implementation
+ /// utilizes that OCRAM before utilizing any FlexRAM OCRAM banks.
+ Ocram,
+}
+
+/// The FlexSPI peripheral that interfaces your flash chip.
+///
+/// The [`RuntimeBuilder`] selects `FlexSpi1` for nearly all chip
+/// families. However, it selects `FlexSpi2` for the 1064 in order
+/// to utilize its on-board flash. You can override the selection
+/// using [`RuntimeBuilder::flexspi()`].
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FlexSpi {
+ /// Interface flash using FlexSPI 1.
+ FlexSpi1,
+ /// Interface flash using FlexSPI 2.
+ FlexSpi2,
+}
+
+impl FlexSpi {
+ fn family_default(family: Family) -> Self {
+ if Family::Imxrt1064 == family {
+ FlexSpi::FlexSpi2
+ } else {
+ FlexSpi::FlexSpi1
+ }
+ }
+ fn start_address(self, family: Family) -> Option<u32> {
+ match (self, family) {
+ // FlexSPI1, 10xx
+ (
+ FlexSpi::FlexSpi1,
+ Family::Imxrt1010
+ | Family::Imxrt1015
+ | Family::Imxrt1020
+ | Family::Imxrt1050
+ | Family::Imxrt1060
+ | Family::Imxrt1064,
+ ) => Some(0x6000_0000),
+ // FlexSPI2 not available on 10xx families
+ (
+ FlexSpi::FlexSpi2,
+ Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050,
+ ) => None,
+ // FlexSPI 2 available on 10xx families
+ (FlexSpi::FlexSpi2, Family::Imxrt1060 | Family::Imxrt1064) => Some(0x7000_0000),
+ // 11xx support
+ (FlexSpi::FlexSpi1, Family::Imxrt1170) => Some(0x3000_0000),
+ (FlexSpi::FlexSpi2, Family::Imxrt1170) => Some(0x6000_0000),
+ }
+ }
+ fn supported_for_family(self, family: Family) -> bool {
+ self.start_address(family).is_some()
+ }
+}
+
+impl Display for Memory {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ match self {
+ Self::Flash => f.write_str("FLASH"),
+ Self::Itcm => f.write_str("ITCM"),
+ Self::Dtcm => f.write_str("DTCM"),
+ Self::Ocram => f.write_str("OCRAM"),
+ }
+ }
+}
+
+/// Define an alias for `name` that maps to a memory block named `placement`.
+fn region_alias(output: &mut dyn Write, name: &str, placement: Memory) -> io::Result<()> {
+ writeln!(output, "REGION_ALIAS(\"REGION_{}\", {});", name, placement)
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct FlashOpts {
+ size: usize,
+ flexspi: FlexSpi,
+}
+
+/// Builder for the i.MX RT runtime.
+///
+/// `RuntimeBuilder` let you assign sections to memory regions. It also lets
+/// you partition FlexRAM DTCM/ITCM/OCRAM. Call [`build()`](RuntimeBuilder::build) to commit the
+/// runtime configuration.
+///
+/// # Behaviors
+///
+/// The implementation tries to place the stack in the lowest-possible memory addresses.
+/// This means the stack will grow down into reserved memory below DTCM and OCRAM for most
+/// chip families. The outlier is the 1170, where the stack will grow into OCRAM backdoor for
+/// the CM4 coprocessor. Be careful here...
+///
+/// Similarly, the implementation tries to place the heap in the highest-possible memory
+/// addresses. This means the heap will grow up into reserved memory above DTCM and OCRAM
+/// for most chip families.
+///
+/// The vector table requires a 1024-byte alignment. The vector table's placement is prioritized
+/// above all other sections, except the stack. If placing the stack and vector table in the
+/// same section (which is the default behavior), consider keeping the stack size as a multiple
+/// of 1 KiB to minimize internal fragmentation.
+///
+/// # Default values
+///
+/// The example below demonstrates the default `RuntimeBuilder` memory placements,
+/// stack sizes, and heap sizes.
+///
+/// ```
+/// use imxrt_rt::{Family, RuntimeBuilder, Memory};
+///
+/// const FLASH_SIZE: usize = 16 * 1024;
+/// let family = Family::Imxrt1060;
+///
+/// let mut b = RuntimeBuilder::from_flexspi(family, FLASH_SIZE);
+/// // FlexRAM banks represent default fuse values.
+/// b.flexram_banks(family.default_flexram_banks());
+/// b.text(Memory::Itcm); // Copied from flash.
+/// b.rodata(Memory::Ocram); // Copied from flash.
+/// b.data(Memory::Ocram); // Copied from flash.
+/// b.vectors(Memory::Dtcm); // Copied from flash.
+/// b.bss(Memory::Ocram);
+/// b.uninit(Memory::Ocram);
+/// b.stack(Memory::Dtcm);
+/// b.stack_size(8 * 1024); // 8 KiB stack.
+/// b.heap(Memory::Dtcm); // Heap in DTCM...
+/// b.heap_size(0); // ...but no space given to the heap.
+///
+/// assert_eq!(b, RuntimeBuilder::from_flexspi(family, FLASH_SIZE));
+/// ```
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct RuntimeBuilder {
+ family: Family,
+ flexram_banks: FlexRamBanks,
+ text: Memory,
+ rodata: Memory,
+ data: Memory,
+ vectors: Memory,
+ bss: Memory,
+ uninit: Memory,
+ stack: Memory,
+ stack_size: usize,
+ heap: Memory,
+ heap_size: usize,
+ flash_opts: Option<FlashOpts>,
+ linker_script_name: String,
+}
+
+const DEFAULT_LINKER_SCRIPT_NAME: &str = "imxrt-link.x";
+
+impl RuntimeBuilder {
+ /// Creates a runtime that can execute and load contents from
+ /// FlexSPI flash.
+ ///
+ /// `flash_size` is the size of your flash component, in bytes.
+ pub fn from_flexspi(family: Family, flash_size: usize) -> 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: 8 * 1024,
+ heap: Memory::Dtcm,
+ heap_size: 0,
+ flash_opts: Some(FlashOpts {
+ size: flash_size,
+ flexspi: FlexSpi::family_default(family),
+ }),
+ linker_script_name: DEFAULT_LINKER_SCRIPT_NAME.into(),
+ }
+ }
+ /// Set the FlexRAM bank allocation.
+ ///
+ /// Use this to customize the sizes of DTCM, ITCM, and OCRAM.
+ /// See the `FlexRamBanks` documentation for requirements on the
+ /// bank allocations.
+ pub fn flexram_banks(&mut self, flexram_banks: FlexRamBanks) -> &mut Self {
+ self.flexram_banks = flexram_banks;
+ self
+ }
+ /// Set the memory placement for code.
+ pub fn text(&mut self, memory: Memory) -> &mut Self {
+ self.text = memory;
+ self
+ }
+ /// Set the memory placement for read-only data.
+ pub fn rodata(&mut self, memory: Memory) -> &mut Self {
+ self.rodata = memory;
+ self
+ }
+ /// Set the memory placement for mutable data.
+ pub fn data(&mut self, memory: Memory) -> &mut Self {
+ self.data = memory;
+ self
+ }
+ /// Set the memory placement for the vector table.
+ pub fn vectors(&mut self, memory: Memory) -> &mut Self {
+ self.vectors = memory;
+ self
+ }
+ /// Set the memory placement for zero-initialized data.
+ pub fn bss(&mut self, memory: Memory) -> &mut Self {
+ self.bss = memory;
+ self
+ }
+ /// Set the memory placement for uninitialized data.
+ pub fn uninit(&mut self, memory: Memory) -> &mut Self {
+ self.uninit = memory;
+ self
+ }
+ /// Set the memory placement for stack memory.
+ pub fn stack(&mut self, memory: Memory) -> &mut Self {
+ self.stack = memory;
+ self
+ }
+ /// Set the size, in bytes, of the stack.
+ pub fn stack_size(&mut self, bytes: usize) -> &mut Self {
+ self.stack_size = bytes;
+ self
+ }
+ /// Set the memory placement for the heap.
+ ///
+ /// Note that the default heap has no size. Use [`heap_size`](Self::heap_size)
+ /// to allocate space for a heap.
+ pub fn heap(&mut self, memory: Memory) -> &mut Self {
+ self.heap = memory;
+ self
+ }
+ /// Set the size, in bytes, of the heap.
+ pub fn heap_size(&mut self, bytes: usize) -> &mut Self {
+ self.heap_size = bytes;
+ self
+ }
+ /// Set the FlexSPI peripheral that interfaces flash.
+ ///
+ /// See the [`FlexSpi`] to understand the default values.
+ /// If this builder is not configuring a flash-loaded runtime, this
+ /// call is silently ignored.
+ pub fn flexspi(&mut self, peripheral: FlexSpi) -> &mut Self {
+ if let Some(flash_opts) = &mut self.flash_opts {
+ flash_opts.flexspi = peripheral;
+ }
+ self
+ }
+
+ /// Set the name of the linker script file.
+ ///
+ /// You can use this to customize the linker script name for your users.
+ /// See the [crate-level documentation](crate#linker-script) for more
+ /// information.
+ pub fn linker_script_name(&mut self, name: &str) -> &mut Self {
+ self.linker_script_name = name.into();
+ self
+ }
+
+ /// Commit the runtime configuration.
+ ///
+ /// # Errors
+ ///
+ /// The implementation ensures that your chip can support the FlexRAM bank
+ /// allocation. An invalid allocation is signaled by an error.
+ ///
+ /// Returns an error if any of the following sections are placed in flash:
+ ///
+ /// - data
+ /// - vectors
+ /// - bss
+ /// - uninit
+ /// - stack
+ /// - heap
+ ///
+ /// The implementation may rely on the _linker_ to signal other errors.
+ /// For example, suppose a runtime configuration with no ITCM banks. If a
+ /// section is placed in ITCM, that error could be signaled here, or through
+ /// 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")?);
+ println!("cargo:rustc-link-search={}", out_dir.display());
+
+ // 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"))?;
+
+ if let Some(flash_opts) = &self.flash_opts {
+ write_flash_memory_map(&mut memory_x, self.family, flash_opts, &self.flexram_banks)?;
+ } else {
+ write_ram_memory_map(&mut memory_x, self.family, &self.flexram_banks)?;
+ }
+
+ #[cfg(feature = "device")]
+ writeln!(&mut memory_x, "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)?;
+ // 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)?;
+
+ 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)?;
+ } 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)?;
+ }
+
+ // Referenced in target code.
+ writeln!(
+ &mut memory_x,
+ "__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(),)?;
+
+ // 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)?;
+
+ Ok(())
+ }
+
+ /// Implement i.MX RT specific sanity checks.
+ ///
+ /// This might not check everything! If the linker may detect a condition, we'll
+ /// let the linker do that.
+ fn check_configurations(&self) -> Result<(), String> {
+ if self.family.flexram_bank_count() < self.flexram_banks.bank_count() {
+ return Err(format!(
+ "Chip {:?} only has {} total FlexRAM banks. Cannot allocate {:?}, a total of {} banks",
+ self.family,
+ self.family.flexram_bank_count(),
+ self.flexram_banks,
+ self.flexram_banks.bank_count()
+ ));
+ }
+ if self.flexram_banks.ocram < self.family.bootrom_ocram_banks() {
+ return Err(format!(
+ "Chip {:?} requires at least {} OCRAM banks for the bootloader ROM",
+ self.family,
+ self.family.bootrom_ocram_banks()
+ ));
+ }
+ if let Some(flash_opts) = &self.flash_opts {
+ if !flash_opts.flexspi.supported_for_family(self.family) {
+ return Err(format!(
+ "Chip {:?} does not support {:?}",
+ self.family, flash_opts.flexspi
+ ));
+ }
+ }
+
+ fn prevent_flash(name: &str, memory: Memory) -> Result<(), String> {
+ if memory == Memory::Flash {
+ Err(format!("Section '{}' cannot be placed in flash", name))
+ } else {
+ Ok(())
+ }
+ }
+ macro_rules! prevent_flash {
+ ($sec:ident) => {
+ prevent_flash(stringify!($sec), self.$sec)
+ };
+ }
+
+ prevent_flash!(data)?;
+ prevent_flash!(vectors)?;
+ prevent_flash!(bss)?;
+ prevent_flash!(uninit)?;
+ prevent_flash!(stack)?;
+ prevent_flash!(heap)?;
+
+ Ok(())
+ }
+}
+
+/// Write RAM-like memory blocks.
+///
+/// Skips a section if there's no FlexRAM block allocated. If a user references one
+/// of this skipped sections, linking fails.
+fn write_flexram_memories(
+ output: &mut dyn Write,
+ family: Family,
+ flexram_banks: &FlexRamBanks,
+) -> io::Result<()> {
+ if flexram_banks.itcm > 0 {
+ writeln!(
+ output,
+ "ITCM (RWX) : ORIGIN = 0x00000000, LENGTH = {:#X}",
+ flexram_banks.itcm * family.flexram_bank_size(),
+ )?;
+ }
+ if flexram_banks.dtcm > 0 {
+ writeln!(
+ output,
+ "DTCM (RWX) : ORIGIN = 0x20000000, LENGTH = {:#X}",
+ flexram_banks.dtcm * family.flexram_bank_size(),
+ )?;
+ }
+
+ let ocram_size =
+ flexram_banks.ocram * family.flexram_bank_size() + family.dedicated_ocram_size();
+ if ocram_size > 0 {
+ writeln!(
+ output,
+ "OCRAM (RWX) : ORIGIN = {:#X}, LENGTH = {:#X}",
+ family.ocram_start(),
+ ocram_size,
+ )?;
+ }
+ Ok(())
+}
+
+/// 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,
+ flash_opts: &FlashOpts,
+ flexram_banks: &FlexRamBanks,
+) -> io::Result<()> {
+ writeln!(
+ output,
+ "/* Memory map for '{:?}' with custom flash length {}. */",
+ family, flash_opts.size
+ )?;
+ writeln!(output, "MEMORY {{")?;
+ writeln!(
+ output,
+ "FLASH (RX) : ORIGIN = {:#X}, LENGTH = {:#X}",
+ flash_opts
+ .flexspi
+ .start_address(family)
+ .expect("Already checked"),
+ flash_opts.size
+ )?;
+ 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(())
+}
+
+/// Generate a linker script MEMORY command that supports RAM execution.
+///
+/// It's like [`write_flash_memory_map`], but it doesn't include the flash
+/// important tidbits.
+fn write_ram_memory_map(
+ output: &mut dyn Write,
+ family: Family,
+ flexram_banks: &FlexRamBanks,
+) -> io::Result<()> {
+ writeln!(
+ output,
+ "/* Memory map for '{:?}' that executes from RAM. */",
+ family,
+ )?;
+ writeln!(output, "MEMORY {{")?;
+ write_flexram_memories(output, family, flexram_banks)?;
+ writeln!(output, "}}")?;
+ Ok(())
+}
+
+/// i.MX RT chip family.
+///
+/// Chip families are designed by reference manuals and produce categories.
+/// Supply this to a [`RuntimeBuilder`] in order to check runtime configurations.
+#[non_exhaustive]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Family {
+ Imxrt1010,
+ Imxrt1015,
+ Imxrt1020,
+ Imxrt1050,
+ Imxrt1060,
+ Imxrt1064,
+ Imxrt1170,
+}
+
+impl Family {
+ /// Family identifier.
+ ///
+ /// These values may be stored in the image and observe by the runtime
+ /// initialzation routine. Make sure these numbers are kept in sync with
+ /// any hard-coded values.
+ const fn id(self) -> u32 {
+ match self {
+ Family::Imxrt1010 => 1010,
+ Family::Imxrt1015 => 1015,
+ Family::Imxrt1020 => 1020,
+ Family::Imxrt1050 => 1050,
+ Family::Imxrt1060 => 1060,
+ Family::Imxrt1064 => 1064,
+ Family::Imxrt1170 => 1170,
+ }
+ }
+ /// How many FlexRAM banks are available?
+ pub const fn flexram_bank_count(self) -> u32 {
+ match self {
+ Family::Imxrt1010 | Family::Imxrt1015 => 4,
+ Family::Imxrt1020 => 8,
+ Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => 16,
+ // No ECC support; treating all banks as equal.
+ Family::Imxrt1170 => 16,
+ }
+ }
+ /// How large (bytes) is each FlexRAM bank?
+ const fn flexram_bank_size(self) -> u32 {
+ 32 * 1024
+ }
+ /// How many OCRAM banks does the boot ROM need?
+ const fn bootrom_ocram_banks(self) -> u32 {
+ match self {
+ Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050 => 1,
+ // 9.5.1. memory maps point at OCRAM2.
+ Family::Imxrt1060 | Family::Imxrt1064 => 0,
+ // Boot ROM uses dedicated OCRAM1.
+ Family::Imxrt1170 => 0,
+ }
+ }
+ /// Where's the FlexSPI configuration bank located?
+ fn fcb_offset(self) -> usize {
+ match self {
+ Family::Imxrt1010 | Family::Imxrt1170 => 0x400,
+ _ => 0x000,
+ }
+ }
+
+ /// Where does the OCRAM region begin?
+ ///
+ /// This includes dedicated any OCRAM regions, if any exist for the chip.
+ fn ocram_start(self) -> u32 {
+ if Family::Imxrt1170 == self {
+ // 256 KiB offset from the OCRAM M4 backdoor.
+ 0x2024_0000
+ } else {
+ // Either starts the FlexRAM OCRAM banks, or the
+ // dedicated OCRAM regions (for supported devices).
+ 0x2020_0000
+ }
+ }
+
+ /// What's the size, in bytes, of the dedicated OCRAM section?
+ ///
+ /// This isn't supported by all chips.
+ const fn dedicated_ocram_size(self) -> u32 {
+ match self {
+ Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050 => 0,
+ Family::Imxrt1060 | Family::Imxrt1064 => 512 * 1024,
+ // - Two dedicated OCRAMs
+ // - Two dedicated OCRAM ECC regions that aren't used for ECC
+ // - One FlexRAM OCRAM ECC region that's strictly OCRAM, without ECC
+ Family::Imxrt1170 => (2 * 512 + 2 * 64 + 128) * 1024,
+ }
+ }
+
+ /// Returns the default FlexRAM bank allocations for this chip.
+ ///
+ /// The default values represent the all-zero fuse values.
+ pub fn default_flexram_banks(self) -> FlexRamBanks {
+ match self {
+ Family::Imxrt1010 | Family::Imxrt1015 => FlexRamBanks {
+ ocram: 2,
+ itcm: 1,
+ dtcm: 1,
+ },
+ Family::Imxrt1020 => FlexRamBanks {
+ ocram: 4,
+ itcm: 2,
+ dtcm: 2,
+ },
+ Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => FlexRamBanks {
+ ocram: 8,
+ itcm: 4,
+ dtcm: 4,
+ },
+ Family::Imxrt1170 => FlexRamBanks {
+ ocram: 0,
+ itcm: 8,
+ dtcm: 8,
+ },
+ }
+ }
+}
+
+/// FlexRAM bank allocations.
+///
+/// Depending on your device, you may need a non-zero number of
+/// OCRAM banks to support the boot ROM. Consult your processor's
+/// reference manual for more information.
+///
+/// You should keep the sum of all banks below or equal to the
+/// total number of banks supported by your device. Unallocated memory
+/// banks are disabled.
+///
+/// Banks are typically 32KiB large.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct FlexRamBanks {
+ /// How many banks are allocated for OCRAM?
+ ///
+ /// This may need to be non-zero to support the boot ROM.
+ /// Consult your reference manual.
+ ///
+ /// Note: these are FlexRAM OCRAM banks. Do not include any banks
+ /// that would represent dedicated OCRAM; the runtime implementation
+ /// allocates those automatically. In fact, if your chip includes
+ /// dedicated OCRAM, you may set this to zero in order to maximize
+ /// DTCM and ITCM utilization.
+ pub ocram: u32,
+ /// How many banks are allocated for ITCM?
+ pub itcm: u32,
+ /// How many banks are allocated for DTCM?
+ pub dtcm: u32,
+}
+
+impl FlexRamBanks {
+ /// Total FlexRAM banks.
+ const fn bank_count(&self) -> u32 {
+ self.ocram + self.itcm + self.dtcm
+ }
+
+ /// Produces the FlexRAM configuration.
+ fn config(&self) -> u32 {
+ assert!(
+ self.bank_count() <= 16,
+ "Something is wrong; this should have been checked earlier."
+ );
+
+ // If a FlexRAM memory type could be allocated
+ // to _all_ memory banks, these would represent
+ // the configuration masks...
+ const OCRAM: u32 = 0x5555_5555; // 0b01...
+ const DTCM: u32 = 0xAAAA_AAAA; // 0b10...
+ const ITCM: u32 = 0xFFFF_FFFF; // 0b11...
+
+ fn mask(bank_count: u32) -> u32 {
+ 1u32.checked_shl(bank_count * 2)
+ .map(|bit| bit - 1)
+ .unwrap_or(u32::MAX)
+ }
+
+ let ocram_mask = mask(self.ocram);
+ let dtcm_mask = mask(self.dtcm).checked_shl(self.ocram * 2).unwrap_or(0);
+ let itcm_mask = mask(self.itcm)
+ .checked_shl((self.ocram + self.dtcm) * 2)
+ .unwrap_or(0);
+
+ (OCRAM & ocram_mask) | (DTCM & dtcm_mask) | (ITCM & itcm_mask)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::FlexRamBanks;
+
+ #[test]
+ fn flexram_config() {
+ /// Testing table of banks and expected configuration mask.
+ #[allow(clippy::unusual_byte_groupings)] // Spacing delimits ITCM / DTCM / OCRAM banks.
+ const TABLE: &[(FlexRamBanks, u32)] = &[
+ (
+ FlexRamBanks {
+ ocram: 16,
+ dtcm: 0,
+ itcm: 0,
+ },
+ 0x55555555,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 0,
+ dtcm: 16,
+ itcm: 0,
+ },
+ 0xAAAAAAAA,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 0,
+ dtcm: 0,
+ itcm: 16,
+ },
+ 0xFFFFFFFF,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 0,
+ dtcm: 0,
+ itcm: 0,
+ },
+ 0,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 1,
+ dtcm: 1,
+ itcm: 1,
+ },
+ 0b11_10_01,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 3,
+ dtcm: 3,
+ itcm: 3,
+ },
+ 0b111111_101010_010101,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 5,
+ dtcm: 5,
+ itcm: 5,
+ },
+ 0b1111111111_1010101010_0101010101,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 1,
+ dtcm: 1,
+ itcm: 14,
+ },
+ 0b1111111111111111111111111111_10_01,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 1,
+ dtcm: 14,
+ itcm: 1,
+ },
+ 0b11_1010101010101010101010101010_01,
+ ),
+ (
+ FlexRamBanks {
+ ocram: 14,
+ dtcm: 1,
+ itcm: 1,
+ },
+ 0b11_10_0101010101010101010101010101,
+ ),
+ ];
+
+ for (banks, expected) in TABLE {
+ let actual = banks.config();
+ assert!(
+ actual == *expected,
+ "\nActual: {actual:#034b}\nExpected: {expected:#034b}\nBanks: {banks:?}"
+ );
+ }
+ }
+}
diff --git a/src/host/imxrt-boot-header.x b/src/host/imxrt-boot-header.x
new file mode 100644
index 0000000..67355a2
--- /dev/null
+++ b/src/host/imxrt-boot-header.x
@@ -0,0 +1,73 @@
+/* This extra file is injected into imxrt-memory.x depending on the
+ * runtime configuration.
+ */
+
+/* If you're ever playing with the boot ROM copy, this is your image size.
+ *
+ * Note that it depends on the section layout! Need to represent contiguous
+ * sections starting from the boot header.
+ */
+__image_size = SIZEOF(.boot) + SIZEOF(.vector_table) + SIZEOF(.text) + SIZEOF(.rodata);
+
+/* END TODO */
+EXTERN(FLEXSPI_CONFIGURATION_BLOCK);
+
+/* # Sections */
+SECTIONS
+{
+ /* Boot header for serial NOR FlexSPI XIP.
+ *
+ * It's 'XIP' in that it starts executing instructions
+ * from flash immediately out of reset. The runtime then
+ * manually copies instructions (data, etc.), and we jump
+ * to that. After that jump, we're no longer XIP.
+ *
+ * The i.MX RT boot ROM also supports a way to copy the
+ * application image by changing the boot data configuration.
+ * Specifically, point the 'start of image' to somewhere other
+ * than the start of flash, and specify how many bytes to copy.
+ * The boot ROM copies the image, then jumps to the vector table.
+ * There's a catch: the boot ROM copies the first 8K from the
+ * start of flash too. This represents the entire boot header,
+ * including the FCB, IVT, and boot data. (NXP docs say that the
+ * initial load region is 4K; my testing shows that it's 8K, and
+ * this aligns with observations of others.) If you ever want to
+ * try this, make sure you're specifing the VMA and LMA of the
+ * boot head section to represent this 8K relocation.
+ */
+ .boot ORIGIN(FLASH):
+ {
+ . += __fcb_offset; /* Changes based on the chip */
+ KEEP(*(.fcb));
+ . = ORIGIN(FLASH) + 0x1000;
+ /* ------------------
+ * Image vector table
+ * ------------------
+ *
+ * Not to be confused with the ARM vector table. This tells the boot ROM
+ * where to find the boot data and (eventual) first vector table.
+ * The IVT needs to reside right here.
+ */
+ __ivt = .;
+ LONG(0x402000D1); /* Header, magic number */
+ LONG(__sivector_table); /* Address of the vectors table */
+ LONG(0x00000000); /* RESERVED */
+ LONG(0x00000000); /* Device Configuration Data (unused) */
+ LONG(__boot_data); /* Address to boot data */
+ LONG(__ivt); /* Self reference */
+ LONG(0x00000000); /* Command Sequence File (unused) */
+ LONG(0x00000000); /* RESERVED */
+ /* ---------
+ * Boot data
+ * ---------
+ */
+ __boot_data = .;
+ LONG(ORIGIN(FLASH)); /* Start of image */
+ LONG(__image_size); /* Length of image */
+ LONG(0x00000000); /* Plugin flag (unused) */
+ LONG(0xDEADBEEF); /* Dummy to align boot data to 16 bytes */
+ *(.Reset); /* Jam the imxrt-rt reset handler into flash. */
+ *(.__pre_init); /* Also jam the pre-init function, since we need it to run before instructions are placed. */
+ . = ORIGIN(FLASH) + 0x2000; /* Reserve the remaining 8K as a convenience for a non-XIP boot. */
+ } > FLASH
+}
diff --git a/src/host/imxrt-link.x b/src/host/imxrt-link.x
new file mode 100644
index 0000000..272ffd8
--- /dev/null
+++ b/src/host/imxrt-link.x
@@ -0,0 +1,198 @@
+/*
+ * This 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);
+ENTRY(Reset);
+
+/* # Exception vectors */
+/* This is effectively weak aliasing at the linker level */
+/* The user can override any of these aliases by defining the corresponding symbol themselves (cf.
+ the `exception!` macro) */
+EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */
+
+EXTERN(DefaultHandler);
+
+PROVIDE(NonMaskableInt = DefaultHandler);
+EXTERN(HardFaultTrampoline);
+PROVIDE(MemoryManagement = DefaultHandler);
+PROVIDE(BusFault = DefaultHandler);
+PROVIDE(UsageFault = DefaultHandler);
+PROVIDE(SecureFault = DefaultHandler);
+PROVIDE(SVCall = DefaultHandler);
+PROVIDE(DebugMonitor = DefaultHandler);
+PROVIDE(PendSV = DefaultHandler);
+PROVIDE(SysTick = DefaultHandler);
+
+PROVIDE(DefaultHandler = DefaultHandler_);
+PROVIDE(HardFault = HardFault_);
+
+/* # Interrupt vectors */
+EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */
+
+/* # Sections */
+SECTIONS
+{
+ .stack (NOLOAD) : ALIGN(8)
+ {
+ __estack = .;
+ . += ALIGN(__stack_size, 8);
+ __sstack = .;
+ /* Symbol expected by cortex-m-rt */
+ _stack_start = __sstack;
+ } > REGION_STACK
+
+ .vector_table : ALIGN(1024)
+ {
+ __vector_table = .;
+ __svector_table = .;
+
+ /* Initial Stack Pointer (SP) value */
+ LONG(__sstack);
+
+ /* Reset vector */
+ KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */
+ __reset_vector = .;
+
+ /* Exceptions */
+ KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */
+ __eexceptions = .;
+
+ /* Device specific interrupts */
+ KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */
+ __evector_table = .;
+ } > REGION_VTABLE AT> REGION_LOAD_VTABLE
+ __sivector_table = LOADADDR(.vector_table);
+
+ .text :
+ {
+ __stext = .;
+ *(.text .text.*);
+ /* Included in .text if not otherwise included in the boot header. */
+ *(.Reset);
+ *(.__pre_init);
+ /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`,
+ so must be placed close to it. */
+ *(.HardFaultTrampoline);
+ *(.HardFault.*);
+ . = ALIGN(4); /* Pad .text to the alignment to workaround overlapping load section bug in old lld */
+ __etext = .;
+ } > REGION_TEXT AT> REGION_LOAD_TEXT
+ __sitext = LOADADDR(.text);
+
+ .rodata : ALIGN(4)
+ {
+ . = ALIGN(4);
+ __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> REGION_LOAD_RODATA
+ __sirodata = LOADADDR(.rodata);
+
+ .data : ALIGN(4)
+ {
+ . = ALIGN(4);
+ __sdata = .;
+ *(.data .data.*);
+ . = ALIGN(4); /* 4-byte align the end (VMA) of this section */
+ __edata = .;
+ } > REGION_DATA AT> REGION_LOAD_DATA
+ __sidata = LOADADDR(.data);
+
+ .bss (NOLOAD) : ALIGN(4)
+ {
+ . = ALIGN(4);
+ __sbss = .;
+ *(.bss .bss.*);
+ *(COMMON); /* Uninitialized C statics */
+ . = ALIGN(4); /* 4-byte align the end (VMA) of this section */
+ __ebss = .;
+ } > REGION_BSS
+
+ .uninit (NOLOAD) : ALIGN(4)
+ {
+ . = ALIGN(4);
+ __suninit = .;
+ *(.uninit .uninit.*);
+ . = ALIGN(4);
+ __euninit = .;
+ } > REGION_UNINIT
+
+ .heap (NOLOAD) : ALIGN(4)
+ {
+ __sheap = .;
+ . += ALIGN(__heap_size, 4);
+ __eheap = .;
+ } > REGION_HEAP
+
+ /* 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 (NOLOAD) :
+ {
+ KEEP(*(.got .got.*));
+ }
+
+ /DISCARD/ :
+ {
+ /* Unused exception related info that only wastes space */
+ *(.ARM.exidx);
+ *(.ARM.exidx.*);
+ *(.ARM.extab.*);
+ }
+}
+
+/* Do not exceed this mark in the error messages below | */
+/* # Alignment checks */
+
+ASSERT(__sstack % 8 == 0 && __estack % 8 == 0, "
+BUG(imxrt-rt): .stack is not 8-byte aligned");
+
+ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, "
+BUG(imxrt-rt): .data is not 4-byte aligned");
+
+ASSERT(__sidata % 4 == 0, "
+BUG(imxrt-rt): the LMA of .data is not 4-byte aligned");
+
+ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, "
+BUG(imxrt-rt): .bss is not 4-byte aligned");
+
+ASSERT(__sheap % 4 == 0, "
+BUG(imxrt-rt): start of .heap is not 4-byte aligned");
+
+/* # Position checks */
+
+/* ## .vector_table */
+ASSERT(__reset_vector == ADDR(.vector_table) + 0x8, "
+BUG(imxrt-rt): the reset vector is missing");
+
+ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, "
+BUG(imxrt-rt): the exception vectors are missing");
+
+ASSERT(SIZEOF(.vector_table) > 0x40, "
+ERROR(imxrt-rt): The interrupt vectors are missing.
+Possible solutions, from most likely to less likely:
+- Link to imxrt-ral, or another compatible device crate
+- Check that you actually use the device/hal/bsp crate in your code
+- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency
+may be enabling it)
+- Supply the interrupt handlers yourself. Check the documentation for details.");
+
+/* # Other checks */
+ASSERT(SIZEOF(.got) == 0, "
+ERROR(imxrt-rt): .got section detected in the input object files
+Dynamic relocations are not supported. If you are linking to C code compiled using
+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 | */
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..76ba2fc
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,167 @@
+//! Runtime and startup support for i.MX RT processors.
+//!
+//! This crate builds on `cortex-m-rt` and adds support for i.MX RT processors.
+//! Using this runtime crate, you can specify FlexRAM sizes and section allocations,
+//! then use it to boot your i.MX RT processor.
+//!
+//! The crate achieves this with
+//!
+//! - a build-time API to define the memory map.
+//! - a runtime library to configure the embedded processor.
+//!
+//! Both APIs are exposed from the same package. The interface changes depending on the
+//! build environment.
+//!
+//! # Getting started
+//!
+//! Make sure you're familiar with [`cortex-m-rt`][cmrt] features. This crate re-exports
+//! the `cortex-m-rt` interface. Use this interface to implement your program's entrypoint,
+//! register exceptions, and interrupts. You should be familiar with specifing a linker
+//! script for your embedded project.
+//!
+//! [cmrt]: https://docs.rs/cortex-m-rt/0.7.1/cortex_m_rt/
+//!
+//! # Dependencies
+//!
+//! In your embedded target, depend on `imxrt-rt` in both of
+//!
+//! - the `[dependencies]` section of your Cargo.toml
+//! - the `[build-dependencies]` section of your Cargo.toml
+//!
+//! Use the same crate version in both locations. If you enable features, you must enable
+//! features in both locations. See the features section for more information.
+//!
+//! ```text
+//! [dependencies.imxrt-rt]
+//! version = # $VERSION
+//!
+//! [build-dependencies.imxrt-rt]
+//! version = # Same as $VERSION
+//! ```
+//!
+//! # Linker script
+//!
+//! **Link against `imxrt-link.x`**, which is automatically made available on the linker search path.
+//! Do not link against `link.x` from `cortex-m-rt`.
+//!
+//! You may change the name of the linker script by using the `RuntimeBuilder`.
+//!
+//! # Host configuration
+//!
+//! In your project, create a `build.rs` script that configures the runtime. The simplest `build.rs`
+//! looks like this:
+//!
+//! ```no_run
+//! use imxrt_rt::{Family, RuntimeBuilder};
+//!
+//! /// CHANGE ME depending on your board's flash size.
+//! const FLASH_SIZE: usize = 16 * 1024 * 1024; // 16 MiB.
+//! /// CHANGE ME depending on your board's chip.
+//! const FAMILY: Family = Family::Imxrt1060;
+//!
+//! fn main() {
+//! RuntimeBuilder::from_flexspi(FAMILY, FLASH_SIZE)
+//! .build()
+//! .unwrap();
+//! }
+//! ```
+//!
+//! This script works for any i.MX RT 1060-based system that has 16 MiB of external flash.
+//! Change the flash size and chip family based on your hardware. It uses the default configuration,
+//! which tries to give a reasonable memory layout for all processors.
+//! To understand the default configuration, see the [`RuntimeBuilder`] documentation.
+//!
+//! A more advanced runtime configuration looks like this:
+//!
+//! ```no_run
+//! # use imxrt_rt::{Family, RuntimeBuilder};
+//! use imxrt_rt::{FlexRamBanks, Memory};
+//! # const FLASH_SIZE: usize = 16 * 1024 * 1024; // 16 MiB.
+//! # const FAMILY: Family = Family::Imxrt1060;
+//!
+//! fn main() {
+//! RuntimeBuilder::from_flexspi(FAMILY, FLASH_SIZE)
+//! .flexram_banks(FlexRamBanks {
+//! ocram: 0,
+//! dtcm: FAMILY.flexram_bank_count() / 2 + 2,
+//! itcm: FAMILY.flexram_bank_count() / 2 - 2,
+//! })
+//! .text(Memory::Itcm)
+//! .vectors(Memory::Itcm)
+//! .rodata(Memory::Dtcm)
+//! .data(Memory::Dtcm)
+//! .bss(Memory::Dtcm)
+//! .uninit(Memory::Dtcm)
+//! .stack(Memory::Dtcm)
+//! .stack_size(4 * 1024)
+//! .heap(Memory::Dtcm)
+//! .heap_size(512)
+//! .build()
+//! .unwrap();
+//! }
+//! ```
+//!
+//! This configuration maximizes the TCM allocation by removing OCRAM blocks. It takes two
+//! banks from ITCM, and gives them to DTCM. It ensures that all sections are allocated to
+//! DTCM instead of OCRAM. It reduces the stack size, and reserves space for a small heap.
+//!
+//! No matter the configuration, the runtime ensures that all contents are copied from flash
+//! into their respective locations before `main()` is called.
+//!
+//! # Target integration
+//!
+//! If your runtime uses flash, link against a FlexSPI configuration block (FCB) crate. The
+//! crate is expected to export a `static FLEXSPI_CONFIGURATION_BLOCK` that describes how the
+//! FlexSPI peripheral interacts with your external flash chip. If an FCB crate doesn't exist
+//! for your hardware, you can use the [`imxrt-boot-gen` crate](https://docs.rs/imxrt-boot-gen/0.2.0/imxrt_boot_gen/)
+//! to define one. See the [`teensy4-fcb` crate](https://docs.rs/teensy4-fcb/0.3.0/teensy4_fcb/)
+//! for an example of an FCB crate that is compatible with this runtime.
+//!
+//! Finally, use `imxrt-rt` in your firmware just as you would use `cortex-m-rt`. See the [`cortex-m-rt`
+//! documentation][cmrt] for examples.
+//!
+//! # Feature flags
+//!
+//! `imxrt-rt` supports the features available in `cortex-m-rt` version 0.7.1. If you enable a feature,
+//! you must enable it in both the `[dependencies]` and `[build-dependencies]` section of your package
+//! manifest. For example, if the `cortex-m-rt` `"device"` feature were needed, then enable this crate's
+//! `"device"` feature in both places.
+//!
+//! ```text
+//! [dependencies.imxrt-rt]
+//! version = # $VERSION
+//! features = ["device"] # Add the feature here...
+//!
+//! [build-dependencies.imxrt-rt]
+//! version = # Same as $VERSION
+//! features = ["device"] # ... and here
+//! ```
+//!
+//! # Limitations
+//!
+//! The crate considers the assignment of FlexRAM memory banks to ITCM/DTCM/OCRAM
+//! an implementation detail. Additionally, the implementation does not care
+//! about the assignment of memory bank power domains. This seems to matter most on
+//! the 1050, which has the widest spread of bank-to-power domain assignment
+//! (according to AN12077).
+//!
+//! There is no support for ECC on 1170. The runtime assumes that OCRAM and TCM ECC
+//! is disabled, and that the corresponding memory banks can be used for OCRAM.
+//!
+//! The runtime installs a `cortex-m-rt` `pre_init` function to configure the runtime.
+//! You cannot also define a `pre_init` function, and this crate does not support any
+//! other mechanism for running code before `main()`.
+//!
+//! The implementation assumes all flash is FlexSPI.
+
+#![cfg_attr(all(target_arch = "arm", target_os = "none"), no_std)]
+
+cfg_if::cfg_if! {
+ if #[cfg(all(target_arch = "arm", target_os = "none"))] {
+ mod target;
+ pub use target::*;
+ } else {
+ mod host;
+ pub use host::*;
+ }
+}
diff --git a/src/target.rs b/src/target.rs
new file mode 100644
index 0000000..163fd8e
--- /dev/null
+++ b/src/target.rs
@@ -0,0 +1,147 @@
+//! i.MX RT target support.
+//!
+//! Defines a `cortex-m-rt` pre-init function that disables watchdogs and initializes TCM.
+//! It then copies instructions, read-only data, and the vector table to their intended location.
+//! This only happens if LMAs and VMAs differ.
+//!
+//! There's a few behaviors worth mentioning:
+//!
+//! - The implementation never clears the INIT_xTCM_EN bits in GPR16 if the xTCM is unused.
+//! The first reason is because we can't do this on the 1170 chips; the bits are reserved and
+//! should always be set (guessing its for the CM4, which always uses TCM). The second reason
+//! is that it doesn't seem necessary; we'll let xTCM initialize out of non-POR reset. From what
+//! I could gather, this would be the case if we set fuse values to specify an all-OCRAM config,
+//! and nothing says we need to flip these bits if the _fuses_ don't allocate xTCM. (Maybe this
+//! automagically happens? Not sure.)
+//! - We're not changing CM7_xTCMSZ to reflect the xTCM sizes. Again, the setting isn't available
+//! on the 1170 chips. It's also OK to keep the POR value, since it represents the maximum-possible
+//! TCM size. This means that users have finer control over xTCM memory sizes, but invalid xTCM accesses
+//! won't cause a bus fault. See 3.1.3.2. in AN12077 for more discussion.
+
+use core::{arch::global_asm, ffi::c_void};
+
+pub use cortex_m_rt::*;
+
+global_asm! {r#"
+.cfi_sections .debug_frame
+.section .__pre_init,"ax"
+.global __pre_init
+.type __pre_init,%function
+.thumb_func
+.cfi_startproc
+
+__pre_init:
+ ldr r0, =__imxrt_family @ Need to know which chip family we're initializing.
+ ldr r1, =1170
+ cmp r0, r1 @ Is this an 1170?
+
+ # Disable RTWODOG3.
+ ite eq
+ ldreq r2, =0x40038000 @ RTWDOG base address for 11xx chips...
+ ldrne r2, =0x400BC000 @ RTWDOG base address for 10xx chips...
+ ldr r3, =0xD928C520 @ RTWDOG magic number
+ str r3, [r2, #4] @ RTWDOG[CNT] = 0xD928C520.
+ ldr r3, [r2] @ r3 = RTWDOG[CS]
+ bic r3, r3, #1<<7 @ r3 = r3 & !(1 << 7), clears enable.
+ str r3, [r2] @ RTWDOG[CS] = r3
+
+ # Prepare FlexRAM regions.
+ ldr r0, =0x400AC000 @ IMXRT_IOMUXC_GPR base address for 10xx chips, overwritten if actually 11xx...
+ ldr r1, =__flexram_config @ Value for GPR17 (and GPR18 for 11xx)
+ itttt eq @ Need a few extra operations to handle 1170 split banks.
+ ldreq r0, =0x400E4000 @ IMXRT_IOMUXC_GPR base address for 11xx chips, overwrite 10xx address...
+ lsreq r2, r1, #16 @ r2 = ((unsigned)r1 >> 16)
+ streq r2, [r0, #72] @ *(IMXRT_IOMUXC_GPR + 18) = r2
+ ubfxeq r1, r1, #0, #16 @ r1 = ((unsigned)r1 >> 0) & 0xFFFF, overwrite r1 with lower halfword.
+ str r1, [r0, #68] @ *(IMXRT_IOMUXC_GPR + 17) = r1
+ ldr r1, [r0, #64] @ r1 = *(IMXRT_IOMUXC_GPR + 16)
+ orr r1, r1, #1<<2 @ r1 |= 1 << 2
+ str r1, [r0, #64] @ *(IMXRT_IOMUXC_GPR + 16) = r1
+
+ # Set the stack pointer.
+ #
+ # This is particularly important for the 11xx. Its boot ROM
+ # doesn't take this step before it calls into our reset
+ # handler. The 10xx boot ROM handles this differently.
+ # Also noted in
+ # https://community.nxp.com/t5/i-MX-RT/RT1176-ROM-code-does-not-set-stack-pointer-correctly/td-p/1388830
+ #
+ # If this feature is published in a future cortex-m-rt version,
+ # we could remove this. See below for VTOR, too.
+ #
+ # Shouldn't matter where we perform this within this function.
+ # We're assuming that the caller isn't using the stack.
+ ldr r0, =__sstack
+ msr msp, r0
+
+ # Conditionally copy text.
+ ldr r0, =__stext
+ ldr r2, =__sitext
+ cmp r2, r0
+ beq 42f
+
+ ldr r1, =__etext
+ 43:
+ cmp r1, r0
+ beq 42f
+ ldm r2!, {{r3}}
+ stm r0!, {{r3}}
+ b 43b
+ 42:
+
+ # Conditionally copy the vector table.
+ ldr r0, =__svector_table
+ ldr r2, =__sivector_table
+ cmp r2, r0
+ beq 52f
+
+ ldr r1, =__evector_table
+ 53:
+ cmp r1, r0
+ beq 52f
+ ldm r2!, {{r3}}
+ stm r0!, {{r3}}
+ b 53b
+ 52:
+
+ # Set VTOR. If this feature is published in a future cortex-m-rt version,
+ # we could remove this.
+ ldr r0, =0xE000ED08
+ ldr r1, =__svector_table
+ str r1, [r0]
+ dsb
+ isb
+
+ # Conditionally copy read-only data.
+ ldr r0, =__srodata
+ ldr r2, =__sirodata
+ cmp r2, r0
+ beq 62f
+
+ ldr r1, =__erodata
+ 63:
+ cmp r1, r0
+ beq 62f
+ ldm r2!, {{r3}}
+ stm r0!, {{r3}}
+ b 63b
+ 62:
+
+ # All done; back to the reset handler.
+ bx lr
+
+.cfi_endproc
+.size __pre_init, . - __pre_init
+"#
+}
+
+/// Returns a pointer to the end of the heap.
+///
+/// The returned pointer is guaranteed to be 4-byte aligned.
+#[inline]
+pub fn heap_end() -> *mut u32 {
+ extern "C" {
+ static mut __eheap: c_void;
+ }
+ unsafe { core::ptr::addr_of_mut!(__eheap) as _ }
+}