synth-backend 0.8.0

ARM encoder, ELF builder, vector table, linker scripts, and MPU configuration
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
//! MPU Region Allocator
//!
//! Allocates MPU regions for WebAssembly linear memories

use crate::mpu::{MPUAttributes, MPUPermissions, MPURegion, MPUSize};
use synth_core::{Error, HardwareCapabilities, Memory, Result};

/// Request for MPU region allocation
#[derive(Debug, Clone)]
pub struct MPUAllocationRequest {
    /// Memory to protect
    pub memory: Memory,

    /// Desired permissions
    pub permissions: MPUPermissions,

    /// Memory attributes
    pub attributes: MPUAttributes,

    /// Preferred base address (None = allocator chooses)
    pub preferred_base: Option<u32>,
}

/// MPU Region Allocator
pub struct MPUAllocator {
    /// Hardware capabilities
    hw_caps: HardwareCapabilities,

    /// Allocated regions
    allocated: Vec<MPURegion>,
}

impl MPUAllocator {
    /// Create a new allocator
    pub fn new(hw_caps: HardwareCapabilities) -> Self {
        Self {
            hw_caps,
            allocated: Vec::new(),
        }
    }

    /// Allocate MPU regions for a memory
    pub fn allocate(&mut self, request: MPUAllocationRequest) -> Result<Vec<MPURegion>> {
        // Calculate required size in bytes
        let size_bytes = request.memory.initial as u64 * 65536; // Pages to bytes

        // Check if we have available regions
        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
            )));
        }

        // Calculate MPU size (must be power of 2)
        let mpu_size = MPUSize::from_bytes(size_bytes)?;
        let actual_size = mpu_size.bytes();

        // Determine base address
        let base_address = request.preferred_base.unwrap_or(0x20000000);

        // Align base address to region size
        let alignment = actual_size as u32;
        let aligned_base = (base_address + alignment - 1) & !(alignment - 1);

        // Create region
        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;

        // Validate region
        region.validate()?;

        // Check for overlaps with existing regions
        for existing in &self.allocated {
            if self.regions_overlap(&region, existing) {
                return Err(Error::HardwareProtectionError(format!(
                    "Region overlap detected: 0x{:08X} overlaps with existing region at 0x{:08X}",
                    region.base_address, existing.base_address
                )));
            }
        }

        // Store allocated region
        self.allocated.push(region.clone());

        Ok(vec![region])
    }

    /// Check if two regions overlap
    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();

        // Check overlap
        !(r1_end <= r2_start || r2_end <= r1_start)
    }

    /// Get all allocated regions
    pub fn allocated_regions(&self) -> &[MPURegion] {
        &self.allocated
    }

    /// Get number of available regions
    pub fn available_regions(&self) -> u8 {
        self.hw_caps.mpu_regions - self.allocated.len() as u8
    }

    /// Generate C initialization code for all regions
    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, // 1 page = 64KB
                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() {
        // Use actual nRF52840 hardware capabilities
        let hw_caps = HardwareCapabilities::nrf52840();
        let mut allocator = MPUAllocator::new(hw_caps);

        // Allocate regions for a realistic WebAssembly module layout

        // Region 0: .text section (executable code in flash)
        // Flash on nRF52840 starts at 0x00000000
        let text_request = MPUAllocationRequest {
            memory: Memory {
                index: 0,
                initial: 2, // 128KB of code
                maximum: None,
                shared: false,
                memory64: false,
            },
            permissions: MPUPermissions::FullRO,
            attributes: MPUAttributes {
                shareable: false,
                cacheable: true,
                bufferable: false,
                execute_never: false, // Code is executable
            },
            preferred_base: Some(0x00000000), // Flash base
        };

        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);

        // Region 1: .rodata section (read-only data in flash)
        let rodata_request = MPUAllocationRequest {
            memory: Memory {
                index: 1,
                initial: 1, // 64KB of read-only data
                maximum: None,
                shared: false,
                memory64: false,
            },
            permissions: MPUPermissions::FullRO,
            attributes: MPUAttributes {
                shareable: false,
                cacheable: true,
                bufferable: false,
                execute_never: true, // Data is not executable
            },
            preferred_base: Some(0x00020000), // After .text
        };

        let rodata_regions = allocator.allocate(rodata_request).unwrap();
        assert_eq!(rodata_regions.len(), 1);

        // Region 2: .data/.bss section (read-write data in RAM)
        // RAM on nRF52840 starts at 0x20000000
        let data_request = MPUAllocationRequest {
            memory: Memory {
                index: 2,
                initial: 1, // 64KB of RAM
                maximum: None,
                shared: false,
                memory64: false,
            },
            permissions: MPUPermissions::FullRW,
            attributes: MPUAttributes::normal(),
            preferred_base: Some(0x20000000), // RAM base
        };

        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);

        // Verify we've used 3 regions out of 8
        assert_eq!(allocator.available_regions(), 5);
        assert_eq!(allocator.allocated_regions().len(), 3);

        // Generate C initialization code
        let init_code = allocator.generate_init_code();

        // Verify the generated code contains all regions
        assert!(init_code.contains("Region 0"));
        assert!(init_code.contains("Region 1"));
        assert!(init_code.contains("Region 2"));
        assert!(init_code.contains("0x00000000")); // Flash base
        assert!(init_code.contains("0x20000000")); // RAM base

        // Print the generated code for manual inspection
        println!("\nGenerated MPU initialization code for nRF52840:");
        println!("{}", init_code);

        // Verify all regions are valid
        for region in allocator.allocated_regions() {
            assert!(region.validate().is_ok());
        }
    }

    #[test]
    fn test_imxrt1062_has_16_regions() {
        // i.MX RT1062 (M7-class) has 16 MPU regions vs 8 on M4-class parts
        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() {
        // Validate that the allocator actually uses all 16 regions on M7
        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() {
        // Negative — M4-class parts must reject the 9th region.
        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();
        }

        // 9th region must fail
        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));
    }
}