#![warn(clippy::wildcard_enum_match_arm)]
use std::{
env,
fmt::Display,
fs::{self, File},
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::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1170 => FlexSpi::FlexSpi1,
}
}
fn start_address(self, family: Family) -> Option<u32> {
match (self, family) {
(
FlexSpi::FlexSpi1,
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1064,
) => Some(0x6000_0000),
(
FlexSpi::FlexSpi2,
Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050,
) => None,
(FlexSpi::FlexSpi2, Family::Imxrt1060 | Family::Imxrt1064) => Some(0x7000_0000),
(FlexSpi::FlexSpi1, Family::Imxrt1170) => Some(0x3000_0000),
(FlexSpi::FlexSpi2, Family::Imxrt1170) => Some(0x6000_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,
flexspi: FlexSpi,
}
#[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: usize,
heap: Memory,
heap_size: usize,
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: 8 * 1024,
heap: Memory::Dtcm,
heap_size: 0,
flash_opts: Some(FlashOpts {
size: flash_size,
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 = bytes;
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 = bytes;
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>> {
self.check_configurations()?;
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
println!("cargo:rustc-link-search={}", out_dir.display());
let mut memory_x = File::create(out_dir.join("imxrt-memory.x"))?;
if let Some(flash_opts) = &self.flash_opts {
write_flash_memory_map(&mut memory_x, self.family, flash_opts, &self.flexram_banks)?;
} else {
write_ram_memory_map(&mut memory_x, self.family, &self.flexram_banks)?;
}
#[cfg(feature = "device")]
writeln!(&mut memory_x, "INCLUDE device.x")?;
region_alias(&mut memory_x, "TEXT", self.text)?;
region_alias(&mut memory_x, "VTABLE", self.vectors)?;
region_alias(&mut memory_x, "RODATA", self.rodata)?;
region_alias(&mut memory_x, "DATA", self.data)?;
region_alias(&mut memory_x, "BSS", self.bss)?;
region_alias(&mut memory_x, "UNINIT", self.uninit)?;
region_alias(&mut memory_x, "STACK", self.stack)?;
region_alias(&mut memory_x, "HEAP", self.heap)?;
writeln!(&mut memory_x, "__stack_size = {:#010X};", self.stack_size)?;
writeln!(&mut memory_x, "__heap_size = {:#010X};", self.heap_size)?;
if self.flash_opts.is_some() {
region_alias(&mut memory_x, "LOAD_VTABLE", Memory::Flash)?;
region_alias(&mut memory_x, "LOAD_TEXT", Memory::Flash)?;
region_alias(&mut memory_x, "LOAD_RODATA", Memory::Flash)?;
region_alias(&mut memory_x, "LOAD_DATA", Memory::Flash)?;
} else {
region_alias(&mut memory_x, "LOAD_VTABLE", self.vectors)?;
region_alias(&mut memory_x, "LOAD_TEXT", self.text)?;
region_alias(&mut memory_x, "LOAD_RODATA", self.rodata)?;
region_alias(&mut memory_x, "LOAD_DATA", self.data)?;
}
writeln!(
&mut memory_x,
"__flexram_config = {:#010X};",
self.flexram_banks.config()
)?;
writeln!(&mut memory_x, "__imxrt_family = {};", self.family.id(),)?;
let link_x = include_bytes!("host/imxrt-link.x");
fs::write(out_dir.join(&self.linker_script_name), link_x)?;
let boot_header_x = include_bytes!("host/imxrt-boot-header.x");
fs::write(out_dir.join("imxrt-boot-header.x"), boot_header_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 '{}' cannot be placed in flash", name))
} 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 {
writeln!(
output,
"ITCM (RWX) : ORIGIN = 0x00000000, LENGTH = {:#X}",
flexram_banks.itcm * family.flexram_bank_size(),
)?;
}
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
.flexspi
.start_address(family)
.expect("Already checked"),
flash_opts.size
)?;
write_flexram_memories(output, family, flexram_banks)?;
writeln!(output, "}}")?;
writeln!(output, "__fcb_offset = {:#X};", family.fcb_offset())?;
writeln!(output, "INCLUDE imxrt-boot-header.x")?;
Ok(())
}
fn write_ram_memory_map(
output: &mut dyn Write,
family: Family,
flexram_banks: &FlexRamBanks,
) -> io::Result<()> {
writeln!(
output,
"/* Memory map for '{:?}' that executes from RAM. */",
family,
)?;
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,
Imxrt1050,
Imxrt1060,
Imxrt1064,
Imxrt1170,
}
impl Family {
const fn id(self) -> u32 {
match self {
Family::Imxrt1010 => 1010,
Family::Imxrt1015 => 1015,
Family::Imxrt1020 => 1020,
Family::Imxrt1050 => 1050,
Family::Imxrt1060 => 1060,
Family::Imxrt1064 => 1064,
Family::Imxrt1170 => 1170,
}
}
pub const fn flexram_bank_count(self) -> u32 {
match self {
Family::Imxrt1010 | Family::Imxrt1015 => 4,
Family::Imxrt1020 => 8,
Family::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => 16,
Family::Imxrt1170 => 16,
}
}
const fn flexram_bank_size(self) -> u32 {
32 * 1024
}
const fn bootrom_ocram_banks(self) -> u32 {
match self {
Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050 => 1,
Family::Imxrt1060 | Family::Imxrt1064 => 0,
Family::Imxrt1170 => 0,
}
}
fn fcb_offset(self) -> usize {
match self {
Family::Imxrt1010 | Family::Imxrt1170 => 0x400,
Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1064 => 0x000,
}
}
fn ocram_start(self) -> u32 {
match self {
Family::Imxrt1170 => 0x2024_0000,
Family::Imxrt1010
| Family::Imxrt1015
| Family::Imxrt1020
| Family::Imxrt1050
| Family::Imxrt1060
| Family::Imxrt1064 => 0x2020_0000,
}
}
const fn dedicated_ocram_size(self) -> u32 {
match self {
Family::Imxrt1010 | Family::Imxrt1015 | Family::Imxrt1020 | Family::Imxrt1050 => 0,
Family::Imxrt1060 | Family::Imxrt1064 => 512 * 1024,
Family::Imxrt1170 => (2 * 512 + 2 * 64 + 128) * 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::Imxrt1050 | Family::Imxrt1060 | Family::Imxrt1064 => FlexRamBanks {
ocram: 8,
itcm: 4,
dtcm: 4,
},
Family::Imxrt1170 => FlexRamBanks {
ocram: 0,
itcm: 8,
dtcm: 8,
},
}
}
}
#[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) -> 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)
}
}
#[cfg(test)]
mod tests {
use super::FlexRamBanks;
#[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();
assert!(
actual == *expected,
"\nActual: {actual:#034b}\nExpected: {expected:#034b}\nBanks: {banks:?}"
);
}
}
}