#![warn(clippy::wildcard_enum_match_arm)]
use std::{
env,
fmt::Display,
fs,
io::{self, Write},
path::PathBuf,
};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Memory {
Flash,
Dtcm,
Itcm,
Ocram,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlexSpi {
FlexSpi1,
FlexSpi2,
}
impl FlexSpi {
fn family_default(family: Family) -> Self {
match family {
Family::Imxrt1064 => FlexSpi::FlexSpi2,
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1040
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1160
| Family::Imxrt1170
| Family::Imxrt1180 => FlexSpi::FlexSpi1,
}
}
fn start_address(self, family: Family) -> Option<u32> {
match (self, family) {
(
FlexSpi::FlexSpi1,
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1040
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1064,
) => Some(0x6000_0000),
(
FlexSpi::FlexSpi2,
Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050,
) => None,
(FlexSpi::FlexSpi2, Family::Imxrt1040 | Family::Imxrt1060 | Family::Imxrt1064) => {
Some(0x7000_0000)
}
(FlexSpi::FlexSpi1, Family::Imxrt1160) => Some(0x3000_0000),
(FlexSpi::FlexSpi2, Family::Imxrt1160) => Some(0x6000_0000),
(FlexSpi::FlexSpi1, Family::Imxrt1170) => Some(0x3000_0000),
(FlexSpi::FlexSpi2, Family::Imxrt1170) => Some(0x6000_0000),
(FlexSpi::FlexSpi1, Family::Imxrt1180) => Some(0x2800_0000),
(FlexSpi::FlexSpi2, Family::Imxrt1180) => Some(0x0400_0000),
}
}
fn supported_for_family(self, family: Family) -> bool {
self.start_address(family).is_some()
}
}
impl Display for Memory {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Flash => f.write_str("FLASH"),
Self::Itcm => f.write_str("ITCM"),
Self::Dtcm => f.write_str("DTCM"),
Self::Ocram => f.write_str("OCRAM"),
}
}
}
fn region_alias(output: &mut dyn Write, name: &str, placement: Memory) -> io::Result<()> {
writeln!(output, "REGION_ALIAS(\"REGION_{name}\", {placement});")
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct FlashOpts {
size: usize,
offset: u32,
flexspi: FlexSpi,
}
impl FlashOpts {
fn flash_origin(&self, family: Family) -> Option<u32> {
self.flexspi
.start_address(family)
.map(|start_address| start_address + self.offset)
}
fn is_boot_image(&self) -> bool {
self.offset == 0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct EnvOverride {
default: usize,
env: Option<String>,
}
impl EnvOverride {
const fn new(default: usize) -> Self {
Self { default, env: None }
}
fn set_env_key(&mut self, key: String) {
self.env = Some(key);
}
fn read(&self) -> Result<usize, Box<dyn std::error::Error>> {
if let Some(env) = &self.env {
println!("cargo:rerun-if-env-changed={env}");
}
if let Some(val) = self.env.as_ref().and_then(|key| env::var(key).ok()) {
let val = if val.ends_with('k') || val.ends_with('K') {
val[..val.len() - 1].parse::<usize>()? * 1024
} else {
val.parse::<usize>()?
};
Ok(val)
} else {
Ok(self.default)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RuntimeBuilder {
family: Family,
flexram_banks: FlexRamBanks,
text: Memory,
rodata: Memory,
data: Memory,
vectors: Memory,
bss: Memory,
uninit: Memory,
stack: Memory,
stack_size: EnvOverride,
heap: Memory,
heap_size: EnvOverride,
flash_opts: Option<FlashOpts>,
linker_script_name: String,
}
const DEFAULT_LINKER_SCRIPT_NAME: &str = "imxrt-link.x";
impl RuntimeBuilder {
pub fn from_flexspi(family: Family, flash_size: usize) -> Self {
Self {
family,
flexram_banks: family.default_flexram_banks(),
text: Memory::Itcm,
rodata: Memory::Ocram,
data: Memory::Ocram,
vectors: Memory::Dtcm,
bss: Memory::Ocram,
uninit: Memory::Ocram,
stack: Memory::Dtcm,
stack_size: EnvOverride::new(8 * 1024),
heap: Memory::Dtcm,
heap_size: EnvOverride::new(0),
flash_opts: Some(FlashOpts {
size: flash_size,
offset: 0,
flexspi: FlexSpi::family_default(family),
}),
linker_script_name: DEFAULT_LINKER_SCRIPT_NAME.into(),
}
}
pub fn in_flash(family: Family, partition_size: usize, partition_offset: u32) -> Self {
Self {
family,
flexram_banks: family.default_flexram_banks(),
text: Memory::Itcm,
rodata: Memory::Ocram,
data: Memory::Ocram,
vectors: Memory::Dtcm,
bss: Memory::Ocram,
uninit: Memory::Ocram,
stack: Memory::Dtcm,
stack_size: EnvOverride::new(8 * 1024),
heap: Memory::Dtcm,
heap_size: EnvOverride::new(0),
flash_opts: Some(FlashOpts {
size: partition_size,
offset: partition_offset,
flexspi: FlexSpi::family_default(family),
}),
linker_script_name: DEFAULT_LINKER_SCRIPT_NAME.into(),
}
}
pub fn flexram_banks(&mut self, flexram_banks: FlexRamBanks) -> &mut Self {
self.flexram_banks = flexram_banks;
self
}
pub fn text(&mut self, memory: Memory) -> &mut Self {
self.text = memory;
self
}
pub fn rodata(&mut self, memory: Memory) -> &mut Self {
self.rodata = memory;
self
}
pub fn data(&mut self, memory: Memory) -> &mut Self {
self.data = memory;
self
}
pub fn vectors(&mut self, memory: Memory) -> &mut Self {
self.vectors = memory;
self
}
pub fn bss(&mut self, memory: Memory) -> &mut Self {
self.bss = memory;
self
}
pub fn uninit(&mut self, memory: Memory) -> &mut Self {
self.uninit = memory;
self
}
pub fn stack(&mut self, memory: Memory) -> &mut Self {
self.stack = memory;
self
}
pub fn stack_size(&mut self, bytes: usize) -> &mut Self {
self.stack_size.default = bytes;
self
}
pub fn stack_size_env_override(&mut self, key: impl AsRef<str>) -> &mut Self {
self.stack_size.set_env_key(key.as_ref().into());
self
}
pub fn heap(&mut self, memory: Memory) -> &mut Self {
self.heap = memory;
self
}
pub fn heap_size(&mut self, bytes: usize) -> &mut Self {
self.heap_size.default = bytes;
self
}
pub fn heap_size_env_override(&mut self, key: impl AsRef<str>) -> &mut Self {
self.heap_size.set_env_key(key.as_ref().into());
self
}
pub fn flexspi(&mut self, peripheral: FlexSpi) -> &mut Self {
if let Some(flash_opts) = &mut self.flash_opts {
flash_opts.flexspi = peripheral;
}
self
}
pub fn linker_script_name(&mut self, name: &str) -> &mut Self {
self.linker_script_name = name.into();
self
}
pub fn build(&self) -> Result<(), Box<dyn std::error::Error>> {
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
println!("cargo:rustc-link-search={}", out_dir.display());
let mut in_memory = Vec::new();
self.write_linker_script(&mut in_memory)?;
fs::write(out_dir.join(&self.linker_script_name), &in_memory)?;
Ok(())
}
fn write_linker_script(
&self,
writer: &mut dyn Write,
) -> Result<(), Box<dyn std::error::Error>> {
self.check_configurations()?;
if let Some(flash_opts) = &self.flash_opts {
write_flash_memory_map(writer, self.family, flash_opts, &self.flexram_banks)?;
if flash_opts.is_boot_image() {
let boot_header_x = match self.family {
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1040
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1064
| Family::Imxrt1160
| Family::Imxrt1170 => include_bytes!("host/imxrt-boot-header.x").as_slice(),
Family::Imxrt1180 => include_bytes!("host/imxrt-boot-header-1180.x").as_slice(),
};
writer.write_all(boot_header_x)?;
}
} else {
write_ram_memory_map(writer, self.family, &self.flexram_banks)?;
}
#[cfg(feature = "device")]
writeln!(writer, "INCLUDE device.x")?;
region_alias(writer, "TEXT", self.text)?;
region_alias(writer, "VTABLE", self.vectors)?;
region_alias(writer, "RODATA", self.rodata)?;
region_alias(writer, "DATA", self.data)?;
region_alias(writer, "BSS", self.bss)?;
region_alias(writer, "UNINIT", self.uninit)?;
region_alias(writer, "STACK", self.stack)?;
region_alias(writer, "HEAP", self.heap)?;
writeln!(writer, "__stack_size = {:#010X};", self.stack_size.read()?)?;
writeln!(writer, "__heap_size = {:#010X};", self.heap_size.read()?)?;
if self.flash_opts.is_some() {
region_alias(writer, "LOAD_VTABLE", Memory::Flash)?;
region_alias(writer, "LOAD_TEXT", Memory::Flash)?;
region_alias(writer, "LOAD_RODATA", Memory::Flash)?;
region_alias(writer, "LOAD_DATA", Memory::Flash)?;
} else {
region_alias(writer, "LOAD_VTABLE", self.vectors)?;
region_alias(writer, "LOAD_TEXT", self.text)?;
region_alias(writer, "LOAD_RODATA", self.rodata)?;
region_alias(writer, "LOAD_DATA", self.data)?;
}
writeln!(
writer,
"__flexram_config = {:#010X};",
self.flexram_banks.config(self.family)
)?;
writeln!(writer, "__imxrt_family = {};", self.family.id(),)?;
let link_x = include_bytes!("host/imxrt-link.x");
writer.write_all(link_x)?;
Ok(())
}
fn check_configurations(&self) -> Result<(), String> {
if self.family.flexram_bank_count() < self.flexram_banks.bank_count() {
return Err(format!(
"Chip {:?} only has {} total FlexRAM banks. Cannot allocate {:?}, a total of {} banks",
self.family,
self.family.flexram_bank_count(),
self.flexram_banks,
self.flexram_banks.bank_count()
));
}
if self.flexram_banks.ocram < self.family.bootrom_ocram_banks() {
return Err(format!(
"Chip {:?} requires at least {} OCRAM banks for the bootloader ROM",
self.family,
self.family.bootrom_ocram_banks()
));
}
if let Some(flash_opts) = &self.flash_opts {
if !flash_opts.flexspi.supported_for_family(self.family) {
return Err(format!(
"Chip {:?} does not support {:?}",
self.family, flash_opts.flexspi
));
}
}
fn prevent_flash(name: &str, memory: Memory) -> Result<(), String> {
if memory == Memory::Flash {
Err(format!("Section '{name}' cannot be placed in flash"))
} else {
Ok(())
}
}
macro_rules! prevent_flash {
($sec:ident) => {
prevent_flash(stringify!($sec), self.$sec)
};
}
prevent_flash!(data)?;
prevent_flash!(vectors)?;
prevent_flash!(bss)?;
prevent_flash!(uninit)?;
prevent_flash!(stack)?;
prevent_flash!(heap)?;
Ok(())
}
}
fn write_flexram_memories(
output: &mut dyn Write,
family: Family,
flexram_banks: &FlexRamBanks,
) -> io::Result<()> {
if flexram_banks.itcm > 0 {
let itcm_size = flexram_banks.itcm * family.flexram_bank_size();
let itcm_start = match family {
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1040
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1064
| Family::Imxrt1160
| Family::Imxrt1170 => 0x00000000,
Family::Imxrt1180 => 0x10000000 - itcm_size,
};
writeln!(
output,
"ITCM (RWX) : ORIGIN = {itcm_start:#X}, LENGTH = {itcm_size:#X}"
)?;
}
if flexram_banks.dtcm > 0 {
writeln!(
output,
"DTCM (RWX) : ORIGIN = 0x20000000, LENGTH = {:#X}",
flexram_banks.dtcm * family.flexram_bank_size(),
)?;
}
let ocram_size =
flexram_banks.ocram * family.flexram_bank_size() + family.dedicated_ocram_size();
if ocram_size > 0 {
writeln!(
output,
"OCRAM (RWX) : ORIGIN = {:#X}, LENGTH = {:#X}",
family.ocram_start(),
ocram_size,
)?;
}
Ok(())
}
fn write_flash_memory_map(
output: &mut dyn Write,
family: Family,
flash_opts: &FlashOpts,
flexram_banks: &FlexRamBanks,
) -> io::Result<()> {
writeln!(
output,
"/* Memory map for '{:?}' with custom flash length {}. */",
family, flash_opts.size
)?;
writeln!(output, "MEMORY {{")?;
writeln!(
output,
"FLASH (RX) : ORIGIN = {:#X}, LENGTH = {:#X}",
flash_opts.flash_origin(family).expect("Already checked"),
flash_opts.size
)?;
write_flexram_memories(output, family, flexram_banks)?;
writeln!(output, "}}")?;
writeln!(output, "__fcb_offset = {:#X};", family.fcb_offset())?;
Ok(())
}
fn write_ram_memory_map(
output: &mut dyn Write,
family: Family,
flexram_banks: &FlexRamBanks,
) -> io::Result<()> {
writeln!(
output,
"/* Memory map for '{family:?}' that executes from RAM. */",
)?;
writeln!(output, "MEMORY {{")?;
write_flexram_memories(output, family, flexram_banks)?;
writeln!(output, "}}")?;
Ok(())
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Family {
Imxrt1010,
Imxrt1015,
Imxrt1020,
Imxrt1040,
Imxrt1050,
Imxrt1060,
Imxrt1064,
Imxrt1160,
Imxrt1170,
Imxrt1180,
}
impl Family {
const fn id(self) -> u32 {
match self {
Family::Imxrt1010 => 1010,
Family::Imxrt1015 => 1015,
Family::Imxrt1020 => 1020,
Family::Imxrt1040 => 1040,
Family::Imxrt1050 => 1050,
Family::Imxrt1060 => 1060,
Family::Imxrt1064 => 1064,
Family::Imxrt1160 => 1160,
Family::Imxrt1170 => 1170,
Family::Imxrt1180 => 1180,
}
}
pub const fn flexram_bank_count(self) -> u32 {
match self {
Family::Imxrt1010 | Family::Imxrt1015 => 4,
Family::Imxrt1020 => 8,
Family::Imxrt1040 | Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => 16,
Family::Imxrt1160 | Family::Imxrt1170 => 16,
Family::Imxrt1180 => 2,
}
}
const fn flexram_bank_size(self) -> u32 {
match self {
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1040
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1064
| Family::Imxrt1160
| Family::Imxrt1170 => 32 * 1024,
Family::Imxrt1180 => 128 * 1024,
}
}
const fn bootrom_ocram_banks(self) -> u32 {
match self {
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1040
| Family::Imxrt1050 => 1,
Family::Imxrt1060 | Family::Imxrt1064 => 0,
Family::Imxrt1160 | Family::Imxrt1170 | Family::Imxrt1180 => 0,
}
}
fn fcb_offset(self) -> usize {
match self {
Family::Imxrt1010 | Family::Imxrt1160 | Family::Imxrt1170 | Family::Imxrt1180 => 0x400,
Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1040
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1064 => 0x000,
}
}
fn ocram_start(self) -> u32 {
match self {
Family::Imxrt1170 => 0x2024_0000,
Family::Imxrt1160 => 0x2034_0000,
Family::Imxrt1180 => 0x2048_4000,
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1040
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1064 => 0x2020_0000,
}
}
const fn dedicated_ocram_size(self) -> u32 {
match self {
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1040
| Family::Imxrt1050 => 0,
Family::Imxrt1060 | Family::Imxrt1064 => 512 * 1024,
Family::Imxrt1160 => (2 * 64 + 128) * 1024,
Family::Imxrt1170 => (2 * 512 + 2 * 64 + 128) * 1024,
Family::Imxrt1180 => (512 + 256 - 16) * 1024,
}
}
pub fn default_flexram_banks(self) -> FlexRamBanks {
match self {
Family::Imxrt1010 | Family::Imxrt1015 => FlexRamBanks {
ocram: 2,
itcm: 1,
dtcm: 1,
},
Family::Imxrt1020 => FlexRamBanks {
ocram: 4,
itcm: 2,
dtcm: 2,
},
Family::Imxrt1040 | Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => {
FlexRamBanks {
ocram: 8,
itcm: 4,
dtcm: 4,
}
}
Family::Imxrt1160 | Family::Imxrt1170 => FlexRamBanks {
ocram: 0,
itcm: 8,
dtcm: 8,
},
Family::Imxrt1180 => FlexRamBanks {
ocram: 0,
itcm: 1,
dtcm: 1,
},
}
}
pub fn flexspi_start_addr(self, flexspi: FlexSpi) -> Option<u32> {
flexspi.start_address(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FlexRamBanks {
pub ocram: u32,
pub itcm: u32,
pub dtcm: u32,
}
impl FlexRamBanks {
const fn bank_count(&self) -> u32 {
self.ocram + self.itcm + self.dtcm
}
fn config(&self, family: Family) -> u32 {
match family {
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1040
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1064
| Family::Imxrt1160
| Family::Imxrt1170 => self.config_gpr(),
Family::Imxrt1180 => self.config_1180(),
}
}
fn config_gpr(&self) -> u32 {
assert!(
self.bank_count() <= 16,
"Something is wrong; this should have been checked earlier."
);
const OCRAM: u32 = 0x5555_5555; const DTCM: u32 = 0xAAAA_AAAA; const ITCM: u32 = 0xFFFF_FFFF;
fn mask(bank_count: u32) -> u32 {
1u32.checked_shl(bank_count * 2)
.map(|bit| bit - 1)
.unwrap_or(u32::MAX)
}
let ocram_mask = mask(self.ocram);
let dtcm_mask = mask(self.dtcm).checked_shl(self.ocram * 2).unwrap_or(0);
let itcm_mask = mask(self.itcm)
.checked_shl((self.ocram + self.dtcm) * 2)
.unwrap_or(0);
(OCRAM & ocram_mask) | (DTCM & dtcm_mask) | (ITCM & itcm_mask)
}
fn config_1180(&self) -> u32 {
match (self.itcm, self.dtcm, self.ocram) {
(1, 1, 0) => 0b00_u32,
(2, 0, 0) => 0b10,
(0, 2, 0) => 0b01,
_ => panic!("Unsupported FlexRAM configuration"),
}
.checked_shl(2)
.unwrap()
}
}
#[cfg(test)]
mod tests {
use crate::Memory;
use super::{Family, FlexRamBanks, RuntimeBuilder};
use std::{error, io};
const ALL_FAMILIES: &[Family] = &[
Family::Imxrt1010,
Family::Imxrt1015,
Family::Imxrt1020,
Family::Imxrt1040,
Family::Imxrt1050,
Family::Imxrt1060,
Family::Imxrt1064,
Family::Imxrt1170,
];
type Error = Box<dyn error::Error>;
#[test]
fn flexram_config() {
#[allow(clippy::unusual_byte_groupings)] const TABLE: &[(FlexRamBanks, u32)] = &[
(
FlexRamBanks {
ocram: 16,
dtcm: 0,
itcm: 0,
},
0x55555555,
),
(
FlexRamBanks {
ocram: 0,
dtcm: 16,
itcm: 0,
},
0xAAAAAAAA,
),
(
FlexRamBanks {
ocram: 0,
dtcm: 0,
itcm: 16,
},
0xFFFFFFFF,
),
(
FlexRamBanks {
ocram: 0,
dtcm: 0,
itcm: 0,
},
0,
),
(
FlexRamBanks {
ocram: 1,
dtcm: 1,
itcm: 1,
},
0b11_10_01,
),
(
FlexRamBanks {
ocram: 3,
dtcm: 3,
itcm: 3,
},
0b111111_101010_010101,
),
(
FlexRamBanks {
ocram: 5,
dtcm: 5,
itcm: 5,
},
0b1111111111_1010101010_0101010101,
),
(
FlexRamBanks {
ocram: 1,
dtcm: 1,
itcm: 14,
},
0b1111111111111111111111111111_10_01,
),
(
FlexRamBanks {
ocram: 1,
dtcm: 14,
itcm: 1,
},
0b11_1010101010101010101010101010_01,
),
(
FlexRamBanks {
ocram: 14,
dtcm: 1,
itcm: 1,
},
0b11_10_0101010101010101010101010101,
),
];
for (banks, expected) in TABLE {
let actual = banks.config(Family::Imxrt1010);
assert!(
actual == *expected,
"\nActual: {actual:#034b}\nExpected: {expected:#034b}\nBanks: {banks:?}"
);
}
}
#[test]
fn runtime_builder_default_from_flexspi() -> Result<(), Error> {
for family in ALL_FAMILIES {
RuntimeBuilder::from_flexspi(*family, 16 * 1024 * 1024)
.write_linker_script(&mut io::sink())?;
}
Ok(())
}
#[test]
fn runtime_builder_from_flexspi_no_flash() -> Result<(), Error> {
RuntimeBuilder::from_flexspi(Family::Imxrt1060, 0).write_linker_script(&mut io::sink())
}
#[test]
fn runtime_builder_too_many_flexram_banks() {
let banks = FlexRamBanks {
itcm: 32,
dtcm: 32,
ocram: 32,
};
for family in ALL_FAMILIES {
let res = RuntimeBuilder::from_flexspi(*family, 16 * 1024)
.flexram_banks(banks)
.write_linker_script(&mut io::sink());
assert!(res.is_err(), "{family:?}");
}
}
#[test]
fn runtime_builder_invalid_flash_section() {
type Placer = fn(&mut RuntimeBuilder) -> &mut RuntimeBuilder;
macro_rules! placement {
($section:ident) => {
(|bldr| bldr.$section(Memory::Flash), stringify!($section))
};
}
let placements: &[(Placer, &'static str)] = &[
placement!(data),
placement!(vectors),
placement!(bss),
placement!(uninit),
placement!(stack),
placement!(heap),
];
for family in ALL_FAMILIES {
for placement in placements {
let mut bldr = RuntimeBuilder::from_flexspi(*family, 16 * 1024);
placement.0(&mut bldr);
let res = bldr.write_linker_script(&mut io::sink());
assert!(res.is_err(), "{:?}, section: {}", family, placement.1);
}
}
}
}