Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a996e69

Browse files
Merge #2422
2422: OpenTitan: Enable PMP for kernel regions r=bradjc a=alistair23 ### Pull Request Overview This PR applies on top of #2420 This PR adds support for marking regions of the kernel as protected by PMP. This allows some regions to have reduced permissions applied to them. This can be used to restrict write/execute to different regions used by the kernel. ### Testing Strategy Boot Tock and an app on OpenTitan FPGA and QEMU. ### TODO or Help Wanted ### Documentation Updated - [X] Updated the relevant files in `/docs`, or no updates are required. ### Formatting - [X] Ran `make prepush`. Co-authored-by: Alistair Francis <[email protected]>
2 parents 106886f + d902dee commit a996e69

File tree

3 files changed

+210
-6
lines changed

3 files changed

+210
-6
lines changed

arch/rv32i/src/pmp.rs

Lines changed: 142 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub struct PMP<const MAX_AVAILABLE_REGIONS_OVER_TWO: usize> {
6262
/// This is a 64-bit mask of locked regions.
6363
/// Each bit that is set in this mask indicates that the region is locked
6464
/// and cannot be used by Tock.
65-
locked_region_mask: u64,
65+
locked_region_mask: Cell<u64>,
6666
/// This is the total number of avaliable regions.
6767
/// This will be between 0 and MAX_AVAILABLE_REGIONS_OVER_TWO * 2 depending
6868
/// on the hardware and previous boot stages.
@@ -115,7 +115,7 @@ impl<const MAX_AVAILABLE_REGIONS_OVER_TWO: usize> PMP<MAX_AVAILABLE_REGIONS_OVER
115115
Self {
116116
last_configured_for: MapCell::empty(),
117117
num_regions,
118-
locked_region_mask,
118+
locked_region_mask: Cell::new(locked_region_mask),
119119
}
120120
}
121121
}
@@ -244,6 +244,7 @@ impl<const MAX_AVAILABLE_REGIONS_OVER_TWO: usize> fmt::Display
244244
}
245245

246246
impl<const MAX_AVAILABLE_REGIONS_OVER_TWO: usize> PMPConfig<MAX_AVAILABLE_REGIONS_OVER_TWO> {
247+
/// Get the first unused region
247248
fn unused_region_number(&self, locked_region_mask: u64) -> Option<usize> {
248249
for (number, region) in self.regions.iter().enumerate() {
249250
if self.app_memory_region.contains(&number) {
@@ -259,6 +260,26 @@ impl<const MAX_AVAILABLE_REGIONS_OVER_TWO: usize> PMPConfig<MAX_AVAILABLE_REGION
259260
}
260261
None
261262
}
263+
264+
/// Get the last unused region
265+
/// The app regions need to be lower then the kernel to ensure they
266+
/// match before the kernel ones.
267+
fn unused_kernel_region_number(&self, locked_region_mask: u64) -> Option<usize> {
268+
for (num, region) in self.regions.iter().rev().enumerate() {
269+
let number = MAX_AVAILABLE_REGIONS_OVER_TWO - num - 1;
270+
if self.app_memory_region.contains(&number) {
271+
continue;
272+
}
273+
// This region exists, but is locked
274+
if locked_region_mask & (1 << number) > 0 {
275+
continue;
276+
}
277+
if region.is_none() {
278+
return Some(number);
279+
}
280+
}
281+
None
282+
}
262283
}
263284

264285
impl<const MAX_AVAILABLE_REGIONS_OVER_TWO: usize> kernel::mpu::MPU
@@ -357,7 +378,7 @@ impl<const MAX_AVAILABLE_REGIONS_OVER_TWO: usize> kernel::mpu::MPU
357378
}
358379
}
359380

360-
let region_num = config.unused_region_number(self.locked_region_mask)?;
381+
let region_num = config.unused_region_number(self.locked_region_mask.get())?;
361382

362383
// Logical region
363384
let mut start = unallocated_memory_start as usize;
@@ -411,7 +432,7 @@ impl<const MAX_AVAILABLE_REGIONS_OVER_TWO: usize> kernel::mpu::MPU
411432
let region_num = if config.app_memory_region.is_some() {
412433
config.app_memory_region.unwrap_or(0)
413434
} else {
414-
config.unused_region_number(self.locked_region_mask)?
435+
config.unused_region_number(self.locked_region_mask.get())?
415436
};
416437

417438
// App memory size is what we actual set the region to. So this region
@@ -556,3 +577,120 @@ impl<const MAX_AVAILABLE_REGIONS_OVER_TWO: usize> kernel::mpu::MPU
556577
}
557578
}
558579
}
580+
581+
/// This is PMP support for kernel regions
582+
/// PMP does not allow a deny by default option, so all regions not marked
583+
/// with the below commands will have full access.
584+
/// This is still a useful implementation as it can be used to limit the
585+
/// kernels access, for example removing execute permission from regions
586+
/// we don't need to execute from and removing write permissions from
587+
/// executable reions.
588+
impl<const MAX_AVAILABLE_REGIONS_OVER_TWO: usize> kernel::mpu::KernelMPU
589+
for PMP<MAX_AVAILABLE_REGIONS_OVER_TWO>
590+
{
591+
type KernelMpuConfig = PMPConfig<MAX_AVAILABLE_REGIONS_OVER_TWO>;
592+
593+
fn allocate_kernel_region(
594+
&self,
595+
memory_start: *const u8,
596+
memory_size: usize,
597+
permissions: mpu::Permissions,
598+
config: &mut Self::KernelMpuConfig,
599+
) -> Option<mpu::Region> {
600+
for region in config.regions.iter() {
601+
if region.is_some() {
602+
if region.unwrap().overlaps(memory_start, memory_size) {
603+
return None;
604+
}
605+
}
606+
}
607+
608+
let region_num = config.unused_kernel_region_number(self.locked_region_mask.get())?;
609+
610+
// Logical region
611+
let mut start = memory_start as usize;
612+
let mut size = memory_size;
613+
614+
// Region start always has to align to 4 bytes
615+
if start % 4 != 0 {
616+
start += 4 - (start % 4);
617+
}
618+
619+
// Region size always has to align to 4 bytes
620+
if size % 4 != 0 {
621+
size += 4 - (size % 4);
622+
}
623+
624+
// Regions must be at least 8 bytes
625+
if size < 8 {
626+
size = 8;
627+
}
628+
629+
let region = PMPRegion::new(start as *const u8, size, permissions);
630+
631+
config.regions[region_num] = Some(region);
632+
633+
// Mark the region as locked so that the app PMP doesn't use it.
634+
let mut mask = self.locked_region_mask.get();
635+
mask |= 1 << region_num;
636+
self.locked_region_mask.set(mask);
637+
638+
Some(mpu::Region::new(start as *const u8, size))
639+
}
640+
641+
fn enable_kernel_mpu(&self, config: &mut Self::KernelMpuConfig) {
642+
for (i, region) in config.regions.iter().rev().enumerate() {
643+
let x = MAX_AVAILABLE_REGIONS_OVER_TWO - i - 1;
644+
match region {
645+
Some(r) => {
646+
let cfg_val = r.cfg.value as usize;
647+
let start = r.location.0 as usize;
648+
let size = r.location.1;
649+
650+
match x % 2 {
651+
0 => {
652+
csr::CSR.pmpaddr_set((x * 2) + 1, (start + size) >> 2);
653+
// Disable access up to the start address
654+
csr::CSR.pmpconfig_modify(
655+
x / 2,
656+
csr::pmpconfig::pmpcfg::r0::CLEAR
657+
+ csr::pmpconfig::pmpcfg::w0::CLEAR
658+
+ csr::pmpconfig::pmpcfg::x0::CLEAR
659+
+ csr::pmpconfig::pmpcfg::a0::CLEAR,
660+
);
661+
csr::CSR.pmpaddr_set(x * 2, start >> 2);
662+
663+
// Set access to end address
664+
csr::CSR
665+
.pmpconfig_set(x / 2, cfg_val << 8 | csr::CSR.pmpconfig_get(x / 2));
666+
// Lock the CSR
667+
csr::CSR.pmpconfig_modify(x / 2, csr::pmpconfig::pmpcfg::l1::SET);
668+
}
669+
1 => {
670+
csr::CSR.pmpaddr_set((x * 2) + 1, (start + size) >> 2);
671+
// Disable access up to the start address
672+
csr::CSR.pmpconfig_modify(
673+
x / 2,
674+
csr::pmpconfig::pmpcfg::r2::CLEAR
675+
+ csr::pmpconfig::pmpcfg::w2::CLEAR
676+
+ csr::pmpconfig::pmpcfg::x2::CLEAR
677+
+ csr::pmpconfig::pmpcfg::a2::CLEAR,
678+
);
679+
csr::CSR.pmpaddr_set(x * 2, start >> 2);
680+
681+
// Set access to end address
682+
csr::CSR.pmpconfig_set(
683+
x / 2,
684+
cfg_val << 24 | csr::CSR.pmpconfig_get(x / 2),
685+
);
686+
// Lock the CSR
687+
csr::CSR.pmpconfig_modify(x / 2, csr::pmpconfig::pmpcfg::l3::SET);
688+
}
689+
_ => break,
690+
}
691+
}
692+
None => {}
693+
};
694+
}
695+
}
696+
}

boards/earlgrey-nexysvideo/src/main.rs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ use kernel::hil;
1717
use kernel::hil::i2c::I2CMaster;
1818
use kernel::hil::led::LedHigh;
1919
use kernel::hil::time::Alarm;
20-
use kernel::Chip;
20+
use kernel::mpu::KernelMPU;
2121
use kernel::Platform;
2222
use kernel::{create_capability, debug, static_init};
23+
use kernel::{mpu, Chip};
2324
use rv32i::csr;
2425

2526
#[allow(dead_code)]
@@ -310,6 +311,24 @@ pub unsafe fn reset_handler() {
310311
static mut _sappmem: u8;
311312
/// End of the RAM region for app memory.
312313
static _eappmem: u8;
314+
/// The start of the kernel stack (Included only for kernel PMP)
315+
static _sstack: u8;
316+
/// The end of the kernel stack (Included only for kernel PMP)
317+
static _estack: u8;
318+
/// The start of the kernel text (Included only for kernel PMP)
319+
static _stext: u8;
320+
/// The end of the kernel text (Included only for kernel PMP)
321+
static _etext: u8;
322+
/// The start of the kernel relocation region
323+
/// (Included only for kernel PMP)
324+
static _srelocate: u8;
325+
/// The end of the kernel relocation region
326+
/// (Included only for kernel PMP)
327+
static _erelocate: u8;
328+
/// The start of the kernel BSS (Included only for kernel PMP)
329+
static _szero: u8;
330+
/// The end of the kernel BSS (Included only for kernel PMP)
331+
static _ezero: u8;
313332
}
314333

315334
let earlgrey_nexysvideo = EarlGreyNexysVideo {
@@ -322,6 +341,53 @@ pub unsafe fn reset_handler() {
322341
i2c_master,
323342
};
324343

344+
// This is PMP support for kernel regions
345+
// PMP does not allow a deny by default option, so all regions not marked
346+
// with the below commands will have full access.
347+
// This is still a useful implementation as it can be used to limit the
348+
// kernels access, for example removing execute permission from regions
349+
// we don't need to execute from and removing write permissions from
350+
// executable reions.
351+
let mut mpu_config = rv32i::pmp::PMPConfig::default();
352+
// The kernel stack
353+
chip.pmp
354+
.allocate_kernel_region(
355+
&_sstack as *const u8,
356+
&_estack as *const u8 as usize - &_sstack as *const u8 as usize,
357+
mpu::Permissions::ReadWriteOnly,
358+
&mut mpu_config,
359+
)
360+
.unwrap();
361+
// The kernel text
362+
chip.pmp
363+
.allocate_kernel_region(
364+
&_stext as *const u8,
365+
&_etext as *const u8 as usize - &_stext as *const u8 as usize,
366+
mpu::Permissions::ReadExecuteOnly,
367+
&mut mpu_config,
368+
)
369+
.unwrap();
370+
// The kernel relocate data
371+
chip.pmp
372+
.allocate_kernel_region(
373+
&_srelocate as *const u8,
374+
&_erelocate as *const u8 as usize - &_srelocate as *const u8 as usize,
375+
mpu::Permissions::ReadWriteOnly,
376+
&mut mpu_config,
377+
)
378+
.unwrap();
379+
// The kernel BSS
380+
chip.pmp
381+
.allocate_kernel_region(
382+
&_szero as *const u8,
383+
&_ezero as *const u8 as usize - &_szero as *const u8 as usize,
384+
mpu::Permissions::ReadWriteOnly,
385+
&mut mpu_config,
386+
)
387+
.unwrap();
388+
389+
chip.pmp.enable_kernel_mpu(&mut mpu_config);
390+
325391
kernel::procs::load_processes(
326392
board_kernel,
327393
chip,

chips/earlgrey/src/chip.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::plic::PLIC;
1616

1717
pub struct EarlGrey<'a, A: 'static + Alarm<'static>, I: InterruptService<()> + 'a> {
1818
userspace_kernel_boundary: SysCall,
19-
pmp: PMP<8>,
19+
pub pmp: PMP<8>,
2020
plic: &'a Plic,
2121
scheduler_timer: kernel::VirtualSchedulerTimer<A>,
2222
timer: &'static crate::timer::RvTimer<'static>,

0 commit comments

Comments
 (0)