imxrt_rt/
host.rs

1//! Host-side configurations for the target.
2//!
3//! See [`RuntimeBuilder::build`] to understand the linker script generation
4//! steps.
5
6// Please explicitly match all `Family` variants. If someone wants to add
7// a new `Family`, this will show help them find all the settings they need
8// to consider.
9#![warn(clippy::wildcard_enum_match_arm)]
10
11use std::{
12    env,
13    fmt::Display,
14    fs,
15    io::{self, Write},
16    path::PathBuf,
17};
18
19/// Memory partitions.
20///
21/// Use with [`RuntimeBuilder`] to specify the placement of sections
22/// in the final program. Note that the `RuntimeBuilder` only does limited
23/// checks on memory placements. Generally, it's OK to place data in ITCM,
24/// and instructions in DTCM; however, this isn't recommended for optimal
25/// performance.
26#[non_exhaustive]
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum Memory {
29    /// Place the section in (external) flash.
30    ///
31    /// Reads and writes are translated into commands on an external
32    /// bus, like FlexSPI.
33    Flash,
34    /// Place the section in data tightly coupled memory (DTCM).
35    Dtcm,
36    /// Place the section in instruction tightly coupled memory (ITCM).
37    Itcm,
38    /// Place the section in on-chip RAM (OCRAM).
39    ///
40    /// If your chip includes dedicated OCRAM memory, the implementation
41    /// utilizes that OCRAM before utilizing any FlexRAM OCRAM banks.
42    Ocram,
43}
44
45/// The FlexSPI peripheral that interfaces your flash chip.
46///
47/// The [`RuntimeBuilder`] selects `FlexSpi1` for nearly all chip
48/// families. However, it selects `FlexSpi2` for the 1064 in order
49/// to utilize its on-board flash. You can override the selection
50/// using [`RuntimeBuilder::flexspi()`].
51#[non_exhaustive]
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum FlexSpi {
54    /// Interface flash using FlexSPI 1.
55    FlexSpi1,
56    /// Interface flash using FlexSPI 2.
57    FlexSpi2,
58}
59
60impl FlexSpi {
61    fn family_default(family: Family) -> Self {
62        match family {
63            Family::Imxrt1064 => FlexSpi::FlexSpi2,
64            Family::Imxrt1010
65            | Family::Imxrt1015
66            | Family::Imxrt1020
67            | Family::Imxrt1040
68            | Family::Imxrt1050
69            | Family::Imxrt1060
70            | Family::Imxrt1160
71            | Family::Imxrt1170
72            | Family::Imxrt1180 => FlexSpi::FlexSpi1,
73        }
74    }
75    fn start_address(self, family: Family) -> Option<u32> {
76        match (self, family) {
77            // FlexSPI1, 10xx
78            (
79                FlexSpi::FlexSpi1,
80                Family::Imxrt1010
81                | Family::Imxrt1015
82                | Family::Imxrt1020
83                | Family::Imxrt1040
84                | Family::Imxrt1050
85                | Family::Imxrt1060
86                | Family::Imxrt1064,
87            ) => Some(0x6000_0000),
88            // FlexSPI2 not available on 10xx families
89            (
90                FlexSpi::FlexSpi2,
91                Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050,
92            ) => None,
93            // FlexSPI 2 available on 10xx families
94            (FlexSpi::FlexSpi2, Family::Imxrt1040 | Family::Imxrt1060 | Family::Imxrt1064) => {
95                Some(0x7000_0000)
96            }
97            // 11xx support
98            (FlexSpi::FlexSpi1, Family::Imxrt1160) => Some(0x3000_0000),
99            (FlexSpi::FlexSpi2, Family::Imxrt1160) => Some(0x6000_0000),
100            (FlexSpi::FlexSpi1, Family::Imxrt1170) => Some(0x3000_0000),
101            (FlexSpi::FlexSpi2, Family::Imxrt1170) => Some(0x6000_0000),
102            (FlexSpi::FlexSpi1, Family::Imxrt1180) => Some(0x2800_0000),
103            (FlexSpi::FlexSpi2, Family::Imxrt1180) => Some(0x0400_0000),
104        }
105    }
106    fn supported_for_family(self, family: Family) -> bool {
107        self.start_address(family).is_some()
108    }
109}
110
111impl Display for Memory {
112    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
113        match self {
114            Self::Flash => f.write_str("FLASH"),
115            Self::Itcm => f.write_str("ITCM"),
116            Self::Dtcm => f.write_str("DTCM"),
117            Self::Ocram => f.write_str("OCRAM"),
118        }
119    }
120}
121
122/// Define an alias for `name` that maps to a memory block named `placement`.
123fn region_alias(output: &mut dyn Write, name: &str, placement: Memory) -> io::Result<()> {
124    writeln!(output, "REGION_ALIAS(\"REGION_{}\", {});", name, placement)
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
128struct FlashOpts {
129    size: usize,
130    offset: u32,
131    flexspi: FlexSpi,
132}
133
134impl FlashOpts {
135    /// Produce the flash address of the image within
136    /// FlexSPI memory.
137    fn flash_origin(&self, family: Family) -> Option<u32> {
138        self.flexspi
139            .start_address(family)
140            .map(|start_address| start_address + self.offset)
141    }
142
143    /// A bootable image (with the boot header) isn't offset
144    /// in flash.
145    fn is_boot_image(&self) -> bool {
146        self.offset == 0
147    }
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
151struct EnvOverride {
152    default: usize,
153    env: Option<String>,
154}
155
156impl EnvOverride {
157    const fn new(default: usize) -> Self {
158        Self { default, env: None }
159    }
160    fn set_env_key(&mut self, key: String) {
161        self.env = Some(key);
162    }
163    fn read(&self) -> Result<usize, Box<dyn std::error::Error>> {
164        if let Some(env) = &self.env {
165            // If the user sets multiple environment variables for the same runtime
166            // property (like stack, heap), we will only re-run when the variable
167            // we care about changes. An example might help:
168            //
169            //      let mut bldr = RuntimeBuilder::from_flexspi(/* ... */);
170            //      bldr.stack_size_env_override("COMMON_STACK");
171            //      if special_condition() {
172            //          bldr.stack_size_env_override("SPECIAL_STACK");
173            //      }
174            //
175            // If we take the branch, then we re-run the build if SPECIAL_STACK
176            // changes. Otherwise, we re-run if COMMON_STACK changes.
177            //
178            // I previously put this into `set_env_key`. That would mean we re-run
179            // the build if _either_ enviroment variable changes. But then I thought
180            // about the user who writes their build script like
181            //
182            //      if special_condition() {
183            //          bldr.stack_size_env_override("SPECIAL_STACK");
184            //      } else {
185            //          bldr.stack_size_env_override("COMMON_STACK");
186            //      }
187            //
188            // and how that would introduce different re-run behavior than the first
189            // example, even though the variable selection is the same. Coupling the
190            // re-run behavior to the variable selection behavior seems less surprising.
191            println!("cargo:rerun-if-env-changed={env}");
192        }
193
194        if let Some(val) = self.env.as_ref().and_then(|key| env::var(key).ok()) {
195            let val = if val.ends_with('k') || val.ends_with('K') {
196                val[..val.len() - 1].parse::<usize>()? * 1024
197            } else {
198                val.parse::<usize>()?
199            };
200            Ok(val)
201        } else {
202            Ok(self.default)
203        }
204    }
205}
206
207/// Builder for the i.MX RT runtime.
208///
209/// `RuntimeBuilder` let you assign sections to memory regions. It also lets
210/// you partition FlexRAM DTCM/ITCM/OCRAM. Call [`build()`](RuntimeBuilder::build) to commit the
211/// runtime configuration.
212///
213/// # Behaviors
214///
215/// The implementation tries to place the stack in the lowest-possible memory addresses.
216/// This means the stack will grow down into reserved memory below DTCM and OCRAM for most
217/// chip families. The outlier is the 1170, where the stack will grow into OCRAM backdoor for
218/// the CM4 coprocessor. Be careful here...
219///
220/// Similarly, the implementation tries to place the heap in the highest-possible memory
221/// addresses. This means the heap will grow up into reserved memory above DTCM and OCRAM
222/// for most chip families.
223///
224/// The vector table requires a 1024-byte alignment. The vector table's placement is prioritized
225/// above all other sections, except the stack. If placing the stack and vector table in the
226/// same section (which is the default behavior), consider keeping the stack size as a multiple
227/// of 1 KiB to minimize internal fragmentation.
228///
229/// # Default values
230///
231/// The example below demonstrates the default `RuntimeBuilder` memory placements,
232/// stack sizes, and heap sizes.
233///
234/// ```
235/// use imxrt_rt::{Family, RuntimeBuilder, Memory};
236///
237/// const FLASH_SIZE: usize = 16 * 1024;
238/// let family = Family::Imxrt1060;
239///
240/// let mut b = RuntimeBuilder::from_flexspi(family, FLASH_SIZE);
241/// // FlexRAM banks represent default fuse values.
242/// b.flexram_banks(family.default_flexram_banks());
243/// b.text(Memory::Itcm);    // Copied from flash.
244/// b.rodata(Memory::Ocram); // Copied from flash.
245/// b.data(Memory::Ocram);   // Copied from flash.
246/// b.vectors(Memory::Dtcm); // Copied from flash.
247/// b.bss(Memory::Ocram);
248/// b.uninit(Memory::Ocram);
249/// b.stack(Memory::Dtcm);
250/// b.stack_size(8 * 1024);  // 8 KiB stack.
251/// b.heap(Memory::Dtcm);    // Heap in DTCM...
252/// b.heap_size(0);          // ...but no space given to the heap.
253///
254/// assert_eq!(b, RuntimeBuilder::from_flexspi(family, FLASH_SIZE));
255/// ```
256///
257/// # Environment overrides
258///
259/// Certain memory regions, like the stack and heap, can be sized using environment
260/// variables. As the provider of the runtime, you can use `*_env_override` methods
261/// to select the environment variable(s) that others may use to set the size, in bytes,
262/// for these memory regions.
263///
264/// The rest of this section describes how environment variables interact with other
265/// methods on this builder. Although the examples use stack size, the concepts apply
266/// to all regions that can be sized with environment variables.
267///
268/// ```no_run
269/// # use imxrt_rt::{Family, RuntimeBuilder, Memory};
270/// # const FLASH_SIZE: usize = 16 * 1024;
271/// # let family = Family::Imxrt1060;
272/// RuntimeBuilder::from_flexspi(family, FLASH_SIZE)
273///     .stack_size_env_override("YOUR_STACK_SIZE")
274///     // ...
275///     # .build().unwrap();
276/// ```
277///
278/// In the above example, if a user set an environment variable `YOUR_STACK_SIZE=1024`, then
279/// the runtime's stack size is 1024. Otherwise, the stack size is the default stack size.
280///
281/// > As a convenience, a user can use a `k` or `K` suffix to specify multiples of 1024 bytes.
282/// > For example, the environment variables `YOUR_STACK_SIZE=4k` and `YOUR_STACK_SIZE=4K` are
283/// > each equivalent to `YOUR_STACK_SIZE=4096`.
284///
285/// ```no_run
286/// # use imxrt_rt::{Family, RuntimeBuilder, Memory};
287/// # const FLASH_SIZE: usize = 16 * 1024;
288/// # let family = Family::Imxrt1060;
289/// RuntimeBuilder::from_flexspi(family, FLASH_SIZE)
290///     .stack_size_env_override("YOUR_STACK_SIZE")
291///     .stack_size(2048)
292///     // ...
293///     # .build().unwrap();
294///
295/// RuntimeBuilder::from_flexspi(family, FLASH_SIZE)
296///     .stack_size(2048)
297///     .stack_size_env_override("YOUR_STACK_SIZE")
298///     // ...
299///     # .build().unwrap();
300/// ```
301///
302/// In the above example, the two builders produce the same runtime. The builder
303/// selects the stack size from the environment variable, if available. Otherwise,
304/// the stack size is 2048 bytes. The call order is irrelevant, since the builder
305/// doesn't consult the environment until you invoke [`build()`](Self::build).
306///
307/// ```no_run
308/// # use imxrt_rt::{Family, RuntimeBuilder, Memory};
309/// # const FLASH_SIZE: usize = 16 * 1024;
310/// # let family = Family::Imxrt1060;
311/// RuntimeBuilder::from_flexspi(family, FLASH_SIZE)
312///     .stack_size_env_override("INVALIDATED")
313///     .stack_size_env_override("YOUR_STACK_SIZE")
314///     // ...
315///     # .build().unwrap();
316/// ````
317///
318/// In the above example, `YOUR_STACK_SIZE` invalidates the call with `INVALIDATED`.
319/// Therefore, `YOUR_STACK_SIZE` controls the stack size, if set. Otherwise, the stack
320/// size is the default stack size.
321#[derive(Debug, Clone, PartialEq, Eq)]
322pub struct RuntimeBuilder {
323    family: Family,
324    flexram_banks: FlexRamBanks,
325    text: Memory,
326    rodata: Memory,
327    data: Memory,
328    vectors: Memory,
329    bss: Memory,
330    uninit: Memory,
331    stack: Memory,
332    stack_size: EnvOverride,
333    heap: Memory,
334    heap_size: EnvOverride,
335    flash_opts: Option<FlashOpts>,
336    linker_script_name: String,
337}
338
339const DEFAULT_LINKER_SCRIPT_NAME: &str = "imxrt-link.x";
340
341impl RuntimeBuilder {
342    /// Creates a runtime that can execute and load contents from
343    /// FlexSPI flash.
344    ///
345    /// `flash_size` is the size of your flash component, in bytes.
346    pub fn from_flexspi(family: Family, flash_size: usize) -> Self {
347        Self {
348            family,
349            flexram_banks: family.default_flexram_banks(),
350            text: Memory::Itcm,
351            rodata: Memory::Ocram,
352            data: Memory::Ocram,
353            vectors: Memory::Dtcm,
354            bss: Memory::Ocram,
355            uninit: Memory::Ocram,
356            stack: Memory::Dtcm,
357            stack_size: EnvOverride::new(8 * 1024),
358            heap: Memory::Dtcm,
359            heap_size: EnvOverride::new(0),
360            flash_opts: Some(FlashOpts {
361                size: flash_size,
362                offset: 0,
363                flexspi: FlexSpi::family_default(family),
364            }),
365            linker_script_name: DEFAULT_LINKER_SCRIPT_NAME.into(),
366        }
367    }
368
369    /// Allocate a flash partition for this program to be booted by your software.
370    ///
371    /// `partition_size` is the size of the flash allocation, in bytes, for this
372    /// program. `partition_offset` describes the byte offset where the partition
373    /// starts. The offset is from the start of the FlexSPI memory region.
374    ///
375    /// The program constructed at this flash location cannot be booted by NXP's boot
376    /// ROM. You should bring your own software to execute this program. Note that
377    /// [the runtime behaviors](RuntimeBuilder) ensure that the vector table is placed
378    /// in flash at the given `partition_offset`.
379    ///
380    /// To compute a partition offset from two absolute flash addresses, use
381    /// [`Family::flexspi_start_addr`] to learn the FlexSPI starting address.
382    pub fn in_flash(family: Family, partition_size: usize, partition_offset: u32) -> Self {
383        Self {
384            family,
385            flexram_banks: family.default_flexram_banks(),
386            text: Memory::Itcm,
387            rodata: Memory::Ocram,
388            data: Memory::Ocram,
389            vectors: Memory::Dtcm,
390            bss: Memory::Ocram,
391            uninit: Memory::Ocram,
392            stack: Memory::Dtcm,
393            stack_size: EnvOverride::new(8 * 1024),
394            heap: Memory::Dtcm,
395            heap_size: EnvOverride::new(0),
396            flash_opts: Some(FlashOpts {
397                size: partition_size,
398                offset: partition_offset,
399                flexspi: FlexSpi::family_default(family),
400            }),
401            linker_script_name: DEFAULT_LINKER_SCRIPT_NAME.into(),
402        }
403    }
404    /// Set the FlexRAM bank allocation.
405    ///
406    /// Use this to customize the sizes of DTCM, ITCM, and OCRAM.
407    /// See the `FlexRamBanks` documentation for requirements on the
408    /// bank allocations.
409    pub fn flexram_banks(&mut self, flexram_banks: FlexRamBanks) -> &mut Self {
410        self.flexram_banks = flexram_banks;
411        self
412    }
413    /// Set the memory placement for code.
414    pub fn text(&mut self, memory: Memory) -> &mut Self {
415        self.text = memory;
416        self
417    }
418    /// Set the memory placement for read-only data.
419    pub fn rodata(&mut self, memory: Memory) -> &mut Self {
420        self.rodata = memory;
421        self
422    }
423    /// Set the memory placement for mutable data.
424    pub fn data(&mut self, memory: Memory) -> &mut Self {
425        self.data = memory;
426        self
427    }
428    /// Set the memory placement for the vector table.
429    pub fn vectors(&mut self, memory: Memory) -> &mut Self {
430        self.vectors = memory;
431        self
432    }
433    /// Set the memory placement for zero-initialized data.
434    pub fn bss(&mut self, memory: Memory) -> &mut Self {
435        self.bss = memory;
436        self
437    }
438    /// Set the memory placement for uninitialized data.
439    pub fn uninit(&mut self, memory: Memory) -> &mut Self {
440        self.uninit = memory;
441        self
442    }
443    /// Set the memory placement for stack memory.
444    pub fn stack(&mut self, memory: Memory) -> &mut Self {
445        self.stack = memory;
446        self
447    }
448    /// Set the size, in bytes, of the stack.
449    pub fn stack_size(&mut self, bytes: usize) -> &mut Self {
450        self.stack_size.default = bytes;
451        self
452    }
453    /// Let end users override the stack size using an environment variable.
454    ///
455    /// See the [environment overrides](Self#environment-overrides) documentation
456    /// for more information.
457    pub fn stack_size_env_override(&mut self, key: impl AsRef<str>) -> &mut Self {
458        self.stack_size.set_env_key(key.as_ref().into());
459        self
460    }
461    /// Set the memory placement for the heap.
462    ///
463    /// Note that the default heap has no size. Use [`heap_size`](Self::heap_size)
464    /// to allocate space for a heap.
465    pub fn heap(&mut self, memory: Memory) -> &mut Self {
466        self.heap = memory;
467        self
468    }
469    /// Set the size, in bytes, of the heap.
470    pub fn heap_size(&mut self, bytes: usize) -> &mut Self {
471        self.heap_size.default = bytes;
472        self
473    }
474    /// Let end users override the heap size using an environment variable.
475    ///
476    /// See the [environment overrides](Self#environment-overrides) documentation
477    /// for more information.
478    pub fn heap_size_env_override(&mut self, key: impl AsRef<str>) -> &mut Self {
479        self.heap_size.set_env_key(key.as_ref().into());
480        self
481    }
482    /// Set the FlexSPI peripheral that interfaces flash.
483    ///
484    /// See the [`FlexSpi`] to understand the default values.
485    /// If this builder is not configuring a flash-loaded runtime, this
486    /// call is silently ignored.
487    pub fn flexspi(&mut self, peripheral: FlexSpi) -> &mut Self {
488        if let Some(flash_opts) = &mut self.flash_opts {
489            flash_opts.flexspi = peripheral;
490        }
491        self
492    }
493
494    /// Set the name of the linker script file.
495    ///
496    /// You can use this to customize the linker script name for your users.
497    /// See the [crate-level documentation](crate#linker-script) for more
498    /// information.
499    pub fn linker_script_name(&mut self, name: &str) -> &mut Self {
500        self.linker_script_name = name.into();
501        self
502    }
503
504    /// Commit the runtime configuration.
505    ///
506    /// `build()` ensures that the generated linker script is available to the
507    /// linker.
508    ///
509    /// # Errors
510    ///
511    /// The implementation ensures that your chip can support the FlexRAM bank
512    /// allocation. An invalid allocation is signaled by an error.
513    ///
514    /// Returns an error if any of the following sections are placed in flash:
515    ///
516    /// - data
517    /// - vectors
518    /// - bss
519    /// - uninit
520    /// - stack
521    /// - heap
522    ///
523    /// The implementation may rely on the _linker_ to signal other errors.
524    /// For example, suppose a runtime configuration with no ITCM banks. If a
525    /// section is placed in ITCM, that error could be signaled here, or through
526    /// the linker. No matter the error path, the implementation ensures that there
527    /// will be an error.
528    pub fn build(&self) -> Result<(), Box<dyn std::error::Error>> {
529        // Since `build` is called from a build script, the output directory
530        // represents the path to the _user's_ crate.
531        let out_dir = PathBuf::from(env::var("OUT_DIR")?);
532        println!("cargo:rustc-link-search={}", out_dir.display());
533
534        // The main linker script expects to INCLUDE this file. This file
535        // uses region aliases to associate region names to actual memory
536        // regions (see the Memory enum).
537        let mut in_memory = Vec::new();
538        self.write_linker_script(&mut in_memory)?;
539        fs::write(out_dir.join(&self.linker_script_name), &in_memory)?;
540        Ok(())
541    }
542
543    /// Write the generated linker script into the provided writer.
544    ///
545    /// Use this if you want more control over where the generated linker script
546    /// ends up. Otherwise, you should prefer [`build()`](Self::build) for an
547    /// easier experience.
548    ///
549    /// Unlike `build()`, this method does not ensure that the linker script is
550    /// available to the linker. Additionally, this method does not consider
551    /// the value set by [`linker_script_name`](Self::linker_script_name).
552    ///
553    /// # Errors
554    ///
555    /// See [`build()`](Self::build) to understand the possible errors.
556    fn write_linker_script(
557        &self,
558        writer: &mut dyn Write,
559    ) -> Result<(), Box<dyn std::error::Error>> {
560        self.check_configurations()?;
561
562        if let Some(flash_opts) = &self.flash_opts {
563            write_flash_memory_map(writer, self.family, flash_opts, &self.flexram_banks)?;
564
565            if flash_opts.is_boot_image() {
566                let boot_header_x = match self.family {
567                    Family::Imxrt1010
568                    | Family::Imxrt1015
569                    | Family::Imxrt1020
570                    | Family::Imxrt1040
571                    | Family::Imxrt1050
572                    | Family::Imxrt1060
573                    | Family::Imxrt1064
574                    | Family::Imxrt1160
575                    | Family::Imxrt1170 => include_bytes!("host/imxrt-boot-header.x").as_slice(),
576                    Family::Imxrt1180 => include_bytes!("host/imxrt-boot-header-1180.x").as_slice(),
577                };
578                writer.write_all(boot_header_x)?;
579            }
580        } else {
581            write_ram_memory_map(writer, self.family, &self.flexram_banks)?;
582        }
583
584        #[cfg(feature = "device")]
585        writeln!(writer, "INCLUDE device.x")?;
586
587        // Keep these alias names in sync with the primary linker script.
588        // The main linker script uses these region aliases for placing
589        // sections. Then, the user specifies the actual placement through
590        // the builder. This saves us the step of actually generating SECTION
591        // commands.
592        region_alias(writer, "TEXT", self.text)?;
593        region_alias(writer, "VTABLE", self.vectors)?;
594        region_alias(writer, "RODATA", self.rodata)?;
595        region_alias(writer, "DATA", self.data)?;
596        region_alias(writer, "BSS", self.bss)?;
597        region_alias(writer, "UNINIT", self.uninit)?;
598
599        region_alias(writer, "STACK", self.stack)?;
600        region_alias(writer, "HEAP", self.heap)?;
601        // Used in the linker script and / or target code.
602        writeln!(writer, "__stack_size = {:#010X};", self.stack_size.read()?)?;
603        writeln!(writer, "__heap_size = {:#010X};", self.heap_size.read()?)?;
604
605        if self.flash_opts.is_some() {
606            // Runtime will see different VMA and LMA, and copy the sections.
607            region_alias(writer, "LOAD_VTABLE", Memory::Flash)?;
608            region_alias(writer, "LOAD_TEXT", Memory::Flash)?;
609            region_alias(writer, "LOAD_RODATA", Memory::Flash)?;
610            region_alias(writer, "LOAD_DATA", Memory::Flash)?;
611        } else {
612            // When the VMA and LMA are equal, the runtime performs no copies.
613            region_alias(writer, "LOAD_VTABLE", self.vectors)?;
614            region_alias(writer, "LOAD_TEXT", self.text)?;
615            region_alias(writer, "LOAD_RODATA", self.rodata)?;
616            region_alias(writer, "LOAD_DATA", self.data)?;
617        }
618
619        // Referenced in target code.
620        writeln!(
621            writer,
622            "__flexram_config = {:#010X};",
623            self.flexram_banks.config(self.family)
624        )?;
625        // The target runtime looks at this value to predicate some pre-init instructions.
626        // Could be helpful for binary identification, but it's an undocumented feature.
627        writeln!(writer, "__imxrt_family = {};", self.family.id(),)?;
628
629        let link_x = include_bytes!("host/imxrt-link.x");
630        writer.write_all(link_x)?;
631
632        Ok(())
633    }
634
635    /// Implement i.MX RT specific sanity checks.
636    ///
637    /// This might not check everything! If the linker may detect a condition, we'll
638    /// let the linker do that.
639    fn check_configurations(&self) -> Result<(), String> {
640        if self.family.flexram_bank_count() < self.flexram_banks.bank_count() {
641            return Err(format!(
642            "Chip {:?} only has {} total FlexRAM banks. Cannot allocate {:?}, a total of {} banks",
643            self.family,
644            self.family.flexram_bank_count(),
645            self.flexram_banks,
646            self.flexram_banks.bank_count()
647        ));
648        }
649        if self.flexram_banks.ocram < self.family.bootrom_ocram_banks() {
650            return Err(format!(
651                "Chip {:?} requires at least {} OCRAM banks for the bootloader ROM",
652                self.family,
653                self.family.bootrom_ocram_banks()
654            ));
655        }
656        if let Some(flash_opts) = &self.flash_opts {
657            if !flash_opts.flexspi.supported_for_family(self.family) {
658                return Err(format!(
659                    "Chip {:?} does not support {:?}",
660                    self.family, flash_opts.flexspi
661                ));
662            }
663        }
664
665        fn prevent_flash(name: &str, memory: Memory) -> Result<(), String> {
666            if memory == Memory::Flash {
667                Err(format!("Section '{}' cannot be placed in flash", name))
668            } else {
669                Ok(())
670            }
671        }
672        macro_rules! prevent_flash {
673            ($sec:ident) => {
674                prevent_flash(stringify!($sec), self.$sec)
675            };
676        }
677
678        prevent_flash!(data)?;
679        prevent_flash!(vectors)?;
680        prevent_flash!(bss)?;
681        prevent_flash!(uninit)?;
682        prevent_flash!(stack)?;
683        prevent_flash!(heap)?;
684
685        Ok(())
686    }
687}
688
689/// Write RAM-like memory blocks.
690///
691/// Skips a section if there's no FlexRAM block allocated. If a user references one
692/// of this skipped sections, linking fails.
693fn write_flexram_memories(
694    output: &mut dyn Write,
695    family: Family,
696    flexram_banks: &FlexRamBanks,
697) -> io::Result<()> {
698    if flexram_banks.itcm > 0 {
699        let itcm_size = flexram_banks.itcm * family.flexram_bank_size();
700        let itcm_start = match family {
701            Family::Imxrt1010
702            | Family::Imxrt1015
703            | Family::Imxrt1020
704            | Family::Imxrt1040
705            | Family::Imxrt1050
706            | Family::Imxrt1060
707            | Family::Imxrt1064
708            | Family::Imxrt1160
709            | Family::Imxrt1170 => 0x00000000,
710            Family::Imxrt1180 => 0x10000000 - itcm_size,
711        };
712        writeln!(
713            output,
714            "ITCM (RWX) : ORIGIN = {itcm_start:#X}, LENGTH = {itcm_size:#X}"
715        )?;
716    }
717    if flexram_banks.dtcm > 0 {
718        writeln!(
719            output,
720            "DTCM (RWX) : ORIGIN = 0x20000000, LENGTH = {:#X}",
721            flexram_banks.dtcm * family.flexram_bank_size(),
722        )?;
723    }
724
725    let ocram_size =
726        flexram_banks.ocram * family.flexram_bank_size() + family.dedicated_ocram_size();
727    if ocram_size > 0 {
728        writeln!(
729            output,
730            "OCRAM (RWX) : ORIGIN = {:#X}, LENGTH = {:#X}",
731            family.ocram_start(),
732            ocram_size,
733        )?;
734    }
735    Ok(())
736}
737
738/// Generate a linker script MEMORY command that includes a FLASH block.
739fn write_flash_memory_map(
740    output: &mut dyn Write,
741    family: Family,
742    flash_opts: &FlashOpts,
743    flexram_banks: &FlexRamBanks,
744) -> io::Result<()> {
745    writeln!(
746        output,
747        "/* Memory map for '{:?}' with custom flash length {}. */",
748        family, flash_opts.size
749    )?;
750    writeln!(output, "MEMORY {{")?;
751    writeln!(
752        output,
753        "FLASH (RX) : ORIGIN = {:#X}, LENGTH = {:#X}",
754        flash_opts.flash_origin(family).expect("Already checked"),
755        flash_opts.size
756    )?;
757    write_flexram_memories(output, family, flexram_banks)?;
758    writeln!(output, "}}")?;
759    writeln!(output, "__fcb_offset = {:#X};", family.fcb_offset())?;
760    Ok(())
761}
762
763/// Generate a linker script MEMORY command that supports RAM execution.
764///
765/// It's like [`write_flash_memory_map`], but it doesn't include the flash
766/// important tidbits.
767fn write_ram_memory_map(
768    output: &mut dyn Write,
769    family: Family,
770    flexram_banks: &FlexRamBanks,
771) -> io::Result<()> {
772    writeln!(
773        output,
774        "/* Memory map for '{:?}' that executes from RAM. */",
775        family,
776    )?;
777    writeln!(output, "MEMORY {{")?;
778    write_flexram_memories(output, family, flexram_banks)?;
779    writeln!(output, "}}")?;
780    Ok(())
781}
782
783/// i.MX RT chip family.
784///
785/// Chip families are designed by reference manuals and produce categories.
786/// Supply this to a [`RuntimeBuilder`] in order to check runtime configurations.
787#[non_exhaustive]
788#[derive(Debug, Clone, Copy, PartialEq, Eq)]
789pub enum Family {
790    Imxrt1010,
791    Imxrt1015,
792    Imxrt1020,
793    Imxrt1040,
794    Imxrt1050,
795    Imxrt1060,
796    Imxrt1064,
797    Imxrt1160,
798    Imxrt1170,
799    Imxrt1180,
800}
801
802impl Family {
803    /// Family identifier.
804    ///
805    /// These values may be stored in the image and observe by the runtime
806    /// initialzation routine. Make sure these numbers are kept in sync with
807    /// any hard-coded values.
808    const fn id(self) -> u32 {
809        match self {
810            Family::Imxrt1010 => 1010,
811            Family::Imxrt1015 => 1015,
812            Family::Imxrt1020 => 1020,
813            Family::Imxrt1040 => 1040,
814            Family::Imxrt1050 => 1050,
815            Family::Imxrt1060 => 1060,
816            Family::Imxrt1064 => 1064,
817            Family::Imxrt1160 => 1160,
818            Family::Imxrt1170 => 1170,
819            Family::Imxrt1180 => 1180,
820        }
821    }
822    /// How many FlexRAM banks are available?
823    pub const fn flexram_bank_count(self) -> u32 {
824        match self {
825            Family::Imxrt1010 | Family::Imxrt1015 => 4,
826            Family::Imxrt1020 => 8,
827            Family::Imxrt1040 | Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => 16,
828            // No ECC support; treating all banks as equal.
829            Family::Imxrt1160 | Family::Imxrt1170 => 16,
830            Family::Imxrt1180 => 2,
831        }
832    }
833    /// How large (bytes) is each FlexRAM bank?
834    const fn flexram_bank_size(self) -> u32 {
835        match self {
836            Family::Imxrt1010
837            | Family::Imxrt1015
838            | Family::Imxrt1020
839            | Family::Imxrt1040
840            | Family::Imxrt1050
841            | Family::Imxrt1060
842            | Family::Imxrt1064
843            | Family::Imxrt1160
844            | Family::Imxrt1170 => 32 * 1024,
845            Family::Imxrt1180 => 128 * 1024,
846        }
847    }
848    /// How many OCRAM banks does the boot ROM need?
849    const fn bootrom_ocram_banks(self) -> u32 {
850        match self {
851            Family::Imxrt1010
852            | Family::Imxrt1015
853            | Family::Imxrt1020
854            | Family::Imxrt1040
855            | Family::Imxrt1050 => 1,
856            // 9.5.1. memory maps point at OCRAM2.
857            Family::Imxrt1060 | Family::Imxrt1064 => 0,
858            // Boot ROM uses dedicated OCRAM1.
859            Family::Imxrt1160 | Family::Imxrt1170 | Family::Imxrt1180 => 0,
860        }
861    }
862    /// Where's the FlexSPI configuration bank located?
863    fn fcb_offset(self) -> usize {
864        match self {
865            Family::Imxrt1010 | Family::Imxrt1160 | Family::Imxrt1170 | Family::Imxrt1180 => 0x400,
866            Family::Imxrt1015
867            | Family::Imxrt1020
868            | Family::Imxrt1040
869            | Family::Imxrt1050
870            | Family::Imxrt1060
871            | Family::Imxrt1064 => 0x000,
872        }
873    }
874
875    /// Where does the OCRAM region begin?
876    ///
877    /// This includes dedicated any OCRAM regions, if any exist for the chip.
878    fn ocram_start(self) -> u32 {
879        match self {
880            // 256 KiB offset from the OCRAM M4 backdoor.
881            Family::Imxrt1170 => 0x2024_0000,
882            // Using the alias regions, assuming ECC is disabled.
883            // The two alias regions, plus the ECC region, provide
884            // the *contiguous* 256 KiB of dedicated OCRAM.
885            Family::Imxrt1160 => 0x2034_0000,
886            // Skip the first 16 KiB, "cannot be safely used by application images".
887            Family::Imxrt1180 => 0x2048_4000,
888            // Either starts the FlexRAM OCRAM banks, or the
889            // dedicated OCRAM regions (for supported devices).
890            Family::Imxrt1010
891            | Family::Imxrt1015
892            | Family::Imxrt1020
893            | Family::Imxrt1040
894            | Family::Imxrt1050
895            | Family::Imxrt1060
896            | Family::Imxrt1064 => 0x2020_0000,
897        }
898    }
899
900    /// What's the size, in bytes, of the dedicated OCRAM section?
901    ///
902    /// This isn't supported by all chips.
903    const fn dedicated_ocram_size(self) -> u32 {
904        match self {
905            Family::Imxrt1010
906            | Family::Imxrt1015
907            | Family::Imxrt1020
908            | Family::Imxrt1040
909            | Family::Imxrt1050 => 0,
910            Family::Imxrt1060 | Family::Imxrt1064 => 512 * 1024,
911            // - Two dedicated OCRAMs
912            // - One FlexRAM OCRAM EC region that's OCRAM when ECC is disabled.
913            Family::Imxrt1160 => (2 * 64 + 128) * 1024,
914            // - Two dedicated OCRAMs
915            // - Two dedicated OCRAM ECC regions that aren't used for ECC
916            // - One FlexRAM OCRAM ECC region that's strictly OCRAM, without ECC
917            Family::Imxrt1170 => (2 * 512 + 2 * 64 + 128) * 1024,
918            // OCRAM1 (512k), OCRAM2 (256k), 16k reserved as a ROM patch area
919            Family::Imxrt1180 => (512 + 256 - 16) * 1024,
920        }
921    }
922
923    /// Returns the default FlexRAM bank allocations for this chip.
924    ///
925    /// The default values represent the all-zero fuse values.
926    pub fn default_flexram_banks(self) -> FlexRamBanks {
927        match self {
928            Family::Imxrt1010 | Family::Imxrt1015 => FlexRamBanks {
929                ocram: 2,
930                itcm: 1,
931                dtcm: 1,
932            },
933            Family::Imxrt1020 => FlexRamBanks {
934                ocram: 4,
935                itcm: 2,
936                dtcm: 2,
937            },
938            Family::Imxrt1040 | Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => {
939                FlexRamBanks {
940                    ocram: 8,
941                    itcm: 4,
942                    dtcm: 4,
943                }
944            }
945            Family::Imxrt1160 | Family::Imxrt1170 => FlexRamBanks {
946                ocram: 0,
947                itcm: 8,
948                dtcm: 8,
949            },
950            Family::Imxrt1180 => FlexRamBanks {
951                ocram: 0,
952                itcm: 1,
953                dtcm: 1,
954            },
955        }
956    }
957
958    /// Returns the starting address for the given `flexspi` instance.
959    ///
960    /// If a FlexSPI instance isn't available for this family, the return
961    /// is `None`. Otherwise, the return is the starting address in the
962    /// MCU's memory map.
963    ///
964    /// ```
965    /// use imxrt_rt::{Family::*, FlexSpi::*};
966    ///
967    /// assert_eq!(Imxrt1060.flexspi_start_addr(FlexSpi1), Some(0x6000_0000));
968    /// assert!(Imxrt1010.flexspi_start_addr(FlexSpi2).is_none());
969    /// ```
970    pub fn flexspi_start_addr(self, flexspi: FlexSpi) -> Option<u32> {
971        flexspi.start_address(self)
972    }
973}
974
975/// FlexRAM bank allocations.
976///
977/// Depending on your device, you may need a non-zero number of
978/// OCRAM banks to support the boot ROM. Consult your processor's
979/// reference manual for more information.
980///
981/// You should keep the sum of all banks below or equal to the
982/// total number of banks supported by your device. Unallocated memory
983/// banks are disabled.
984///
985/// Banks are typically 32KiB large.
986#[derive(Debug, Clone, Copy, PartialEq, Eq)]
987pub struct FlexRamBanks {
988    /// How many banks are allocated for OCRAM?
989    ///
990    /// This may need to be non-zero to support the boot ROM.
991    /// Consult your reference manual.
992    ///
993    /// Note: these are FlexRAM OCRAM banks. Do not include any banks
994    /// that would represent dedicated OCRAM; the runtime implementation
995    /// allocates those automatically. In fact, if your chip includes
996    /// dedicated OCRAM, you may set this to zero in order to maximize
997    /// DTCM and ITCM utilization.
998    pub ocram: u32,
999    /// How many banks are allocated for ITCM?
1000    pub itcm: u32,
1001    /// How many banks are allocated for DTCM?
1002    pub dtcm: u32,
1003}
1004
1005impl FlexRamBanks {
1006    /// Total FlexRAM banks.
1007    const fn bank_count(&self) -> u32 {
1008        self.ocram + self.itcm + self.dtcm
1009    }
1010
1011    /// Produces the FlexRAM configuration.
1012    fn config(&self, family: Family) -> u32 {
1013        match family {
1014            Family::Imxrt1010
1015            | Family::Imxrt1015
1016            | Family::Imxrt1020
1017            | Family::Imxrt1040
1018            | Family::Imxrt1050
1019            | Family::Imxrt1060
1020            | Family::Imxrt1064
1021            | Family::Imxrt1160
1022            | Family::Imxrt1170 => self.config_gpr(),
1023            Family::Imxrt1180 => self.config_1180(),
1024        }
1025    }
1026
1027    /// Produces the FlexRAM configuration for families using GPR17.
1028    fn config_gpr(&self) -> u32 {
1029        assert!(
1030            self.bank_count() <= 16,
1031            "Something is wrong; this should have been checked earlier."
1032        );
1033
1034        // If a FlexRAM memory type could be allocated
1035        // to _all_ memory banks, these would represent
1036        // the configuration masks...
1037        const OCRAM: u32 = 0x5555_5555; // 0b01...
1038        const DTCM: u32 = 0xAAAA_AAAA; // 0b10...
1039        const ITCM: u32 = 0xFFFF_FFFF; // 0b11...
1040
1041        fn mask(bank_count: u32) -> u32 {
1042            1u32.checked_shl(bank_count * 2)
1043                .map(|bit| bit - 1)
1044                .unwrap_or(u32::MAX)
1045        }
1046
1047        let ocram_mask = mask(self.ocram);
1048        let dtcm_mask = mask(self.dtcm).checked_shl(self.ocram * 2).unwrap_or(0);
1049        let itcm_mask = mask(self.itcm)
1050            .checked_shl((self.ocram + self.dtcm) * 2)
1051            .unwrap_or(0);
1052
1053        (OCRAM & ocram_mask) | (DTCM & dtcm_mask) | (ITCM & itcm_mask)
1054    }
1055
1056    fn config_1180(&self) -> u32 {
1057        match (self.itcm, self.dtcm, self.ocram) {
1058            (1, 1, 0) => 0b00_u32,
1059            (2, 0, 0) => 0b10,
1060            (0, 2, 0) => 0b01,
1061            _ => panic!("Unsupported FlexRAM configuration"),
1062        }
1063        .checked_shl(2)
1064        .unwrap()
1065    }
1066}
1067
1068#[cfg(test)]
1069mod tests {
1070    use crate::Memory;
1071
1072    use super::{Family, FlexRamBanks, RuntimeBuilder};
1073    use std::{error, io};
1074
1075    const ALL_FAMILIES: &[Family] = &[
1076        Family::Imxrt1010,
1077        Family::Imxrt1015,
1078        Family::Imxrt1020,
1079        Family::Imxrt1040,
1080        Family::Imxrt1050,
1081        Family::Imxrt1060,
1082        Family::Imxrt1064,
1083        Family::Imxrt1170,
1084    ];
1085    type Error = Box<dyn error::Error>;
1086
1087    #[test]
1088    fn flexram_config() {
1089        /// Testing table of banks and expected configuration mask.
1090        #[allow(clippy::unusual_byte_groupings)] // Spacing delimits ITCM / DTCM / OCRAM banks.
1091        const TABLE: &[(FlexRamBanks, u32)] = &[
1092            (
1093                FlexRamBanks {
1094                    ocram: 16,
1095                    dtcm: 0,
1096                    itcm: 0,
1097                },
1098                0x55555555,
1099            ),
1100            (
1101                FlexRamBanks {
1102                    ocram: 0,
1103                    dtcm: 16,
1104                    itcm: 0,
1105                },
1106                0xAAAAAAAA,
1107            ),
1108            (
1109                FlexRamBanks {
1110                    ocram: 0,
1111                    dtcm: 0,
1112                    itcm: 16,
1113                },
1114                0xFFFFFFFF,
1115            ),
1116            (
1117                FlexRamBanks {
1118                    ocram: 0,
1119                    dtcm: 0,
1120                    itcm: 0,
1121                },
1122                0,
1123            ),
1124            (
1125                FlexRamBanks {
1126                    ocram: 1,
1127                    dtcm: 1,
1128                    itcm: 1,
1129                },
1130                0b11_10_01,
1131            ),
1132            (
1133                FlexRamBanks {
1134                    ocram: 3,
1135                    dtcm: 3,
1136                    itcm: 3,
1137                },
1138                0b111111_101010_010101,
1139            ),
1140            (
1141                FlexRamBanks {
1142                    ocram: 5,
1143                    dtcm: 5,
1144                    itcm: 5,
1145                },
1146                0b1111111111_1010101010_0101010101,
1147            ),
1148            (
1149                FlexRamBanks {
1150                    ocram: 1,
1151                    dtcm: 1,
1152                    itcm: 14,
1153                },
1154                0b1111111111111111111111111111_10_01,
1155            ),
1156            (
1157                FlexRamBanks {
1158                    ocram: 1,
1159                    dtcm: 14,
1160                    itcm: 1,
1161                },
1162                0b11_1010101010101010101010101010_01,
1163            ),
1164            (
1165                FlexRamBanks {
1166                    ocram: 14,
1167                    dtcm: 1,
1168                    itcm: 1,
1169                },
1170                0b11_10_0101010101010101010101010101,
1171            ),
1172        ];
1173
1174        for (banks, expected) in TABLE {
1175            let actual = banks.config(Family::Imxrt1010);
1176            assert!(
1177                actual == *expected,
1178                "\nActual:   {actual:#034b}\nExpected: {expected:#034b}\nBanks: {banks:?}"
1179            );
1180        }
1181    }
1182
1183    #[test]
1184    fn runtime_builder_default_from_flexspi() -> Result<(), Error> {
1185        for family in ALL_FAMILIES {
1186            RuntimeBuilder::from_flexspi(*family, 16 * 1024 * 1024)
1187                .write_linker_script(&mut io::sink())?;
1188        }
1189        Ok(())
1190    }
1191
1192    /// Strange but currently allowed.
1193    #[test]
1194    fn runtime_builder_from_flexspi_no_flash() -> Result<(), Error> {
1195        RuntimeBuilder::from_flexspi(Family::Imxrt1060, 0).write_linker_script(&mut io::sink())
1196    }
1197
1198    #[test]
1199    fn runtime_builder_too_many_flexram_banks() {
1200        let banks = FlexRamBanks {
1201            itcm: 32,
1202            dtcm: 32,
1203            ocram: 32,
1204        };
1205        for family in ALL_FAMILIES {
1206            let res = RuntimeBuilder::from_flexspi(*family, 16 * 1024)
1207                .flexram_banks(banks)
1208                .write_linker_script(&mut io::sink());
1209            assert!(res.is_err(), "{family:?}");
1210        }
1211    }
1212
1213    #[test]
1214    fn runtime_builder_invalid_flash_section() {
1215        type Placer = fn(&mut RuntimeBuilder) -> &mut RuntimeBuilder;
1216        macro_rules! placement {
1217            ($section:ident) => {
1218                (|bldr| bldr.$section(Memory::Flash), stringify!($section))
1219            };
1220        }
1221        let placements: &[(Placer, &'static str)] = &[
1222            placement!(data),
1223            placement!(vectors),
1224            placement!(bss),
1225            placement!(uninit),
1226            placement!(stack),
1227            placement!(heap),
1228        ];
1229
1230        for family in ALL_FAMILIES {
1231            for placement in placements {
1232                let mut bldr = RuntimeBuilder::from_flexspi(*family, 16 * 1024);
1233                placement.0(&mut bldr);
1234                let res = bldr.write_linker_script(&mut io::sink());
1235                assert!(res.is_err(), "{:?}, section: {}", family, placement.1);
1236            }
1237        }
1238    }
1239}