aarch32_cpu/
pmsav8.rs

1//! Support for the PMSAv8-32 EL1 MPU
2//!
3//! See Part C: Armv8-R Protected Memory System Architecture in [ARM
4//! Architecture Reference Manual Supplement - ARMv8, for the ARMv8-R AArch32
5//! architecture profile][armv8r]
6//!
7//! [armv8r]: https://developer.arm.com/documentation/ddi0568/latest/
8
9use arbitrary_int::{u26, u3};
10
11use crate::register;
12
13#[doc(inline)]
14pub use register::hprbar::{AccessPerms as El2AccessPerms, Shareability as El2Shareability};
15#[doc(inline)]
16pub use register::prbar::{AccessPerms as El1AccessPerms, Shareability as El1Shareability};
17
18/// Ways this API can fail
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum Error {
21    /// Found too many regions
22    TooManyRegions,
23    /// Found an invalid MAIR selector (only 0..=7 is valid)
24    InvalidMair(u8),
25    /// Found a region with invalid alignment
26    UnalignedRegion(core::ops::RangeInclusive<*mut u8>),
27}
28
29/// Represents our PMSAv8-32 EL1 MPU
30pub struct El1Mpu();
31
32impl El1Mpu {
33    /// Create an MPU handle
34    ///
35    /// # Safety
36    ///
37    /// Only create one of these at any given time, as they access shared
38    /// mutable state within the processor and do read-modify-writes on that state.
39    pub unsafe fn new() -> El1Mpu {
40        El1Mpu()
41    }
42
43    /// How many MPU regions are there?
44    pub fn num_regions(&self) -> u8 {
45        register::Mpuir::read().dregions()
46    }
47
48    /// Access the current state of a region
49    pub fn get_region(&mut self, idx: u8) -> Option<El1Region> {
50        if idx >= self.num_regions() {
51            return None;
52        }
53        register::Prselr::write(register::Prselr(idx as u32));
54        let prbar = register::Prbar::read();
55        let prlar = register::Prlar::read();
56        let start_addr = (prbar.base().value() << 6) as *mut u8;
57        let end_addr = ((prlar.limit().value() << 6) | 0x3F) as *mut u8;
58        Some(El1Region {
59            range: start_addr..=end_addr,
60            shareability: prbar.shareability(),
61            access: prbar.access_perms(),
62            no_exec: prbar.nx(),
63            mair: prlar.mair().value(),
64            enable: prlar.enabled(),
65        })
66    }
67
68    /// Write a single region to the EL1 MPU
69    ///
70    /// ## Arguments
71    ///
72    /// - `region`: The [El1Region] object containing the configuration for the MPU region.
73    /// - `idx`: The index of the region to be configured.
74    ///
75    /// ## Errors
76    ///
77    /// Returns:
78    /// - [Error::UnalignedRegion] if the region's start address is not 64-byte aligned.
79    /// - [Error::UnalignedRegion] if the region's end address is not 63-byte aligned.
80    /// - [Error::InvalidMair] if the region's MAIR index is invalid (greater than 7).
81    pub fn set_region(&mut self, idx: u8, region: &El1Region) -> Result<(), Error> {
82        let start = *(region.range.start()) as usize as u32;
83        // Check for 64-byte alignment (0x3F is six bits)
84        if start & 0x3F != 0 {
85            return Err(Error::UnalignedRegion(region.range.clone()));
86        }
87        let end = *(region.range.end()) as usize as u32;
88        if end & 0x3F != 0x3F {
89            return Err(Error::UnalignedRegion(region.range.clone()));
90        }
91        if region.mair > 7 {
92            return Err(Error::InvalidMair(region.mair));
93        }
94        register::Prselr::write(register::Prselr(idx as u32));
95        register::Prbar::write({
96            let mut bar = register::Prbar::new_with_raw_value(0);
97            bar.set_base(u26::from_u32(start >> 6));
98            bar.set_access_perms(region.access);
99            bar.set_nx(region.no_exec);
100            bar.set_shareability(region.shareability);
101            bar
102        });
103        register::Prlar::write({
104            let mut lar = register::Prlar::new_with_raw_value(0);
105            lar.set_limit(u26::from_u32(end >> 6));
106            lar.set_enabled(region.enable);
107            lar.set_mair(u3::from_u8(region.mair));
108            lar
109        });
110
111        Ok(())
112    }
113
114    /// Writes a subset of EL1 MPU regions starting from a specified index.
115    ///
116    /// ## Arguments
117    ///
118    /// - `regions_starting_idx`: The starting index for the regions to be reconfigured.
119    /// - `regions`: A slice of [El1Region] objects that will overwrite the previous regions starting from `regions_starting_idx`.
120    pub fn set_regions(
121        &mut self,
122        regions_starting_idx: u8,
123        regions: &[El1Region],
124    ) -> Result<(), Error> {
125        if regions.len().saturating_add(regions_starting_idx as usize) > self.num_regions() as usize
126        {
127            return Err(Error::TooManyRegions);
128        }
129
130        for (idx, region) in regions.iter().enumerate() {
131            self.set_region(idx as u8 + regions_starting_idx, region)?;
132        }
133
134        Ok(())
135    }
136
137    /// Set the memory attributes to MAIR0 and MAIR1
138    pub fn set_attributes(&mut self, memattrs: &[MemAttr]) {
139        let mem_attr0 = memattrs.get(0).map(|m| m.to_bits()).unwrap_or(0) as u32;
140        let mem_attr1 = memattrs.get(1).map(|m| m.to_bits()).unwrap_or(0) as u32;
141        let mem_attr2 = memattrs.get(2).map(|m| m.to_bits()).unwrap_or(0) as u32;
142        let mem_attr3 = memattrs.get(3).map(|m| m.to_bits()).unwrap_or(0) as u32;
143        let mair0 = mem_attr3 << 24 | mem_attr2 << 16 | mem_attr1 << 8 | mem_attr0;
144        unsafe {
145            register::Mair0::write(register::Mair0(mair0));
146        }
147        let mem_attr0 = memattrs.get(4).map(|m| m.to_bits()).unwrap_or(0) as u32;
148        let mem_attr1 = memattrs.get(5).map(|m| m.to_bits()).unwrap_or(0) as u32;
149        let mem_attr2 = memattrs.get(6).map(|m| m.to_bits()).unwrap_or(0) as u32;
150        let mem_attr3 = memattrs.get(7).map(|m| m.to_bits()).unwrap_or(0) as u32;
151        let mair1 = mem_attr3 << 24 | mem_attr2 << 16 | mem_attr1 << 8 | mem_attr0;
152        unsafe {
153            register::Mair1::write(register::Mair1(mair1));
154        }
155    }
156
157    /// Enable or disable the background region
158    pub fn background_region_enable(&mut self, enable: bool) {
159        register::Sctlr::modify(|r| {
160            r.set_br(enable);
161        });
162    }
163
164    /// Configure the EL1 MPU
165    ///
166    /// Write regions, attributes and enable/disable the background region
167    /// with a single [El1Config] struct.
168    pub fn configure(&mut self, config: &El1Config) -> Result<(), Error> {
169        self.set_regions(0, config.regions)?;
170
171        self.set_attributes(config.memory_attributes);
172
173        self.background_region_enable(config.background_config);
174
175        Ok(())
176    }
177
178    /// Enable the EL1 MPU
179    pub fn enable(&mut self) {
180        register::Sctlr::modify(|r| {
181            r.set_m(true);
182        });
183    }
184
185    /// Disable the EL1 MPU
186    pub fn disable(&mut self) {
187        register::Sctlr::modify(|r| {
188            r.set_m(false);
189        });
190    }
191}
192
193/// Represents our PMSAv8-32 EL2 MPU
194pub struct El2Mpu();
195
196impl El2Mpu {
197    /// Create an EL2 MPU handle
198    ///
199    /// # Safety
200    ///
201    /// Only create one of these at any given time, as they access shared
202    /// mutable state within the processor and do read-modify-writes on that state.
203    pub unsafe fn new() -> El2Mpu {
204        El2Mpu()
205    }
206
207    /// How many EL2 MPU regions are there?
208    pub fn num_regions(&self) -> u8 {
209        register::Hmpuir::read().region()
210    }
211
212    /// Access the current state of a region
213    pub fn get_region(&mut self, idx: u8) -> Option<El2Region> {
214        if idx >= self.num_regions() {
215            return None;
216        }
217        register::Hprselr::write(register::Hprselr(idx as u32));
218        let hprbar = register::Hprbar::read();
219        let hprlar = register::Hprlar::read();
220        let start_addr = (hprbar.base().value() << 6) as *mut u8;
221        let end_addr = ((hprlar.limit().value() << 6) | 0x3F) as *mut u8;
222        Some(El2Region {
223            range: start_addr..=end_addr,
224            shareability: hprbar.shareability(),
225            access: hprbar.access_perms(),
226            no_exec: hprbar.nx(),
227            mair: hprlar.mair().value(),
228            enable: hprlar.enabled(),
229        })
230    }
231
232    /// Write a single region to the EL2 MPU
233    ///
234    /// ## Arguments
235    ///
236    /// - `region`: The [El2Region] object containing the configuration for the MPU region.
237    /// - `idx`: The index of the region to be configured.
238    ///
239    /// ## Errors
240    ///
241    /// Returns:
242    /// - [Error::UnalignedRegion] if the region's start address is not 64-byte aligned.
243    /// - [Error::UnalignedRegion] if the region's end address is not 63-byte aligned.
244    /// - [Error::InvalidMair] if the region's MAIR index is invalid (greater than 7).
245    pub fn set_region(&mut self, idx: u8, region: &El2Region) -> Result<(), Error> {
246        let start = *(region.range.start()) as usize as u32;
247        // Check for 64-byte alignment (0x3F is six bits)
248        if start & 0x3F != 0 {
249            return Err(Error::UnalignedRegion(region.range.clone()));
250        }
251        let end = *(region.range.end()) as usize as u32;
252        if end & 0x3F != 0x3F {
253            return Err(Error::UnalignedRegion(region.range.clone()));
254        }
255        if region.mair > 7 {
256            return Err(Error::InvalidMair(region.mair));
257        }
258        register::Hprselr::write(register::Hprselr(idx as u32));
259        register::Hprbar::write({
260            let mut bar = register::Hprbar::new_with_raw_value(0);
261            bar.set_base(u26::from_u32(start >> 6));
262            bar.set_access_perms(region.access);
263            bar.set_nx(region.no_exec);
264            bar.set_shareability(region.shareability);
265            bar
266        });
267        register::Hprlar::write({
268            let mut lar = register::Hprlar::new_with_raw_value(0);
269            lar.set_limit(u26::from_u32(end >> 6));
270            lar.set_enabled(region.enable);
271            lar.set_mair(u3::from_u8(region.mair));
272            lar
273        });
274
275        Ok(())
276    }
277
278    /// Writes a subset of EL2 MPU regions starting from a specified index.
279    ///
280    /// ## Arguments
281    ///
282    /// - `regions_starting_idx`: The starting index for the regions to be reconfigured.
283    /// - `regions`: A slice of [El2Region] objects that will overwrite the previous regions starting from `regions_starting_idx`.
284    pub fn set_regions(
285        &mut self,
286        regions_starting_idx: u8,
287        regions: &[El2Region],
288    ) -> Result<(), Error> {
289        if regions.len().saturating_add(regions_starting_idx as usize) > self.num_regions() as usize
290        {
291            return Err(Error::TooManyRegions);
292        }
293
294        for (idx, region) in regions.iter().enumerate() {
295            self.set_region(idx as u8 + regions_starting_idx, region)?;
296        }
297
298        Ok(())
299    }
300
301    /// Set the memory attributes to HMAIR0 and HMAIR1
302    pub fn set_attributes(&mut self, memattrs: &[MemAttr]) {
303        let mem_attr0 = memattrs.get(0).map(|m| m.to_bits()).unwrap_or(0) as u32;
304        let mem_attr1 = memattrs.get(1).map(|m| m.to_bits()).unwrap_or(0) as u32;
305        let mem_attr2 = memattrs.get(2).map(|m| m.to_bits()).unwrap_or(0) as u32;
306        let mem_attr3 = memattrs.get(3).map(|m| m.to_bits()).unwrap_or(0) as u32;
307        let hmair0 = mem_attr3 << 24 | mem_attr2 << 16 | mem_attr1 << 8 | mem_attr0;
308        unsafe {
309            register::Hmair0::write(register::Hmair0(hmair0));
310        }
311        let mem_attr0 = memattrs.get(4).map(|m| m.to_bits()).unwrap_or(0) as u32;
312        let mem_attr1 = memattrs.get(5).map(|m| m.to_bits()).unwrap_or(0) as u32;
313        let mem_attr2 = memattrs.get(6).map(|m| m.to_bits()).unwrap_or(0) as u32;
314        let mem_attr3 = memattrs.get(7).map(|m| m.to_bits()).unwrap_or(0) as u32;
315        let hmair1 = mem_attr3 << 24 | mem_attr2 << 16 | mem_attr1 << 8 | mem_attr0;
316        unsafe {
317            register::Hmair1::write(register::Hmair1(hmair1));
318        }
319    }
320
321    /// Enable or disable the background region
322    pub fn background_region_enable(&mut self, enable: bool) {
323        register::Hsctlr::modify(|r| {
324            r.set_br(enable);
325        });
326    }
327
328    /// Configure the EL2 MPU
329    ///
330    /// Write regions, attributes and enable/disable the background region with a single [El2Config] struct.
331    pub fn configure(&mut self, config: &El2Config) -> Result<(), Error> {
332        self.set_regions(0, config.regions)?;
333
334        self.set_attributes(config.memory_attributes);
335
336        self.background_region_enable(config.background_config);
337
338        Ok(())
339    }
340
341    /// Enable the EL2 MPU
342    pub fn enable(&mut self) {
343        register::Hsctlr::modify(|r| {
344            r.set_m(true);
345        });
346    }
347
348    /// Disable the EL2 MPU
349    pub fn disable(&mut self) {
350        register::Hsctlr::modify(|r| {
351            r.set_m(false);
352        });
353    }
354}
355
356/// Configuration for the PMSAv8-32 EL1 MPU
357#[derive(Clone, Debug, PartialEq, Eq)]
358pub struct El1Config<'a> {
359    /// Background Config Enable
360    ///
361    /// If true, use the default MMU config if no other region matches an address
362    pub background_config: bool,
363    /// Information about each Region.
364    ///
365    /// If you pass more regions than the MPU supports, you get an error.
366    pub regions: &'a [El1Region],
367    /// Information about each Memory Attribute
368    ///
369    /// If you pass more MemAttrs than the MPU supports (8), you get an error.
370    pub memory_attributes: &'a [MemAttr],
371}
372
373/// Configuration for the PMSAv8-32 MPU
374#[derive(Clone, Debug, PartialEq, Eq)]
375pub struct El1Region {
376    /// The range of the region
377    ///
378    /// * The first address must be a multiple of 32.
379    /// * The length must be a multiple of 32.
380    pub range: core::ops::RangeInclusive<*mut u8>,
381    /// Shareability of the region
382    pub shareability: El1Shareability,
383    /// Access for the region
384    pub access: El1AccessPerms,
385    /// Is region no-exec?
386    pub no_exec: bool,
387    /// Which Memory Attribute applies here?
388    ///
389    /// Selects from the eight attributes in {MAIR0, MAIR1}.
390    ///
391    /// Only values 0..=7 are valid here.
392    pub mair: u8,
393    /// Is this region enabled?
394    pub enable: bool,
395}
396
397// Creating a static Region is fine - the pointers within it
398// only go to the MPU and aren't accessed via Rust code
399unsafe impl Sync for El1Region {}
400
401/// Configuration for the PMSAv8-32 EL2 MPU
402#[derive(Clone, Debug, PartialEq, Eq)]
403pub struct El2Config<'a> {
404    /// Background Config Enable
405    ///
406    /// If true, use the default MMU config if no other region matches an address
407    pub background_config: bool,
408    /// Information about each Region.
409    ///
410    /// If you pass more regions than the MPU supports, you get an error.
411    pub regions: &'a [El2Region],
412    /// Information about each Memory Attribute
413    ///
414    /// If you pass more MemAttrs than the MPU supports (8), you get an error.
415    pub memory_attributes: &'a [MemAttr],
416}
417
418/// Configuration for the PMSAv8-32 EL2 MPU
419#[derive(Clone, Debug, PartialEq, Eq)]
420pub struct El2Region {
421    /// The range of the region
422    ///
423    /// * The first address must be a multiple of 32.
424    /// * The length must be a multiple of 32.
425    pub range: core::ops::RangeInclusive<*mut u8>,
426    /// Shareability of the region
427    pub shareability: El2Shareability,
428    /// Access for the region
429    pub access: El2AccessPerms,
430    /// Is region no-exec?
431    pub no_exec: bool,
432    /// Which Memory Attribute applies here?
433    ///
434    /// Selects from the eight attributes in {HMAIR0, HMAIR1}.
435    ///
436    /// Only values 0..=7 are valid here.
437    pub mair: u8,
438    /// Is this region enabled?
439    pub enable: bool,
440}
441
442// Creating a static El2Region is fine - the pointers within it
443// only go to the MPU and aren't accessed via Rust code
444unsafe impl Sync for El2Region {}
445
446/// Describes the memory ordering and cacheability of a region
447#[derive(Debug, Clone, PartialEq, Eq)]
448pub enum MemAttr {
449    /// Strongly-ordered memory
450    StronglyOrdered,
451    /// Device memory
452    DeviceMemory,
453    /// Normal memory
454    NormalMemory {
455        /// Controls outer access
456        outer: Cacheable,
457        /// Controls inner access
458        inner: Cacheable,
459    },
460}
461
462impl MemAttr {
463    /// Convert this memory attribute to an 8-bit value we can write to MAIRx
464    const fn to_bits(&self) -> u8 {
465        match self {
466            MemAttr::StronglyOrdered => 0b0000_0000,
467            MemAttr::DeviceMemory => 0b0000_0100,
468            MemAttr::NormalMemory { outer, inner } => {
469                let outer_bits = outer.to_bits();
470                let inner_bits = inner.to_bits();
471                outer_bits << 4 | inner_bits
472            }
473        }
474    }
475}
476
477/// Cacheability of a region
478#[derive(Debug, Clone, PartialEq, Eq)]
479pub enum Cacheable {
480    WriteThroughTransient(RwAllocPolicy),
481    WriteBackTransient(RwAllocPolicy),
482    WriteThroughNonTransient(RwAllocPolicy),
483    WriteBackNonTransient(RwAllocPolicy),
484    NonCacheable,
485}
486
487impl Cacheable {
488    const fn to_bits(&self) -> u8 {
489        match self {
490            Cacheable::WriteThroughTransient(rw_alloc) => 0b0000 | (*rw_alloc as u8),
491            Cacheable::WriteBackTransient(rw_alloc) => 0b0100 | (*rw_alloc as u8),
492            Cacheable::WriteThroughNonTransient(rw_alloc) => 0b1000 | (*rw_alloc as u8),
493            Cacheable::WriteBackNonTransient(rw_alloc) => 0b1100 | (*rw_alloc as u8),
494            Cacheable::NonCacheable => 0b0100,
495        }
496    }
497}
498
499/// Cache allocation policy
500#[derive(Copy, Debug, Clone, PartialEq, Eq)]
501#[repr(u8)]
502pub enum RwAllocPolicy {
503    /// Write-allocate
504    W = 0b01,
505    /// Read-allocate
506    R = 0b10,
507    /// Read-allocate and Write-allocate
508    RW = 0b11,
509}
510
511#[cfg(test)]
512mod test {
513    use super::*;
514
515    #[test]
516    fn mem_attr_strong() {
517        let mem_attr = MemAttr::StronglyOrdered;
518        assert_eq!(mem_attr.to_bits(), 0b0000_0000);
519    }
520
521    #[test]
522    fn mem_attr_device() {
523        let mem_attr = MemAttr::DeviceMemory;
524        assert_eq!(mem_attr.to_bits(), 0b0000_0100);
525    }
526
527    #[test]
528    fn mem_attr_normal() {
529        let mem_attr = MemAttr::NormalMemory {
530            outer: Cacheable::NonCacheable,
531            inner: Cacheable::WriteBackNonTransient(RwAllocPolicy::W),
532        };
533        assert_eq!(
534            mem_attr.to_bits(),
535            0b0100_1101,
536            "0b{:08b}",
537            mem_attr.to_bits()
538        );
539    }
540}