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 '{name}' cannot be placed in flash"))
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 '{family:?}' that executes from RAM. */",
775 )?;
776 writeln!(output, "MEMORY {{")?;
777 write_flexram_memories(output, family, flexram_banks)?;
778 writeln!(output, "}}")?;
779 Ok(())
780}
781
782/// i.MX RT chip family.
783///
784/// Chip families are designed by reference manuals and produce categories.
785/// Supply this to a [`RuntimeBuilder`] in order to check runtime configurations.
786#[non_exhaustive]
787#[derive(Debug, Clone, Copy, PartialEq, Eq)]
788pub enum Family {
789 Imxrt1010,
790 Imxrt1015,
791 Imxrt1020,
792 Imxrt1040,
793 Imxrt1050,
794 Imxrt1060,
795 Imxrt1064,
796 Imxrt1160,
797 Imxrt1170,
798 Imxrt1180,
799}
800
801impl Family {
802 /// Family identifier.
803 ///
804 /// These values may be stored in the image and observe by the runtime
805 /// initialzation routine. Make sure these numbers are kept in sync with
806 /// any hard-coded values.
807 const fn id(self) -> u32 {
808 match self {
809 Family::Imxrt1010 => 1010,
810 Family::Imxrt1015 => 1015,
811 Family::Imxrt1020 => 1020,
812 Family::Imxrt1040 => 1040,
813 Family::Imxrt1050 => 1050,
814 Family::Imxrt1060 => 1060,
815 Family::Imxrt1064 => 1064,
816 Family::Imxrt1160 => 1160,
817 Family::Imxrt1170 => 1170,
818 Family::Imxrt1180 => 1180,
819 }
820 }
821 /// How many FlexRAM banks are available?
822 pub const fn flexram_bank_count(self) -> u32 {
823 match self {
824 Family::Imxrt1010 | Family::Imxrt1015 => 4,
825 Family::Imxrt1020 => 8,
826 Family::Imxrt1040 | Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => 16,
827 // No ECC support; treating all banks as equal.
828 Family::Imxrt1160 | Family::Imxrt1170 => 16,
829 Family::Imxrt1180 => 2,
830 }
831 }
832 /// How large (bytes) is each FlexRAM bank?
833 const fn flexram_bank_size(self) -> u32 {
834 match self {
835 Family::Imxrt1010
836 | Family::Imxrt1015
837 | Family::Imxrt1020
838 | Family::Imxrt1040
839 | Family::Imxrt1050
840 | Family::Imxrt1060
841 | Family::Imxrt1064
842 | Family::Imxrt1160
843 | Family::Imxrt1170 => 32 * 1024,
844 Family::Imxrt1180 => 128 * 1024,
845 }
846 }
847 /// How many OCRAM banks does the boot ROM need?
848 const fn bootrom_ocram_banks(self) -> u32 {
849 match self {
850 Family::Imxrt1010
851 | Family::Imxrt1015
852 | Family::Imxrt1020
853 | Family::Imxrt1040
854 | Family::Imxrt1050 => 1,
855 // 9.5.1. memory maps point at OCRAM2.
856 Family::Imxrt1060 | Family::Imxrt1064 => 0,
857 // Boot ROM uses dedicated OCRAM1.
858 Family::Imxrt1160 | Family::Imxrt1170 | Family::Imxrt1180 => 0,
859 }
860 }
861 /// Where's the FlexSPI configuration bank located?
862 fn fcb_offset(self) -> usize {
863 match self {
864 Family::Imxrt1010 | Family::Imxrt1160 | Family::Imxrt1170 | Family::Imxrt1180 => 0x400,
865 Family::Imxrt1015
866 | Family::Imxrt1020
867 | Family::Imxrt1040
868 | Family::Imxrt1050
869 | Family::Imxrt1060
870 | Family::Imxrt1064 => 0x000,
871 }
872 }
873
874 /// Where does the OCRAM region begin?
875 ///
876 /// This includes dedicated any OCRAM regions, if any exist for the chip.
877 fn ocram_start(self) -> u32 {
878 match self {
879 // 256 KiB offset from the OCRAM M4 backdoor.
880 Family::Imxrt1170 => 0x2024_0000,
881 // Using the alias regions, assuming ECC is disabled.
882 // The two alias regions, plus the ECC region, provide
883 // the *contiguous* 256 KiB of dedicated OCRAM.
884 Family::Imxrt1160 => 0x2034_0000,
885 // Skip the first 16 KiB, "cannot be safely used by application images".
886 Family::Imxrt1180 => 0x2048_4000,
887 // Either starts the FlexRAM OCRAM banks, or the
888 // dedicated OCRAM regions (for supported devices).
889 Family::Imxrt1010
890 | Family::Imxrt1015
891 | Family::Imxrt1020
892 | Family::Imxrt1040
893 | Family::Imxrt1050
894 | Family::Imxrt1060
895 | Family::Imxrt1064 => 0x2020_0000,
896 }
897 }
898
899 /// What's the size, in bytes, of the dedicated OCRAM section?
900 ///
901 /// This isn't supported by all chips.
902 const fn dedicated_ocram_size(self) -> u32 {
903 match self {
904 Family::Imxrt1010
905 | Family::Imxrt1015
906 | Family::Imxrt1020
907 | Family::Imxrt1040
908 | Family::Imxrt1050 => 0,
909 Family::Imxrt1060 | Family::Imxrt1064 => 512 * 1024,
910 // - Two dedicated OCRAMs
911 // - One FlexRAM OCRAM EC region that's OCRAM when ECC is disabled.
912 Family::Imxrt1160 => (2 * 64 + 128) * 1024,
913 // - Two dedicated OCRAMs
914 // - Two dedicated OCRAM ECC regions that aren't used for ECC
915 // - One FlexRAM OCRAM ECC region that's strictly OCRAM, without ECC
916 Family::Imxrt1170 => (2 * 512 + 2 * 64 + 128) * 1024,
917 // OCRAM1 (512k), OCRAM2 (256k), 16k reserved as a ROM patch area
918 Family::Imxrt1180 => (512 + 256 - 16) * 1024,
919 }
920 }
921
922 /// Returns the default FlexRAM bank allocations for this chip.
923 ///
924 /// The default values represent the all-zero fuse values.
925 pub fn default_flexram_banks(self) -> FlexRamBanks {
926 match self {
927 Family::Imxrt1010 | Family::Imxrt1015 => FlexRamBanks {
928 ocram: 2,
929 itcm: 1,
930 dtcm: 1,
931 },
932 Family::Imxrt1020 => FlexRamBanks {
933 ocram: 4,
934 itcm: 2,
935 dtcm: 2,
936 },
937 Family::Imxrt1040 | Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => {
938 FlexRamBanks {
939 ocram: 8,
940 itcm: 4,
941 dtcm: 4,
942 }
943 }
944 Family::Imxrt1160 | Family::Imxrt1170 => FlexRamBanks {
945 ocram: 0,
946 itcm: 8,
947 dtcm: 8,
948 },
949 Family::Imxrt1180 => FlexRamBanks {
950 ocram: 0,
951 itcm: 1,
952 dtcm: 1,
953 },
954 }
955 }
956
957 /// Returns the starting address for the given `flexspi` instance.
958 ///
959 /// If a FlexSPI instance isn't available for this family, the return
960 /// is `None`. Otherwise, the return is the starting address in the
961 /// MCU's memory map.
962 ///
963 /// ```
964 /// use imxrt_rt::{Family::*, FlexSpi::*};
965 ///
966 /// assert_eq!(Imxrt1060.flexspi_start_addr(FlexSpi1), Some(0x6000_0000));
967 /// assert!(Imxrt1010.flexspi_start_addr(FlexSpi2).is_none());
968 /// ```
969 pub fn flexspi_start_addr(self, flexspi: FlexSpi) -> Option<u32> {
970 flexspi.start_address(self)
971 }
972}
973
974/// FlexRAM bank allocations.
975///
976/// Depending on your device, you may need a non-zero number of
977/// OCRAM banks to support the boot ROM. Consult your processor's
978/// reference manual for more information.
979///
980/// You should keep the sum of all banks below or equal to the
981/// total number of banks supported by your device. Unallocated memory
982/// banks are disabled.
983///
984/// Banks are typically 32KiB large.
985#[derive(Debug, Clone, Copy, PartialEq, Eq)]
986pub struct FlexRamBanks {
987 /// How many banks are allocated for OCRAM?
988 ///
989 /// This may need to be non-zero to support the boot ROM.
990 /// Consult your reference manual.
991 ///
992 /// Note: these are FlexRAM OCRAM banks. Do not include any banks
993 /// that would represent dedicated OCRAM; the runtime implementation
994 /// allocates those automatically. In fact, if your chip includes
995 /// dedicated OCRAM, you may set this to zero in order to maximize
996 /// DTCM and ITCM utilization.
997 pub ocram: u32,
998 /// How many banks are allocated for ITCM?
999 pub itcm: u32,
1000 /// How many banks are allocated for DTCM?
1001 pub dtcm: u32,
1002}
1003
1004impl FlexRamBanks {
1005 /// Total FlexRAM banks.
1006 const fn bank_count(&self) -> u32 {
1007 self.ocram + self.itcm + self.dtcm
1008 }
1009
1010 /// Produces the FlexRAM configuration.
1011 fn config(&self, family: Family) -> u32 {
1012 match family {
1013 Family::Imxrt1010
1014 | Family::Imxrt1015
1015 | Family::Imxrt1020
1016 | Family::Imxrt1040
1017 | Family::Imxrt1050
1018 | Family::Imxrt1060
1019 | Family::Imxrt1064
1020 | Family::Imxrt1160
1021 | Family::Imxrt1170 => self.config_gpr(),
1022 Family::Imxrt1180 => self.config_1180(),
1023 }
1024 }
1025
1026 /// Produces the FlexRAM configuration for families using GPR17.
1027 fn config_gpr(&self) -> u32 {
1028 assert!(
1029 self.bank_count() <= 16,
1030 "Something is wrong; this should have been checked earlier."
1031 );
1032
1033 // If a FlexRAM memory type could be allocated
1034 // to _all_ memory banks, these would represent
1035 // the configuration masks...
1036 const OCRAM: u32 = 0x5555_5555; // 0b01...
1037 const DTCM: u32 = 0xAAAA_AAAA; // 0b10...
1038 const ITCM: u32 = 0xFFFF_FFFF; // 0b11...
1039
1040 fn mask(bank_count: u32) -> u32 {
1041 1u32.checked_shl(bank_count * 2)
1042 .map(|bit| bit - 1)
1043 .unwrap_or(u32::MAX)
1044 }
1045
1046 let ocram_mask = mask(self.ocram);
1047 let dtcm_mask = mask(self.dtcm).checked_shl(self.ocram * 2).unwrap_or(0);
1048 let itcm_mask = mask(self.itcm)
1049 .checked_shl((self.ocram + self.dtcm) * 2)
1050 .unwrap_or(0);
1051
1052 (OCRAM & ocram_mask) | (DTCM & dtcm_mask) | (ITCM & itcm_mask)
1053 }
1054
1055 fn config_1180(&self) -> u32 {
1056 match (self.itcm, self.dtcm, self.ocram) {
1057 (1, 1, 0) => 0b00_u32,
1058 (2, 0, 0) => 0b10,
1059 (0, 2, 0) => 0b01,
1060 _ => panic!("Unsupported FlexRAM configuration"),
1061 }
1062 .checked_shl(2)
1063 .unwrap()
1064 }
1065}
1066
1067#[cfg(test)]
1068mod tests {
1069 use crate::Memory;
1070
1071 use super::{Family, FlexRamBanks, RuntimeBuilder};
1072 use std::{error, io};
1073
1074 const ALL_FAMILIES: &[Family] = &[
1075 Family::Imxrt1010,
1076 Family::Imxrt1015,
1077 Family::Imxrt1020,
1078 Family::Imxrt1040,
1079 Family::Imxrt1050,
1080 Family::Imxrt1060,
1081 Family::Imxrt1064,
1082 Family::Imxrt1170,
1083 ];
1084 type Error = Box<dyn error::Error>;
1085
1086 #[test]
1087 fn flexram_config() {
1088 /// Testing table of banks and expected configuration mask.
1089 #[allow(clippy::unusual_byte_groupings)] // Spacing delimits ITCM / DTCM / OCRAM banks.
1090 const TABLE: &[(FlexRamBanks, u32)] = &[
1091 (
1092 FlexRamBanks {
1093 ocram: 16,
1094 dtcm: 0,
1095 itcm: 0,
1096 },
1097 0x55555555,
1098 ),
1099 (
1100 FlexRamBanks {
1101 ocram: 0,
1102 dtcm: 16,
1103 itcm: 0,
1104 },
1105 0xAAAAAAAA,
1106 ),
1107 (
1108 FlexRamBanks {
1109 ocram: 0,
1110 dtcm: 0,
1111 itcm: 16,
1112 },
1113 0xFFFFFFFF,
1114 ),
1115 (
1116 FlexRamBanks {
1117 ocram: 0,
1118 dtcm: 0,
1119 itcm: 0,
1120 },
1121 0,
1122 ),
1123 (
1124 FlexRamBanks {
1125 ocram: 1,
1126 dtcm: 1,
1127 itcm: 1,
1128 },
1129 0b11_10_01,
1130 ),
1131 (
1132 FlexRamBanks {
1133 ocram: 3,
1134 dtcm: 3,
1135 itcm: 3,
1136 },
1137 0b111111_101010_010101,
1138 ),
1139 (
1140 FlexRamBanks {
1141 ocram: 5,
1142 dtcm: 5,
1143 itcm: 5,
1144 },
1145 0b1111111111_1010101010_0101010101,
1146 ),
1147 (
1148 FlexRamBanks {
1149 ocram: 1,
1150 dtcm: 1,
1151 itcm: 14,
1152 },
1153 0b1111111111111111111111111111_10_01,
1154 ),
1155 (
1156 FlexRamBanks {
1157 ocram: 1,
1158 dtcm: 14,
1159 itcm: 1,
1160 },
1161 0b11_1010101010101010101010101010_01,
1162 ),
1163 (
1164 FlexRamBanks {
1165 ocram: 14,
1166 dtcm: 1,
1167 itcm: 1,
1168 },
1169 0b11_10_0101010101010101010101010101,
1170 ),
1171 ];
1172
1173 for (banks, expected) in TABLE {
1174 let actual = banks.config(Family::Imxrt1010);
1175 assert!(
1176 actual == *expected,
1177 "\nActual: {actual:#034b}\nExpected: {expected:#034b}\nBanks: {banks:?}"
1178 );
1179 }
1180 }
1181
1182 #[test]
1183 fn runtime_builder_default_from_flexspi() -> Result<(), Error> {
1184 for family in ALL_FAMILIES {
1185 RuntimeBuilder::from_flexspi(*family, 16 * 1024 * 1024)
1186 .write_linker_script(&mut io::sink())?;
1187 }
1188 Ok(())
1189 }
1190
1191 /// Strange but currently allowed.
1192 #[test]
1193 fn runtime_builder_from_flexspi_no_flash() -> Result<(), Error> {
1194 RuntimeBuilder::from_flexspi(Family::Imxrt1060, 0).write_linker_script(&mut io::sink())
1195 }
1196
1197 #[test]
1198 fn runtime_builder_too_many_flexram_banks() {
1199 let banks = FlexRamBanks {
1200 itcm: 32,
1201 dtcm: 32,
1202 ocram: 32,
1203 };
1204 for family in ALL_FAMILIES {
1205 let res = RuntimeBuilder::from_flexspi(*family, 16 * 1024)
1206 .flexram_banks(banks)
1207 .write_linker_script(&mut io::sink());
1208 assert!(res.is_err(), "{family:?}");
1209 }
1210 }
1211
1212 #[test]
1213 fn runtime_builder_invalid_flash_section() {
1214 type Placer = fn(&mut RuntimeBuilder) -> &mut RuntimeBuilder;
1215 macro_rules! placement {
1216 ($section:ident) => {
1217 (|bldr| bldr.$section(Memory::Flash), stringify!($section))
1218 };
1219 }
1220 let placements: &[(Placer, &'static str)] = &[
1221 placement!(data),
1222 placement!(vectors),
1223 placement!(bss),
1224 placement!(uninit),
1225 placement!(stack),
1226 placement!(heap),
1227 ];
1228
1229 for family in ALL_FAMILIES {
1230 for placement in placements {
1231 let mut bldr = RuntimeBuilder::from_flexspi(*family, 16 * 1024);
1232 placement.0(&mut bldr);
1233 let res = bldr.write_linker_script(&mut io::sink());
1234 assert!(res.is_err(), "{:?}, section: {}", family, placement.1);
1235 }
1236 }
1237 }
1238}