Skip to main content

synth_backend/
mpu_allocator.rs

1//! MPU Region Allocator
2//!
3//! Allocates MPU regions for WebAssembly linear memories
4
5use crate::mpu::{MPUAttributes, MPUPermissions, MPURegion, MPUSize};
6use synth_core::{Error, HardwareCapabilities, Memory, Result};
7
8/// Request for MPU region allocation
9#[derive(Debug, Clone)]
10pub struct MPUAllocationRequest {
11    /// Memory to protect
12    pub memory: Memory,
13
14    /// Desired permissions
15    pub permissions: MPUPermissions,
16
17    /// Memory attributes
18    pub attributes: MPUAttributes,
19
20    /// Preferred base address (None = allocator chooses)
21    pub preferred_base: Option<u32>,
22}
23
24/// MPU Region Allocator
25pub struct MPUAllocator {
26    /// Hardware capabilities
27    hw_caps: HardwareCapabilities,
28
29    /// Allocated regions
30    allocated: Vec<MPURegion>,
31}
32
33impl MPUAllocator {
34    /// Create a new allocator
35    pub fn new(hw_caps: HardwareCapabilities) -> Self {
36        Self {
37            hw_caps,
38            allocated: Vec::new(),
39        }
40    }
41
42    /// Allocate MPU regions for a memory
43    pub fn allocate(&mut self, request: MPUAllocationRequest) -> Result<Vec<MPURegion>> {
44        // Calculate required size in bytes
45        let size_bytes = request.memory.initial as u64 * 65536; // Pages to bytes
46
47        // Check if we have available regions
48        if self.allocated.len() >= self.hw_caps.mpu_regions as usize {
49            return Err(Error::HardwareProtectionError(format!(
50                "No MPU regions available (max: {})",
51                self.hw_caps.mpu_regions
52            )));
53        }
54
55        // Calculate MPU size (must be power of 2)
56        let mpu_size = MPUSize::from_bytes(size_bytes)?;
57        let actual_size = mpu_size.bytes();
58
59        // Determine base address
60        let base_address = request.preferred_base.unwrap_or(0x20000000);
61
62        // Align base address to region size
63        let alignment = actual_size as u32;
64        let aligned_base = (base_address + alignment - 1) & !(alignment - 1);
65
66        // Create region
67        let region_number = self.allocated.len() as u8;
68        let mut region = MPURegion::new(region_number, aligned_base, mpu_size);
69        region.permissions = request.permissions;
70        region.attributes = request.attributes;
71
72        // Validate region
73        region.validate()?;
74
75        // Check for overlaps with existing regions
76        for existing in &self.allocated {
77            if self.regions_overlap(&region, existing) {
78                return Err(Error::HardwareProtectionError(format!(
79                    "Region overlap detected: 0x{:08X} overlaps with existing region at 0x{:08X}",
80                    region.base_address, existing.base_address
81                )));
82            }
83        }
84
85        // Store allocated region
86        self.allocated.push(region.clone());
87
88        Ok(vec![region])
89    }
90
91    /// Check if two regions overlap
92    fn regions_overlap(&self, r1: &MPURegion, r2: &MPURegion) -> bool {
93        let r1_start = r1.base_address as u64;
94        let r1_end = r1_start + r1.size.bytes();
95        let r2_start = r2.base_address as u64;
96        let r2_end = r2_start + r2.size.bytes();
97
98        // Check overlap
99        !(r1_end <= r2_start || r2_end <= r1_start)
100    }
101
102    /// Get all allocated regions
103    pub fn allocated_regions(&self) -> &[MPURegion] {
104        &self.allocated
105    }
106
107    /// Get number of available regions
108    pub fn available_regions(&self) -> u8 {
109        self.hw_caps.mpu_regions - self.allocated.len() as u8
110    }
111
112    /// Generate C initialization code for all regions
113    pub fn generate_init_code(&self) -> String {
114        let mut code = String::new();
115
116        code.push_str("/* MPU Initialization Code */\n");
117        code.push_str("/* Generated by Synth WebAssembly Component Synthesizer */\n\n");
118        code.push_str("#include <stdint.h>\n\n");
119        code.push_str("/* MPU Register Addresses (ARM Cortex-M) */\n");
120        code.push_str("#define MPU_TYPE  (*((volatile uint32_t*)0xE000ED90))\n");
121        code.push_str("#define MPU_CTRL  (*((volatile uint32_t*)0xE000ED94))\n");
122        code.push_str("#define MPU_RNR   (*((volatile uint32_t*)0xE000ED98))\n");
123        code.push_str("#define MPU_RBAR  (*((volatile uint32_t*)0xE000ED9C))\n");
124        code.push_str("#define MPU_RASR  (*((volatile uint32_t*)0xE000EDA0))\n\n");
125        code.push_str("/* MPU Control Register bits */\n");
126        code.push_str("#define MPU_CTRL_ENABLE        (1 << 0)\n");
127        code.push_str("#define MPU_CTRL_HFNMIENA      (1 << 1)\n");
128        code.push_str("#define MPU_CTRL_PRIVDEFENA    (1 << 2)\n\n");
129        code.push_str("void mpu_init(void) {\n");
130        code.push_str("    /* Disable MPU during configuration */\n");
131        code.push_str("    MPU_CTRL = 0;\n\n");
132
133        for region in &self.allocated {
134            code.push_str(&format!(
135                "    /* Region {}: 0x{:08X} - {} bytes */\n",
136                region.number,
137                region.base_address,
138                region.size.bytes()
139            ));
140            code.push_str(&format!("    MPU_RNR = {};\n", region.number));
141            code.push_str(&format!("    MPU_RBAR = 0x{:08X};\n", region.rbar()));
142            code.push_str(&format!("    MPU_RASR = 0x{:08X};\n\n", region.rasr()));
143        }
144
145        code.push_str("    /* Enable MPU with default memory map for privileged access */\n");
146        code.push_str("    MPU_CTRL = MPU_CTRL_ENABLE | MPU_CTRL_PRIVDEFENA;\n");
147        code.push_str("}\n");
148
149        code
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use synth_core::{CortexMVariant, TargetArch};
157
158    fn test_hardware() -> HardwareCapabilities {
159        HardwareCapabilities {
160            arch: TargetArch::ARMCortexM(CortexMVariant::M4F),
161            has_mpu: true,
162            mpu_regions: 8,
163            has_pmp: false,
164            pmp_entries: 0,
165            has_fpu: true,
166            fpu_precision: Some(synth_core::FPUPrecision::Single),
167            has_simd: false,
168            simd_level: None,
169            xip_capable: true,
170            flash_size: 1024 * 1024,
171            ram_size: 256 * 1024,
172        }
173    }
174
175    #[test]
176    fn test_allocate_single_region() {
177        let mut allocator = MPUAllocator::new(test_hardware());
178
179        let request = MPUAllocationRequest {
180            memory: Memory {
181                index: 0,
182                initial: 1, // 1 page = 64KB
183                maximum: None,
184                shared: false,
185                memory64: false,
186            },
187            permissions: MPUPermissions::FullRW,
188            attributes: MPUAttributes::normal(),
189            preferred_base: Some(0x20000000),
190        };
191
192        let regions = allocator.allocate(request).unwrap();
193        assert_eq!(regions.len(), 1);
194        assert_eq!(regions[0].size, MPUSize::Size64KB);
195    }
196
197    #[test]
198    fn test_available_regions() {
199        let mut allocator = MPUAllocator::new(test_hardware());
200        assert_eq!(allocator.available_regions(), 8);
201
202        let request = MPUAllocationRequest {
203            memory: Memory {
204                index: 0,
205                initial: 1,
206                maximum: None,
207                shared: false,
208                memory64: false,
209            },
210            permissions: MPUPermissions::FullRW,
211            attributes: MPUAttributes::normal(),
212            preferred_base: Some(0x20000000),
213        };
214
215        allocator.allocate(request).unwrap();
216        assert_eq!(allocator.available_regions(), 7);
217    }
218
219    #[test]
220    fn test_generate_init_code() {
221        let mut allocator = MPUAllocator::new(test_hardware());
222
223        let request = MPUAllocationRequest {
224            memory: Memory {
225                index: 0,
226                initial: 1,
227                maximum: None,
228                shared: false,
229                memory64: false,
230            },
231            permissions: MPUPermissions::FullRW,
232            attributes: MPUAttributes::normal(),
233            preferred_base: Some(0x20000000),
234        };
235
236        allocator.allocate(request).unwrap();
237        let code = allocator.generate_init_code();
238
239        assert!(code.contains("void mpu_init(void)"));
240        assert!(code.contains("MPU_CTRL"));
241        assert!(code.contains("Region 0"));
242    }
243
244    #[test]
245    fn test_nrf52840_configuration() {
246        // Use actual nRF52840 hardware capabilities
247        let hw_caps = HardwareCapabilities::nrf52840();
248        let mut allocator = MPUAllocator::new(hw_caps);
249
250        // Allocate regions for a realistic WebAssembly module layout
251
252        // Region 0: .text section (executable code in flash)
253        // Flash on nRF52840 starts at 0x00000000
254        let text_request = MPUAllocationRequest {
255            memory: Memory {
256                index: 0,
257                initial: 2, // 128KB of code
258                maximum: None,
259                shared: false,
260                memory64: false,
261            },
262            permissions: MPUPermissions::FullRO,
263            attributes: MPUAttributes {
264                shareable: false,
265                cacheable: true,
266                bufferable: false,
267                execute_never: false, // Code is executable
268            },
269            preferred_base: Some(0x00000000), // Flash base
270        };
271
272        let text_regions = allocator.allocate(text_request).unwrap();
273        assert_eq!(text_regions.len(), 1);
274        assert_eq!(text_regions[0].base_address, 0x00000000);
275        assert!(text_regions[0].size.bytes() >= 128 * 1024);
276
277        // Region 1: .rodata section (read-only data in flash)
278        let rodata_request = MPUAllocationRequest {
279            memory: Memory {
280                index: 1,
281                initial: 1, // 64KB of read-only data
282                maximum: None,
283                shared: false,
284                memory64: false,
285            },
286            permissions: MPUPermissions::FullRO,
287            attributes: MPUAttributes {
288                shareable: false,
289                cacheable: true,
290                bufferable: false,
291                execute_never: true, // Data is not executable
292            },
293            preferred_base: Some(0x00020000), // After .text
294        };
295
296        let rodata_regions = allocator.allocate(rodata_request).unwrap();
297        assert_eq!(rodata_regions.len(), 1);
298
299        // Region 2: .data/.bss section (read-write data in RAM)
300        // RAM on nRF52840 starts at 0x20000000
301        let data_request = MPUAllocationRequest {
302            memory: Memory {
303                index: 2,
304                initial: 1, // 64KB of RAM
305                maximum: None,
306                shared: false,
307                memory64: false,
308            },
309            permissions: MPUPermissions::FullRW,
310            attributes: MPUAttributes::normal(),
311            preferred_base: Some(0x20000000), // RAM base
312        };
313
314        let data_regions = allocator.allocate(data_request).unwrap();
315        assert_eq!(data_regions.len(), 1);
316        assert_eq!(data_regions[0].base_address, 0x20000000);
317        assert_eq!(data_regions[0].permissions, MPUPermissions::FullRW);
318
319        // Verify we've used 3 regions out of 8
320        assert_eq!(allocator.available_regions(), 5);
321        assert_eq!(allocator.allocated_regions().len(), 3);
322
323        // Generate C initialization code
324        let init_code = allocator.generate_init_code();
325
326        // Verify the generated code contains all regions
327        assert!(init_code.contains("Region 0"));
328        assert!(init_code.contains("Region 1"));
329        assert!(init_code.contains("Region 2"));
330        assert!(init_code.contains("0x00000000")); // Flash base
331        assert!(init_code.contains("0x20000000")); // RAM base
332
333        // Print the generated code for manual inspection
334        println!("\nGenerated MPU initialization code for nRF52840:");
335        println!("{}", init_code);
336
337        // Verify all regions are valid
338        for region in allocator.allocated_regions() {
339            assert!(region.validate().is_ok());
340        }
341    }
342
343    #[test]
344    fn test_imxrt1062_has_16_regions() {
345        // i.MX RT1062 (M7-class) has 16 MPU regions vs 8 on M4-class parts
346        let hw_caps = HardwareCapabilities::imxrt1062();
347        assert_eq!(hw_caps.mpu_regions, 16);
348
349        let allocator = MPUAllocator::new(hw_caps);
350        assert_eq!(allocator.available_regions(), 16);
351    }
352
353    #[test]
354    fn test_m7_can_allocate_more_than_8_regions() {
355        // Validate that the allocator actually uses all 16 regions on M7
356        let mut allocator = MPUAllocator::new(HardwareCapabilities::imxrt1062());
357
358        for i in 0u32..16 {
359            let request = MPUAllocationRequest {
360                memory: Memory {
361                    index: i,
362                    initial: 1,
363                    maximum: None,
364                    shared: false,
365                    memory64: false,
366                },
367                permissions: MPUPermissions::FullRW,
368                attributes: MPUAttributes::normal(),
369                preferred_base: Some(0x20000000 + i * 0x10000),
370            };
371            allocator.allocate(request).unwrap_or_else(|e| {
372                panic!("region {} allocation failed: {:?}", i, e);
373            });
374        }
375
376        assert_eq!(allocator.available_regions(), 0);
377        assert_eq!(allocator.allocated_regions().len(), 16);
378    }
379
380    #[test]
381    fn test_m4_class_caps_at_8_regions() {
382        // Negative — M4-class parts must reject the 9th region.
383        let mut allocator = MPUAllocator::new(HardwareCapabilities::nrf52840());
384
385        for i in 0u32..8 {
386            let request = MPUAllocationRequest {
387                memory: Memory {
388                    index: i,
389                    initial: 1,
390                    maximum: None,
391                    shared: false,
392                    memory64: false,
393                },
394                permissions: MPUPermissions::FullRW,
395                attributes: MPUAttributes::normal(),
396                preferred_base: Some(0x20000000 + i * 0x10000),
397            };
398            allocator.allocate(request).unwrap();
399        }
400
401        // 9th region must fail
402        let overflow = MPUAllocationRequest {
403            memory: Memory {
404                index: 8,
405                initial: 1,
406                maximum: None,
407                shared: false,
408                memory64: false,
409            },
410            permissions: MPUPermissions::FullRW,
411            attributes: MPUAttributes::normal(),
412            preferred_base: Some(0x20100000),
413        };
414        assert!(allocator.allocate(overflow).is_err());
415    }
416
417    #[test]
418    fn test_stm32h743_has_16_regions_and_double_fpu() {
419        let caps = HardwareCapabilities::stm32h743();
420        assert_eq!(caps.mpu_regions, 16);
421        assert!(caps.has_fpu);
422        assert_eq!(caps.fpu_precision, Some(synth_core::FPUPrecision::Double));
423    }
424}