Skip to content

Commit

Permalink
runtime: Enable PMP (#62)
Browse files Browse the repository at this point in the history
I wasn't able to use an existing PMP layout due to our potential for multiple MMIO blocks as well as our unified RAM block: the existing PMP layouts that Tock has built in expect things to be aligned to neater boundaries with power of two sizes.

So, I modified the standard kernel EPMP layout to support what we need.

The "E" is used to indicate we support the SMEPMP extension, which provides some additional mode bits that allow more fine-grained control of shared areas between machine and user mode and allowing us to set a default deny all policy.

I moved around our memory map a little to make the PMP setup a little easier and reduce the number of regions we need, but left it flexible enough that we can still accommodate whatever memory map changes we need to make in the future. I moved the emulator-only UART and control peripherals to be in the 0x1000_0000 range, which will be accessible from user mode, while the 0x2000_0000 peripherals will only be accessible by the kernel.

This also fixes a bug in the CPU emulator's PMP calculation when using natural power of 2 regions (NAPOT). I've also ported that fix over to Caliptra core.
  • Loading branch information
swenson authored Dec 18, 2024
1 parent e0a5333 commit c7160df
Show file tree
Hide file tree
Showing 13 changed files with 728 additions and 37 deletions.
6 changes: 3 additions & 3 deletions emulator/cpu/src/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ pub struct CoverageBitmaps<'a> {
pub iccm: &'a bit_vec::BitVec,
}

const ICCM_SIZE: usize = 128 * 1024;
const ICCM_ORG: usize = 0x40000000;
const ICCM_SIZE: usize = RAM_SIZE as usize;
const ICCM_ORG: usize = RAM_OFFSET as usize;
const ICCM_UPPER: usize = ICCM_ORG + ICCM_SIZE - 1;

const ROM_SIZE: usize = 48 * 1024;
const ROM_SIZE: usize = emulator_types::ROM_SIZE as usize;
const ROM_ORG: usize = 0x00000000;
const ROM_UPPER: usize = ROM_ORG + ROM_SIZE - 1;

Expand Down
52 changes: 43 additions & 9 deletions emulator/cpu/src/csr_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,16 @@ impl Csr {
pub const PMPCFG_START: RvAddr = 0x3A0;

/// PMP configuration register range end, inclusive
pub const PMPCFG_END: RvAddr = 0x3A3;
pub const PMPCFG_END: RvAddr = 0x3AF;

/// PMP address register range start, inclusive
pub const PMPADDR_START: RvAddr = 0x3B0;

/// PMP address register range end, inclusive
pub const PMPADDR_END: RvAddr = 0x3C0;
pub const PMPADDR_END: RvAddr = 0x3EF;

/// Number of PMP address/cfg registers
pub const PMPCOUNT: usize = 16;
pub const PMPCOUNT: usize = 64;

/// Create a new Configurations and Status register
///
Expand Down Expand Up @@ -766,8 +766,9 @@ impl CsrFile {
return Ok(false);
}

let addr = addr as u64;
let pmpaddr = self.any_read(RvPrivMode::M, Csr::PMPADDR_START + index as RvAddr)?;
let pmpaddr_shift = pmpaddr << 2;
let pmpaddr_shift = (pmpaddr as u64) << 2;
let addr_top;
let addr_bottom;

Expand All @@ -777,7 +778,9 @@ impl CsrFile {
// otherwise it's the previous one
addr_top = pmpaddr_shift;
addr_bottom = if index > 0 {
self.any_read(RvPrivMode::M, Csr::PMPADDR_START + (index - 1) as RvAddr)? << 2
(self.any_read(RvPrivMode::M, Csr::PMPADDR_START + (index - 1) as RvAddr)?
as u64)
<< 2
} else {
0
};
Expand All @@ -788,13 +791,12 @@ impl CsrFile {
addr_bottom = pmpaddr_shift;
}
RvPmpAddrMode::Napot => {
// Range from 8..32
addr_top = pmpaddr_shift + (1 << (pmpaddr.trailing_ones() + 3));
addr_bottom = pmpaddr_shift;
let (bot, top) = decode_napot_pmpaddr(pmpaddr);
addr_bottom = bot;
addr_top = top;
}
_ => unreachable!(),
}

Ok(addr >= addr_bottom && addr < addr_top)
}

Expand Down Expand Up @@ -977,12 +979,44 @@ impl CsrFile {
}
}

// Returns the base address (inclusive) and end address (exclusive) of a NAPOT PMP address
fn decode_napot_pmpaddr(addr: u32) -> (u64, u64) {
let bits = addr.trailing_ones();
let addr = addr as u64;
let base = (addr & !((1 << bits) - 1)) << 2;
(base, base + (1 << (bits + 3)))
}

#[cfg(test)]
mod tests {

use super::*;
use std::rc::Rc;

#[test]
fn test_decode_napot_pmpaddr() {
assert_eq!((0x0000_0000, 0x0000_0008), decode_napot_pmpaddr(0x00000000));
assert_eq!((0x0000_0000, 0x0000_0010), decode_napot_pmpaddr(0x00000001));
assert_eq!((0x0040_0000, 0x0040_8000), decode_napot_pmpaddr(0x00100fff));
assert_eq!((0x1000_0000, 0x2000_0000), decode_napot_pmpaddr(0x05ffffff));
assert_eq!(
(0x0000_0000, 0x1_0000_0000),
decode_napot_pmpaddr(0x1fffffff)
);
assert_eq!(
(0x0000_0000, 0x2_0000_0000),
decode_napot_pmpaddr(0x3fffffff)
);
assert_eq!(
(0x0000_0000, 0x4_0000_0000),
decode_napot_pmpaddr(0x7fffffff)
);
assert_eq!(
(0x0000_0000, 0x8_0000_0000),
decode_napot_pmpaddr(0xffffffff)
);
}

#[test]
fn test_u_mode_read_m_mode_csr() {
let clock = Rc::new(Clock::new());
Expand Down
12 changes: 6 additions & 6 deletions emulator/periph/src/root_bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ pub struct CaliptraRootBus {
#[peripheral(offset = 0x0000_0000, len = 0xc000)]
pub rom: Rom,

#[peripheral(offset = 0x2000_0000, len = 0x40)]
pub spi: SpiHost,

#[peripheral(offset = 0x2000_1000, len = 0x100)]
#[peripheral(offset = 0x1000_1000, len = 0x100)]
pub uart: Uart,

#[peripheral(offset = 0x2000_f000, len = 0x4)]
#[peripheral(offset = 0x1000_2000, len = 0x4)]
pub ctrl: EmuCtrl,

#[peripheral(offset = 0x3000_4000, len = 0x1000)]
#[peripheral(offset = 0x2000_0000, len = 0x40)]
pub spi: SpiHost,

#[peripheral(offset = 0x2000_1000, len = 0x1000)]
pub otp: Otp,

#[peripheral(offset = 0x4000_0000, len = 0x60000)]
Expand Down
6 changes: 3 additions & 3 deletions rom/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub fn panic_fmt(_pi: &PanicInfo) -> ! {

// Cause the emulator to exit
unsafe {
write_volatile(0x200f0000 as *mut u32, 0);
write_volatile(0x1000_2000 as *mut u32, 0);
}
unreachable!()
}
Expand All @@ -33,10 +33,10 @@ fn write_byte(b: u8) {
// # Safety
// Accesses memory-mapped registers.
unsafe {
write_volatile(0x2000_1041 as *mut u8, b);
write_volatile(0x1000_1041 as *mut u8, b);
}
}

fn _read_byte() -> u8 {
unsafe { read_volatile(0x2000_1041 as *mut u8) }
unsafe { read_volatile(0x1000_1041 as *mut u8) }
}
2 changes: 1 addition & 1 deletion romtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub(crate) fn print_to_console(buf: &str) {
for b in buf.bytes() {
// Print to this address for emulator output
unsafe {
core::ptr::write_volatile(0x2000_1041 as *mut u8, b);
core::ptr::write_volatile(0x1000_1041 as *mut u8, b);
}
}
}
6 changes: 6 additions & 0 deletions runtime/kernel_layout.ld
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,13 @@ SECTIONS
_eattributes = LOADADDR(.attributes) + SIZEOF(.attributes);*/
_sattributes = 0;
_eattributes = 0;
_srom = ORIGIN(rom);
_erom = ORIGIN(rom) + LENGTH(rom);
_sprog = ORIGIN(prog);
_eprog = ORIGIN(prog) + LENGTH(prog);
_ssram = ORIGIN(ram);
_esram = ORIGIN(ram) + LENGTH(ram);


/* This.. this is a dirty, not-fully-understood, hack. In some way, linking is
* a multi-pass process. i.e., if you were to ASSERT(0, "how many links")
Expand Down
50 changes: 49 additions & 1 deletion runtime/src/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
use crate::chip::VeeRDefaultPeripherals;
use crate::chip::TIMERS;
use crate::components as runtime_components;
use crate::pmp::CodeRegion;
use crate::pmp::DataRegion;
use crate::pmp::KernelTextRegion;
use crate::pmp::MMIORegion;
use crate::pmp::VeeRProtectionMMLEPMP;
use crate::timers::InternalTimers;

use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm};
Expand All @@ -17,6 +22,7 @@ use kernel::scheduler::cooperative::CooperativeSched;
use kernel::utilities::registers::interfaces::ReadWriteable;
use kernel::{create_capability, debug, static_init};
use rv32i::csr;
use rv32i::pmp::{NAPOTRegionSpec, TORRegionSpec};

// These symbols are defined in the linker script.
extern "C" {
Expand All @@ -28,6 +34,18 @@ extern "C" {
static mut _sappmem: u8;
/// End of the RAM region for app memory.
static _eappmem: u8;
/// The start of the kernel text (Included only for kernel PMP)
static _stext: u8;
/// The end of the kernel text (Included only for kernel PMP)
static _etext: u8;
/// The start of the kernel / app / storage flash (Included only for kernel PMP)
static _srom: u8;
/// The end of the kernel / app / storage flash (Included only for kernel PMP)
static _eprog: u8;
/// The start of the kernel / app RAM (Included only for kernel PMP)
static _ssram: u8;
/// The end of the kernel / app RAM (Included only for kernel PMP)
static _esram: u8;

pub(crate) static _pic_vector_table: u8;
}
Expand Down Expand Up @@ -155,6 +173,36 @@ pub unsafe fn main() {
// only machine mode
rv32i::configure_trap_handler();

// Set up memory protection immediately after setting the trap handler, to
// ensure that much of the board initialization routine runs with ePMP
// protection.

// fixed regions to allow user mode direct access to emulator control and UART
let user_mmio = [MMIORegion(
NAPOTRegionSpec::new(0x1000_0000 as *const u8, 0x1000_0000).unwrap(),
)];
// additional MMIO for machine only peripherals
let machine_mmio = [
MMIORegion(NAPOTRegionSpec::new(0x2000_0000 as *const u8, 0x2000_0000).unwrap()),
MMIORegion(NAPOTRegionSpec::new(0x6000_0000 as *const u8, 0x1_0000).unwrap()),
];

let epmp = VeeRProtectionMMLEPMP::new(
CodeRegion(
TORRegionSpec::new(core::ptr::addr_of!(_srom), core::ptr::addr_of!(_eprog)).unwrap(),
),
DataRegion(
TORRegionSpec::new(core::ptr::addr_of!(_ssram), core::ptr::addr_of!(_esram)).unwrap(),
),
// use the MMIO for the PIC
&user_mmio[..],
&machine_mmio[..],
KernelTextRegion(
TORRegionSpec::new(core::ptr::addr_of!(_stext), core::ptr::addr_of!(_etext)).unwrap(),
),
)
.unwrap();

// initialize capabilities
let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability);
let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability);
Expand Down Expand Up @@ -199,7 +247,7 @@ pub unsafe fn main() {
VeeRDefaultPeripherals::new(&*mux_alarm)
);

let chip = static_init!(VeeRChip, crate::chip::VeeR::new(peripherals));
let chip = static_init!(VeeRChip, crate::chip::VeeR::new(peripherals, epmp));
chip.init();
CHIP = Some(chip);

Expand Down
12 changes: 6 additions & 6 deletions runtime/src/chip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use crate::flash_ctrl;
use crate::io::SemihostUart;
use crate::pmp::{VeeRPMP, VeeRProtectionMMLEPMP};
use crate::timers::{InternalTimers, TimerInterrupts};
use capsules_core::virtualizers::virtual_alarm::MuxAlarm;
use core::fmt::Write;
Expand All @@ -18,7 +19,6 @@ use kernel::platform::chip::{Chip, InterruptService};
use kernel::utilities::registers::interfaces::{ReadWriteable, Readable};
use kernel::utilities::StaticRef;
use rv32i::csr::{mcause, mie::mie, CSR};
use rv32i::pmp::{simple::SimplePMP, PMPUserMPU};
use rv32i::syscall::SysCall;

use crate::pic::Pic;
Expand All @@ -40,7 +40,7 @@ pub struct VeeR<'a, I: InterruptService + 'a> {
pic: &'a Pic,
timers: &'static InternalTimers<'static>,
pub peripherals: &'a I,
pmp: PMPUserMPU<4, SimplePMP<8>>,
pmp: VeeRPMP,
}

pub struct VeeRDefaultPeripherals<'a> {
Expand Down Expand Up @@ -89,14 +89,14 @@ impl<'a> InterruptService for VeeRDefaultPeripherals<'a> {

impl<'a, I: InterruptService + 'a> VeeR<'a, I> {
/// # Safety
/// Accesses memory-mapped registers.
pub unsafe fn new(pic_interrupt_service: &'a I) -> Self {
/// Accesses memory<-mapped registers.
pub unsafe fn new(pic_interrupt_service: &'a I, epmp: VeeRProtectionMMLEPMP) -> Self {
Self {
userspace_kernel_boundary: SysCall::new(),
pic: &*addr_of!(PIC),
timers: &*addr_of!(TIMERS),
peripherals: pic_interrupt_service,
pmp: PMPUserMPU::new(SimplePMP::new().unwrap()),
pmp: rv32i::pmp::PMPUserMPU::new(epmp),
}
}

Expand Down Expand Up @@ -130,7 +130,7 @@ impl<'a, I: InterruptService + 'a> VeeR<'a, I> {
}

impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for VeeR<'a, I> {
type MPU = PMPUserMPU<4, SimplePMP<8>>;
type MPU = VeeRPMP;
type UserspaceKernelBoundary = SysCall;

fn mpu(&self) -> &Self::MPU {
Expand Down
6 changes: 3 additions & 3 deletions runtime/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub fn exit_emulator(exit_code: u32) -> ! {
// Safety: This is a safe memory address to write to for exiting the emulator.
unsafe {
// By writing to this address we can exit the emulator.
write_volatile(0x2000_f000 as *mut u32, exit_code);
write_volatile(0x1000_2000 as *mut u32, exit_code);
}
unreachable!()
}
Expand All @@ -70,15 +70,15 @@ impl IoWrite for Writer {
for b in buf {
// Print to this address for emulator output
unsafe {
write_volatile(0x2000_1041 as *mut u8, *b);
write_volatile(0x1000_1041 as *mut u8, *b);
}
}
buf.len()
}
}

fn read_byte() -> u8 {
unsafe { read_volatile(0x2000_1041 as *mut u8) }
unsafe { read_volatile(0x1000_1041 as *mut u8) }
}

pub struct SemihostUart<'a> {
Expand Down
2 changes: 2 additions & 0 deletions runtime/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub mod io;
#[cfg(target_arch = "riscv32")]
mod pic;
#[cfg(target_arch = "riscv32")]
mod pmp;
#[cfg(target_arch = "riscv32")]
#[allow(unused_imports)]
mod tests;
#[cfg(target_arch = "riscv32")]
Expand Down
Loading

0 comments on commit c7160df

Please sign in to comment.