opentitanlib/test_utils/epmp.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
// Copyright lowRISC contributors (OpenTitan project).
// 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.
#[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;
/// 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(())
}
}