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}