opentitanlib/test_utils/
epmp.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::{Result, ensure};
6use thiserror::Error;
7
8pub mod constants {
9    /// In `pmpNcfg`, bit 7 represents a locked entry.
10    pub const EPMP_CFG_LOCKED: u8 = 1 << 7;
11    /// In `pmpNcfg`, bit 2 represents an executable region.
12    pub const EPMP_CFG_EXEC: u8 = 1 << 2;
13    /// In `pmpNcfg`, bit 2 represents an writable region.
14    pub const EPMP_CFG_WRITE: u8 = 1 << 1;
15    /// In `pmpNcfg`, bit 2 represents an readable region.
16    pub const EPMP_CFG_READ: u8 = 1 << 0;
17    /// Some common configurations.
18    pub const EPMP_CFG_UNLOCKED: u8 = 0;
19    pub const EPMP_CFG_LOCKED_NONE: u8 = EPMP_CFG_LOCKED;
20    pub const EPMP_CFG_LRO: u8 = EPMP_CFG_LOCKED | EPMP_CFG_READ;
21    pub const EPMP_CFG_LRW: u8 = EPMP_CFG_LOCKED | EPMP_CFG_READ | EPMP_CFG_WRITE;
22    pub const EPMP_CFG_LRX: u8 = EPMP_CFG_LOCKED | EPMP_CFG_READ | EPMP_CFG_EXEC;
23    pub const EPMP_CFG_LRWX: u8 = EPMP_CFG_LOCKED | EPMP_CFG_READ | EPMP_CFG_WRITE | EPMP_CFG_EXEC;
24
25    pub const EPMP_CFG_RO: u8 = EPMP_CFG_READ;
26    pub const EPMP_CFG_RW: u8 = EPMP_CFG_READ | EPMP_CFG_WRITE;
27    pub const EPMP_CFG_RX: u8 = EPMP_CFG_READ | EPMP_CFG_EXEC;
28    pub const EPMP_CFG_RWX: u8 = EPMP_CFG_READ | EPMP_CFG_WRITE | EPMP_CFG_EXEC;
29
30    /// Machine Mode Lockdown: ePMP regions with the L bit are machine-mode only.
31    pub const EPMP_MSECCFG_MML: u32 = 1;
32    /// Machine Mode Whitelist Policy: deny instead of ignore memory accesses with no matching
33    /// rule.
34    pub const EPMP_MSECCFG_MMWP: u32 = 2;
35    /// Rule Locking Bypass: allow modification of locked ePMP entries.
36    pub const EPMP_MSECCFG_RLB: u32 = 4;
37}
38
39#[derive(Debug, Error)]
40pub enum EpmpError {
41    #[error("Invalid raw EPMP length (cfg={0}, addr={1})")]
42    InvalidLength(usize, usize),
43}
44
45/// Represents the different EPMP region kinds.
46#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
47#[repr(u8)]
48pub enum EpmpRegionKind {
49    #[default]
50    Off = 0,
51    Tor = 1,
52    Na4 = 2,
53    Napot = 3,
54}
55
56const EPMP_ADDR_SHIFT: u8 = 3;
57const EPMP_ADDR_MASK: u8 = 3;
58
59/// Represents the address range associated with the PMP entry.
60#[derive(Clone, Debug, Default, PartialEq, Eq)]
61pub struct EpmpAddressRange(pub u64, pub u64);
62
63impl EpmpAddressRange {
64    pub fn start(&self) -> u64 {
65        self.0
66    }
67    pub fn end(&self) -> u64 {
68        self.1
69    }
70}
71
72/// A decoded ePMP entry.
73#[derive(Clone, Debug, Default, PartialEq, Eq)]
74pub struct EpmpEntry {
75    /// The bit pattern present in `pmpNcfg`.
76    pub cfg: u8,
77    /// The region kind encoded ni the `cfg` field.
78    pub kind: EpmpRegionKind,
79    /// The address range for this entry.
80    pub range: EpmpAddressRange,
81}
82
83#[derive(Clone, Debug, Default)]
84pub struct Epmp {
85    pub entry: Vec<EpmpEntry>,
86}
87
88impl Epmp {
89    /// Creates an `Epmp` from the raw RISCV32 form of the ePMP.
90    pub fn from_raw_rv32(pmpcfg: &[u32], pmpaddr: &[u32]) -> Result<Self> {
91        ensure!(
92            pmpcfg.len() * 4 == pmpaddr.len(),
93            EpmpError::InvalidLength(pmpcfg.len(), pmpaddr.len())
94        );
95        let pmpcfg = pmpcfg
96            .iter()
97            .flat_map(|word| word.to_le_bytes())
98            .collect::<Vec<u8>>();
99        let mut entry = Vec::with_capacity(pmpaddr.len());
100        for i in 0..pmpaddr.len() {
101            let region_kind = match (pmpcfg[i] >> EPMP_ADDR_SHIFT) & EPMP_ADDR_MASK {
102                0 => EpmpRegionKind::Off,
103                1 => EpmpRegionKind::Tor,
104                2 => EpmpRegionKind::Na4,
105                3 => EpmpRegionKind::Napot,
106                _ => unreachable!(),
107            };
108            let addr = match region_kind {
109                EpmpRegionKind::Off => EpmpAddressRange(0, 0),
110                EpmpRegionKind::Tor => {
111                    if i == 0 {
112                        EpmpAddressRange(0, (pmpaddr[i] as u64) << 2)
113                    } else {
114                        EpmpAddressRange((pmpaddr[i - 1] as u64) << 2, (pmpaddr[i] as u64) << 2)
115                    }
116                }
117                EpmpRegionKind::Na4 => {
118                    EpmpAddressRange((pmpaddr[i] as u64) << 2, ((pmpaddr[i] as u64) << 2) + 4)
119                }
120                EpmpRegionKind::Napot => {
121                    let size = pmpaddr[i].trailing_ones();
122                    let addr = ((pmpaddr[i] & (pmpaddr[i] + 1)) as u64) << 2;
123                    let length = (1 << (size + 3)) as u64;
124                    EpmpAddressRange(addr, addr + length)
125                }
126            };
127            entry.push(EpmpEntry {
128                cfg: pmpcfg[i] & !(EPMP_ADDR_MASK << EPMP_ADDR_SHIFT),
129                kind: region_kind,
130                range: addr,
131            });
132        }
133        Ok(Epmp { entry })
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::constants::*;
140    use super::*;
141
142    // This particular set of PMPCFG / PMPADDR values was captured from the ROM
143    // and manually modified to encompass all of the decode options.
144    // The modifications are:
145    // - Addition of entry 6: a duplicate of entry 2 with the perms set to RWX.
146    const PMPCFG: [u32; 4] = [0x998d00, 0x1f998d, 0x8b000000, 0x9b909f00];
147    const PMPADDR: [u32; 16] = [
148        0x2000, 0x3411, 0x2fff, 0x8000100, 0x8001203, 0x801ffff, 0x2fff, 0x0, 0x0, 0x0, 0x10000000,
149        0x14000000, 0x0, 0x41ff, 0x4007000, 0x4003fff,
150    ];
151
152    #[test]
153    fn test_epmp_decode() -> Result<()> {
154        let epmp = Epmp::from_raw_rv32(&PMPCFG, &PMPADDR)?;
155
156        // The 0th entry is the bottom of a TOR range.  The decoded version of entry 0 should
157        // appear as an "off" region.
158        assert!(matches!(
159            epmp.entry[0],
160            EpmpEntry {
161                cfg: 0,
162                kind: EpmpRegionKind::Off,
163                range: EpmpAddressRange(0, 0)
164            }
165        ));
166        // The first entry is a TOR region representing the .text section of the ROM.
167        assert!(matches!(
168            epmp.entry[1],
169            EpmpEntry {
170                cfg: EPMP_CFG_LRX,
171                kind: EpmpRegionKind::Tor,
172                range: EpmpAddressRange(0x8000, 0xd044)
173            }
174        ));
175        // The second entry is a Napot region representing the entire 32K of ROM.
176        assert!(matches!(
177            epmp.entry[2],
178            EpmpEntry {
179                cfg: EPMP_CFG_LRO,
180                kind: EpmpRegionKind::Napot,
181                range: EpmpAddressRange(0x8000, 0x10000)
182            }
183        ));
184        // The sixth entry is a Napot region representing the entire 32K of ROM, but is set to
185        // RWX (tests the decode of the `cfg` field with lock bit clear).
186        assert!(matches!(
187            epmp.entry[6],
188            EpmpEntry {
189                cfg: EPMP_CFG_RWX,
190                kind: EpmpRegionKind::Napot,
191                range: EpmpAddressRange(0x8000, 0x10000)
192            }
193        ));
194
195        // The thirteenth entry is a Napot region represening the 4K RVDM RAM (LRWX).
196        assert!(matches!(
197            epmp.entry[13],
198            EpmpEntry {
199                cfg: EPMP_CFG_LRWX,
200                kind: EpmpRegionKind::Napot,
201                range: EpmpAddressRange(0x10000, 0x11000)
202            }
203        ));
204
205        // The 14th entry is an Na4 region in RAM representing the no-access stack guard.
206        eprintln!("entry14 = {:?}", epmp.entry[14]);
207        assert!(matches!(
208            epmp.entry[14],
209            EpmpEntry {
210                cfg: EPMP_CFG_LOCKED_NONE,
211                kind: EpmpRegionKind::Na4,
212                range: EpmpAddressRange(0x1001_c000, 0x1001_c004)
213            }
214        ));
215        // The 15th entry is an Napot region in RAM representing a read-write region.
216        assert!(matches!(
217            epmp.entry[15],
218            EpmpEntry {
219                cfg: EPMP_CFG_LRW,
220                kind: EpmpRegionKind::Napot,
221                range: EpmpAddressRange(0x1000_0000, 0x1002_0000)
222            }
223        ));
224        Ok(())
225    }
226}