cortex_mpu/
lib.rs

1#![no_std]
2
3use cortex_m::{asm, peripheral::MPU};
4
5pub use arrayvec::ArrayVec;
6
7fn update_mpu_unprivileged(mpu: &mut MPU, f: impl FnOnce(&mut MPU)) {
8    const CTRL_ENABLE: u32 = 1 << 0;
9    const _CTRL_HFNMIENA: u32 = 1 << 1;
10    const CTRL_PRIVDEFENA: u32 = 1 << 2;
11
12    // Atomic MPU updates:
13    // Turn off interrupts, turn off MPU, reconfigure, turn it back on, reenable interrupts.
14    // Turning off interrupts is not needed when the old configuration only applied to
15    // unprivileged thread code: The entire operation is interruptible, as long as the
16    // processor is never made to run any other thread-mode code.
17
18    // https://developer.arm.com/docs/dui0553/latest/cortex-m4-peripherals/optional-memory-protection-unit/updating-an-mpu-region
19    asm::dsb();
20
21    // Disable MPU while we update the regions
22    unsafe {
23        mpu.ctrl.write(0);
24    }
25
26    f(mpu);
27
28    unsafe {
29        // Enable MPU, but not for privileged code
30        mpu.ctrl.write(CTRL_ENABLE | CTRL_PRIVDEFENA);
31    }
32
33    asm::dsb();
34    asm::isb();
35}
36
37/// The Cortex-M0+ MPU.
38pub mod cortex_m0p {
39    use super::*;
40
41    /// Wrapper around the Cortex-M0+ Memory Protection Unit (MPU).
42    pub struct Mpu(MPU);
43
44    impl Mpu {
45        /// The smallest supported region size.
46        pub const MIN_REGION_SIZE: Size = Size::S256B;
47
48        /// Number of supported memory regions.
49        pub const REGION_COUNT: u8 = 8;
50
51        const REGION_COUNT_USIZE: usize = Self::REGION_COUNT as usize;
52
53        /// Creates a new MPU wrapper, taking ownership of the `MPU` peripheral.
54        ///
55        /// # Safety
56        ///
57        /// This function is safe to call if the processor is a Cortex-M0+ and has an MPU.
58        pub unsafe fn new(raw: MPU) -> Self {
59            Mpu(raw)
60        }
61
62        /// Consumes `self` and returns the raw MPU peripheral.
63        pub fn into_inner(self) -> MPU {
64            self.0
65        }
66
67        /// Configures the MPU to restrict access to software running in unprivileged mode.
68        ///
69        /// Any violation of the MPU settings will cause a *HardFault* exception. The Cortex-M0+
70        /// does not have a dedicated memory management exception.
71        ///
72        /// Unprivileged code will only be allowed to access memory inside one of the given
73        /// `regions`.
74        ///
75        /// Code running in privileged mode will not be restricted by the MPU, except that regions
76        /// that have `executable` set to `false` will be marked as ***N**ever e**X**excute* (`NX`),
77        /// which is enforced even for privileged code.
78        pub fn configure_unprivileged(
79            &mut self,
80            regions: &ArrayVec<[Region; Self::REGION_COUNT_USIZE]>,
81        ) {
82            // Safety: This is safe because it does not affect the privileged code calling it.
83            // Unprivileged, untrusted (non-Rust) code is always unsafe to call, so this doesn't
84            // have to be unsafe as well. If called by unprivileged code, the register writes will
85            // fault, which is also safe.
86
87            update_mpu_unprivileged(&mut self.0, |mpu| {
88                for (i, region) in regions.iter().enumerate() {
89                    unsafe {
90                        {
91                            let addr = (region.base_addr as u32) & !0b11111;
92                            let valid = 1 << 4;
93                            let region = i as u32;
94                            mpu.rbar.write(addr | valid | region);
95                        }
96
97                        {
98                            let xn = if region.executable { 0 } else { 1 << 28 };
99                            let ap = (region.permissions as u32) << 24;
100                            let scb = region.attributes.to_bits() << 16;
101                            let srd = u32::from(region.subregions.bits()) << 8;
102                            let size = u32::from(region.size.bits()) << 1;
103                            let enable = 1;
104
105                            mpu.rasr.write(xn | ap | scb | srd | size | enable);
106                        }
107                    }
108                }
109
110                // Disable the remaining regions
111                for i in regions.len()..usize::from(Self::REGION_COUNT) {
112                    unsafe {
113                        let addr = 0;
114                        let valid = 1 << 4;
115                        let region = i as u32;
116                        mpu.rbar.write(addr | valid | region);
117
118                        mpu.rasr.write(0); // disable region
119                    }
120                }
121            });
122        }
123    }
124
125    /// Memory region properties.
126    #[derive(Debug, Copy, Clone)]
127    pub struct Region {
128        /// Starting address of the region (lowest address).
129        ///
130        /// This must be aligned to the region's `size`.
131        pub base_addr: usize,
132        /// Size of the region.
133        pub size: Size,
134        /// The subregions to enable or disable.
135        pub subregions: Subregions,
136        /// Whether to allow instruction fetches from this region.
137        ///
138        /// If this is `false`, the region will be marked as NX (Never eXecute).
139        /// This affects both privileged and unprivileged code, regardless of
140        /// other MPU settings.
141        pub executable: bool,
142        /// Data access permissions for the region.
143        pub permissions: AccessPermission,
144        /// Memory type and cache policy attributes.
145        pub attributes: MemoryAttributes,
146    }
147
148    /// Describes memory type, cache policy, and shareability.
149    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
150    pub enum MemoryAttributes {
151        /// Shareable, non-cached, strongly-ordered memory region.
152        StronglyOrdered,
153
154        /// Non-cached device peripheral region. Always considered shareable.
155        Device,
156
157        /// Normal memory region (ie. "actual" memory, such as Flash or SRAM).
158        Normal {
159            /// Whether the region is accessible by more than one bus master
160            /// (eg. a DMA engine or a second MCU core).
161            shareable: bool,
162
163            /// Cache policy of the region.
164            cache_policy: CachePolicy,
165        },
166    }
167
168    impl MemoryAttributes {
169        /// Turns `self` into its bit-level representation, in order `0bSCB`.
170        fn to_bits(self) -> u32 {
171            macro_rules! bits {
172                ( C=$c:literal, B=$b:literal, S=$s:ident ) => {
173                    (if $s { 1 } else { 0 } << 2) | ($c << 1) | $b
174                };
175                ( C=$c:literal, B=$b:literal, S=$s:literal ) => {
176                    ($s << 2) | ($c << 1) | $b
177                };
178            }
179
180            match self {
181                Self::StronglyOrdered => bits!(C = 0, B = 0, S = 0),
182                Self::Device => bits!(C = 0, B = 1, S = 0),
183                Self::Normal {
184                    shareable,
185                    cache_policy,
186                } => match cache_policy {
187                    CachePolicy::WriteThrough => bits!(C = 1, B = 0, S = shareable),
188                    CachePolicy::WriteBack => bits!(C = 1, B = 1, S = shareable),
189                },
190            }
191        }
192    }
193
194    /// The caching policy for a "normal" memory region.
195    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
196    pub enum CachePolicy {
197        /// Write-through, no write allocate.
198        WriteThrough,
199
200        /// Write-back cacheable region, no write-allocate.
201        WriteBack,
202    }
203}
204
205/// The Cortex-M4 MPU.
206pub mod cortex_m4 {
207    use super::*;
208
209    /// Wrapper around the Cortex-M4 Memory Protection Unit (MPU).
210    pub struct Mpu(MPU);
211
212    impl Mpu {
213        /// The smallest supported region size.
214        pub const MIN_REGION_SIZE: Size = Size::S32B;
215
216        /// Number of supported memory regions.
217        pub const REGION_COUNT: u8 = 8;
218
219        const REGION_COUNT_USIZE: usize = Self::REGION_COUNT as usize;
220
221        /// Creates a new MPU wrapper, taking ownership of the `MPU` peripheral.
222        ///
223        /// # Safety
224        ///
225        /// This is safe to call if the processor is a Cortex-M4 or M4F and has an MPU.
226        pub unsafe fn new(raw: MPU) -> Self {
227            Mpu(raw)
228        }
229
230        /// Consumes `self` and returns the raw MPU peripheral.
231        pub fn into_inner(self) -> MPU {
232            self.0
233        }
234
235        /// Configures the MPU to restrict access to software running in unprivileged mode.
236        ///
237        /// Code running in privileged mode will not be restricted by the MPU.
238        pub fn configure_unprivileged(
239            &mut self,
240            regions: &ArrayVec<[Region; Self::REGION_COUNT_USIZE]>,
241        ) {
242            // Safety: This is safe because it does not affect the privileged code calling it.
243            // Unprivileged, untrusted (non-Rust) code is always unsafe to call, so this doesn't
244            // have to be unsafe as well. If called by unprivileged code, the register writes will
245            // fault, which is also safe.
246
247            update_mpu_unprivileged(&mut self.0, |mpu| {
248                for (i, region) in regions.iter().enumerate() {
249                    unsafe {
250                        {
251                            let addr = (region.base_addr as u32) & !0b11111;
252                            let valid = 1 << 4;
253                            let region = i as u32;
254                            mpu.rbar.write(addr | valid | region);
255                        }
256
257                        {
258                            let xn = if region.executable { 0 } else { 1 << 28 };
259                            let ap = (region.permissions as u32) << 24;
260                            let texscb = region.attributes.to_bits() << 16;
261                            let srd = u32::from(region.subregions.bits()) << 8;
262                            let size = u32::from(region.size.bits()) << 1;
263                            let enable = 1;
264
265                            mpu.rasr.write(xn | ap | texscb | srd | size | enable);
266                        }
267                    }
268                }
269
270                // Disable the remaining regions
271                for i in regions.len()..usize::from(Self::REGION_COUNT) {
272                    unsafe {
273                        let addr = 0;
274                        let valid = 1 << 4;
275                        let region = i as u32;
276                        mpu.rbar.write(addr | valid | region);
277
278                        mpu.rasr.write(0); // disable region
279                    }
280                }
281            });
282        }
283    }
284
285    /// Memory region properties.
286    #[derive(Debug, Copy, Clone)]
287    pub struct Region {
288        /// Starting address of the region (lowest address).
289        ///
290        /// This must be aligned to the region's `size`.
291        pub base_addr: usize,
292        /// Size of the region.
293        pub size: Size,
294        /// The subregions to enable or disable.
295        pub subregions: Subregions,
296        /// Whether to allow instruction fetches from this region.
297        ///
298        /// If this is `false`, the region will be marked as NX (Never eXecute).
299        /// This affects both privileged and unprivileged code, regardless of
300        /// other MPU settings.
301        pub executable: bool,
302        /// Data access permissions for the region.
303        pub permissions: AccessPermission,
304        /// Memory type and cache policy attributes.
305        pub attributes: MemoryAttributes,
306    }
307
308    /// Describes memory type, cache policy, and shareability.
309    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
310    pub enum MemoryAttributes {
311        /// Shareable, non-cached, strongly-ordered memory region.
312        StronglyOrdered,
313
314        /// Non-cached device peripheral region.
315        Device {
316            /// Whether the region is accessible by more than one bus master (eg. a
317            /// DMA engine or a second MCU core).
318            shareable: bool,
319        },
320
321        /// Normal memory region (ie. "actual" memory, such as Flash or SRAM).
322        Normal {
323            /// Whether the region is accessible by more than one bus master (eg. a
324            /// DMA engine or a second MCU core).
325            shareable: bool,
326
327            /// How this region should be cached (?).
328            cache_policy: CachePolicy,
329        },
330    }
331
332    impl MemoryAttributes {
333        /// Turns `self` into its bit-level representation, consisting of the TEX, C, B, and S bits.
334        fn to_bits(self) -> u32 {
335            macro_rules! bits {
336                ( TEX=$tex:literal, C=$c:literal, B=$b:literal, S=$s:ident ) => {
337                    ($tex << 3) | (if $s { 1 } else { 0 } << 2) | ($c << 1) | $b
338                };
339                ( TEX=$tex:literal, C=$c:literal, B=$b:literal, S=$s:literal ) => {
340                    ($tex << 3) | ($s << 2) | ($c << 1) | $b
341                };
342            }
343
344            match self {
345                Self::StronglyOrdered => bits!(TEX = 0b000, C = 0, B = 0, S = 0),
346                Self::Device { shareable: false } => bits!(TEX = 0b010, C = 0, B = 0, S = 0),
347                Self::Device { shareable: true } => bits!(TEX = 0b000, C = 0, B = 1, S = 0),
348                Self::Normal {
349                    shareable,
350                    cache_policy,
351                } => match cache_policy {
352                    CachePolicy::NonCacheable => bits!(TEX = 0b001, C = 0, B = 0, S = shareable),
353                    CachePolicy::WriteThrough => bits!(TEX = 0b000, C = 1, B = 0, S = shareable),
354                    CachePolicy::WriteBack {
355                        write_allocate: false,
356                    } => bits!(TEX = 0b000, C = 1, B = 1, S = shareable),
357                    CachePolicy::WriteBack {
358                        write_allocate: true,
359                    } => bits!(TEX = 0b001, C = 1, B = 1, S = shareable),
360                },
361            }
362        }
363    }
364
365    /// The caching policy for a "normal" memory region.
366    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
367    pub enum CachePolicy {
368        /// Non-cacheable memory region.
369        NonCacheable,
370
371        /// Write-through, no write allocate.
372        WriteThrough,
373
374        /// Write-back cacheable region.
375        WriteBack {
376            /// Whether a write miss loads the missed cache row into cache.
377            write_allocate: bool,
378        },
379        // FIXME: There's also mixed "outer"/"inner" policies, but I don't know what that even means.
380    }
381}
382
383/// Data access permissions for a memory region from unprivileged code.
384#[derive(Debug, Copy, Clone, PartialEq, Eq)]
385pub enum AccessPermission {
386    /// Any data access (read or write) will generate a fault.
387    NoAccess = 0b01,
388
389    /// Any write access will generate a fault.
390    ReadOnly = 0b10,
391
392    /// Region unprotected, both reads and writes are allowed.
393    ReadWrite = 0b11,
394}
395
396/// Subregion Disable (SRD) bits for the 8 subregions in a region.
397///
398/// Note that some cores do not support subregions for small region sizes. Check the core's User
399/// Guide for more information.
400#[derive(Debug, Copy, Clone, PartialEq, Eq)]
401pub struct Subregions(u8);
402
403impl Subregions {
404    /// None of the 8 subregions are enabled. Equivalent to disabling the entire region.
405    pub const NONE: Self = Subregions(0xff);
406
407    /// All 8 subregions are enabled.
408    pub const ALL: Self = Subregions(0);
409
410    /// Creates a `Subregions` mask from raw Subregion Disable (SRD) bits.
411    ///
412    /// The least significant bit disables the lowest 1/8th of the region, and so on.
413    pub fn from_disable_bits(bits: u8) -> Self {
414        Subregions(bits)
415    }
416
417    /// Returns the raw 8-bit Subregion Disable Bits value.
418    pub fn bits(self) -> u8 {
419        self.0
420    }
421}
422
423/// By default, all subregions are enabled.
424impl Default for Subregions {
425    fn default() -> Self {
426        Self::ALL
427    }
428}
429
430/// Memory region size value (5 bits).
431///
432/// Memory regions must have a size that is a power of two, and their base address must be naturally
433/// aligned (ie. aligned to their size).
434///
435/// There is a core-specific minimum size exposed as `Mpu::MIN_REGION_SIZE`.
436#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
437pub struct Size(u8);
438
439impl Size {
440    pub const S32B: Self = Size(4);
441
442    pub const S64B: Self = Size(5);
443
444    pub const S128B: Self = Size(6);
445
446    pub const S256B: Self = Size(7);
447
448    pub const S512B: Self = Size(8);
449
450    pub const S1K: Self = Size(9);
451
452    pub const S2K: Self = Size(10);
453
454    pub const S4K: Self = Size(11);
455
456    pub const S8K: Self = Size(12);
457
458    pub const S16K: Self = Size(13);
459
460    pub const S32K: Self = Size(14);
461
462    pub const S64K: Self = Size(15);
463
464    pub const S128K: Self = Size(16);
465
466    pub const S256K: Self = Size(17);
467
468    pub const S512K: Self = Size(18);
469
470    pub const S1M: Self = Size(19);
471
472    pub const S2M: Self = Size(20);
473
474    pub const S4M: Self = Size(21);
475
476    pub const S8M: Self = Size(22);
477
478    pub const S16M: Self = Size(23);
479
480    pub const S32M: Self = Size(24);
481
482    pub const S64M: Self = Size(25);
483
484    pub const S128M: Self = Size(26);
485
486    pub const S256M: Self = Size(27);
487
488    pub const S512M: Self = Size(28);
489
490    pub const S1G: Self = Size(29);
491
492    pub const S2G: Self = Size(30);
493
494    /// The entire 4 GiB memory space.
495    pub const S4G: Self = Size(31);
496
497    /// Creates a `Size` from a raw 5-bit value.
498    ///
499    /// The `bits` encode a region size of `2^(bits + 1)`. For example, a 1 KiB region would use
500    /// `0b01001` (9): `2^(9+1) = 2^10 = 1024`.
501    pub const fn from_raw_bits(bits: u8) -> Self {
502        Size(bits)
503    }
504
505    /// Returns the raw 5-bit value encoding the region size.
506    pub const fn bits(self) -> u8 {
507        self.0
508    }
509}