cortex-m 0.7.8

Low level access to Cortex-M processors
Documentation
//! Security Attribution Unit
//!
//! *NOTE* Available only on Armv8-M and Armv8.1-M, for the following Rust target triples:
//!   * `thumbv8m.base-none-eabi`
//!   * `thumbv8m.main-none-eabi`
//!   * `thumbv8m.main-none-eabihf`
//!
//! For reference please check the section B8.3 of the Armv8-M Architecture Reference Manual.

use crate::interrupt;
use crate::peripheral::SAU;
use bitfield::bitfield;
use volatile_register::{RO, RW};

/// Register block
#[repr(C)]
pub struct RegisterBlock {
    /// Control Register
    pub ctrl: RW<Ctrl>,
    /// Type Register
    pub _type: RO<Type>,
    /// Region Number Register
    pub rnr: RW<Rnr>,
    /// Region Base Address Register
    pub rbar: RW<Rbar>,
    /// Region Limit Address Register
    pub rlar: RW<Rlar>,
    /// Secure Fault Status Register
    pub sfsr: RO<Sfsr>,
    /// Secure Fault Address Register
    pub sfar: RO<Sfar>,
}

bitfield! {
    /// Control Register description
    #[repr(C)]
    #[derive(Copy, Clone)]
    pub struct Ctrl(u32);
    get_enable, set_enable: 0;
    get_allns, set_allns: 1;
}

bitfield! {
    /// Type Register description
    #[repr(C)]
    #[derive(Copy, Clone)]
    pub struct Type(u32);
    u8;
    sregion, _: 7, 0;
}

bitfield! {
    /// Region Number Register description
    #[repr(C)]
    #[derive(Copy, Clone)]
    pub struct Rnr(u32);
    u8;
    get_region, set_region: 7, 0;
}

bitfield! {
    /// Region Base Address Register description
    #[repr(C)]
    #[derive(Copy, Clone)]
    pub struct Rbar(u32);
    u32;
    get_baddr, set_baddr: 31, 5;
}

bitfield! {
    /// Region Limit Address Register description
    #[repr(C)]
    #[derive(Copy, Clone)]
    pub struct Rlar(u32);
    u32;
    get_laddr, set_laddr: 31, 5;
    get_nsc, set_nsc: 1;
    get_enable, set_enable: 0;
}

bitfield! {
    /// Secure Fault Status Register description
    #[repr(C)]
    #[derive(Copy, Clone)]
    pub struct Sfsr(u32);
    impl Debug;
    /// Invalid Entry Point
    pub invep, _: 0;
    /// Invalid Integrity Signature
    pub invis, _: 1;
    /// Invalid Exception Return
    pub inver, _: 2;
    /// Attribution Unit Violation
    pub auviol, _: 3;
    /// Invalid Transition
    pub invtran, _: 4;
    /// Lazy state preservation error
    pub lsperr, _: 5;
    /// SFAR is valid
    pub sfarvalid, _: 6;
    /// Lazy state error
    pub lserr, _: 7;
}

bitfield! {
    /// Secure Fault Address Register description
    #[repr(C)]
    #[derive(Copy, Clone)]
    pub struct Sfar(u32);
    impl Debug;
    u32;
    /// Faulting memory address
    ///
    /// Only valid if SFSR.SFARVALID = 1
    pub address, _: 31, 0;
}

/// Possible attribute of a SAU region.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SauRegionAttribute {
    /// SAU region is Secure
    Secure,
    /// SAU region is Non-Secure Callable
    NonSecureCallable,
    /// SAU region is Non-Secure
    NonSecure,
}

/// Description of a SAU region.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SauRegion {
    /// First address of the region, its 5 least significant bits must be set to zero.
    pub base_address: u32,
    /// Last address of the region, its 5 least significant bits must be set to one.
    pub limit_address: u32,
    /// Attribute of the region.
    pub attribute: SauRegionAttribute,
}

/// Possible error values returned by the SAU methods.
#[derive(Debug)]
pub enum SauError {
    /// The region number parameter to set or get a region must be between 0 and
    /// region_numbers() - 1.
    RegionNumberTooBig,
    /// Bits 0 to 4 of the base address of a SAU region must be set to zero.
    WrongBaseAddress,
    /// Bits 0 to 4 of the limit address of a SAU region must be set to one.
    WrongLimitAddress,
    /// The number of regions passed to [`SAU::init`] exceeds the number of regions implemented
    /// in hardware (as reported by [`SAU::region_numbers`]).
    TooManyRegions,
}

impl SAU {
    /// Get the number of implemented SAU regions.
    #[inline]
    pub fn region_numbers(&self) -> u8 {
        self._type.read().sregion()
    }

    /// Disable the SAU and mark all memory Non-Secure (ALLNS mode).
    ///
    /// Sets `CTRL.ALLNS = 1`, `CTRL.ENABLE = 0`. When the SAU is disabled with
    /// ALLNS set, the entire address space is treated as Non-Secure (subject to
    /// any IDAU overrides). Use this when running entirely in Non-Secure mode
    /// with no security boundary enforcement.
    ///
    /// To re-enable security boundaries, call [`init`](Self::init) or
    /// [`enable`](Self::enable) after programming regions.
    #[inline]
    pub fn disable_allns(&mut self) {
        unsafe {
            self.ctrl.write(Ctrl(0b10)); // ALLNS=1, ENABLE=0
        }
    }

    /// Program SAU regions and enable the SAU.
    ///
    /// This is a convenience wrapper around [`set_region`](Self::set_region) +
    /// [`enable`](Self::enable):
    ///
    /// 1. Disables the SAU temporarily.
    /// 2. Programs all regions from `regions`.
    /// 3. Re-enables the SAU.
    ///
    /// Memory not covered by any enabled region is treated as Secure once the
    /// SAU is enabled.
    ///
    /// To also enable the `SecureFault` exception so TrustZone violations
    /// surface as a dedicated fault rather than escalating to `HardFault`, call
    /// `scb.enable(cortex_m::peripheral::scb::Exception::SecureFault)` after
    /// this.
    ///
    /// # Errors
    /// Returns [`SauError::TooManyRegions`] if `regions.len()` exceeds the
    /// number of regions implemented in hardware (see
    /// [`region_numbers`](Self::region_numbers). Returns other [`SauError`]
    /// variants if any region descriptor has a misaligned base or limit
    /// address.
    ///
    /// On error the SAU is left disabled (in the state set at step 1 above).
    #[inline]
    pub fn init(&mut self, regions: &[SauRegion]) -> Result<(), SauError> {
        if regions.len() > self.region_numbers() as usize {
            return Err(SauError::TooManyRegions);
        }
        // Disable while reprogramming to avoid partial-update windows.
        unsafe {
            self.ctrl.write(Ctrl(0));
        }
        for (i, &region) in regions.iter().enumerate() {
            self.set_region(i as u8, region)?;
        }
        self.enable();
        Ok(())
    }

    /// Enable the SAU.
    #[inline]
    pub fn enable(&mut self) {
        unsafe {
            self.ctrl.modify(|mut ctrl| {
                ctrl.set_enable(true);
                ctrl
            });
        }
    }

    /// Set a SAU region to a region number.
    /// SAU regions must be 32 bytes aligned and their sizes must be a multiple of 32 bytes. It
    /// means that the 5 least significant bits of the base address of a SAU region must be set to
    /// zero and the 5 least significant bits of the limit address must be set to one.
    /// The region number must be valid.
    /// This function is executed under a critical section to prevent having inconsistent results.
    #[inline]
    pub fn set_region(&mut self, region_number: u8, region: SauRegion) -> Result<(), SauError> {
        interrupt::free(|_| {
            let base_address = region.base_address;
            let limit_address = region.limit_address;
            let attribute = region.attribute;

            if region_number >= self.region_numbers() {
                Err(SauError::RegionNumberTooBig)
            } else if base_address & 0x1F != 0 {
                Err(SauError::WrongBaseAddress)
            } else if limit_address & 0x1F != 0x1F {
                Err(SauError::WrongLimitAddress)
            } else {
                // All fields of these registers are going to be modified so we don't need to read them
                // before.
                let mut rnr = Rnr(0);
                let mut rbar = Rbar(0);
                let mut rlar = Rlar(0);

                rnr.set_region(region_number);
                rbar.set_baddr(base_address >> 5);
                rlar.set_laddr(limit_address >> 5);

                match attribute {
                    SauRegionAttribute::Secure => {
                        rlar.set_nsc(false);
                        rlar.set_enable(false);
                    }
                    SauRegionAttribute::NonSecureCallable => {
                        rlar.set_nsc(true);
                        rlar.set_enable(true);
                    }
                    SauRegionAttribute::NonSecure => {
                        rlar.set_nsc(false);
                        rlar.set_enable(true);
                    }
                }

                unsafe {
                    self.rnr.write(rnr);
                    self.rbar.write(rbar);
                    self.rlar.write(rlar);
                }

                Ok(())
            }
        })
    }

    /// Get a region from the SAU.
    /// The region number must be valid.
    /// This function is executed under a critical section to prevent having inconsistent results.
    #[inline]
    pub fn get_region(&mut self, region_number: u8) -> Result<SauRegion, SauError> {
        interrupt::free(|_| {
            if region_number >= self.region_numbers() {
                Err(SauError::RegionNumberTooBig)
            } else {
                unsafe {
                    self.rnr.write(Rnr(region_number.into()));
                }

                let rbar = self.rbar.read();
                let rlar = self.rlar.read();

                let attribute = match (rlar.get_enable(), rlar.get_nsc()) {
                    (false, _) => SauRegionAttribute::Secure,
                    (true, false) => SauRegionAttribute::NonSecure,
                    (true, true) => SauRegionAttribute::NonSecureCallable,
                };

                Ok(SauRegion {
                    base_address: rbar.get_baddr() << 5,
                    limit_address: (rlar.get_laddr() << 5) | 0x1F,
                    attribute,
                })
            }
        })
    }
}