[wip] modernise memory layout for MMU
This commit is contained in:
parent
dba700ad7a
commit
53c6139f20
|
@ -47,13 +47,11 @@ pub fn print_features() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
register_bitfields! {
|
register_bitfields! {u64,
|
||||||
u64,
|
|
||||||
|
|
||||||
// AArch64 Reference Manual page 2150
|
// AArch64 Reference Manual page 2150
|
||||||
STAGE1_DESCRIPTOR [
|
STAGE1_DESCRIPTOR [
|
||||||
/// Execute-never
|
/// Privileged execute-never
|
||||||
XN OFFSET(54) NUMBITS(1) [
|
PXN OFFSET(53) NUMBITS(1) [
|
||||||
False = 0,
|
False = 0,
|
||||||
True = 1
|
True = 1
|
||||||
],
|
],
|
||||||
|
@ -97,14 +95,146 @@ register_bitfields! {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FOUR_KIB: usize = 4 * 1024;
|
||||||
|
const FOUR_KIB_SHIFT: usize = 12; // log2(4 * 1024)
|
||||||
|
|
||||||
|
const TWO_MIB: usize = 2 * 1024 * 1024;
|
||||||
|
const TWO_MIB_SHIFT: usize = 21; // log2(2 * 1024 * 1024)
|
||||||
|
|
||||||
|
/// A descriptor pointing to the next page table.
|
||||||
|
struct TableDescriptor(register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>);
|
||||||
|
|
||||||
|
impl TableDescriptor {
|
||||||
|
fn new(next_lvl_table_addr: usize) -> Result<TableDescriptor, &'static str> {
|
||||||
|
if next_lvl_table_addr % FOUR_KIB != 0 {
|
||||||
|
return Err("TableDescriptor: Address is not 4 KiB aligned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let shifted = next_lvl_table_addr >> FOUR_KIB_SHIFT;
|
||||||
|
|
||||||
|
Ok(TableDescriptor(
|
||||||
|
STAGE1_DESCRIPTOR::VALID::True
|
||||||
|
+ STAGE1_DESCRIPTOR::TYPE::Table
|
||||||
|
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> u64 {
|
||||||
|
self.0.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A function that maps the generic memory range attributes to HW-specific
|
||||||
|
/// attributes of the MMU.
|
||||||
|
fn into_mmu_attributes(
|
||||||
|
attribute_fields: AttributeFields,
|
||||||
|
) -> register::FieldValue<u64, STAGE1_DESCRIPTOR::Register> {
|
||||||
|
use crate::memory::{AccessPermissions, MemAttributes};
|
||||||
|
|
||||||
|
// Memory attributes
|
||||||
|
let mut desc = match attribute_fields.mem_attributes {
|
||||||
|
MemAttributes::CacheableDRAM => {
|
||||||
|
STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL)
|
||||||
|
}
|
||||||
|
MemAttributes::NonCacheableDRAM => {
|
||||||
|
STAGE1_DESCRIPTOR::SH::InnerShareable
|
||||||
|
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE)
|
||||||
|
}
|
||||||
|
MemAttributes::Device => {
|
||||||
|
STAGE1_DESCRIPTOR::SH::OuterShareable
|
||||||
|
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE_NGNRE)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Access Permissions
|
||||||
|
desc += match attribute_fields.acc_perms {
|
||||||
|
AccessPermissions::ReadOnly => STAGE1_DESCRIPTOR::AP::RO_EL1,
|
||||||
|
AccessPermissions::ReadWrite => STAGE1_DESCRIPTOR::AP::RW_EL1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute Never
|
||||||
|
desc += if attribute_fields.execute_never {
|
||||||
|
STAGE1_DESCRIPTOR::PXN::True
|
||||||
|
} else {
|
||||||
|
STAGE1_DESCRIPTOR::PXN::False
|
||||||
|
};
|
||||||
|
|
||||||
|
desc
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Level2 block descriptor with 2 MiB aperture.
|
||||||
|
///
|
||||||
|
/// The output points to physical memory.
|
||||||
|
struct Lvl2BlockDescriptor(register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>);
|
||||||
|
|
||||||
|
impl Lvl2BlockDescriptor {
|
||||||
|
fn new(
|
||||||
|
output_addr: usize,
|
||||||
|
attribute_fields: AttributeFields,
|
||||||
|
) -> Result<Lvl2BlockDescriptor, &'static str> {
|
||||||
|
if output_addr % TWO_MIB != 0 {
|
||||||
|
return Err("BlockDescriptor: Address is not 2 MiB aligned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let shifted = output_addr >> TWO_MIB_SHIFT;
|
||||||
|
|
||||||
|
Ok(Lvl2BlockDescriptor(
|
||||||
|
STAGE1_DESCRIPTOR::VALID::True
|
||||||
|
+ STAGE1_DESCRIPTOR::AF::True
|
||||||
|
+ into_mmu_attributes(attribute_fields)
|
||||||
|
+ STAGE1_DESCRIPTOR::TYPE::Block
|
||||||
|
+ STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(shifted as u64),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> u64 {
|
||||||
|
self.0.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A page descriptor with 4 KiB aperture.
|
||||||
|
///
|
||||||
|
/// The output points to physical memory.
|
||||||
|
struct PageDescriptor(register::FieldValue<u64, STAGE1_DESCRIPTOR::Register>);
|
||||||
|
|
||||||
|
impl PageDescriptor {
|
||||||
|
fn new(
|
||||||
|
output_addr: usize,
|
||||||
|
attribute_fields: AttributeFields,
|
||||||
|
) -> Result<PageDescriptor, &'static str> {
|
||||||
|
if output_addr % FOUR_KIB != 0 {
|
||||||
|
return Err("PageDescriptor: Address is not 4 KiB aligned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let shifted = output_addr >> FOUR_KIB_SHIFT;
|
||||||
|
|
||||||
|
Ok(PageDescriptor(
|
||||||
|
STAGE1_DESCRIPTOR::VALID::True
|
||||||
|
+ STAGE1_DESCRIPTOR::AF::True
|
||||||
|
+ into_mmu_attributes(attribute_fields)
|
||||||
|
+ STAGE1_DESCRIPTOR::TYPE::Table
|
||||||
|
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(shifted as u64),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> u64 {
|
||||||
|
self.0.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait BaseAddr {
|
trait BaseAddr {
|
||||||
fn base_addr(&self) -> u64;
|
fn base_addr_u64(&self) -> u64;
|
||||||
|
fn base_addr_usize(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BaseAddr for [u64; 512] {
|
impl BaseAddr for [u64; 512] {
|
||||||
fn base_addr(&self) -> u64 {
|
fn base_addr_u64(&self) -> u64 {
|
||||||
self as *const u64 as u64
|
self as *const u64 as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn base_addr_usize(&self) -> usize {
|
||||||
|
self as *const u64 as usize
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const NUM_ENTRIES_4KIB: usize = 512;
|
const NUM_ENTRIES_4KIB: usize = 512;
|
||||||
|
@ -120,16 +250,13 @@ static mut LVL2_TABLE: PageTable = PageTable {
|
||||||
entries: [0; NUM_ENTRIES_4KIB],
|
entries: [0; NUM_ENTRIES_4KIB],
|
||||||
};
|
};
|
||||||
|
|
||||||
static mut SINGLE_LVL3_TABLE: PageTable = PageTable {
|
static mut LVL3_TABLE: PageTable = PageTable {
|
||||||
entries: [0; NUM_ENTRIES_4KIB],
|
entries: [0; NUM_ENTRIES_4KIB],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Set up identity mapped page tables for the first 1 gigabyte of address space.
|
/// Setup function for the MAIR_EL1 register.
|
||||||
/// default: 880 MB ARM ram, 128MB VC
|
fn set_up_mair() {
|
||||||
pub unsafe fn init() {
|
// Define the three memory types that we will map. Normal DRAM, Uncached and device.
|
||||||
print_features();
|
|
||||||
|
|
||||||
// First, define the three memory types that we will map. Normal DRAM, Uncached and device.
|
|
||||||
MAIR_EL1.write(
|
MAIR_EL1.write(
|
||||||
// Attribute 2
|
// Attribute 2
|
||||||
MAIR_EL1::Attr2_HIGH::Device
|
MAIR_EL1::Attr2_HIGH::Device
|
||||||
|
@ -141,105 +268,69 @@ pub unsafe fn init() {
|
||||||
+ MAIR_EL1::Attr0_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
|
+ MAIR_EL1::Attr0_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
|
||||||
+ MAIR_EL1::Attr0_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc,
|
+ MAIR_EL1::Attr0_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Three descriptive consts for indexing into the correct MAIR_EL1 attributes.
|
// Three descriptive consts for indexing into the correct MAIR_EL1 attributes.
|
||||||
mod mair {
|
mod mair {
|
||||||
pub const NORMAL: u64 = 0;
|
pub const NORMAL: u64 = 0;
|
||||||
pub const NORMAL_NC: u64 = 1;
|
pub const NORMAL_NON_CACHEABLE: u64 = 1;
|
||||||
pub const DEVICE_NGNRE: u64 = 2;
|
pub const DEVICE_NGNRE: u64 = 2;
|
||||||
// DEVICE_GRE
|
// DEVICE_GRE
|
||||||
// DEVICE_NGNRNE
|
// DEVICE_NGNRNE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the first LVL2 entry, pointing to a 4KiB table base address.
|
/// Set up identity mapped page tables for the first 1 gigabyte of address space.
|
||||||
let lvl3_base: u64 = SINGLE_LVL3_TABLE.entries.base_addr() >> 12;
|
/// default: 880 MB ARM ram, 128MB VC
|
||||||
LVL2_TABLE.entries[0] = (STAGE1_DESCRIPTOR::VALID::True
|
pub unsafe fn init() -> Result<(), &'static str> {
|
||||||
+ STAGE1_DESCRIPTOR::TYPE::Table
|
// Prepare the memory attribute indirection register.
|
||||||
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(lvl3_base))
|
set_up_mair();
|
||||||
.value;
|
|
||||||
|
|
||||||
// For educational purposes and fun, let the start of the second 2 MiB block
|
// Point the first 2 MiB of virtual addresses to the follow-up LVL3
|
||||||
// point to the 2 MiB aperture which contains the UART's base address.
|
// page-table.
|
||||||
let uart_phys_base: u64 = (crate::platform::mini_uart::UART1_BASE >> 21).into();
|
LVL2_TABLE.entries[0] = match TableDescriptor::new(LVL3_TABLE.entries.base_addr_usize()) {
|
||||||
LVL2_TABLE.entries[1] = (STAGE1_DESCRIPTOR::VALID::True
|
Err(s) => return Err(s),
|
||||||
+ STAGE1_DESCRIPTOR::TYPE::Block
|
Ok(d) => d.value(),
|
||||||
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE_NGNRE)
|
|
||||||
+ STAGE1_DESCRIPTOR::AP::RW_EL1
|
|
||||||
+ STAGE1_DESCRIPTOR::SH::OuterShareable
|
|
||||||
+ STAGE1_DESCRIPTOR::AF::True
|
|
||||||
+ STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(uart_phys_base)
|
|
||||||
+ STAGE1_DESCRIPTOR::XN::True)
|
|
||||||
.value;
|
|
||||||
|
|
||||||
// Fill the rest of the LVL2 (2MiB) entries as block
|
|
||||||
// descriptors. Differentiate between normal, VC and device mem.
|
|
||||||
let vc_base: u64 = (0x3700_0000u32 >> 21).into();
|
|
||||||
|
|
||||||
let mmio_base: u64 = (crate::platform::rpi3::BcmHost::get_peripheral_address() >> 21).into();
|
|
||||||
let common = STAGE1_DESCRIPTOR::VALID::True
|
|
||||||
+ STAGE1_DESCRIPTOR::TYPE::Block
|
|
||||||
+ STAGE1_DESCRIPTOR::AP::RW_EL1
|
|
||||||
+ STAGE1_DESCRIPTOR::AF::True
|
|
||||||
+ STAGE1_DESCRIPTOR::XN::True;
|
|
||||||
|
|
||||||
// Notice the skip(2)
|
|
||||||
for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(2) {
|
|
||||||
let j: u64 = i as u64;
|
|
||||||
|
|
||||||
let mem_attr = if j >= mmio_base {
|
|
||||||
STAGE1_DESCRIPTOR::SH::OuterShareable
|
|
||||||
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE_NGNRE)
|
|
||||||
} else if j >= vc_base {
|
|
||||||
STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NC)
|
|
||||||
} else {
|
|
||||||
STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(j)).value;
|
// Fill the rest of the LVL2 (2 MiB) entries as block descriptors.
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, fill the single LVL3 table (4 KiB granule). Differentiate
|
|
||||||
// between code+RO and RW pages.
|
|
||||||
//
|
//
|
||||||
// Using the linker script, we ensure that the RO area is consecutive and 4
|
// Notice the skip(1) which makes the iteration start at the second 2 MiB
|
||||||
// KiB aligned, and we export the boundaries via symbols.
|
// block (0x20_0000).
|
||||||
extern "C" {
|
for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) {
|
||||||
// The inclusive start of the read-only area, aka the address of the
|
let virt_addr = block_descriptor_nr << TWO_MIB_SHIFT;
|
||||||
// first byte of the area.
|
|
||||||
static mut __ro_start: u64;
|
|
||||||
|
|
||||||
// The non-inclusive end of the read-only area, aka the address of the
|
let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
|
||||||
// first byte _after_ the RO area.
|
Err(s) => return Err(s),
|
||||||
static mut __ro_end: u64;
|
Ok((a, b)) => (a, b),
|
||||||
}
|
|
||||||
|
|
||||||
const PAGESIZE: u64 = 4096;
|
|
||||||
let ro_first_page_index: u64 = &__ro_start as *const _ as u64 / PAGESIZE;
|
|
||||||
|
|
||||||
// Notice the subtraction to calculate the last page index of the RO area
|
|
||||||
// and not the first page index after the RO area.
|
|
||||||
let ro_last_page_index: u64 = (&__ro_end as *const _ as u64 / PAGESIZE) - 1;
|
|
||||||
|
|
||||||
let common = STAGE1_DESCRIPTOR::VALID::True
|
|
||||||
+ STAGE1_DESCRIPTOR::TYPE::Table
|
|
||||||
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL)
|
|
||||||
+ STAGE1_DESCRIPTOR::SH::InnerShareable
|
|
||||||
+ STAGE1_DESCRIPTOR::AF::True;
|
|
||||||
|
|
||||||
for (i, entry) in SINGLE_LVL3_TABLE.entries.iter_mut().enumerate() {
|
|
||||||
let j: u64 = i as u64;
|
|
||||||
|
|
||||||
let mem_attr = if j < ro_first_page_index || j > ro_last_page_index {
|
|
||||||
STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True
|
|
||||||
} else {
|
|
||||||
STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False
|
|
||||||
};
|
};
|
||||||
|
|
||||||
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value;
|
let block_desc = match Lvl2BlockDescriptor::new(output_addr, attribute_fields) {
|
||||||
|
Err(s) => return Err(s),
|
||||||
|
Ok(desc) => desc,
|
||||||
|
};
|
||||||
|
|
||||||
|
*entry = block_desc.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, fill the single LVL3 table (4 KiB granule).
|
||||||
|
for (page_descriptor_nr, entry) in LVL3_TABLE.entries.iter_mut().enumerate() {
|
||||||
|
let virt_addr = page_descriptor_nr << FOUR_KIB_SHIFT;
|
||||||
|
|
||||||
|
let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
|
||||||
|
Err(s) => return Err(s),
|
||||||
|
Ok((a, b)) => (a, b),
|
||||||
|
};
|
||||||
|
|
||||||
|
let page_desc = match PageDescriptor::new(output_addr, attribute_fields) {
|
||||||
|
Err(s) => return Err(s),
|
||||||
|
Ok(desc) => desc,
|
||||||
|
};
|
||||||
|
|
||||||
|
*entry = page_desc.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Point to the LVL2 table base address in TTBR0.
|
// Point to the LVL2 table base address in TTBR0.
|
||||||
TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr());
|
TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64());
|
||||||
|
|
||||||
// Configure various settings of stage 1 of the EL1 translation regime.
|
// Configure various settings of stage 1 of the EL1 translation regime.
|
||||||
let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
|
let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
|
||||||
|
@ -269,7 +360,6 @@ pub unsafe fn init() {
|
||||||
* been dynamically patched at the PoU.
|
* been dynamically patched at the PoU.
|
||||||
*/
|
*/
|
||||||
barrier::isb(barrier::SY);
|
barrier::isb(barrier::SY);
|
||||||
asm!("ic iallu
|
|
||||||
dsb nsh" :::: "volatile");
|
Ok(())
|
||||||
barrier::isb(barrier::SY);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,6 +179,7 @@ fn print_help() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_mmu() {
|
fn init_mmu() {
|
||||||
|
mmu::print_features();
|
||||||
unsafe {
|
unsafe {
|
||||||
mmu::init();
|
mmu::init();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue