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}