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}