pub const PMP_COUNT: usize = 16;
const A_SHIFT: u8 = 3;
const A_MASK: u8 = 0x3;
const PMP_R: u8 = 1 << 0;
const PMP_W: u8 = 1 << 1;
const PMP_X: u8 = 1 << 2;
const PMP_L: u8 = 1 << 7;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PmpAddrMatch {
Off = 0,
Tor = 1,
Na4 = 2,
Napot = 3,
}
impl PmpAddrMatch {
pub const fn from_bits(bits: u8) -> Self {
match bits & A_MASK {
0 => Self::Off,
1 => Self::Tor,
2 => Self::Na4,
_ => Self::Napot,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PmpResult {
Allow,
Deny,
NoMatch,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PmpEntry {
pub cfg: u8,
pub addr: u64,
}
impl PmpEntry {
pub const fn match_mode(&self) -> PmpAddrMatch {
PmpAddrMatch::from_bits((self.cfg >> A_SHIFT) & A_MASK)
}
pub const fn is_readable(&self) -> bool {
self.cfg & PMP_R != 0
}
pub const fn is_writable(&self) -> bool {
self.cfg & PMP_W != 0
}
pub const fn is_executable(&self) -> bool {
self.cfg & PMP_X != 0
}
pub const fn is_locked(&self) -> bool {
self.cfg & PMP_L != 0
}
}
#[derive(Debug)]
pub struct Pmp {
entries: Vec<PmpEntry>,
}
impl Default for Pmp {
fn default() -> Self {
Self::new()
}
}
impl Pmp {
pub fn new() -> Self {
let entries = (0..PMP_COUNT).map(|_| PmpEntry { cfg: 0, addr: 0 }).collect();
Self { entries }
}
pub fn entries(&self) -> &[PmpEntry] {
&self.entries
}
pub fn restore(&mut self, entries: &[PmpEntry]) {
for (entry, saved) in self.entries.iter_mut().zip(entries) {
*entry = saved.clone();
}
}
pub fn set_cfg(&mut self, idx: usize, cfg: u8) {
if idx < self.entries.len() {
if self.entries[idx].cfg & PMP_L != 0 {
return;
}
self.entries[idx].cfg = cfg;
}
}
pub fn set_addr(&mut self, idx: usize, addr: u64) {
if idx < self.entries.len() {
if self.entries[idx].cfg & PMP_L != 0 {
return;
}
self.entries[idx].addr = addr;
}
}
pub fn get_cfg(&self, idx: usize) -> u8 {
if idx < self.entries.len() { self.entries[idx].cfg } else { 0 }
}
pub fn get_addr(&self, idx: usize) -> u64 {
if idx < self.entries.len() { self.entries[idx].addr } else { 0 }
}
const fn napot_range(pmpaddr: u64) -> (u64, u64) {
let trailing = (!pmpaddr).trailing_zeros() as u64;
let size_bits = trailing + 3;
if size_bits >= 64 {
return (0, u64::MAX);
}
let size = 1u64 << size_bits;
let mask = size - 1;
let base = (pmpaddr << 2) & !mask;
(base, base.wrapping_add(size))
}
const fn na4_range(pmpaddr: u64) -> (u64, u64) {
let base = pmpaddr << 2;
(base, base + 4)
}
#[allow(clippy::fn_params_excessive_bools)]
pub fn check(
&self,
byte_addr: u64,
size: u64,
is_read: bool,
is_write: bool,
is_exec: bool,
is_machine_mode: bool,
) -> PmpResult {
let access_end = byte_addr.saturating_add(size);
for i in 0..self.entries.len() {
let entry = &self.entries[i];
let mode = entry.match_mode();
if mode == PmpAddrMatch::Off {
continue;
}
let (lo, hi) = match mode {
PmpAddrMatch::Tor => {
let hi = entry.addr << 2;
let lo = if i == 0 { 0 } else { self.entries[i - 1].addr << 2 };
(lo, hi)
}
PmpAddrMatch::Na4 => Self::na4_range(entry.addr),
PmpAddrMatch::Napot => Self::napot_range(entry.addr),
PmpAddrMatch::Off => continue,
};
let any_byte_match = byte_addr < hi && access_end > lo;
if any_byte_match {
let all_bytes_match = byte_addr >= lo && access_end <= hi;
if !all_bytes_match {
return PmpResult::Deny;
}
if is_machine_mode && !entry.is_locked() {
return PmpResult::Allow;
}
let permitted = (!is_read || entry.is_readable())
&& (!is_write || entry.is_writable())
&& (!is_exec || entry.is_executable());
return if permitted { PmpResult::Allow } else { PmpResult::Deny };
}
}
if is_machine_mode { PmpResult::Allow } else { PmpResult::NoMatch }
}
}