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