Skip to main content

synth_backend/
mpu.rs

1//! ARM Cortex-M Memory Protection Unit (MPU) Support
2
3use synth_core::{Error, Result};
4
5/// MPU Region Size
6///
7/// ARM Cortex-M MPU requires power-of-2 sized regions
8/// Values represent the power of 2 (e.g., Size32B = 5 means 2^5 = 32 bytes)
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum MPUSize {
11    /// 32 bytes (2^5)
12    Size32B = 5,
13    /// 64 bytes (2^6)
14    Size64B = 6,
15    /// 128 bytes (2^7)
16    Size128B = 7,
17    /// 256 bytes (2^8)
18    Size256B = 8,
19    /// 512 bytes (2^9)
20    Size512B = 9,
21    /// 1 KB (2^10)
22    Size1KB = 10,
23    /// 2 KB (2^11)
24    Size2KB = 11,
25    /// 4 KB (2^12)
26    Size4KB = 12,
27    /// 8 KB (2^13)
28    Size8KB = 13,
29    /// 16 KB (2^14)
30    Size16KB = 14,
31    /// 32 KB (2^15)
32    Size32KB = 15,
33    /// 64 KB (2^16)
34    Size64KB = 16,
35    /// 128 KB (2^17)
36    Size128KB = 17,
37    /// 256 KB (2^18)
38    Size256KB = 18,
39    /// 512 KB (2^19)
40    Size512KB = 19,
41    /// 1 MB (2^20)
42    Size1MB = 20,
43    /// 2 MB (2^21)
44    Size2MB = 21,
45    /// 4 MB (2^22)
46    Size4MB = 22,
47    /// 8 MB (2^23)
48    Size8MB = 23,
49    /// 16 MB (2^24)
50    Size16MB = 24,
51    /// 32 MB (2^25)
52    Size32MB = 25,
53    /// 64 MB (2^26)
54    Size64MB = 26,
55    /// 128 MB (2^27)
56    Size128MB = 27,
57    /// 256 MB (2^28)
58    Size256MB = 28,
59    /// 512 MB (2^29)
60    Size512MB = 29,
61    /// 1 GB (2^30)
62    Size1GB = 30,
63    /// 2 GB (2^31)
64    Size2GB = 31,
65    /// 4 GB (2^32)
66    Size4GB = 32,
67}
68
69impl MPUSize {
70    /// Get size in bytes
71    pub fn bytes(&self) -> u64 {
72        1u64 << (*self as u8)
73    }
74
75    /// Create from byte size (rounds up to next power of 2)
76    pub fn from_bytes(bytes: u64) -> Result<Self> {
77        if bytes < 32 {
78            return Err(Error::HardwareProtectionError(
79                "MPU minimum region size is 32 bytes".to_string(),
80            ));
81        }
82        if bytes > (1u64 << 32) {
83            return Err(Error::HardwareProtectionError(
84                "MPU maximum region size is 4GB".to_string(),
85            ));
86        }
87
88        // Find the smallest power of 2 >= bytes
89        let bit_pos = 64 - bytes.leading_zeros() - 1;
90        let power = if bytes == (1u64 << bit_pos) {
91            bit_pos
92        } else {
93            bit_pos + 1
94        };
95
96        match power {
97            5 => Ok(MPUSize::Size32B),
98            6 => Ok(MPUSize::Size64B),
99            7 => Ok(MPUSize::Size128B),
100            8 => Ok(MPUSize::Size256B),
101            9 => Ok(MPUSize::Size512B),
102            10 => Ok(MPUSize::Size1KB),
103            11 => Ok(MPUSize::Size2KB),
104            12 => Ok(MPUSize::Size4KB),
105            13 => Ok(MPUSize::Size8KB),
106            14 => Ok(MPUSize::Size16KB),
107            15 => Ok(MPUSize::Size32KB),
108            16 => Ok(MPUSize::Size64KB),
109            17 => Ok(MPUSize::Size128KB),
110            18 => Ok(MPUSize::Size256KB),
111            19 => Ok(MPUSize::Size512KB),
112            20 => Ok(MPUSize::Size1MB),
113            21 => Ok(MPUSize::Size2MB),
114            22 => Ok(MPUSize::Size4MB),
115            23 => Ok(MPUSize::Size8MB),
116            24 => Ok(MPUSize::Size16MB),
117            25 => Ok(MPUSize::Size32MB),
118            26 => Ok(MPUSize::Size64MB),
119            27 => Ok(MPUSize::Size128MB),
120            28 => Ok(MPUSize::Size256MB),
121            29 => Ok(MPUSize::Size512MB),
122            30 => Ok(MPUSize::Size1GB),
123            31 => Ok(MPUSize::Size2GB),
124            32 => Ok(MPUSize::Size4GB),
125            _ => unreachable!(),
126        }
127    }
128}
129
130/// MPU Access Permissions
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum MPUPermissions {
133    /// No access
134    NoAccess = 0,
135    /// Privileged read/write, user no access
136    PrivilegedRW = 1,
137    /// Privileged read/write, user read-only
138    PrivilegedRWUserRO = 2,
139    /// Full read/write access
140    FullRW = 3,
141    /// Privileged read-only
142    PrivilegedRO = 5,
143    /// Read-only (privileged and user)
144    FullRO = 6,
145}
146
147/// MPU Memory Attributes
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct MPUAttributes {
150    /// Shareable
151    pub shareable: bool,
152    /// Cacheable
153    pub cacheable: bool,
154    /// Bufferable
155    pub bufferable: bool,
156    /// Execute Never (XN)
157    pub execute_never: bool,
158}
159
160impl MPUAttributes {
161    /// Normal memory (cacheable, bufferable)
162    pub fn normal() -> Self {
163        Self {
164            shareable: false,
165            cacheable: true,
166            bufferable: true,
167            execute_never: false,
168        }
169    }
170
171    /// Device memory (non-cacheable, bufferable)
172    ///
173    /// Per ARM architecture: Device memory has TEX=0b000, C=0, B=1.
174    /// This distinguishes it from Strongly-ordered (TEX=0b000, C=0, B=0).
175    pub fn device() -> Self {
176        Self {
177            shareable: true,
178            cacheable: false,
179            bufferable: true,
180            execute_never: true,
181        }
182    }
183
184    /// Strongly-ordered memory
185    pub fn strongly_ordered() -> Self {
186        Self {
187            shareable: true,
188            cacheable: false,
189            bufferable: false,
190            execute_never: false,
191        }
192    }
193}
194
195/// MPU Region Configuration
196#[derive(Debug, Clone)]
197pub struct MPURegion {
198    /// Region number (0-7 or 0-15 depending on implementation)
199    pub number: u8,
200
201    /// Base address (must be aligned to region size)
202    pub base_address: u32,
203
204    /// Region size
205    pub size: MPUSize,
206
207    /// Access permissions
208    pub permissions: MPUPermissions,
209
210    /// Memory attributes
211    pub attributes: MPUAttributes,
212
213    /// Subregion disable mask (8 bits, 1 = disabled)
214    pub subregion_disable: u8,
215
216    /// Region enabled
217    pub enabled: bool,
218}
219
220impl MPURegion {
221    /// Create a new MPU region
222    pub fn new(number: u8, base_address: u32, size: MPUSize) -> Self {
223        Self {
224            number,
225            base_address,
226            size,
227            permissions: MPUPermissions::FullRW,
228            attributes: MPUAttributes::normal(),
229            subregion_disable: 0,
230            enabled: true,
231        }
232    }
233
234    /// Validate region configuration
235    pub fn validate(&self) -> Result<()> {
236        // Check alignment
237        let size_bytes = self.size.bytes();
238        if size_bytes > u32::MAX as u64 {
239            return Err(Error::HardwareProtectionError(
240                "Region size exceeds 32-bit address space".to_string(),
241            ));
242        }
243
244        let alignment = size_bytes as u32;
245        #[allow(clippy::manual_is_multiple_of)]
246        // is_multiple_of unstable in Rust 1.84 (Bazel toolchain)
247        if alignment == 0 || self.base_address % alignment != 0 {
248            return Err(Error::HardwareProtectionError(format!(
249                "Base address 0x{:08X} not aligned to region size {} bytes",
250                self.base_address, size_bytes
251            )));
252        }
253
254        Ok(())
255    }
256
257    /// Get RASR (Region Attribute and Size Register) value
258    pub fn rasr(&self) -> u32 {
259        let mut rasr = 0u32;
260
261        // Enable bit
262        if self.enabled {
263            rasr |= 1 << 0;
264        }
265
266        // Size field (bits 1-5)
267        rasr |= ((self.size as u32) << 1) & 0x3E;
268
269        // Subregion disable (bits 8-15)
270        rasr |= (self.subregion_disable as u32) << 8;
271
272        // Attributes (bits 16-21)
273        if self.attributes.bufferable {
274            rasr |= 1 << 16;
275        }
276        if self.attributes.cacheable {
277            rasr |= 1 << 17;
278        }
279        if self.attributes.shareable {
280            rasr |= 1 << 18;
281        }
282
283        // TEX field (bits 19-21) - set to 0 for normal memory
284
285        // Access permissions (bits 24-26)
286        rasr |= (self.permissions as u32) << 24;
287
288        // Execute Never (bit 28)
289        if self.attributes.execute_never {
290            rasr |= 1 << 28;
291        }
292
293        rasr
294    }
295
296    /// Get RBAR (Region Base Address Register) value
297    pub fn rbar(&self) -> u32 {
298        let mut rbar = self.base_address & 0xFFFFFFE0; // Clear lower 5 bits
299        rbar |= (self.number as u32) & 0xF; // Region number in bits 0-3
300        rbar |= 1 << 4; // VALID bit
301        rbar
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308
309    #[test]
310    fn test_mpu_size_from_bytes() {
311        assert_eq!(MPUSize::from_bytes(32).unwrap(), MPUSize::Size32B);
312        assert_eq!(MPUSize::from_bytes(64).unwrap(), MPUSize::Size64B);
313        assert_eq!(MPUSize::from_bytes(100).unwrap(), MPUSize::Size128B); // Rounds up
314        assert_eq!(MPUSize::from_bytes(1024).unwrap(), MPUSize::Size1KB);
315        assert_eq!(MPUSize::from_bytes(65536).unwrap(), MPUSize::Size64KB);
316    }
317
318    #[test]
319    fn test_mpu_size_bytes() {
320        assert_eq!(MPUSize::Size32B.bytes(), 32);
321        assert_eq!(MPUSize::Size1KB.bytes(), 1024);
322        assert_eq!(MPUSize::Size64KB.bytes(), 65536);
323        assert_eq!(MPUSize::Size1MB.bytes(), 1048576);
324    }
325
326    #[test]
327    fn test_mpu_region_alignment() {
328        let region = MPURegion::new(0, 0x20000000, MPUSize::Size64KB);
329        assert!(region.validate().is_ok());
330
331        let misaligned = MPURegion::new(0, 0x20000100, MPUSize::Size64KB);
332        assert!(misaligned.validate().is_err());
333    }
334
335    #[test]
336    fn test_mpu_rasr_generation() {
337        let region = MPURegion::new(0, 0x20000000, MPUSize::Size64KB);
338        let rasr = region.rasr();
339
340        // Check enable bit
341        assert_eq!(rasr & 0x1, 1);
342
343        // Check size field
344        let size_field = (rasr >> 1) & 0x1F;
345        assert_eq!(size_field, MPUSize::Size64KB as u32);
346    }
347}