use crate::mpu::{MPUAttributes, MPUPermissions, MPURegion, MPUSize};
use synth_core::{Error, HardwareCapabilities, Memory, Result};
#[derive(Debug, Clone)]
pub struct MPUAllocationRequest {
pub memory: Memory,
pub permissions: MPUPermissions,
pub attributes: MPUAttributes,
pub preferred_base: Option<u32>,
}
pub struct MPUAllocator {
hw_caps: HardwareCapabilities,
allocated: Vec<MPURegion>,
}
impl MPUAllocator {
pub fn new(hw_caps: HardwareCapabilities) -> Self {
Self {
hw_caps,
allocated: Vec::new(),
}
}
pub fn allocate(&mut self, request: MPUAllocationRequest) -> Result<Vec<MPURegion>> {
let size_bytes = request.memory.initial as u64 * 65536;
if self.allocated.len() >= self.hw_caps.mpu_regions as usize {
return Err(Error::HardwareProtectionError(format!(
"No MPU regions available (max: {})",
self.hw_caps.mpu_regions
)));
}
let mpu_size = MPUSize::from_bytes(size_bytes)?;
let actual_size = mpu_size.bytes();
let base_address = request.preferred_base.unwrap_or(0x20000000);
let alignment = actual_size as u32;
let aligned_base = (base_address + alignment - 1) & !(alignment - 1);
let region_number = self.allocated.len() as u8;
let mut region = MPURegion::new(region_number, aligned_base, mpu_size);
region.permissions = request.permissions;
region.attributes = request.attributes;
region.validate()?;
for existing in &self.allocated {
if self.regions_overlap(®ion, existing) {
return Err(Error::HardwareProtectionError(format!(
"Region overlap detected: 0x{:08X} overlaps with existing region at 0x{:08X}",
region.base_address, existing.base_address
)));
}
}
self.allocated.push(region.clone());
Ok(vec![region])
}
fn regions_overlap(&self, r1: &MPURegion, r2: &MPURegion) -> bool {
let r1_start = r1.base_address as u64;
let r1_end = r1_start + r1.size.bytes();
let r2_start = r2.base_address as u64;
let r2_end = r2_start + r2.size.bytes();
!(r1_end <= r2_start || r2_end <= r1_start)
}
pub fn allocated_regions(&self) -> &[MPURegion] {
&self.allocated
}
pub fn available_regions(&self) -> u8 {
self.hw_caps.mpu_regions - self.allocated.len() as u8
}
pub fn generate_init_code(&self) -> String {
let mut code = String::new();
code.push_str("/* MPU Initialization Code */\n");
code.push_str("/* Generated by Synth WebAssembly Component Synthesizer */\n\n");
code.push_str("#include <stdint.h>\n\n");
code.push_str("/* MPU Register Addresses (ARM Cortex-M) */\n");
code.push_str("#define MPU_TYPE (*((volatile uint32_t*)0xE000ED90))\n");
code.push_str("#define MPU_CTRL (*((volatile uint32_t*)0xE000ED94))\n");
code.push_str("#define MPU_RNR (*((volatile uint32_t*)0xE000ED98))\n");
code.push_str("#define MPU_RBAR (*((volatile uint32_t*)0xE000ED9C))\n");
code.push_str("#define MPU_RASR (*((volatile uint32_t*)0xE000EDA0))\n\n");
code.push_str("/* MPU Control Register bits */\n");
code.push_str("#define MPU_CTRL_ENABLE (1 << 0)\n");
code.push_str("#define MPU_CTRL_HFNMIENA (1 << 1)\n");
code.push_str("#define MPU_CTRL_PRIVDEFENA (1 << 2)\n\n");
code.push_str("void mpu_init(void) {\n");
code.push_str(" /* Disable MPU during configuration */\n");
code.push_str(" MPU_CTRL = 0;\n\n");
for region in &self.allocated {
code.push_str(&format!(
" /* Region {}: 0x{:08X} - {} bytes */\n",
region.number,
region.base_address,
region.size.bytes()
));
code.push_str(&format!(" MPU_RNR = {};\n", region.number));
code.push_str(&format!(" MPU_RBAR = 0x{:08X};\n", region.rbar()));
code.push_str(&format!(" MPU_RASR = 0x{:08X};\n\n", region.rasr()));
}
code.push_str(" /* Enable MPU with default memory map for privileged access */\n");
code.push_str(" MPU_CTRL = MPU_CTRL_ENABLE | MPU_CTRL_PRIVDEFENA;\n");
code.push_str("}\n");
code
}
}
#[cfg(test)]
mod tests {
use super::*;
use synth_core::{CortexMVariant, TargetArch};
fn test_hardware() -> HardwareCapabilities {
HardwareCapabilities {
arch: TargetArch::ARMCortexM(CortexMVariant::M4F),
has_mpu: true,
mpu_regions: 8,
has_pmp: false,
pmp_entries: 0,
has_fpu: true,
fpu_precision: Some(synth_core::FPUPrecision::Single),
has_simd: false,
simd_level: None,
xip_capable: true,
flash_size: 1024 * 1024,
ram_size: 256 * 1024,
}
}
#[test]
fn test_allocate_single_region() {
let mut allocator = MPUAllocator::new(test_hardware());
let request = MPUAllocationRequest {
memory: Memory {
index: 0,
initial: 1, maximum: None,
shared: false,
memory64: false,
},
permissions: MPUPermissions::FullRW,
attributes: MPUAttributes::normal(),
preferred_base: Some(0x20000000),
};
let regions = allocator.allocate(request).unwrap();
assert_eq!(regions.len(), 1);
assert_eq!(regions[0].size, MPUSize::Size64KB);
}
#[test]
fn test_available_regions() {
let mut allocator = MPUAllocator::new(test_hardware());
assert_eq!(allocator.available_regions(), 8);
let request = MPUAllocationRequest {
memory: Memory {
index: 0,
initial: 1,
maximum: None,
shared: false,
memory64: false,
},
permissions: MPUPermissions::FullRW,
attributes: MPUAttributes::normal(),
preferred_base: Some(0x20000000),
};
allocator.allocate(request).unwrap();
assert_eq!(allocator.available_regions(), 7);
}
#[test]
fn test_generate_init_code() {
let mut allocator = MPUAllocator::new(test_hardware());
let request = MPUAllocationRequest {
memory: Memory {
index: 0,
initial: 1,
maximum: None,
shared: false,
memory64: false,
},
permissions: MPUPermissions::FullRW,
attributes: MPUAttributes::normal(),
preferred_base: Some(0x20000000),
};
allocator.allocate(request).unwrap();
let code = allocator.generate_init_code();
assert!(code.contains("void mpu_init(void)"));
assert!(code.contains("MPU_CTRL"));
assert!(code.contains("Region 0"));
}
#[test]
fn test_nrf52840_configuration() {
let hw_caps = HardwareCapabilities::nrf52840();
let mut allocator = MPUAllocator::new(hw_caps);
let text_request = MPUAllocationRequest {
memory: Memory {
index: 0,
initial: 2, maximum: None,
shared: false,
memory64: false,
},
permissions: MPUPermissions::FullRO,
attributes: MPUAttributes {
shareable: false,
cacheable: true,
bufferable: false,
execute_never: false, },
preferred_base: Some(0x00000000), };
let text_regions = allocator.allocate(text_request).unwrap();
assert_eq!(text_regions.len(), 1);
assert_eq!(text_regions[0].base_address, 0x00000000);
assert!(text_regions[0].size.bytes() >= 128 * 1024);
let rodata_request = MPUAllocationRequest {
memory: Memory {
index: 1,
initial: 1, maximum: None,
shared: false,
memory64: false,
},
permissions: MPUPermissions::FullRO,
attributes: MPUAttributes {
shareable: false,
cacheable: true,
bufferable: false,
execute_never: true, },
preferred_base: Some(0x00020000), };
let rodata_regions = allocator.allocate(rodata_request).unwrap();
assert_eq!(rodata_regions.len(), 1);
let data_request = MPUAllocationRequest {
memory: Memory {
index: 2,
initial: 1, maximum: None,
shared: false,
memory64: false,
},
permissions: MPUPermissions::FullRW,
attributes: MPUAttributes::normal(),
preferred_base: Some(0x20000000), };
let data_regions = allocator.allocate(data_request).unwrap();
assert_eq!(data_regions.len(), 1);
assert_eq!(data_regions[0].base_address, 0x20000000);
assert_eq!(data_regions[0].permissions, MPUPermissions::FullRW);
assert_eq!(allocator.available_regions(), 5);
assert_eq!(allocator.allocated_regions().len(), 3);
let init_code = allocator.generate_init_code();
assert!(init_code.contains("Region 0"));
assert!(init_code.contains("Region 1"));
assert!(init_code.contains("Region 2"));
assert!(init_code.contains("0x00000000")); assert!(init_code.contains("0x20000000"));
println!("\nGenerated MPU initialization code for nRF52840:");
println!("{}", init_code);
for region in allocator.allocated_regions() {
assert!(region.validate().is_ok());
}
}
#[test]
fn test_imxrt1062_has_16_regions() {
let hw_caps = HardwareCapabilities::imxrt1062();
assert_eq!(hw_caps.mpu_regions, 16);
let allocator = MPUAllocator::new(hw_caps);
assert_eq!(allocator.available_regions(), 16);
}
#[test]
fn test_m7_can_allocate_more_than_8_regions() {
let mut allocator = MPUAllocator::new(HardwareCapabilities::imxrt1062());
for i in 0u32..16 {
let request = MPUAllocationRequest {
memory: Memory {
index: i,
initial: 1,
maximum: None,
shared: false,
memory64: false,
},
permissions: MPUPermissions::FullRW,
attributes: MPUAttributes::normal(),
preferred_base: Some(0x20000000 + i * 0x10000),
};
allocator.allocate(request).unwrap_or_else(|e| {
panic!("region {} allocation failed: {:?}", i, e);
});
}
assert_eq!(allocator.available_regions(), 0);
assert_eq!(allocator.allocated_regions().len(), 16);
}
#[test]
fn test_m4_class_caps_at_8_regions() {
let mut allocator = MPUAllocator::new(HardwareCapabilities::nrf52840());
for i in 0u32..8 {
let request = MPUAllocationRequest {
memory: Memory {
index: i,
initial: 1,
maximum: None,
shared: false,
memory64: false,
},
permissions: MPUPermissions::FullRW,
attributes: MPUAttributes::normal(),
preferred_base: Some(0x20000000 + i * 0x10000),
};
allocator.allocate(request).unwrap();
}
let overflow = MPUAllocationRequest {
memory: Memory {
index: 8,
initial: 1,
maximum: None,
shared: false,
memory64: false,
},
permissions: MPUPermissions::FullRW,
attributes: MPUAttributes::normal(),
preferred_base: Some(0x20100000),
};
assert!(allocator.allocate(overflow).is_err());
}
#[test]
fn test_stm32h743_has_16_regions_and_double_fpu() {
let caps = HardwareCapabilities::stm32h743();
assert_eq!(caps.mpu_regions, 16);
assert!(caps.has_fpu);
assert_eq!(caps.fpu_precision, Some(synth_core::FPUPrecision::Double));
}
}