use anyhow::{ensure, Result};
use thiserror::Error;
pub mod constants {
pub const EPMP_CFG_LOCKED: u8 = 1 << 7;
pub const EPMP_CFG_EXEC: u8 = 1 << 2;
pub const EPMP_CFG_WRITE: u8 = 1 << 1;
pub const EPMP_CFG_READ: u8 = 1 << 0;
pub const EPMP_CFG_UNLOCKED: u8 = 0;
pub const EPMP_CFG_LOCKED_NONE: u8 = EPMP_CFG_LOCKED;
pub const EPMP_CFG_LRO: u8 = EPMP_CFG_LOCKED | EPMP_CFG_READ;
pub const EPMP_CFG_LRW: u8 = EPMP_CFG_LOCKED | EPMP_CFG_READ | EPMP_CFG_WRITE;
pub const EPMP_CFG_LRX: u8 = EPMP_CFG_LOCKED | EPMP_CFG_READ | EPMP_CFG_EXEC;
pub const EPMP_CFG_LRWX: u8 = EPMP_CFG_LOCKED | EPMP_CFG_READ | EPMP_CFG_WRITE | EPMP_CFG_EXEC;
pub const EPMP_CFG_RO: u8 = EPMP_CFG_READ;
pub const EPMP_CFG_RW: u8 = EPMP_CFG_READ | EPMP_CFG_WRITE;
pub const EPMP_CFG_RX: u8 = EPMP_CFG_READ | EPMP_CFG_EXEC;
pub const EPMP_CFG_RWX: u8 = EPMP_CFG_READ | EPMP_CFG_WRITE | EPMP_CFG_EXEC;
pub const EPMP_MSECCFG_MML: u32 = 1;
pub const EPMP_MSECCFG_MMWP: u32 = 2;
pub const EPMP_MSECCFG_RLB: u32 = 4;
}
#[derive(Debug, Error)]
pub enum EpmpError {
#[error("Invalid raw EPMP length (cfg={0}, addr={1})")]
InvalidLength(usize, usize),
}
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum EpmpRegionKind {
#[default]
Off = 0,
Tor = 1,
Na4 = 2,
Napot = 3,
}
const EPMP_ADDR_SHIFT: u8 = 3;
const EPMP_ADDR_MASK: u8 = 3;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct EpmpAddressRange(pub u64, pub u64);
impl EpmpAddressRange {
pub fn start(&self) -> u64 {
self.0
}
pub fn end(&self) -> u64 {
self.1
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct EpmpEntry {
pub cfg: u8,
pub kind: EpmpRegionKind,
pub range: EpmpAddressRange,
}
#[derive(Clone, Debug, Default)]
pub struct Epmp {
pub entry: Vec<EpmpEntry>,
}
impl Epmp {
pub fn from_raw_rv32(pmpcfg: &[u32], pmpaddr: &[u32]) -> Result<Self> {
ensure!(
pmpcfg.len() * 4 == pmpaddr.len(),
EpmpError::InvalidLength(pmpcfg.len(), pmpaddr.len())
);
let pmpcfg = pmpcfg
.iter()
.flat_map(|word| word.to_le_bytes())
.collect::<Vec<u8>>();
let mut entry = Vec::with_capacity(pmpaddr.len());
for i in 0..pmpaddr.len() {
let region_kind = match (pmpcfg[i] >> EPMP_ADDR_SHIFT) & EPMP_ADDR_MASK {
0 => EpmpRegionKind::Off,
1 => EpmpRegionKind::Tor,
2 => EpmpRegionKind::Na4,
3 => EpmpRegionKind::Napot,
_ => unreachable!(),
};
let addr = match region_kind {
EpmpRegionKind::Off => EpmpAddressRange(0, 0),
EpmpRegionKind::Tor => {
if i == 0 {
EpmpAddressRange(0, (pmpaddr[i] as u64) << 2)
} else {
EpmpAddressRange((pmpaddr[i - 1] as u64) << 2, (pmpaddr[i] as u64) << 2)
}
}
EpmpRegionKind::Na4 => {
EpmpAddressRange((pmpaddr[i] as u64) << 2, ((pmpaddr[i] as u64) << 2) + 4)
}
EpmpRegionKind::Napot => {
let size = pmpaddr[i].trailing_ones();
let addr = ((pmpaddr[i] & (pmpaddr[i] + 1)) as u64) << 2;
let length = (1 << (size + 3)) as u64;
EpmpAddressRange(addr, addr + length)
}
};
entry.push(EpmpEntry {
cfg: pmpcfg[i] & !(EPMP_ADDR_MASK << EPMP_ADDR_SHIFT),
kind: region_kind,
range: addr,
});
}
Ok(Epmp { entry })
}
}
#[cfg(test)]
mod tests {
use super::constants::*;
use super::*;
const PMPCFG: [u32; 4] = [0x998d00, 0x1f998d, 0x8b000000, 0x9b909f00];
const PMPADDR: [u32; 16] = [
0x2000, 0x3411, 0x2fff, 0x8000100, 0x8001203, 0x801ffff, 0x2fff, 0x0, 0x0, 0x0, 0x10000000,
0x14000000, 0x0, 0x41ff, 0x4007000, 0x4003fff,
];
#[test]
fn test_epmp_decode() -> Result<()> {
let epmp = Epmp::from_raw_rv32(&PMPCFG, &PMPADDR)?;
assert!(matches!(
epmp.entry[0],
EpmpEntry {
cfg: 0,
kind: EpmpRegionKind::Off,
range: EpmpAddressRange(0, 0)
}
));
assert!(matches!(
epmp.entry[1],
EpmpEntry {
cfg: EPMP_CFG_LRX,
kind: EpmpRegionKind::Tor,
range: EpmpAddressRange(0x8000, 0xd044)
}
));
assert!(matches!(
epmp.entry[2],
EpmpEntry {
cfg: EPMP_CFG_LRO,
kind: EpmpRegionKind::Napot,
range: EpmpAddressRange(0x8000, 0x10000)
}
));
assert!(matches!(
epmp.entry[6],
EpmpEntry {
cfg: EPMP_CFG_RWX,
kind: EpmpRegionKind::Napot,
range: EpmpAddressRange(0x8000, 0x10000)
}
));
assert!(matches!(
epmp.entry[13],
EpmpEntry {
cfg: EPMP_CFG_LRWX,
kind: EpmpRegionKind::Napot,
range: EpmpAddressRange(0x10000, 0x11000)
}
));
eprintln!("entry14 = {:?}", epmp.entry[14]);
assert!(matches!(
epmp.entry[14],
EpmpEntry {
cfg: EPMP_CFG_LOCKED_NONE,
kind: EpmpRegionKind::Na4,
range: EpmpAddressRange(0x1001_c000, 0x1001_c004)
}
));
assert!(matches!(
epmp.entry[15],
EpmpEntry {
cfg: EPMP_CFG_LRW,
kind: EpmpRegionKind::Napot,
range: EpmpAddressRange(0x1000_0000, 0x1002_0000)
}
));
Ok(())
}
}