diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/host.rs | 822 | ||||
| -rw-r--r-- | src/host/imxrt-boot-header.x | 73 | ||||
| -rw-r--r-- | src/host/imxrt-link.x | 198 | ||||
| -rw-r--r-- | src/lib.rs | 167 | ||||
| -rw-r--r-- | src/target.rs | 147 |
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 _ } +} |
