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(())
    }
}