use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EmbeddedArch {
ArmCortexM0,
ArmCortexM0Plus,
ArmCortexM3,
ArmCortexM4,
ArmCortexM7,
ArmCortexM23,
ArmCortexM33,
ArmCortexM35P,
ArmCortexM55,
ArmCortexM85,
Riscv32Imac,
Riscv32ImacZicsr,
Riscv32Imc,
Riscv64Imac,
Avr,
AvrXmega,
Msp430,
Msp430x,
XtensaLx6,
XtensaLx7,
}
impl EmbeddedArch {
pub fn target_triple(&self) -> &'static str {
match self {
Self::ArmCortexM0 => "thumbv6m-none-eabi",
Self::ArmCortexM0Plus => "thumbv6m-none-eabi",
Self::ArmCortexM3 => "thumbv7m-none-eabi",
Self::ArmCortexM4 => "thumbv7em-none-eabihf",
Self::ArmCortexM7 => "thumbv7em-none-eabihf",
Self::ArmCortexM23 => "thumbv8m.base-none-eabi",
Self::ArmCortexM33 => "thumbv8m.main-none-eabihf",
Self::ArmCortexM35P => "thumbv8m.main-none-eabihf",
Self::ArmCortexM55 => "thumbv8.1m.main-none-eabihf",
Self::ArmCortexM85 => "thumbv8.1m.main-none-eabihf",
Self::Riscv32Imac => "riscv32-unknown-elf",
Self::Riscv32ImacZicsr => "riscv32-unknown-elf",
Self::Riscv32Imc => "riscv32-unknown-elf",
Self::Riscv64Imac => "riscv64-unknown-elf",
Self::Avr => "avr-unknown-unknown",
Self::AvrXmega => "avr-unknown-unknown",
Self::Msp430 => "msp430-none-elf",
Self::Msp430x => "msp430-none-elf",
Self::XtensaLx6 => "xtensa-esp32-none-elf",
Self::XtensaLx7 => "xtensa-esp32s2-none-elf",
}
}
pub fn cpu_flag(&self) -> &'static str {
match self {
Self::ArmCortexM0 => "cortex-m0",
Self::ArmCortexM0Plus => "cortex-m0plus",
Self::ArmCortexM3 => "cortex-m3",
Self::ArmCortexM4 => "cortex-m4",
Self::ArmCortexM7 => "cortex-m7",
Self::ArmCortexM23 => "cortex-m23",
Self::ArmCortexM33 => "cortex-m33",
Self::ArmCortexM35P => "cortex-m35p",
Self::ArmCortexM55 => "cortex-m55",
Self::ArmCortexM85 => "cortex-m85",
Self::Riscv32Imac => "generic-rv32",
Self::Riscv32ImacZicsr => "generic-rv32",
Self::Riscv32Imc => "generic-rv32",
Self::Riscv64Imac => "generic-rv64",
Self::Avr => "atmega328p",
Self::AvrXmega => "atxmega128a1",
Self::Msp430 => "msp430",
Self::Msp430x => "msp430x",
Self::XtensaLx6 => "esp32",
Self::XtensaLx7 => "esp32s2",
}
}
pub fn pointer_width(&self) -> u32 {
match self {
Self::ArmCortexM0
| Self::ArmCortexM0Plus
| Self::ArmCortexM3
| Self::ArmCortexM4
| Self::ArmCortexM7
| Self::ArmCortexM23
| Self::ArmCortexM33
| Self::ArmCortexM35P
| Self::ArmCortexM55
| Self::ArmCortexM85 => 32,
Self::Riscv32Imac | Self::Riscv32ImacZicsr | Self::Riscv32Imc => 32,
Self::Riscv64Imac => 64,
Self::Avr | Self::AvrXmega => 16,
Self::Msp430 | Self::Msp430x => 16,
Self::XtensaLx6 | Self::XtensaLx7 => 32,
}
}
pub fn family(&self) -> EmbeddedArchFamily {
match self {
Self::ArmCortexM0
| Self::ArmCortexM0Plus
| Self::ArmCortexM3
| Self::ArmCortexM4
| Self::ArmCortexM7
| Self::ArmCortexM23
| Self::ArmCortexM33
| Self::ArmCortexM35P
| Self::ArmCortexM55
| Self::ArmCortexM85 => EmbeddedArchFamily::Arm,
Self::Riscv32Imac
| Self::Riscv32ImacZicsr
| Self::Riscv32Imc
| Self::Riscv64Imac => EmbeddedArchFamily::RiscV,
Self::Avr | Self::AvrXmega => EmbeddedArchFamily::Avr,
Self::Msp430 | Self::Msp430x => EmbeddedArchFamily::Msp430,
Self::XtensaLx6 | Self::XtensaLx7 => EmbeddedArchFamily::Xtensa,
}
}
pub fn is_thumb_only(&self) -> bool {
matches!(self.family(), EmbeddedArchFamily::Arm)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EmbeddedArchFamily {
Arm,
RiscV,
Avr,
Msp430,
Xtensa,
}
pub struct EmbeddedTriple {
pub arch: String,
pub vendor: String,
pub os: String,
pub abi: String,
}
impl EmbeddedTriple {
pub fn parse(triple: &str) -> Option<Self> {
let parts: Vec<&str> = triple.split('-').collect();
if parts.len() < 3 {
return None;
}
let arch = parts[0].to_string();
let vendor = parts[1].to_string();
let os = parts[2].to_string();
let abi = if parts.len() > 3 {
parts[3..].join("-")
} else {
String::new()
};
Some(Self {
arch,
vendor,
os,
abi,
})
}
pub fn identify_arch(&self) -> Option<EmbeddedArch> {
match self.arch.as_str() {
"thumbv6m" => Some(EmbeddedArch::ArmCortexM0),
"thumbv7m" => Some(EmbeddedArch::ArmCortexM3),
"thumbv7em" => Some(EmbeddedArch::ArmCortexM4),
"thumbv8m.base" => Some(EmbeddedArch::ArmCortexM23),
"thumbv8m.main" => Some(EmbeddedArch::ArmCortexM33),
"thumbv8.1m.main" => Some(EmbeddedArch::ArmCortexM55),
"riscv32" => Some(EmbeddedArch::Riscv32Imac),
"riscv64" => Some(EmbeddedArch::Riscv64Imac),
"avr" => Some(EmbeddedArch::Avr),
"msp430" => Some(EmbeddedArch::Msp430),
"xtensa" => Some(EmbeddedArch::XtensaLx6),
_ => None,
}
}
pub fn is_bare_metal(&self) -> bool {
matches!(self.os.as_str(), "none" | "unknown" | "elf")
}
}
impl fmt::Display for EmbeddedTriple {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.abi.is_empty() {
write!(f, "{}-{}-{}", self.arch, self.vendor, self.os)
} else {
write!(f, "{}-{}-{}-{}", self.arch, self.vendor, self.os, self.abi)
}
}
}
pub struct BareMetalStartup {
pub arch: EmbeddedArch,
pub stack_top: u64,
pub heap_start: u64,
pub heap_size: u64,
pub bss_start: u64,
pub bss_size: u64,
pub data_load_addr: u64,
pub data_start: u64,
pub data_size: u64,
}
impl BareMetalStartup {
pub fn new(arch: EmbeddedArch) -> Self {
Self {
arch,
stack_top: 0x20010000,
heap_start: 0x20001000,
heap_size: 0x1000,
bss_start: 0x20000000,
bss_size: 0x1000,
data_load_addr: 0x08000100,
data_start: 0x20000000,
data_size: 0x100,
}
}
pub fn generate_cortex_m_startup(&self) -> String {
let mut asm = String::new();
asm.push_str("// ARM Cortex-M startup code\n");
asm.push_str(" .syntax unified\n");
asm.push_str(" .cpu cortex-m4\n");
asm.push_str(" .thumb\n\n");
asm.push_str(" .section .vector_table, \"a\"\n");
asm.push_str(" .global __vector_table\n");
asm.push_str("__vector_table:\n");
asm.push_str(&format!(
" .word {:#010x} // initial stack pointer\n",
self.stack_top
));
asm.push_str(" .word _start // reset handler\n");
for i in 1..16 {
asm.push_str(&format!(
" .word default_handler // exception {}\n",
i
));
}
for i in 16..48 {
asm.push_str(&format!(
" .word default_handler // IRQ {}\n",
i - 16
));
}
asm.push('\n');
asm.push_str(" .section .text._start, \"ax\"\n");
asm.push_str(" .global _start\n");
asm.push_str(" .type _start, %function\n");
asm.push_str("_start:\n");
asm.push_str(" // Initialize .data section\n");
asm.push_str(" ldr r0, =__data_load_addr\n");
asm.push_str(" ldr r1, =__data_start\n");
asm.push_str(" ldr r2, =__data_end\n");
asm.push_str(" cmp r1, r2\n");
asm.push_str(" beq 1f\n");
asm.push_str("0: ldr r3, [r0], #4\n");
asm.push_str(" str r3, [r1], #4\n");
asm.push_str(" cmp r1, r2\n");
asm.push_str(" bne 0b\n");
asm.push_str("1:\n");
asm.push_str(" // Zero .bss section\n");
asm.push_str(" ldr r1, =__bss_start\n");
asm.push_str(" ldr r2, =__bss_end\n");
asm.push_str(" movs r0, #0\n");
asm.push_str(" cmp r1, r2\n");
asm.push_str(" beq 3f\n");
asm.push_str("2: str r0, [r1], #4\n");
asm.push_str(" cmp r1, r2\n");
asm.push_str(" bne 2b\n");
asm.push_str("3:\n");
asm.push_str(" // Call main\n");
asm.push_str(" bl main\n");
asm.push_str(" // Infinite loop if main returns\n");
asm.push_str(" b .\n\n");
asm.push_str(" .global default_handler\n");
asm.push_str(" .type default_handler, %function\n");
asm.push_str("default_handler:\n");
asm.push_str(" b .\n");
asm
}
pub fn generate_riscv_startup(&self) -> String {
let mut asm = String::new();
asm.push_str("// RISC-V bare-metal startup\n");
asm.push_str(" .section .text.init, \"ax\"\n");
asm.push_str(" .global _start\n");
asm.push_str(" .align 4\n");
asm.push_str("_start:\n");
asm.push_str(&format!(
" la sp, {:#010x} // initialize stack pointer\n",
self.stack_top
));
asm.push_str(" // Initialize .data section\n");
asm.push_str(" la t0, __data_load_addr\n");
asm.push_str(" la t1, __data_start\n");
asm.push_str(" la t2, __data_end\n");
asm.push_str(" beq t1, t2, 2f\n");
asm.push_str("1: lw t3, 0(t0)\n");
asm.push_str(" sw t3, 0(t1)\n");
asm.push_str(" addi t0, t0, 4\n");
asm.push_str(" addi t1, t1, 4\n");
asm.push_str(" bne t1, t2, 1b\n");
asm.push_str("2:\n");
asm.push_str(" // Zero .bss section\n");
asm.push_str(" la t1, __bss_start\n");
asm.push_str(" la t2, __bss_end\n");
asm.push_str(" beq t1, t2, 4f\n");
asm.push_str("3: sw zero, 0(t1)\n");
asm.push_str(" addi t1, t1, 4\n");
asm.push_str(" bne t1, t2, 3b\n");
asm.push_str("4:\n");
asm.push_str(" // Jump to main\n");
asm.push_str(" call main\n");
asm.push_str(" // Spin if main returns\n");
asm.push_str(" j .\n\n");
asm.push_str(" .section .vector_table, \"a\"\n");
asm.push_str(" .align 6\n");
asm.push_str("__vector_table:\n");
asm.push_str(" j _start // reset\n");
for i in 1..16 {
asm.push_str(&format!(" j default_handler // exception {}\n", i));
}
asm.push_str("default_handler:\n");
asm.push_str(" j .\n");
asm
}
pub fn generate_avr_startup(&self) -> String {
let mut asm = String::new();
asm.push_str("// AVR startup code\n");
asm.push_str(" .section .vectors, \"ax\"\n");
asm.push_str(" .global __vectors\n");
asm.push_str("__vectors:\n");
asm.push_str(&format!(
" rjmp _start // reset vector\n"
));
for i in 1..26 {
asm.push_str(&format!(" rjmp __bad_interrupt // vector {}\n", i));
}
asm.push('\n');
asm.push_str(" .section .text._start, \"ax\"\n");
asm.push_str(" .global _start\n");
asm.push_str("_start:\n");
asm.push_str(" // Set stack pointer\n");
asm.push_str(" ldi r16, lo8(RAMEND)\n");
asm.push_str(" out 0x3d, r16\n");
asm.push_str(" ldi r16, hi8(RAMEND)\n");
asm.push_str(" out 0x3e, r16\n");
asm.push_str(" // Clear .bss\n");
asm.push_str(" ldi r30, lo8(__bss_start)\n");
asm.push_str(" ldi r31, hi8(__bss_start)\n");
asm.push_str(" ldi r26, lo8(__bss_end)\n");
asm.push_str(" ldi r27, hi8(__bss_end)\n");
asm.push_str(" rjmp 2f\n");
asm.push_str("1: st Z+, r1\n");
asm.push_str("2: cp r30, r26\n");
asm.push_str(" cpc r31, r27\n");
asm.push_str(" brlo 1b\n");
asm.push_str(" // Copy .data\n");
asm.push_str(" ldi r30, lo8(__data_load_addr)\n");
asm.push_str(" ldi r31, hi8(__data_load_addr)\n");
asm.push_str(" ldi r26, lo8(__data_start)\n");
asm.push_str(" ldi r27, hi8(__data_start)\n");
asm.push_str(" rjmp 4f\n");
asm.push_str("3: ld r0, Z+\n");
asm.push_str(" st X+, r0\n");
asm.push_str("4: ...\n"); asm.push_str(" // Call main\n");
asm.push_str(" rcall main\n");
asm.push_str(" rjmp .\n\n");
asm.push_str("__bad_interrupt:\n");
asm.push_str(" rjmp .\n");
asm
}
pub fn linker_symbols_asm(&self) -> String {
format!(
"// Linker symbols\n\
.global __data_load_addr\n\
.set __data_load_addr, {data_load}\n\
.global __data_start\n\
.set __data_start, {data_start}\n\
.global __data_end\n\
.set __data_end, {data_end}\n\
.global __bss_start\n\
.set __bss_start, {bss_start}\n\
.global __bss_end\n\
.set __bss_end, {bss_end}\n",
data_load = self.data_load_addr,
data_start = self.data_start,
data_end = self.data_start + self.data_size,
bss_start = self.bss_start,
bss_end = self.bss_start + self.bss_size,
)
}
}
impl Default for BareMetalStartup {
fn default() -> Self {
Self::new(EmbeddedArch::ArmCortexM4)
}
}
#[derive(Debug, Clone)]
pub struct MemoryRegion {
pub name: String,
pub origin: u64,
pub length: u64,
pub attributes: Vec<String>,
}
impl MemoryRegion {
pub fn new(name: &str, origin: u64, length: u64) -> Self {
Self {
name: name.to_string(),
origin,
length,
attributes: Vec::new(),
}
}
pub fn with_attribute(mut self, attr: &str) -> Self {
self.attributes.push(attr.to_string());
self
}
}
#[derive(Debug, Clone)]
pub struct SectionPlacement {
pub name: String,
pub region: String,
pub load_address: Option<u64>,
pub alignment: u32,
pub keep: bool,
}
impl SectionPlacement {
pub fn new(name: &str, region: &str) -> Self {
Self {
name: name.to_string(),
region: region.to_string(),
load_address: None,
alignment: 4,
keep: false,
}
}
pub fn with_alignment(mut self, align: u32) -> Self {
self.alignment = align;
self
}
pub fn with_load_address(mut self, addr: u64) -> Self {
self.load_address = Some(addr);
self
}
pub fn with_keep(mut self) -> Self {
self.keep = true;
self
}
}
pub struct LinkerScript {
pub arch: EmbeddedArch,
pub memory_regions: Vec<MemoryRegion>,
pub sections: Vec<SectionPlacement>,
pub entry_point: String,
pub stack_size: u64,
pub heap_size: u64,
}
impl LinkerScript {
pub fn new(arch: EmbeddedArch) -> Self {
let (memory_regions, sections) = default_memory_layout(arch);
Self {
arch,
memory_regions,
sections,
entry_point: "_start".to_string(),
stack_size: 0x2000,
heap_size: 0x8000,
}
}
pub fn generate(&self) -> String {
let mut script = String::new();
script.push_str("/* Auto-generated linker script */\n");
script.push_str(&format!("OUTPUT_FORMAT(\"{}\")\n", self.output_format()));
script.push_str(&format!("ENTRY({})\n\n", self.entry_point));
script.push_str("MEMORY\n{\n");
for region in &self.memory_regions {
let attrs = if region.attributes.is_empty() {
String::new()
} else {
format!(" ({})", region.attributes.join(" "))
};
script.push_str(&format!(
" {} (rwx){} : ORIGIN = {:#010x}, LENGTH = {:#x}\n",
region.name, attrs, region.origin, region.length
));
}
script.push_str("}\n\n");
script.push_str("SECTIONS\n{\n");
for section in &self.sections {
if section.keep {
script.push_str(&format!(" KEEP(*({}))\n", section.name));
} else {
script.push_str(&format!(
" .{} : ALIGN({})\n {{\n *(.{} .{}.*)\n }} >{}\n",
section.name.replace('.', '_'),
section.alignment,
section.name,
section.name,
section.region
));
}
}
script.push_str(" PROVIDE(__stack_top = ORIGIN(RAM) + LENGTH(RAM));\n");
script.push_str(" PROVIDE(__heap_start = __stack_top - ");
script.push_str(&format!("{});\n", self.heap_size));
script.push_str("}\n");
script
}
fn output_format(&self) -> &'static str {
match self.arch.family() {
EmbeddedArchFamily::Arm => "elf32-littlearm",
EmbeddedArchFamily::RiscV => "elf32-littleriscv",
EmbeddedArchFamily::Avr => "elf32-avr",
EmbeddedArchFamily::Msp430 => "elf32-msp430",
EmbeddedArchFamily::Xtensa => "elf32-xtensa-le",
}
}
pub fn add_memory_region(&mut self, region: MemoryRegion) {
self.memory_regions.push(region);
}
pub fn add_section(&mut self, section: SectionPlacement) {
self.sections.push(section);
}
pub fn with_stack_size(mut self, size: u64) -> Self {
self.stack_size = size;
self
}
pub fn with_heap_size(mut self, size: u64) -> Self {
self.heap_size = size;
self
}
pub fn get_region(&self, name: &str) -> Option<&MemoryRegion> {
self.memory_regions.iter().find(|r| r.name == name)
}
}
fn default_memory_layout(arch: EmbeddedArch) -> (Vec<MemoryRegion>, Vec<SectionPlacement>) {
match arch.family() {
EmbeddedArchFamily::Arm => (
vec![
MemoryRegion::new("FLASH", 0x08000000, 0x00100000),
MemoryRegion::new("RAM", 0x20000000, 0x00040000),
MemoryRegion::new("CCMRAM", 0x10000000, 0x00010000)
.with_attribute("w"),
],
vec![
SectionPlacement::new(".vector_table", "FLASH")
.with_alignment(512),
SectionPlacement::new(".text", "FLASH"),
SectionPlacement::new(".rodata", "FLASH"),
SectionPlacement::new(".data", "RAM")
.with_load_address(0x08000000 + 0x1000),
SectionPlacement::new(".bss", "RAM"),
SectionPlacement::new(".heap", "RAM"),
SectionPlacement::new(".stack", "RAM"),
],
),
EmbeddedArchFamily::RiscV => (
vec![
MemoryRegion::new("FLASH", 0x20000000, 0x00100000),
MemoryRegion::new("RAM", 0x80000000, 0x00010000),
],
vec![
SectionPlacement::new(".text", "FLASH"),
SectionPlacement::new(".rodata", "FLASH"),
SectionPlacement::new(".data", "RAM"),
SectionPlacement::new(".bss", "RAM"),
],
),
EmbeddedArchFamily::Avr => (
vec![
MemoryRegion::new("FLASH", 0x0000, 0x8000),
MemoryRegion::new("RAM", 0x0100, 0x0800),
MemoryRegion::new("EEPROM", 0x0000, 0x0400),
],
vec![
SectionPlacement::new(".vectors", "FLASH").with_alignment(256),
SectionPlacement::new(".text", "FLASH"),
SectionPlacement::new(".data", "RAM"),
SectionPlacement::new(".bss", "RAM"),
],
),
EmbeddedArchFamily::Msp430 => (
vec![
MemoryRegion::new("FRAM", 0xC000, 0x4000),
MemoryRegion::new("RAM", 0x1C00, 0x0800),
],
vec![
SectionPlacement::new(".vectors", "FRAM").with_alignment(512),
SectionPlacement::new(".text", "FRAM"),
SectionPlacement::new(".data", "RAM"),
SectionPlacement::new(".bss", "RAM"),
],
),
EmbeddedArchFamily::Xtensa => (
vec![
MemoryRegion::new("IROM", 0x400D0000, 0x00300000),
MemoryRegion::new("DROM", 0x3F400000, 0x00400000),
MemoryRegion::new("IRAM", 0x40080000, 0x00020000),
MemoryRegion::new("DRAM", 0x3FFB0000, 0x00050000),
],
vec![
SectionPlacement::new(".text", "IROM"),
SectionPlacement::new(".rodata", "DROM"),
SectionPlacement::new(".data", "DRAM"),
SectionPlacement::new(".bss", "DRAM"),
],
),
}
}
#[derive(Debug, Clone)]
pub struct InterruptVector {
pub number: u32,
pub name: String,
pub handler: String,
pub priority: u32,
pub enabled: bool,
}
impl InterruptVector {
pub fn new(number: u32, name: &str, handler: &str) -> Self {
Self {
number,
name: name.to_string(),
handler: handler.to_string(),
priority: 0,
enabled: true,
}
}
pub fn with_priority(mut self, prio: u32) -> Self {
self.priority = prio;
self
}
}
pub struct CmNvic {
pub vectors: Vec<InterruptVector>,
pub priority_grouping: u32,
pub systick_enabled: bool,
}
impl CmNvic {
pub fn new() -> Self {
Self {
vectors: Vec::new(),
priority_grouping: 0,
systick_enabled: true,
}
}
pub fn system_exceptions() -> Vec<InterruptVector> {
vec![
InterruptVector::new(0, "Reset", "_start"),
InterruptVector::new(1, "NMI", "nmi_handler").with_priority(0),
InterruptVector::new(2, "HardFault", "hard_fault_handler"),
InterruptVector::new(3, "MemManage", "mem_manage_handler"),
InterruptVector::new(4, "BusFault", "bus_fault_handler"),
InterruptVector::new(5, "UsageFault", "usage_fault_handler"),
InterruptVector::new(7, "SVCall", "svcall_handler"),
InterruptVector::new(8, "DebugMon", "debug_mon_handler"),
InterruptVector::new(10, "PendSV", "pend_sv_handler"),
InterruptVector::new(11, "SysTick", "sys_tick_handler"),
]
}
pub fn register_irq(&mut self, irq_num: u32, name: &str, handler: &str) {
self.vectors.push(InterruptVector::new(
16 + irq_num,
name,
handler,
));
}
pub fn generate_vector_table_asm(&self, stack_top: u64) -> String {
let mut asm = String::new();
asm.push_str(" .section .vector_table, \"a\"\n");
asm.push_str(" .global __vector_table\n");
asm.push_str("__vector_table:\n");
asm.push_str(&format!(" .word {:#010x}\n", stack_top));
for v in &self.vectors {
asm.push_str(&format!(
" .word {} // {}\n",
v.handler, v.name
));
}
let total = 16 + 240; for _ in self.vectors.len()..total {
asm.push_str(" .word default_handler\n");
}
asm
}
pub fn enable_irq(&mut self, irq_num: u32) {
if let Some(v) = self.vectors
.iter_mut()
.find(|v| v.number == 16 + irq_num)
{
v.enabled = true;
}
}
pub fn disable_irq(&mut self, irq_num: u32) {
if let Some(v) = self.vectors
.iter_mut()
.find(|v| v.number == 16 + irq_num)
{
v.enabled = false;
}
}
pub fn set_priority(&mut self, irq_num: u32, priority: u32) {
let clamped = priority.min(255);
if let Some(v) = self.vectors
.iter_mut()
.find(|v| v.number == 16 + irq_num)
{
v.priority = clamped;
}
}
}
impl Default for CmNvic {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum RiscVInterruptMode {
Direct,
Vectored,
Clinted,
}
#[derive(Debug, Clone)]
pub struct RiscVInterruptController {
pub mode: RiscVInterruptMode,
pub num_interrupts: u32,
pub mtime_addr: u64,
pub mtimecmp_addr: u64,
pub mtime_freq: u64,
}
impl RiscVInterruptController {
pub fn new_plic(num_interrupts: u32) -> Self {
Self {
mode: RiscVInterruptMode::Direct,
num_interrupts,
mtime_addr: 0x200BFF8,
mtimecmp_addr: 0x2004000,
mtime_freq: 10_000_000,
}
}
pub fn new_clic(num_interrupts: u32) -> Self {
Self {
mode: RiscVInterruptMode::Vectored,
num_interrupts,
mtime_addr: 0x200BFF8,
mtimecmp_addr: 0x2004000,
mtime_freq: 10_000_000,
}
}
pub fn generate_trap_handler_asm(&self) -> String {
let mut asm = String::new();
asm.push_str(" .section .text.trap, \"ax\"\n");
asm.push_str(" .align 4\n");
asm.push_str(" .global trap_entry\n");
asm.push_str("trap_entry:\n");
asm.push_str(" // Save context\n");
asm.push_str(" addi sp, sp, -128\n");
asm.push_str(" sw ra, 0(sp)\n");
asm.push_str(" sw t0, 4(sp)\n");
asm.push_str(" sw t1, 8(sp)\n");
asm.push_str(" sw a0, 12(sp)\n");
asm.push_str(" sw a1, 16(sp)\n");
asm.push_str(" // Read mcause\n");
asm.push_str(" csrr a0, mcause\n");
asm.push_str(" csrr a1, mepc\n");
asm.push_str(" // Call C handler\n");
asm.push_str(" call trap_handler\n");
asm.push_str(" // Restore context\n");
asm.push_str(" lw ra, 0(sp)\n");
asm.push_str(" lw t0, 4(sp)\n");
asm.push_str(" lw t1, 8(sp)\n");
asm.push_str(" lw a0, 12(sp)\n");
asm.push_str(" lw a1, 16(sp)\n");
asm.push_str(" addi sp, sp, 128\n");
asm.push_str(" mret\n");
asm
}
}
impl Default for RiscVInterruptController {
fn default() -> Self {
Self::new_plic(32)
}
}
pub struct AvrInterrupts {
pub vectors: Vec<InterruptVector>,
}
impl AvrInterrupts {
pub fn new() -> Self {
Self {
vectors: Vec::new(),
}
}
pub fn atmega328p_vectors() -> Vec<InterruptVector> {
vec![
InterruptVector::new(0, "RESET", "__vectors"),
InterruptVector::new(1, "INT0", "int0_handler"),
InterruptVector::new(2, "INT1", "int1_handler"),
InterruptVector::new(3, "PCINT0", "pcint0_handler"),
InterruptVector::new(4, "PCINT1", "pcint1_handler"),
InterruptVector::new(5, "PCINT2", "pcint2_handler"),
InterruptVector::new(6, "WDT", "wdt_handler"),
InterruptVector::new(7, "TIMER2_COMPA", "timer2_compa_handler"),
InterruptVector::new(8, "TIMER2_COMPB", "timer2_compb_handler"),
InterruptVector::new(9, "TIMER2_OVF", "timer2_ovf_handler"),
InterruptVector::new(10, "TIMER1_CAPT", "timer1_capt_handler"),
InterruptVector::new(11, "TIMER1_COMPA", "timer1_compa_handler"),
InterruptVector::new(12, "TIMER1_COMPB", "timer1_compb_handler"),
InterruptVector::new(13, "TIMER1_OVF", "timer1_ovf_handler"),
InterruptVector::new(14, "TIMER0_COMPA", "timer0_compa_handler"),
InterruptVector::new(15, "TIMER0_COMPB", "timer0_compb_handler"),
InterruptVector::new(16, "TIMER0_OVF", "timer0_ovf_handler"),
InterruptVector::new(17, "SPI_STC", "spi_stc_handler"),
InterruptVector::new(18, "USART_RX", "usart_rx_handler"),
InterruptVector::new(19, "USART_UDRE", "usart_udre_handler"),
InterruptVector::new(20, "USART_TX", "usart_tx_handler"),
InterruptVector::new(21, "ADC", "adc_handler"),
InterruptVector::new(22, "EE_READY", "ee_ready_handler"),
InterruptVector::new(23, "ANALOG_COMP", "analog_comp_handler"),
InterruptVector::new(24, "TWI", "twi_handler"),
InterruptVector::new(25, "SPM_READY", "spm_ready_handler"),
]
}
}
impl Default for AvrInterrupts {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MinimalLibc {
pub use_newlib_nano: bool,
pub syscalls_implemented: Vec<String>,
pub heap_impl: Option<String>,
pub stdout_device: Option<String>,
pub stderr_device: Option<String>,
pub stdin_device: Option<String>,
}
impl MinimalLibc {
pub fn new() -> Self {
Self {
use_newlib_nano: true,
syscalls_implemented: Vec::new(),
heap_impl: None,
stdout_device: Some("usart1".to_string()),
stderr_device: Some("usart1".to_string()),
stdin_device: Some("usart1".to_string()),
}
}
pub fn required_syscalls() -> Vec<&'static str> {
vec![
"_sbrk", "_write", "_read", "_close", "_fstat", "_isatty",
"_lseek", "_exit", "_kill", "_getpid", "_open",
]
}
pub fn generate_write_syscall(&self) -> String {
let mut c = String::new();
c.push_str("// Minimal _write syscall for semihosting/UART\n");
c.push_str("#include <sys/stat.h>\n");
c.push_str("#include <unistd.h>\n\n");
c.push_str("int _write(int fd, const char *buf, int len) {\n");
c.push_str(" if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {\n");
c.push_str(" for (int i = 0; i < len; i++) {\n");
c.push_str(" // Write to semihosting or UART\n");
c.push_str(" __asm__ volatile (\n");
c.push_str(" \"mov r0, #0x03\\n\" // SYS_WRITEC\n");
c.push_str(" \"mov r1, %[c]\\n\"\n");
c.push_str(" \"bkpt #0xAB\\n\"\n");
c.push_str(" : : [c] \"r\" (buf[i]) : \"r0\", \"r1\"\n");
c.push_str(" );\n");
c.push_str(" }\n");
c.push_str(" return len;\n");
c.push_str(" }\n");
c.push_str(" return -1;\n");
c.push_str("}\n");
c
}
pub fn generate_sbrk_syscall(&self, heap_start: u64, heap_size: u64) -> String {
format!(
"// _sbrk implementation for embedded heap\n\
#include <unistd.h>\n\n\
static char *heap_end = (char *){heap_start:#x};\n\
static char *heap_limit = (char *)({heap_start:#x} + {heap_size:#x});\n\n\
void *_sbrk(int incr) {{\n\
char *prev_heap_end = heap_end;\n\
if (heap_end + incr > heap_limit) {{\n\
return (void *)-1; // ENOMEM\n\
}}\n\
heap_end += incr;\n\
return prev_heap_end;\n\
}}\n",
heap_start = heap_start,
heap_size = heap_size,
)
}
pub fn generate_exit_syscall(&self) -> &'static str {
"void _exit(int status) {\n while (1) { __asm__ volatile (\"wfi\"); }\n}\n"
}
pub fn generate_newlib_startup(&self, arch: EmbeddedArch) -> String {
let startup = BareMetalStartup::new(arch);
match arch.family() {
EmbeddedArchFamily::Arm => startup.generate_cortex_m_startup(),
EmbeddedArchFamily::RiscV => startup.generate_riscv_startup(),
EmbeddedArchFamily::Avr => startup.generate_avr_startup(),
_ => String::from("// Unsupported arch for newlib startup\n"),
}
}
pub fn has_syscall(&self, name: &str) -> bool {
self.syscalls_implemented.iter().any(|s| s == name)
}
pub fn implement_syscall(&mut self, name: &str) {
if !self.has_syscall(name) {
self.syscalls_implemented.push(name.to_string());
}
}
}
impl Default for MinimalLibc {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SemihostingOp {
SysOpen = 0x01,
SysClose = 0x02,
SysWritec = 0x03,
SysWrite0 = 0x04,
SysWrite = 0x05,
SysRead = 0x06,
SysReadc = 0x07,
SysIsError = 0x08,
SysIsTty = 0x09,
SysSeek = 0x0A,
SysFlen = 0x0C,
SysTmpNam = 0x0D,
SysRemove = 0x0E,
SysRename = 0x0F,
SysClock = 0x10,
SysTime = 0x11,
SysSystem = 0x12,
SysErrno = 0x13,
SysGetCmdLine = 0x15,
SysHeapInfo = 0x16,
SysExit = 0x18,
SysExitExtended = 0x20,
SysElapsed = 0x30,
SysTickFreq = 0x31,
}
impl SemihostingOp {
pub fn as_u32(self) -> u32 {
self as u32
}
}
pub struct Semihosting {
pub enabled: bool,
pub target: SemihostingTarget,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SemihostingTarget {
ArmBkpt,
RiscVEbreak,
Qemu,
Custom(u32),
}
impl Semihosting {
pub fn new(target: SemihostingTarget) -> Self {
Self {
enabled: true,
target,
}
}
pub fn arm_semihosting_asm(&self, op: SemihostingOp, param: u32) -> String {
format!(
" mov r0, #{op}\n\
mov r1, #{param}\n\
bkpt #0xAB\n",
op = op.as_u32(),
param = param,
)
}
pub fn riscv_semihosting_asm(&self, op: SemihostingOp, param: u32) -> String {
format!(
" li a0, {op}\n\
li a1, {param}\n\
ebreak\n",
op = op.as_u32(),
param = param,
)
}
pub fn write_string_asm(&self, msg_addr: u64) -> String {
match self.target {
SemihostingTarget::ArmBkpt => {
format!(
" mov r0, #0x04\n\
ldr r1, ={msg:#x}\n\
bkpt #0xAB\n",
msg = msg_addr,
)
}
SemihostingTarget::RiscVEbreak => {
format!(
" li a0, 4\n\
li a1, {msg}\n\
ebreak\n",
msg = msg_addr,
)
}
_ => String::new(),
}
}
pub fn exit_asm(&self, status: u32) -> String {
match self.target {
SemihostingTarget::ArmBkpt => {
format!(
" mov r0, #0x18\n\
mov r1, #{status}\n\
bkpt #0xAB\n",
status = status,
)
}
SemihostingTarget::RiscVEbreak => {
format!(
" li a0, 0x18\n\
li a1, {status}\n\
ebreak\n",
status = status,
)
}
SemihostingTarget::Qemu => {
format!(
" // QEMU semihosting exit\n\
.insn 0x00100073 // ebreak with custom encoding\n",
)
}
_ => String::new(),
}
}
pub fn test_pass_asm(&self) -> String {
let msg = "PASS\\n\\0";
match self.target {
SemihostingTarget::ArmBkpt => {
format!(
" ldr r0, =msg\n\
mov r1, #0x04\n\
bkpt #0xAB\n\
msg:\n\
.asciz \"PASS\\n\"\n",
)
}
SemihostingTarget::RiscVEbreak => {
format!(
" la a0, msg\n\
ebreak\n\
msg:\n\
.asciz \"PASS\\n\"\n",
)
}
_ => String::from(" // semihosting test PASS\n"),
}
}
}
impl Default for Semihosting {
fn default() -> Self {
Self::new(SemihostingTarget::ArmBkpt)
}
}
#[derive(Debug, Clone)]
pub struct ItmStimulusPort {
pub port_number: u32,
pub enabled: bool,
pub privilege: ItmPrivilege,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ItmPrivilege {
Any,
Privileged,
}
pub struct ItmTrace {
pub enabled: bool,
pub stimulus_ports: Vec<ItmStimulusPort>,
pub trace_bus_id: u32,
pub global_timestamp_enabled: bool,
pub local_timestamp_enabled: bool,
pub sync_packets_enabled: bool,
pub swo_baud_rate: u32,
}
impl ItmTrace {
pub fn new() -> Self {
let mut ports = Vec::new();
for i in 0..32 {
ports.push(ItmStimulusPort {
port_number: i,
enabled: i < 8, privilege: ItmPrivilege::Any,
});
}
Self {
enabled: true,
stimulus_ports: ports,
trace_bus_id: 0,
global_timestamp_enabled: true,
local_timestamp_enabled: false,
sync_packets_enabled: true,
swo_baud_rate: 2_000_000,
}
}
pub fn enable_port(&mut self, port: u32) {
if let Some(p) = self.stimulus_ports.iter_mut().find(|p| p.port_number == port) {
p.enabled = true;
}
}
pub fn disable_port(&mut self, port: u32) {
if let Some(p) = self.stimulus_ports.iter_mut().find(|p| p.port_number == port) {
p.enabled = false;
}
}
pub fn write_byte(&self, port: u32, byte: u8) -> bool {
if let Some(p) = self.stimulus_ports.iter().find(|p| p.port_number == port) {
p.enabled
} else {
false
}
}
pub fn generate_init_code(&self) -> String {
let mut c = String::new();
c.push_str("// ITM trace initialization\n");
c.push_str("void itm_init(void) {\n");
c.push_str(" // Enable trace in Debug Exception and Monitor Control Register\n");
c.push_str(" *((volatile uint32_t *)0xE000EDFC) |= (1 << 24); // TRCENA\n\n");
c.push_str(" // Unlock ITM registers\n");
c.push_str(" *((volatile uint32_t *)0xE0000FB0) = 0xC5ACCE55;\n\n");
c.push_str(" // Configure trace control\n");
c.push_str(&format!(
" *((volatile uint32_t *)0xE0000E80) = 0x{:08X}; // ITM TCR\n",
self.trace_control_register()
));
c.push('\n');
for port in &self.stimulus_ports {
if port.enabled {
c.push_str(&format!(
" *((volatile uint32_t *)0xE0000E00) |= (1 << {}); // Enable port {}\n",
port.port_number, port.port_number
));
}
}
c.push_str("}\n");
c.push('\n');
c.push_str("void itm_send_char(char c) {\n");
c.push_str(" // Wait for stimulus port 0 to be ready\n");
c.push_str(" while (!(*((volatile uint32_t *)0xE0000000) & 1));\n");
c.push_str(" *((volatile uint32_t *)0xE0000000) = c;\n");
c.push_str("}\n");
c
}
fn trace_control_register(&self) -> u32 {
let mut tcr = 0u32;
if self.enabled {
tcr |= 1 << 0; }
if self.global_timestamp_enabled {
tcr |= 1 << 1; }
if self.sync_packets_enabled {
tcr |= 1 << 2; }
if self.local_timestamp_enabled {
tcr |= 1 << 3; }
tcr |= (self.trace_bus_id & 0x7F) << 16; tcr
}
pub fn set_swo_baud_rate(&mut self, baud: u32) {
self.swo_baud_rate = baud;
}
}
impl Default for ItmTrace {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MpuRegionAttributes {
pub read_enable: bool,
pub write_enable: bool,
pub execute_enable: bool,
pub cacheable: bool,
pub bufferable: bool,
pub shareable: bool,
pub tex: u8,
}
impl Default for MpuRegionAttributes {
fn default() -> Self {
Self {
read_enable: true,
write_enable: true,
execute_enable: false,
cacheable: false,
bufferable: false,
shareable: false,
tex: 0,
}
}
}
impl MpuRegionAttributes {
pub fn ram() -> Self {
Self {
read_enable: true,
write_enable: true,
execute_enable: false,
cacheable: true,
bufferable: true,
shareable: false,
tex: 1,
}
}
pub fn flash() -> Self {
Self {
read_enable: true,
write_enable: false,
execute_enable: true,
cacheable: true,
bufferable: false,
shareable: false,
tex: 0,
}
}
pub fn device() -> Self {
Self {
read_enable: true,
write_enable: true,
execute_enable: false,
cacheable: false,
bufferable: false,
shareable: true,
tex: 2,
}
}
pub fn encode(&self, size_log2: u8, subregion_disable: u8) -> u32 {
let mut rasr = 0u32;
rasr |= 1; rasr |= (size_log2 as u32 & 0x1F) << 1; rasr |= (subregion_disable as u32 & 0xFF) << 8; let mut ap = 0u32;
if self.read_enable && self.write_enable {
ap = 3; } else if self.read_enable {
ap = 2; }
rasr |= ap << 24; rasr |= (self.tex as u32 & 0x07) << 19; if self.cacheable {
rasr |= 1 << 17;
} if self.bufferable {
rasr |= 1 << 16;
} if self.shareable {
rasr |= 1 << 18;
} rasr
}
}
#[derive(Debug, Clone)]
pub struct MpuRegion {
pub region_number: u32,
pub base_address: u64,
pub size: u64,
pub subregion_disable: u8,
pub attributes: MpuRegionAttributes,
}
impl MpuRegion {
pub fn new(region_number: u32, base: u64, size: u64) -> Self {
Self {
region_number,
base_address: base,
size,
subregion_disable: 0,
attributes: MpuRegionAttributes::default(),
}
}
pub fn with_attributes(mut self, attrs: MpuRegionAttributes) -> Self {
self.attributes = attrs;
self
}
pub fn size_log2(&self) -> u8 {
let size = self.size.max(32);
(63 - size.leading_zeros()) as u8
}
}
pub struct MpuConfig {
pub enabled: bool,
pub hfnmiena: bool,
pub privdefena: bool,
pub regions: Vec<MpuRegion>,
pub max_regions: u32,
}
impl MpuConfig {
pub fn new(max_regions: u32) -> Self {
Self {
enabled: false,
hfnmiena: false,
privdefena: true,
regions: Vec::new(),
max_regions,
}
}
pub fn cortex_m4_default() -> Self {
let mut mpu = Self::new(8);
mpu.add_region(MpuRegion::new(0, 0x08000000, 0x00100000)
.with_attributes(MpuRegionAttributes::flash()));
mpu.add_region(MpuRegion::new(1, 0x20000000, 0x00040000)
.with_attributes(MpuRegionAttributes::ram()));
mpu.add_region(MpuRegion::new(2, 0x40000000, 0x20000000)
.with_attributes(MpuRegionAttributes::device()));
mpu
}
pub fn add_region(&mut self, region: MpuRegion) {
if (self.regions.len() as u32) < self.max_regions {
self.regions.push(region);
}
}
pub fn remove_region(&mut self, region_number: u32) {
self.regions.retain(|r| r.region_number != region_number);
}
pub fn generate_init_code(&self) -> String {
let mut c = String::new();
c.push_str("void mpu_init(void) {\n");
c.push_str(" // Disable MPU during configuration\n");
c.push_str(" *((volatile uint32_t *)0xE000ED94) &= ~1;\n\n");
for region in &self.regions {
c.push_str(&format!(
" // Region {}: base={:#010x}, size={:#x}\n",
region.region_number, region.base_address, region.size
));
c.push_str(&format!(
" *((volatile uint32_t *)0xE000ED9C) = {};\n",
region.region_number
));
c.push_str(&format!(
" *((volatile uint32_t *)0xE000EDA0) = {:#010x} | (1 << 4);\n",
region.base_address
));
c.push_str(&format!(
" *((volatile uint32_t *)0xE000EDA4) = {:#010x};\n",
region.attributes.encode(region.size_log2(), region.subregion_disable)
));
c.push('\n');
}
c.push_str(" // Enable MPU\n");
if self.privdefena {
c.push_str(" *((volatile uint32_t *)0xE000ED94) |= (1 << 0) | (1 << 2);\n");
} else {
c.push_str(" *((volatile uint32_t *)0xE000ED94) |= (1 << 0);\n");
}
c.push_str(" __asm__ volatile (\"dsb\\nisb\\n\");\n");
c.push_str("}\n");
c
}
}
impl Default for MpuConfig {
fn default() -> Self {
Self::cortex_m4_default()
}
}
pub const STACK_CANARY_VALUE: u32 = 0xDEADBEEF;
#[derive(Debug, Clone)]
pub enum StackProtection {
None,
Canary { value: u32 },
MpuGuard { region_size: u64 },
SplitStack { segment_size: u64 },
Combined { canary_value: u32, guard_size: u64 },
}
impl StackProtection {
pub fn canary() -> Self {
StackProtection::Canary {
value: STACK_CANARY_VALUE,
}
}
pub fn mpu_guard(size: u64) -> Self {
StackProtection::MpuGuard { region_size: size }
}
pub fn split_stack(segment_size: u64) -> Self {
StackProtection::SplitStack { segment_size }
}
pub fn combined(canary: u32, guard_size: u64) -> Self {
StackProtection::Combined {
canary_value: canary,
guard_size,
}
}
pub fn is_enabled(&self) -> bool {
!matches!(self, StackProtection::None)
}
pub fn generate_canary_init(&self) -> String {
match self {
StackProtection::Canary { value } | StackProtection::Combined { canary_value: value, .. } => {
format!(
"// Stack canary initialization\n\
extern uint32_t __stack_top;\n\
*((volatile uint32_t *)&__stack_top) = {:#010x};\n",
value
)
}
_ => String::new(),
}
}
pub fn generate_canary_check(&self) -> String {
match self {
StackProtection::Canary { value } | StackProtection::Combined { canary_value: value, .. } => {
format!(
"// Stack canary check\n\
extern uint32_t __stack_top;\n\
if (*((volatile uint32_t *)&__stack_top) != {:#010x}) {{\n\
// Stack overflow detected!\n\
stack_overflow_handler();\n\
}}\n",
value
)
}
_ => String::new(),
}
}
pub fn generate_mpu_guard_init(&self) -> String {
match self {
StackProtection::MpuGuard { region_size }
| StackProtection::Combined {
guard_size: region_size,
..
} => {
format!(
"// MPU guard region for stack overflow detection\n\
// Configure region at stack bottom as no-access\n\
*((volatile uint32_t *)0xE000ED9C) = 7; // Select region 7\n\
*((volatile uint32_t *)0xE000EDA0) = END_OF_STACK - {size:#x};\n\
*((volatile uint32_t *)0xE000EDA4) = 0x00000001 | (({size_log2}) << 1);\n",
size = region_size,
size_log2 = (63 - region_size.leading_zeros()),
)
}
_ => String::new(),
}
}
pub fn split_stack_attribute(&self) -> Option<String> {
match self {
StackProtection::SplitStack { .. } => Some("split-stack".to_string()),
_ => None,
}
}
}
impl Default for StackProtection {
fn default() -> Self {
StackProtection::canary()
}
}
#[derive(Debug, Clone)]
pub struct HeapConfig {
pub heap_start: u64,
pub heap_size: u64,
pub min_align: u32,
pub use_dlmalloc: bool,
pub use_tlsf: bool,
pub custom_impl: Option<String>,
}
impl HeapConfig {
pub fn new(start: u64, size: u64) -> Self {
Self {
heap_start: start,
heap_size: size,
min_align: 8,
use_dlmalloc: false,
use_tlsf: true,
custom_impl: None,
}
}
pub fn cortex_m4_default() -> Self {
Self::new(0x20001000, 0x10000)
}
pub fn riscv_default() -> Self {
Self::new(0x80001000, 0x20000)
}
pub fn avr_default() -> Self {
Self::new(0x0200, 0x0400)
}
pub fn generate_sbrk(&self) -> String {
format!(
"// sbrk() implementation for embedded system\n\
#include <sys/types.h>\n\
#include <errno.h>\n\n\
static uint8_t *__heap_ptr = (uint8_t *){start:#x};\n\
static uint8_t *__heap_limit = (uint8_t *)({start:#x} + {size:#x});\n\n\
void *_sbrk(int incr) {{\n\
uint8_t *prev_heap_ptr = __heap_ptr;\n\
if (__heap_ptr + incr > __heap_limit) {{\n\
errno = ENOMEM;\n\
return (void *)-1;\n\
}}\n\
__heap_ptr += incr;\n\
return (void *)prev_heap_ptr;\n\
}}\n",
start = self.heap_start,
size = self.heap_size,
)
}
pub fn generate_dlmalloc_config(&self) -> String {
format!(
"// dlmalloc configuration\n\
#define HAVE_MORECORE 0\n\
#define HAVE_MMAP 0\n\
#define MSPACES 1\n\
#define ONLY_MSPACES 1\n\
#define DEFAULT_GRANULARITY {align}\n\n\
static mspace __embedded_mspace;\n\n\
void embedded_malloc_init(void) {{\n\
__embedded_mspace = create_mspace_with_base(\n\
(void *){start:#x}, {size:#x}, 0);\n\
}}\n\n\
void *malloc(size_t sz) {{\n\
return mspace_malloc(__embedded_mspace, sz);\n\
}}\n\
void free(void *ptr) {{\n\
mspace_free(__embedded_mspace, ptr);\n\
}}\n",
start = self.heap_start,
size = self.heap_size,
align = self.min_align,
)
}
pub fn generate_tlsf_config(&self) -> String {
format!(
"// TLSF allocator configuration\n\
static unsigned char tlsf_pool[{size:#x}] __attribute__((aligned({align})));\n\
static tlsf_t tlsf_instance;\n\n\
void tlsf_heap_init(void) {{\n\
tlsf_instance = tlsf_create_with_pool(tlsf_pool, {size:#x});\n\
}}\n\n\
void *malloc(size_t size) {{\n\
return tlsf_malloc(tlsf_instance, size);\n\
}}\n\
void free(void *ptr) {{\n\
tlsf_free(tlsf_instance, ptr);\n\
}}\n",
size = self.heap_size,
align = self.min_align,
)
}
pub fn with_custom_impl(mut self, impl_code: &str) -> Self {
self.custom_impl = Some(impl_code.to_string());
self
}
pub fn linker_heap_symbols(&self) -> String {
format!(
" PROVIDE(__heap_start = {start:#010x});\n\
PROVIDE(__heap_end = {end:#010x});\n",
start = self.heap_start,
end = self.heap_start + self.heap_size,
)
}
}
impl Default for HeapConfig {
fn default() -> Self {
Self::cortex_m4_default()
}
}
#[derive(Debug, Clone)]
pub struct EmbeddedProjectConfig {
pub name: String,
pub arch: EmbeddedArch,
pub startup: BareMetalStartup,
pub linker: LinkerScript,
pub nvic: Option<CmNvic>,
pub riscv_intc: Option<RiscVInterruptController>,
pub semihosting: Option<Semihosting>,
pub itm: Option<ItmTrace>,
pub mpu: Option<MpuConfig>,
pub stack_protection: StackProtection,
pub heap: HeapConfig,
pub libc: MinimalLibc,
pub compiler_flags: Vec<String>,
pub linker_flags: Vec<String>,
}
impl EmbeddedProjectConfig {
pub fn new(name: &str, arch: EmbeddedArch) -> Self {
let mut flags = Vec::new();
flags.push("-ffreestanding".to_string());
flags.push("-nostdlib".to_string());
flags.push("-fno-exceptions".to_string());
flags.push("-fno-rtti".to_string());
flags.push(format!("-mcpu={}", arch.cpu_flag()));
flags.push(format!("--target={}", arch.target_triple()));
Self {
name: name.to_string(),
arch,
startup: BareMetalStartup::new(arch),
linker: LinkerScript::new(arch),
nvic: None,
riscv_intc: None,
semihosting: None,
itm: None,
mpu: None,
stack_protection: StackProtection::canary(),
heap: HeapConfig::default(),
libc: MinimalLibc::new(),
compiler_flags: flags.clone(),
linker_flags: vec![
"-nostartfiles".to_string(),
"-Wl,-T,linker.ld".to_string(),
format!("-Wl,-Map={}.map", name),
],
}
}
pub fn with_semihosting(mut self, target: SemihostingTarget) -> Self {
self.semihosting = Some(Semihosting::new(target));
self
}
pub fn with_itm(mut self) -> Self {
self.itm = Some(ItmTrace::new());
self
}
pub fn with_mpu(mut self, mpu: MpuConfig) -> Self {
self.mpu = Some(mpu);
self
}
pub fn with_stack_protection(mut self, sp: StackProtection) -> Self {
self.stack_protection = sp;
self
}
pub fn with_heap(mut self, heap: HeapConfig) -> Self {
self.heap = heap;
self
}
pub fn add_flag(&mut self, flag: &str) {
self.compiler_flags.push(flag.to_string());
}
pub fn generate_all(&self) -> EmbeddedProjectFiles {
let startup_asm = match self.arch.family() {
EmbeddedArchFamily::Arm => self.startup.generate_cortex_m_startup(),
EmbeddedArchFamily::RiscV => self.startup.generate_riscv_startup(),
EmbeddedArchFamily::Avr => self.startup.generate_avr_startup(),
_ => String::from("// unsupported arch for startup\n"),
};
let linker_ld = self.linker.generate();
let heap_c = self.heap.generate_sbrk();
let mpu_c = self.mpu.as_ref()
.map(|m| m.generate_init_code())
.unwrap_or_default();
let itm_c = self.itm.as_ref()
.map(|i| i.generate_init_code())
.unwrap_or_default();
EmbeddedProjectFiles {
startup_asm,
linker_script: linker_ld,
heap_sbrk: heap_c,
mpu_init: mpu_c,
itm_init: itm_c,
compiler_flags: self.compiler_flags.clone(),
}
}
}
impl Default for EmbeddedProjectConfig {
fn default() -> Self {
Self::new("default", EmbeddedArch::ArmCortexM4)
}
}
#[derive(Debug, Clone)]
pub struct EmbeddedProjectFiles {
pub startup_asm: String,
pub linker_script: String,
pub heap_sbrk: String,
pub mpu_init: String,
pub itm_init: String,
pub compiler_flags: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_embedded_arch_triples() {
assert_eq!(EmbeddedArch::ArmCortexM4.target_triple(), "thumbv7em-none-eabihf");
assert_eq!(EmbeddedArch::Riscv32Imac.target_triple(), "riscv32-unknown-elf");
assert_eq!(EmbeddedArch::Avr.target_triple(), "avr-unknown-unknown");
assert_eq!(EmbeddedArch::Msp430.target_triple(), "msp430-none-elf");
}
#[test]
fn test_embedded_arch_cpu_flags() {
assert_eq!(EmbeddedArch::ArmCortexM4.cpu_flag(), "cortex-m4");
assert_eq!(EmbeddedArch::ArmCortexM0.cpu_flag(), "cortex-m0");
assert_eq!(EmbeddedArch::ArmCortexM33.cpu_flag(), "cortex-m33");
}
#[test]
fn test_embedded_arch_pointer_width() {
assert_eq!(EmbeddedArch::ArmCortexM4.pointer_width(), 32);
assert_eq!(EmbeddedArch::Riscv64Imac.pointer_width(), 64);
assert_eq!(EmbeddedArch::Avr.pointer_width(), 16);
}
#[test]
fn test_embedded_arch_family() {
assert_eq!(EmbeddedArch::ArmCortexM4.family(), EmbeddedArchFamily::Arm);
assert_eq!(EmbeddedArch::Riscv32Imac.family(), EmbeddedArchFamily::RiscV);
assert_eq!(EmbeddedArch::Avr.family(), EmbeddedArchFamily::Avr);
assert_eq!(EmbeddedArch::XtensaLx6.family(), EmbeddedArchFamily::Xtensa);
}
#[test]
fn test_embedded_triple_parse() {
let t = EmbeddedTriple::parse("thumbv7em-none-eabihf").unwrap();
assert_eq!(t.arch, "thumbv7em");
assert_eq!(t.vendor, "none");
assert_eq!(t.os, "eabihf");
assert!(t.is_bare_metal());
}
#[test]
fn test_embedded_triple_identify_arch() {
let t = EmbeddedTriple::parse("thumbv7em-none-eabi").unwrap();
let arch = t.identify_arch().unwrap();
assert_eq!(arch, EmbeddedArch::ArmCortexM4);
}
#[test]
fn test_bare_metal_startup_cortex_m() {
let startup = BareMetalStartup::new(EmbeddedArch::ArmCortexM4);
let asm = startup.generate_cortex_m_startup();
assert!(asm.contains("_start"));
assert!(asm.contains("vector_table"));
assert!(asm.contains("bkpt"));
}
#[test]
fn test_bare_metal_startup_riscv() {
let startup = BareMetalStartup::new(EmbeddedArch::Riscv32Imac);
let asm = startup.generate_riscv_startup();
assert!(asm.contains("_start"));
assert!(asm.contains("vector_table"));
assert!(asm.contains("call main"));
}
#[test]
fn test_bare_metal_startup_avr() {
let startup = BareMetalStartup::new(EmbeddedArch::Avr);
let asm = startup.generate_avr_startup();
assert!(asm.contains("_start"));
assert!(asm.contains("__vectors"));
assert!(asm.contains("rcall main"));
}
#[test]
fn test_linker_script_arm() {
let ls = LinkerScript::new(EmbeddedArch::ArmCortexM4);
let script = ls.generate();
assert!(script.contains("MEMORY"));
assert!(script.contains("FLASH"));
assert!(script.contains("RAM"));
assert!(script.contains("SECTIONS"));
}
#[test]
fn test_linker_script_riscv() {
let ls = LinkerScript::new(EmbeddedArch::Riscv32Imac);
let script = ls.generate();
assert!(script.contains("MEMORY"));
assert!(script.contains("SECTIONS"));
}
#[test]
fn test_memory_region() {
let region = MemoryRegion::new("FLASH", 0x08000000, 0x100000)
.with_attribute("rx");
assert_eq!(region.name, "FLASH");
assert_eq!(region.origin, 0x08000000);
assert_eq!(region.attributes.len(), 1);
}
#[test]
fn test_section_placement() {
let section = SectionPlacement::new(".text", "FLASH")
.with_alignment(8)
.with_load_address(0x08001000);
assert_eq!(section.region, "FLASH");
assert_eq!(section.alignment, 8);
assert!(section.load_address.is_some());
}
#[test]
fn test_cm_nvic_system_exceptions() {
let exceptions = CmNvic::system_exceptions();
assert_eq!(exceptions.len(), 10);
assert_eq!(exceptions[0].name, "Reset");
assert_eq!(exceptions[2].name, "HardFault");
}
#[test]
fn test_cm_nvic_vector_table() {
let mut nvic = CmNvic::new();
nvic.register_irq(0, "USART1", "usart1_handler");
let asm = nvic.generate_vector_table_asm(0x20010000);
assert!(asm.contains("vector_table"));
assert!(asm.contains("0x20010000"));
assert!(asm.contains("usart1_handler"));
}
#[test]
fn test_cm_nvic_enable_disable() {
let mut nvic = CmNvic::new();
nvic.register_irq(5, "TIM2", "tim2_handler");
nvic.disable_irq(5);
if let Some(v) = nvic.vectors.iter().find(|v| v.number == 21) {
assert!(!v.enabled);
}
nvic.enable_irq(5);
if let Some(v) = nvic.vectors.iter().find(|v| v.number == 21) {
assert!(v.enabled);
}
}
#[test]
fn test_riscv_interrupt_controller() {
let ic = RiscVInterruptController::new_plic(64);
assert_eq!(ic.num_interrupts, 64);
let asm = ic.generate_trap_handler_asm();
assert!(asm.contains("trap_entry"));
assert!(asm.contains("mcause"));
}
#[test]
fn test_avr_interrupts() {
let vectors = AvrInterrupts::atmega328p_vectors();
assert_eq!(vectors.len(), 26);
assert_eq!(vectors[0].name, "RESET");
assert_eq!(vectors[19].name, "USART_TX");
}
#[test]
fn test_minimal_libc_required_syscalls() {
let syscalls = MinimalLibc::required_syscalls();
assert!(syscalls.contains(&"_sbrk"));
assert!(syscalls.contains(&"_write"));
assert!(syscalls.contains(&"_exit"));
}
#[test]
fn test_minimal_libc_generate_write() {
let libc = MinimalLibc::new();
let code = libc.generate_write_syscall();
assert!(code.contains("_write"));
assert!(code.contains("SYS_WRITEC"));
assert!(code.contains("bkpt #0xAB"));
}
#[test]
fn test_minimal_libc_generate_sbrk() {
let libc = MinimalLibc::new();
let code = libc.generate_sbrk_syscall(0x20001000, 0x10000);
assert!(code.contains("_sbrk"));
assert!(code.contains("0x20001000"));
}
#[test]
fn test_semihosting_arm_ops() {
let sh = Semihosting::new(SemihostingTarget::ArmBkpt);
let asm = sh.arm_semihosting_asm(SemihostingOp::SysWritec, b'A' as u32);
assert!(asm.contains("bkpt #0xAB"));
assert!(asm.contains("mov r0, #0x03"));
}
#[test]
fn test_semihosting_riscv_ops() {
let sh = Semihosting::new(SemihostingTarget::RiscVEbreak);
let asm = sh.riscv_semihosting_asm(SemihostingOp::SysExit, 0);
assert!(asm.contains("ebreak"));
assert!(asm.contains("li a0, 0x18"));
}
#[test]
fn test_semihosting_exit() {
let sh = Semihosting::new(SemihostingTarget::ArmBkpt);
let asm = sh.exit_asm(0);
assert!(asm.contains("SYS_EXIT"));
}
#[test]
fn test_itm_init_code() {
let itm = ItmTrace::new();
let code = itm.generate_init_code();
assert!(code.contains("itm_init"));
assert!(code.contains("itm_send_char"));
assert!(code.contains("TRCENA"));
}
#[test]
fn test_mpu_region_attributes_encode() {
let attrs = MpuRegionAttributes::ram();
let rasr = attrs.encode(10, 0);
assert!(rasr & 1 != 0); }
#[test]
fn test_mpu_config_init() {
let mpu = MpuConfig::cortex_m4_default();
assert_eq!(mpu.regions.len(), 3);
let code = mpu.generate_init_code();
assert!(code.contains("mpu_init"));
assert!(code.contains("dsb"));
}
#[test]
fn test_stack_canary_init() {
let sp = StackProtection::canary();
let code = sp.generate_canary_init();
assert!(code.contains("0xDEADBEEF"));
}
#[test]
fn test_stack_canary_check() {
let sp = StackProtection::canary();
let code = sp.generate_canary_check();
assert!(code.contains("stack_overflow_handler"));
}
#[test]
fn test_heap_config_sbrk() {
let heap = HeapConfig::new(0x20000000, 0x10000);
let code = heap.generate_sbrk();
assert!(code.contains("_sbrk"));
assert!(code.contains("ENOMEM"));
}
#[test]
fn test_embedded_project_config_new() {
let config = EmbeddedProjectConfig::new("test", EmbeddedArch::ArmCortexM4);
assert_eq!(config.name, "test");
assert!(config.compiler_flags.iter().any(|f| f.contains("freestanding")));
}
#[test]
fn test_embedded_project_generate_all() {
let config = EmbeddedProjectConfig::new("proj", EmbeddedArch::ArmCortexM4)
.with_semihosting(SemihostingTarget::ArmBkpt)
.with_itm()
.with_mpu(MpuConfig::cortex_m4_default());
let files = config.generate_all();
assert!(!files.startup_asm.is_empty());
assert!(!files.linker_script.is_empty());
assert!(!files.heap_sbrk.is_empty());
}
#[test]
fn test_stack_protection_split_stack() {
let sp = StackProtection::split_stack(4096);
assert!(sp.split_stack_attribute().is_some());
}
#[test]
fn test_heap_config_tlsf() {
let heap = HeapConfig::new(0x80000000, 0x20000);
let code = heap.generate_tlsf_config();
assert!(code.contains("tlsf_create_with_pool"));
}
#[test]
fn test_mpu_region_attributes_device() {
let attrs = MpuRegionAttributes::device();
assert!(attrs.read_enable);
assert!(attrs.write_enable);
assert!(!attrs.execute_enable);
assert!(attrs.shareable);
}
#[test]
fn test_embedded_arch_is_thumb_only() {
assert!(EmbeddedArch::ArmCortexM4.is_thumb_only());
assert!(!EmbeddedArch::Riscv32Imac.is_thumb_only());
assert!(!EmbeddedArch::Avr.is_thumb_only());
}
#[test]
fn test_linker_script_with_sizes() {
let ls = LinkerScript::new(EmbeddedArch::ArmCortexM4)
.with_stack_size(8192)
.with_heap_size(32768);
assert_eq!(ls.stack_size, 8192);
assert_eq!(ls.heap_size, 32768);
}
}