/src/mpu.rs
#![allow(dead_code)]

use core::cell::RefCell;
use cortex_m::interrupt::{self, Mutex};
use cortex_m::peripheral::MPU;

#[derive(Copy, Clone, Debug)]
pub enum MemoryPermission {
    None,
    RO,
    RW,
}

#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct MemoryRange {
    pub start: u32,
    pub length: u32,
}

#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct MemoryRegion {
    pub start: u32,
    pub length: u32,
    pub permissions: MemoryPermission,
    pub xn: bool,
}

static MPU_MANAGER: Mutex<RefCell<Option<MpuManager>>> = Mutex::new(RefCell::new(None));

pub fn init(mpu: MPU) {
    interrupt::free(|cs| MPU_MANAGER.borrow(cs).replace(Some(MpuManager::new(mpu))));
}

pub fn add_region(region: &MemoryRegion) {
    interrupt::free(|cs| {
        let mut rm = MPU_MANAGER.borrow(cs).borrow_mut();
        let manager = rm.as_mut().unwrap();
        unsafe { manager.add_region(&region) };
    });
}

pub fn add_regions(regions: &[MemoryRegion]) {
    interrupt::free(|cs| {
        let mut rm = MPU_MANAGER.borrow(cs).borrow_mut();
        let manager = rm.as_mut().unwrap();
        for region in regions {
            unsafe { manager.add_region(&region) };
        }
    });
}

pub fn enable() {
    interrupt::free(|cs| {
        let mut rm = MPU_MANAGER.borrow(cs).borrow_mut();
        let manager = rm.as_mut().unwrap();
        manager.set_mpu_enable(true);
    });
}

pub fn disable() {
    interrupt::free(|cs| {
        let mut rm = MPU_MANAGER.borrow(cs).borrow_mut();
        let manager = rm.as_mut().unwrap();
        manager.set_mpu_enable(false);
    });
}

struct MpuManager {
    mpu: MPU,
    n_mpu_regions: u32,
}

impl MpuManager {
    pub fn new(mpu: MPU) -> MpuManager {
        // Enable default memory map for privileged software
        unsafe { mpu.ctrl.write(0x4) };

        MpuManager {
            mpu,
            n_mpu_regions: 0,
        }
    }

    unsafe fn write_mpu_region(&mut self, start: u32, length: u32, region: &MemoryRegion) {
        if self.n_mpu_regions == 8 {
            panic!("Out of MPU regions");
        }

        // We don't check the start value here. Let the fault handler handle it.
        self.mpu.rbar.write(start | 0b10000 | self.n_mpu_regions);
        self.mpu.rasr.write(
            if region.xn { 1 << 28 } else { 0 } |  // XN
            match region.permissions {
                MemoryPermission::None => 0b001,
                MemoryPermission::RO   => 0b010,
                MemoryPermission::RW   => 0b011,
            } << 24 |                              // AP
            0b001000 << 16 |                       // TEX S C B
            (length.trailing_zeros() - 1) << 1 |   // SIZE
            1                                      // ENABLE
        );
        self.n_mpu_regions += 1;
    }

    unsafe fn map_mpu_region(&mut self, start: u32, length: u32, region: &MemoryRegion) {
        if length.count_ones() > 1 {
            // The region is not a power of two. Create a MPU region for
            // the next lowest power of two and then iterate on the
            // remainder.
            let nl_length = length.next_power_of_two() >> 1;
            self.write_mpu_region(start, nl_length, region);
            self.map_mpu_region(start + nl_length, length - nl_length, region);
        } else {
            self.write_mpu_region(start, length, region);
        }
    }

    pub unsafe fn add_region(&mut self, region: &MemoryRegion) {
        if region.length == 0 {
            return;
        }
        self.map_mpu_region(region.start, region.length, region);
    }

    pub fn set_mpu_enable(&mut self, enable: bool) {
        unsafe {
            self.mpu.ctrl.modify(|v| (v & 0xFFFFFFFE) | (enable as u32))
        };
    }
}