blob: 5d2655a3d7e87528de0ea4c55d562df96fb31577 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
use anyhow::{ensure, Result};
use thiserror::Error;
pub mod constants {
/// In `pmpNcfg`, bit 7 represents a locked entry.
pub const EPMP_CFG_LOCKED: u8 = 1 << 7;
/// In `pmpNcfg`, bit 2 represents an executable region.
pub const EPMP_CFG_EXEC: u8 = 1 << 2;
/// In `pmpNcfg`, bit 2 represents an writable region.
pub const EPMP_CFG_WRITE: u8 = 1 << 1;
/// In `pmpNcfg`, bit 2 represents an readable region.
pub const EPMP_CFG_READ: u8 = 1 << 0;
/// Some common configurations.
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;
/// Machine Mode Lockdown: ePMP regions with the L bit are machine-mode only.
pub const EPMP_MSECCFG_MML: u32 = 1;
/// Machine Mode Whitelist Policy: deny instead of ignore memory accesses with no matching
/// rule.
pub const EPMP_MSECCFG_MMWP: u32 = 2;
/// Rule Locking Bypass: allow modification of locked ePMP entries.
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),
}
/// Represents the different EPMP region kinds.
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum EpmpRegionKind {
Off = 0,
Tor = 1,
Na4 = 2,
Napot = 3,
}
impl Default for EpmpRegionKind {
fn default() -> Self {
EpmpRegionKind::Off
}
}
const EPMP_ADDR_SHIFT: u8 = 3;
const EPMP_ADDR_MASK: u8 = 3;
/// Represents the address range associated with the PMP entry.
#[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
}
}
/// A decoded ePMP entry.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct EpmpEntry {
/// The bit pattern present in `pmpNcfg`.
pub cfg: u8,
/// The region kind encoded ni the `cfg` field.
pub kind: EpmpRegionKind,
/// The address range for this entry.
pub range: EpmpAddressRange,
}
#[derive(Clone, Debug, Default)]
pub struct Epmp {
pub entry: Vec<EpmpEntry>,
}
impl Epmp {
/// Creates an `Epmp` from the raw RISCV32 form of the 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::*;
// This particular set of PMPCFG / PMPADDR values was captured from the ROM
// and manually modified to encompass all of the decode options.
// The modifications are:
// - Addition of entry 6: a duplicate of entry 2 with the perms set to RWX.
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)?;
// The 0th entry is the bottom of a TOR range. The decoded version of entry 0 should
// appear as an "off" region.
assert!(matches!(
epmp.entry[0],
EpmpEntry {
cfg: 0,
kind: EpmpRegionKind::Off,
range: EpmpAddressRange(0, 0)
}
));
// The first entry is a TOR region representing the .text section of the ROM.
assert!(matches!(
epmp.entry[1],
EpmpEntry {
cfg: EPMP_CFG_LRX,
kind: EpmpRegionKind::Tor,
range: EpmpAddressRange(0x8000, 0xd044)
}
));
// The second entry is a Napot region representing the entire 32K of ROM.
assert!(matches!(
epmp.entry[2],
EpmpEntry {
cfg: EPMP_CFG_LRO,
kind: EpmpRegionKind::Napot,
range: EpmpAddressRange(0x8000, 0x10000)
}
));
// The sixth entry is a Napot region representing the entire 32K of ROM, but is set to
// RWX (tests the decode of the `cfg` field with lock bit clear).
assert!(matches!(
epmp.entry[6],
EpmpEntry {
cfg: EPMP_CFG_RWX,
kind: EpmpRegionKind::Napot,
range: EpmpAddressRange(0x8000, 0x10000)
}
));
// The thirteenth entry is a Napot region represening the 4K RVDM RAM (LRWX).
assert!(matches!(
epmp.entry[13],
EpmpEntry {
cfg: EPMP_CFG_LRWX,
kind: EpmpRegionKind::Napot,
range: EpmpAddressRange(0x10000, 0x11000)
}
));
// The 14th entry is an Na4 region in RAM representing the no-access stack guard.
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)
}
));
// The 15th entry is an Napot region in RAM representing a read-write region.
assert!(matches!(
epmp.entry[15],
EpmpEntry {
cfg: EPMP_CFG_LRW,
kind: EpmpRegionKind::Napot,
range: EpmpAddressRange(0x1000_0000, 0x1002_0000)
}
));
Ok(())
}
}