Rewrite the kernel's static virtual memory mapping.
parent
5011d5e507
commit
47996b4b78
Binary file not shown.
Binary file not shown.
@ -0,0 +1,221 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
pub mod mmu;
|
||||
|
||||
/// System memory map.
|
||||
#[rustfmt::skip]
|
||||
pub mod map {
|
||||
pub const START: usize = 0x0000_0000;
|
||||
pub const END: usize = 0x3FFF_FFFF;
|
||||
|
||||
pub mod physical {
|
||||
pub const MMIO_BASE: usize = 0x3F00_0000;
|
||||
pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880;
|
||||
pub const GPIO_BASE: usize = MMIO_BASE + 0x0020_0000;
|
||||
pub const UART_BASE: usize = MMIO_BASE + 0x0020_1000;
|
||||
pub const MMIO_END: usize = super::END;
|
||||
}
|
||||
|
||||
pub mod virt {
|
||||
pub const KERN_STACK_START: usize = super::START;
|
||||
pub const KERN_STACK_END: usize = 0x0007_FFFF;
|
||||
|
||||
// The last 4 KiB slot in the first 2 MiB
|
||||
pub const REMAPPED_UART_BASE: usize = 0x001F_F000;
|
||||
pub const REMAPPED_UART_END: usize = 0x001F_FFFF;
|
||||
}
|
||||
}
|
||||
|
||||
/// Types used for compiling the virtual memory layout of the kernel using
|
||||
/// address ranges.
|
||||
pub mod kernel_mem_range {
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum MemAttributes {
|
||||
CacheableDRAM,
|
||||
Device,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum AccessPermissions {
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Translation {
|
||||
Identity,
|
||||
Offset(usize),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct AttributeFields {
|
||||
pub mem_attributes: MemAttributes,
|
||||
pub acc_perms: AccessPermissions,
|
||||
pub execute_never: bool,
|
||||
}
|
||||
|
||||
impl Default for AttributeFields {
|
||||
fn default() -> AttributeFields {
|
||||
AttributeFields {
|
||||
mem_attributes: MemAttributes::CacheableDRAM,
|
||||
acc_perms: AccessPermissions::ReadWrite,
|
||||
execute_never: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Descriptor {
|
||||
pub virtual_range: fn() -> RangeInclusive<usize>,
|
||||
pub translation: Translation,
|
||||
pub attribute_fields: AttributeFields,
|
||||
}
|
||||
}
|
||||
|
||||
use kernel_mem_range::*;
|
||||
|
||||
/// A virtual memory layout that is agnostic of the paging granularity that the
|
||||
/// hardware MMU will use.
|
||||
///
|
||||
/// Contains only special ranges, aka anything that is _not_ normal cacheable
|
||||
/// DRAM.
|
||||
static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 5] = [
|
||||
// Kernel stack
|
||||
Descriptor {
|
||||
virtual_range: || {
|
||||
RangeInclusive::new(map::virt::KERN_STACK_START, map::virt::KERN_STACK_END)
|
||||
},
|
||||
translation: Translation::Identity,
|
||||
attribute_fields: AttributeFields {
|
||||
mem_attributes: MemAttributes::CacheableDRAM,
|
||||
acc_perms: AccessPermissions::ReadWrite,
|
||||
execute_never: true,
|
||||
},
|
||||
},
|
||||
// Kernel code and RO data
|
||||
Descriptor {
|
||||
virtual_range: || {
|
||||
// Using the linker script, we ensure that the RO area is consecutive and 4
|
||||
// KiB aligned, and we export the boundaries via symbols:
|
||||
//
|
||||
// [__ro_start, __ro_end)
|
||||
extern "C" {
|
||||
// The inclusive start of the read-only area, aka the address of the
|
||||
// first byte of the area.
|
||||
static __ro_start: u64;
|
||||
|
||||
// The exclusive end of the read-only area, aka the address of
|
||||
// the first byte _after_ the RO area.
|
||||
static __ro_end: u64;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// Notice the subtraction to turn the exclusive end into an
|
||||
// inclusive end
|
||||
RangeInclusive::new(
|
||||
&__ro_start as *const _ as usize,
|
||||
&__ro_end as *const _ as usize - 1,
|
||||
)
|
||||
}
|
||||
},
|
||||
translation: Translation::Identity,
|
||||
attribute_fields: AttributeFields {
|
||||
mem_attributes: MemAttributes::CacheableDRAM,
|
||||
acc_perms: AccessPermissions::ReadOnly,
|
||||
execute_never: false,
|
||||
},
|
||||
},
|
||||
// Kernel data and BSS
|
||||
Descriptor {
|
||||
virtual_range: || {
|
||||
extern "C" {
|
||||
static __ro_end: u64;
|
||||
static __bss_end: u64;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
RangeInclusive::new(
|
||||
&__ro_end as *const _ as usize,
|
||||
&__bss_end as *const _ as usize - 1,
|
||||
)
|
||||
}
|
||||
},
|
||||
translation: Translation::Identity,
|
||||
attribute_fields: AttributeFields {
|
||||
mem_attributes: MemAttributes::CacheableDRAM,
|
||||
acc_perms: AccessPermissions::ReadWrite,
|
||||
execute_never: true,
|
||||
},
|
||||
},
|
||||
// Remapped UART
|
||||
Descriptor {
|
||||
virtual_range: || {
|
||||
RangeInclusive::new(map::virt::REMAPPED_UART_BASE, map::virt::REMAPPED_UART_END)
|
||||
},
|
||||
translation: Translation::Offset(map::physical::UART_BASE),
|
||||
attribute_fields: AttributeFields {
|
||||
mem_attributes: MemAttributes::Device,
|
||||
acc_perms: AccessPermissions::ReadWrite,
|
||||
execute_never: true,
|
||||
},
|
||||
},
|
||||
// Device MMIO
|
||||
Descriptor {
|
||||
virtual_range: || RangeInclusive::new(map::physical::MMIO_BASE, map::physical::MMIO_END),
|
||||
translation: Translation::Identity,
|
||||
attribute_fields: AttributeFields {
|
||||
mem_attributes: MemAttributes::Device,
|
||||
acc_perms: AccessPermissions::ReadWrite,
|
||||
execute_never: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/// For a given virtual address, find and return the output address and
|
||||
/// according attributes.
|
||||
///
|
||||
/// If the address is not covered in VIRTUAL_LAYOUT, return a default for normal
|
||||
/// cacheable DRAM.
|
||||
fn get_virt_addr_properties(virt_addr: usize) -> Result<(usize, AttributeFields), &'static str> {
|
||||
if virt_addr > map::END {
|
||||
return Err("Address out of range.");
|
||||
}
|
||||
|
||||
for i in KERNEL_VIRTUAL_LAYOUT.iter() {
|
||||
if (i.virtual_range)().contains(&virt_addr) {
|
||||
let output_addr = match i.translation {
|
||||
Translation::Identity => virt_addr,
|
||||
Translation::Offset(a) => a + (virt_addr - (i.virtual_range)().start()),
|
||||
};
|
||||
|
||||
return Ok((output_addr, i.attribute_fields));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((virt_addr, AttributeFields::default()))
|
||||
}
|
@ -0,0 +1,355 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use crate::memory::{get_virt_addr_properties, AttributeFields};
|
||||
use crate::uart;
|
||||
use cortex_a::{barrier, regs::*};
|
||||
use register::register_bitfields;
|
||||
|
||||
/// Parse the ID_AA64MMFR0_EL1 register for runtime information about supported
|
||||
/// MMU features.
|
||||
pub fn print_features(uart: &uart::Uart) {
|
||||
let mmfr = ID_AA64MMFR0_EL1.extract();
|
||||
|
||||
if let Some(ID_AA64MMFR0_EL1::TGran4::Value::Supported) =
|
||||
mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran4)
|
||||
{
|
||||
uart.puts("[i] MMU: 4 KiB granule supported!\n");
|
||||
}
|
||||
|
||||
if let Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_40) =
|
||||
mmfr.read_as_enum(ID_AA64MMFR0_EL1::PARange)
|
||||
{
|
||||
uart.puts("[i] MMU: Up to 40 Bit physical address range supported!\n");
|
||||
}
|
||||
}
|
||||
|
||||
register_bitfields! {u64,
|
||||
// AArch64 Reference Manual page 2150
|
||||
STAGE1_DESCRIPTOR [
|
||||
/// Privileged execute-never
|
||||
PXN OFFSET(53) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
],
|
||||
|
||||
/// Various address fields, depending on use case
|
||||
LVL2_OUTPUT_ADDR_4KiB OFFSET(21) NUMBITS(27) [], // [47:21]
|
||||
NEXT_LVL_TABLE_ADDR_4KiB OFFSET(12) NUMBITS(36) [], // [47:12]
|
||||
|
||||
/// Access flag
|
||||
AF OFFSET(10) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
],
|
||||
|
||||
/// Shareability field
|
||||
SH OFFSET(8) NUMBITS(2) [
|
||||
OuterShareable = 0b10,
|
||||
InnerShareable = 0b11
|
||||
],
|
||||
|
||||
/// Access Permissions
|
||||
AP OFFSET(6) NUMBITS(2) [
|
||||
RW_EL1 = 0b00,
|
||||
RW_EL1_EL0 = 0b01,
|
||||
RO_EL1 = 0b10,
|
||||
RO_EL1_EL0 = 0b11
|
||||
],
|
||||
|
||||
/// Memory attributes index into the MAIR_EL1 register
|
||||
AttrIndx OFFSET(2) NUMBITS(3) [],
|
||||
|
||||
TYPE OFFSET(1) NUMBITS(1) [
|
||||
Block = 0,
|
||||
Table = 1
|
||||
],
|
||||
|
||||
VALID OFFSET(0) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
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::Device => {
|
||||
STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE)
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
/// Constants for indexing the MAIR_EL1.
|
||||
#[allow(dead_code)]
|
||||
mod mair {
|
||||
pub const DEVICE: u64 = 0;
|
||||
pub const NORMAL: u64 = 1;
|
||||
}
|
||||
|
||||
/// Setup function for the MAIR_EL1 register.
|
||||
fn set_up_mair() {
|
||||
// Define the three memory types that we will map. Cacheable and
|
||||
// non-cacheable normal DRAM, and device.
|
||||
MAIR_EL1.write(
|
||||
// Attribute 1
|
||||
MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
|
||||
+ MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc
|
||||
|
||||
// Attribute 0
|
||||
+ MAIR_EL1::Attr0_HIGH::Device
|
||||
+ MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE,
|
||||
);
|
||||
}
|
||||
|
||||
trait BaseAddr {
|
||||
fn base_addr_u64(&self) -> u64;
|
||||
fn base_addr_usize(&self) -> usize;
|
||||
}
|
||||
|
||||
impl BaseAddr for [u64; 512] {
|
||||
fn base_addr_u64(&self) -> 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;
|
||||
|
||||
// A wrapper struct is needed here so that the align attribute can be used.
|
||||
#[repr(C)]
|
||||
#[repr(align(4096))]
|
||||
struct PageTable {
|
||||
entries: [u64; NUM_ENTRIES_4KIB],
|
||||
}
|
||||
|
||||
/// The LVL2 page table containng the 2 MiB entries.
|
||||
static mut LVL2_TABLE: PageTable = PageTable {
|
||||
entries: [0; NUM_ENTRIES_4KIB],
|
||||
};
|
||||
|
||||
/// The LVL3 page table containing the 4 KiB entries.
|
||||
///
|
||||
/// The first entry of the LVL2_TABLE will forward to this table.
|
||||
static mut LVL3_TABLE: PageTable = PageTable {
|
||||
entries: [0; NUM_ENTRIES_4KIB],
|
||||
};
|
||||
|
||||
/// Set up identity mapped page tables for the first 1 GiB of address space.
|
||||
///
|
||||
/// The first 2 MiB are 4 KiB granule, the rest 2 MiB.
|
||||
pub unsafe fn init() -> Result<(), &'static str> {
|
||||
// Prepare the memory attribute indirection register.
|
||||
set_up_mair();
|
||||
|
||||
// Point the first 2 MiB of virtual addresses to the follow-up LVL3
|
||||
// page-table.
|
||||
LVL2_TABLE.entries[0] = match TableDescriptor::new(LVL3_TABLE.entries.base_addr_usize()) {
|
||||
Err(s) => return Err(s),
|
||||
Ok(d) => d.value(),
|
||||
};
|
||||
|
||||
// Fill the rest of the LVL2 (2 MiB) entries as block descriptors.
|
||||
//
|
||||
// Notice the skip(1) which makes the iteration start at the second 2 MiB
|
||||
// block (0x20_0000).
|
||||
for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) {
|
||||
let virt_addr = block_descriptor_nr << TWO_MIB_SHIFT;
|
||||
|
||||
let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
|
||||
Err(s) => return Err(s),
|
||||
Ok((a, b)) => (a, b),
|
||||
};
|
||||
|
||||
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.
|
||||
TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64());
|
||||
|
||||
// Configure various settings of stage 1 of the EL1 translation regime.
|
||||
let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
|
||||
TCR_EL1.write(
|
||||
TCR_EL1::TBI0::Ignored
|
||||
+ TCR_EL1::IPS.val(ips)
|
||||
+ TCR_EL1::TG0::KiB_4 // 4 KiB granule
|
||||
+ TCR_EL1::SH0::Inner
|
||||
+ TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
|
||||
+ TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
|
||||
+ TCR_EL1::EPD0::EnableTTBR0Walks
|
||||
+ TCR_EL1::T0SZ.val(34), // Start walks at level 2
|
||||
);
|
||||
|
||||
// Switch the MMU on.
|
||||
//
|
||||
// First, force all previous changes to be seen before the MMU is enabled.
|
||||
barrier::isb(barrier::SY);
|
||||
|
||||
// Enable the MMU and turn on data and instruction caching.
|
||||
SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable);
|
||||
|
||||
// Force MMU init to complete before next instruction
|
||||
barrier::isb(barrier::SY);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,273 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use super::uart;
|
||||
use cortex_a::{barrier, regs::*};
|
||||
use register::register_bitfields;
|
||||
|
||||
/// Parse the ID_AA64MMFR0_EL1 register for runtime information about supported
|
||||
/// MMU features.
|
||||
pub fn print_features(uart: &uart::Uart) {
|
||||
let mmfr = ID_AA64MMFR0_EL1.extract();
|
||||
|
||||
if let Some(ID_AA64MMFR0_EL1::TGran4::Value::Supported) =
|
||||
mmfr.read_as_enum(ID_AA64MMFR0_EL1::TGran4)
|
||||
{
|
||||
uart.puts("[i] MMU: 4 KiB granule supported!\n");
|
||||
}
|
||||
|
||||
if let Some(ID_AA64MMFR0_EL1::PARange::Value::Bits_40) =
|
||||
mmfr.read_as_enum(ID_AA64MMFR0_EL1::PARange)
|
||||
{
|
||||
uart.puts("[i] MMU: Up to 40 Bit physical address range supported!\n");
|
||||
}
|
||||
}
|
||||
|
||||
register_bitfields! {u64,
|
||||
// AArch64 Reference Manual page 2150
|
||||
STAGE1_DESCRIPTOR [
|
||||
/// Execute-never
|
||||
XN OFFSET(54) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
],
|
||||
|
||||
/// Various address fields, depending on use case
|
||||
LVL2_OUTPUT_ADDR_4KiB OFFSET(21) NUMBITS(27) [], // [47:21]
|
||||
NEXT_LVL_TABLE_ADDR_4KiB OFFSET(12) NUMBITS(36) [], // [47:12]
|
||||
|
||||
/// Access flag
|
||||
AF OFFSET(10) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
],
|
||||
|
||||
/// Shareability field
|
||||
SH OFFSET(8) NUMBITS(2) [
|
||||
OuterShareable = 0b10,
|
||||
InnerShareable = 0b11
|
||||
],
|
||||
|
||||
/// Access Permissions
|
||||
AP OFFSET(6) NUMBITS(2) [
|
||||
RW_EL1 = 0b00,
|
||||
RW_EL1_EL0 = 0b01,
|
||||
RO_EL1 = 0b10,
|
||||
RO_EL1_EL0 = 0b11
|
||||
],
|
||||
|
||||
/// Memory attributes index into the MAIR_EL1 register
|
||||
AttrIndx OFFSET(2) NUMBITS(3) [],
|
||||
|
||||
TYPE OFFSET(1) NUMBITS(1) [
|
||||
Block = 0,
|
||||
Table = 1
|
||||
],
|
||||
|
||||
VALID OFFSET(0) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
trait BaseAddr {
|
||||
fn base_addr(&self) -> u64;
|
||||
}
|
||||
|
||||
impl BaseAddr for [u64; 512] {
|
||||
fn base_addr(&self) -> u64 {
|
||||
self as *const u64 as u64
|
||||
}
|
||||
}
|
||||
|
||||
const NUM_ENTRIES_4KIB: usize = 512;
|
||||
|
||||
// We need a wrapper struct here so that we can make use of the align attribute.
|
||||
#[repr(C)]
|
||||
#[repr(align(4096))]
|
||||
struct PageTable {
|
||||
entries: [u64; NUM_ENTRIES_4KIB],
|
||||
}
|
||||
|
||||
static mut LVL2_TABLE: PageTable = PageTable {
|
||||
entries: [0; NUM_ENTRIES_4KIB],
|
||||
};
|
||||
static mut SINGLE_LVL3_TABLE: PageTable = PageTable {
|
||||
entries: [0; NUM_ENTRIES_4KIB],
|
||||
};
|
||||
|
||||
/// Set up identity mapped page tables for the first 1 GiB of address space.
|
||||
pub unsafe fn init() {
|
||||
// First, define the two memory types that we will map. Cacheable normal DRAM and
|
||||
// device.
|
||||
MAIR_EL1.write(
|
||||
// Attribute 1
|
||||
MAIR_EL1::Attr1_HIGH::Device
|
||||
+ MAIR_EL1::Attr1_LOW_DEVICE::Device_nGnRE
|
||||
// Attribute 0
|
||||
+ MAIR_EL1::Attr0_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
|
||||
+ MAIR_EL1::Attr0_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc,
|
||||
);
|
||||
|
||||
// Descriptive consts for indexing into the correct MAIR_EL1 attributes.
|
||||
mod mair {
|
||||
pub const NORMAL: u64 = 0;
|
||||
pub const DEVICE: u64 = 1;
|
||||
}
|
||||
|
||||
// The first 2 MiB.
|
||||
//
|
||||
// Set up the first LVL2 entry, pointing to the base address of a follow-up
|
||||
// table containing 4 KiB pages.
|
||||
//
|
||||
// 0x0000_0000_0000_0000 |
|
||||
// |> 2 MiB
|
||||
// 0x0000_0000_001F_FFFF |
|
||||
let lvl3_base: u64 = SINGLE_LVL3_TABLE.entries.base_addr() >> 12;
|
||||
LVL2_TABLE.entries[0] = (STAGE1_DESCRIPTOR::VALID::True
|
||||
+ STAGE1_DESCRIPTOR::TYPE::Table
|
||||
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(lvl3_base))
|
||||
.value;
|
||||
|
||||
// For educational purposes and fun, let the start of the second 2 MiB block
|
||||
// point to the 2 MiB aperture which contains the UART's base address.
|
||||
//
|
||||
// 0x0000_0000_0020_0000 |
|
||||
// |> 2 MiB
|
||||
// 0x0000_0000_003F_FFFF |
|
||||
let uart_phys_base: u64 = (uart::UART_PHYS_BASE >> 21).into();
|
||||
LVL2_TABLE.entries[1] = (STAGE1_DESCRIPTOR::VALID::True
|
||||
+ STAGE1_DESCRIPTOR::TYPE::Block
|
||||
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE)
|
||||
+ 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 (2 MiB) entries as block descriptors.
|
||||
//
|
||||
// Differentiate between
|
||||
// - cacheable DRAM
|
||||
// - device memory
|
||||
//
|
||||
// Ranges are stored in memory.rs.
|
||||
//
|
||||
// 0x0000_0000_0040_0000 |
|
||||
// |> 1004 MiB cacheable DRAM
|
||||
// 0x0000_0000_3EFF_FFFF |
|
||||
// 0x0000_0000_3F00_0000 |
|
||||
// |> 16 MiB device (MMIO)
|
||||
// 0x0000_0000_4000_0000 |
|
||||
let mmio_first_block_index: u64 = (super::MMIO_BASE >> 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) which makes the iteration start at the third 2 MiB
|
||||
// block (0x40_0000).
|
||||
for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(2) {
|
||||
let j: u64 = i as u64;
|
||||
|
||||
let mem_attr = if j >= mmio_first_block_index {
|
||||
STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE)
|
||||
} else {
|
||||
STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL)
|
||||
};
|
||||
|
||||
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(j)).value;
|
||||
}
|
||||
|
||||
// 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
|
||||
// KiB aligned, and we export the boundaries via symbols.
|
||||
extern "C" {
|
||||
// The inclusive start of the read-only area, aka the address of the
|
||||
// first byte of the area.
|
||||
static mut __ro_start: u64;
|
||||
|
||||
// The non-inclusive end of the read-only area, aka the address of the
|
||||
// first byte _after_ the RO area.
|
||||
static mut __ro_end: u64;
|
||||
}
|
||||
|
||||
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 (ro_first_page_index..=ro_last_page_index).contains(&j) {
|
||||
STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False
|
||||
} else {
|
||||
STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True
|
||||
};
|
||||
|
||||
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value;
|
||||
}
|
||||
|
||||
// Point to the LVL2 table base address in TTBR0.
|
||||
TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr());
|
||||
|
||||
// Configure various settings of stage 1 of the EL1 translation regime.
|
||||
let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
|
||||
TCR_EL1.write(
|
||||
TCR_EL1::TBI0::Ignored
|
||||
+ TCR_EL1::IPS.val(ips)
|
||||
+ TCR_EL1::TG0::KiB_4 // 4 KiB granule
|
||||
+ TCR_EL1::SH0::Inner
|
||||
+ TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
|
||||
+ TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
|
||||
+ TCR_EL1::EPD0::EnableTTBR0Walks
|
||||
+ TCR_EL1::T0SZ.val(34), // Start walks at level 2
|
||||
);
|
||||
|
||||
// Switch the MMU on.
|
||||
//
|
||||
// First, force all previous changes to be seen before the MMU is enabled.
|
||||
barrier::isb(barrier::SY);
|
||||
|
||||
// Enable the MMU and turn on data and instruction caching.
|
||||
SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable);
|
||||
|
||||
// Force MMU init to complete before next instruction
|
||||
barrier::isb(barrier::SY);
|
||||
}
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,225 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
pub mod mmu;
|
||||
|
||||
/// System memory map.
|
||||
#[rustfmt::skip]
|
||||
pub mod map {
|
||||
pub const START: usize = 0x0000_0000;
|
||||
pub const END: usize = 0x3FFF_FFFF;
|
||||
|
||||
pub mod physical {
|
||||
pub const MMIO_BASE: usize = 0x3F00_0000;
|
||||
pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880;
|
||||
pub const GPIO_BASE: usize = MMIO_BASE + 0x0020_0000;
|
||||
pub const UART_BASE: usize = MMIO_BASE + 0x0020_1000;
|
||||
pub const MMIO_END: usize = super::END;
|
||||
}
|
||||
|
||||
pub mod virt {
|
||||
pub const KERN_STACK_START: usize = super::START;
|
||||
pub const KERN_STACK_END: usize = 0x0007_FFFF;
|
||||
|
||||
// The second 2 MiB block
|
||||
pub const CACHEABLE_START: usize = 0x0020_0000;
|
||||
|
||||
// The third 2 MiB block
|
||||
pub const NON_CACHEABLE_START: usize = 0x0040_0000;
|
||||
pub const NON_CACHEABLE_END: usize = 0x005F_FFFF;
|
||||
}
|
||||
}
|
||||
|
||||
/// Types used for compiling the virtual memory layout of the kernel using
|
||||
/// address ranges.
|
||||
pub mod kernel_mem_range {
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum MemAttributes {
|
||||
CacheableDRAM,
|
||||
NonCacheableDRAM,
|
||||
Device,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum AccessPermissions {
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Translation {
|
||||
Identity,
|
||||
Offset(usize),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct AttributeFields {
|
||||
pub mem_attributes: MemAttributes,
|
||||
pub acc_perms: AccessPermissions,
|
||||
pub execute_never: bool,
|
||||
}
|
||||
|
||||
impl Default for AttributeFields {
|
||||
fn default() -> AttributeFields {
|
||||
AttributeFields {
|
||||
mem_attributes: MemAttributes::CacheableDRAM,
|
||||
acc_perms: AccessPermissions::ReadWrite,
|
||||
execute_never: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Descriptor {
|
||||
pub virtual_range: fn() -> RangeInclusive<usize>,
|
||||
pub translation: Translation,
|
||||
pub attribute_fields: AttributeFields,
|
||||
}
|
||||
}
|
||||
|
||||
use kernel_mem_range::*;
|
||||
|
||||
/// A virtual memory layout that is agnostic of the paging granularity that the
|
||||
/// hardware MMU will use.
|
||||
///
|
||||
/// Contains only special ranges, aka anything that is _not_ normal cacheable
|
||||
/// DRAM.
|
||||
static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 5] = [
|
||||
// Kernel stack
|
||||
Descriptor {
|
||||
virtual_range: || {
|
||||
RangeInclusive::new(map::virt::KERN_STACK_START, map::virt::KERN_STACK_END)
|
||||
},
|
||||
translation: Translation::Identity,
|
||||
attribute_fields: AttributeFields {
|
||||
mem_attributes: MemAttributes::CacheableDRAM,
|
||||
acc_perms: AccessPermissions::ReadWrite,
|
||||
execute_never: true,
|
||||
},
|
||||
},
|
||||
// Kernel code and RO data
|
||||
Descriptor {
|
||||
virtual_range: || {
|
||||
// Using the linker script, we ensure that the RO area is consecutive and 4
|
||||
// KiB aligned, and we export the boundaries via symbols:
|
||||
//
|
||||
// [__ro_start, __ro_end)
|
||||
extern "C" {
|
||||
// The inclusive start of the read-only area, aka the address of the
|
||||
// first byte of the area.
|
||||
static __ro_start: u64;
|
||||
|
||||
// The exclusive end of the read-only area, aka the address of
|
||||
// the first byte _after_ the RO area.
|
||||
static __ro_end: u64;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// Notice the subtraction to turn the exclusive end into an
|
||||
// inclusive end
|
||||
RangeInclusive::new(
|
||||
&__ro_start as *const _ as usize,
|
||||
&__ro_end as *const _ as usize - 1,
|
||||
)
|
||||
}
|
||||
},
|
||||
translation: Translation::Identity,
|
||||
attribute_fields: AttributeFields {
|
||||
mem_attributes: MemAttributes::CacheableDRAM,
|
||||
acc_perms: AccessPermissions::ReadOnly,
|
||||
execute_never: false,
|
||||
},
|
||||
},
|
||||
// Kernel data and BSS
|
||||
Descriptor {
|
||||
virtual_range: || {
|
||||
extern "C" {
|
||||
static __ro_end: u64;
|
||||
static __bss_end: u64;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
RangeInclusive::new(
|
||||
&__ro_end as *const _ as usize,
|
||||
&__bss_end as *const _ as usize - 1,
|
||||
)
|
||||
}
|
||||
},
|
||||
translation: Translation::Identity,
|
||||
attribute_fields: AttributeFields {
|
||||
mem_attributes: MemAttributes::CacheableDRAM,
|
||||
acc_perms: AccessPermissions::ReadWrite,
|
||||
execute_never: true,
|
||||
},
|
||||
},
|
||||
// Non-cacheable DRAM
|
||||
Descriptor {
|
||||
virtual_range: || {
|
||||
RangeInclusive::new(map::virt::NON_CACHEABLE_START, map::virt::NON_CACHEABLE_END)
|
||||
},
|
||||
translation: Translation::Offset(map::virt::CACHEABLE_START),
|
||||
attribute_fields: AttributeFields {
|
||||
mem_attributes: MemAttributes::NonCacheableDRAM,
|
||||
acc_perms: AccessPermissions::ReadWrite,
|
||||
execute_never: true,
|
||||
},
|
||||
},
|
||||
// Device MMIO
|
||||
Descriptor {
|
||||
virtual_range: || RangeInclusive::new(map::physical::MMIO_BASE, map::physical::MMIO_END),
|
||||
translation: Translation::Identity,
|
||||
attribute_fields: AttributeFields {
|
||||
mem_attributes: MemAttributes::Device,
|
||||
acc_perms: AccessPermissions::ReadWrite,
|
||||
execute_never: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/// For a given virtual address, find and return the output address and
|
||||
/// according attributes.
|
||||
///
|
||||
/// If the address is not covered in VIRTUAL_LAYOUT, return a default for normal
|
||||
/// cacheable DRAM.
|
||||
fn get_virt_addr_properties(virt_addr: usize) -> Result<(usize, AttributeFields), &'static str> {
|
||||
if virt_addr > map::END {
|
||||
return Err("Address out of range.");
|
||||
}
|
||||
|
||||
for i in KERNEL_VIRTUAL_LAYOUT.iter() {
|
||||
if (i.virtual_range)().contains(&virt_addr) {
|
||||
let output_addr = match i.translation {
|
||||
Translation::Identity => virt_addr,
|
||||
Translation::Offset(a) => a + (virt_addr - (i.virtual_range)().start()),
|
||||
};
|
||||
|
||||
return Ok((output_addr, i.attribute_fields));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((virt_addr, AttributeFields::default()))
|
||||
}
|
@ -0,0 +1,345 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use crate::memory::{get_virt_addr_properties, AttributeFields};
|
||||
use cortex_a::{barrier, regs::*};
|
||||
use register::register_bitfields;
|
||||
|
||||
register_bitfields! {u64,
|
||||
// AArch64 Reference Manual page 2150
|
||||
STAGE1_DESCRIPTOR [
|
||||
/// Privileged execute-never
|
||||
PXN OFFSET(53) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
],
|
||||
|
||||
/// Various address fields, depending on use case
|
||||
LVL2_OUTPUT_ADDR_4KiB OFFSET(21) NUMBITS(27) [], // [47:21]
|
||||
NEXT_LVL_TABLE_ADDR_4KiB OFFSET(12) NUMBITS(36) [], // [47:12]
|
||||
|
||||
/// Access flag
|
||||
AF OFFSET(10) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
],
|
||||
|
||||
/// Shareability field
|
||||
SH OFFSET(8) NUMBITS(2) [
|
||||
OuterShareable = 0b10,
|
||||
InnerShareable = 0b11
|
||||
],
|
||||
|
||||
/// Access Permissions
|
||||
AP OFFSET(6) NUMBITS(2) [
|
||||
RW_EL1 = 0b00,
|
||||
RW_EL1_EL0 = 0b01,
|
||||
RO_EL1 = 0b10,
|
||||
RO_EL1_EL0 = 0b11
|
||||
],
|
||||
|
||||
/// Memory attributes index into the MAIR_EL1 register
|
||||
AttrIndx OFFSET(2) NUMBITS(3) [],
|
||||
|
||||
TYPE OFFSET(1) NUMBITS(1) [
|
||||
Block = 0,
|
||||
Table = 1
|
||||
],
|
||||
|
||||
VALID OFFSET(0) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
};
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
/// Constants for indexing the MAIR_EL1.
|
||||
#[allow(dead_code)]
|
||||
mod mair {
|
||||
pub const DEVICE: u64 = 0;
|
||||
pub const NORMAL: u64 = 1;
|
||||
pub const NORMAL_NON_CACHEABLE: u64 = 2;
|
||||
}
|
||||
|
||||
/// Setup function for the MAIR_EL1 register.
|
||||
fn set_up_mair() {
|
||||
// Define the three memory types that we will map. Cacheable and
|
||||
// non-cacheable normal DRAM, and device.
|
||||
MAIR_EL1.write(
|
||||
// Attribute 2
|
||||
MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable
|
||||
+ MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable
|
||||
|
||||
// Attribute 1
|
||||
+ MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
|
||||
+ MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc
|
||||
|
||||
// Attribute 0
|
||||
+ MAIR_EL1::Attr0_HIGH::Device
|
||||
+ MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE,
|
||||
);
|
||||
}
|
||||
|
||||
trait BaseAddr {
|
||||
fn base_addr_u64(&self) -> u64;
|
||||
fn base_addr_usize(&self) -> usize;
|
||||
}
|
||||
|
||||
impl BaseAddr for [u64; 512] {
|
||||
fn base_addr_u64(&self) -> 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;
|
||||
|
||||
// A wrapper struct is needed here so that the align attribute can be used.
|
||||
#[repr(C)]
|
||||
#[repr(align(4096))]
|
||||
struct PageTable {
|
||||
entries: [u64; NUM_ENTRIES_4KIB],
|
||||
}
|
||||
|
||||
/// The LVL2 page table containng the 2 MiB entries.
|
||||
static mut LVL2_TABLE: PageTable = PageTable {
|
||||
entries: [0; NUM_ENTRIES_4KIB],
|
||||
};
|
||||
|
||||
/// The LVL3 page table containing the 4 KiB entries.
|
||||
///
|
||||
/// The first entry of the LVL2_TABLE will forward to this table.
|
||||
static mut LVL3_TABLE: PageTable = PageTable {
|
||||
entries: [0; NUM_ENTRIES_4KIB],
|
||||
};
|
||||
|
||||
/// Set up identity mapped page tables for the first 1 GiB of address space.
|
||||
///
|
||||
/// The first 2 MiB are 4 KiB granule, the rest 2 MiB.
|
||||
pub unsafe fn init() -> Result<(), &'static str> {
|
||||
// Prepare the memory attribute indirection register.
|
||||
set_up_mair();
|
||||
|
||||
// Point the first 2 MiB of virtual addresses to the follow-up LVL3
|
||||
// page-table.
|
||||
LVL2_TABLE.entries[0] = match TableDescriptor::new(LVL3_TABLE.entries.base_addr_usize()) {
|
||||
Err(s) => return Err(s),
|
||||
Ok(d) => d.value(),
|
||||
};
|
||||
|
||||
// Fill the rest of the LVL2 (2 MiB) entries as block descriptors.
|
||||
//
|
||||
// Notice the skip(1) which makes the iteration start at the second 2 MiB
|
||||
// block (0x20_0000).
|
||||
for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) {
|
||||
let virt_addr = block_descriptor_nr << TWO_MIB_SHIFT;
|
||||
|
||||
let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
|
||||
Err(s) => return Err(s),
|
||||
Ok((a, b)) => (a, b),
|
||||
};
|
||||
|
||||
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.
|
||||
TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr_u64());
|
||||
|
||||
// Configure various settings of stage 1 of the EL1 translation regime.
|
||||
let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
|
||||
TCR_EL1.write(
|
||||
TCR_EL1::TBI0::Ignored
|
||||
+ TCR_EL1::IPS.val(ips)
|
||||
+ TCR_EL1::TG0::KiB_4 // 4 KiB granule
|
||||
+ TCR_EL1::SH0::Inner
|
||||
+ TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
|
||||
+ TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
|
||||
+ TCR_EL1::EPD0::EnableTTBR0Walks
|
||||
+ TCR_EL1::T0SZ.val(34), // Start walks at level 2
|
||||
);
|
||||
|
||||
// Switch the MMU on.
|
||||
//
|
||||
// First, force all previous changes to be seen before the MMU is enabled.
|
||||
barrier::isb(barrier::SY);
|
||||
|
||||
// Enable the MMU and turn on data and instruction caching.
|
||||
SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable);
|
||||
|
||||
// Force MMU init to complete before next instruction
|
||||
barrier::isb(barrier::SY);
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use cortex_a::{barrier, regs::*};
|
||||
use register::register_bitfields;
|
||||
|
||||
register_bitfields! {u64,
|
||||
// AArch64 Reference Manual page 2150
|
||||
STAGE1_DESCRIPTOR [
|
||||
/// Execute-never
|
||||
XN OFFSET(54) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
],
|
||||
|
||||
/// Various address fields, depending on use case
|
||||
LVL2_OUTPUT_ADDR_4KiB OFFSET(21) NUMBITS(27) [], // [47:21]
|
||||
NEXT_LVL_TABLE_ADDR_4KiB OFFSET(12) NUMBITS(36) [], // [47:12]
|
||||
|
||||
/// Access flag
|
||||
AF OFFSET(10) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
],
|
||||
|
||||
/// Shareability field
|
||||
SH OFFSET(8) NUMBITS(2) [
|
||||
OuterShareable = 0b10,
|
||||
InnerShareable = 0b11
|
||||
],
|
||||
|
||||
/// Access Permissions
|
||||
AP OFFSET(6) NUMBITS(2) [
|
||||
RW_EL1 = 0b00,
|
||||
RW_EL1_EL0 = 0b01,
|
||||
RO_EL1 = 0b10,
|
||||
RO_EL1_EL0 = 0b11
|
||||
],
|
||||
|
||||
/// Memory attributes index into the MAIR_EL1 register
|
||||
AttrIndx OFFSET(2) NUMBITS(3) [],
|
||||
|
||||
TYPE OFFSET(1) NUMBITS(1) [
|
||||
Block = 0,
|
||||
Table = 1
|
||||
],
|
||||
|
||||
VALID OFFSET(0) NUMBITS(1) [
|
||||
False = 0,
|
||||
True = 1
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
trait BaseAddr {
|
||||
fn base_addr(&self) -> u64;
|
||||
}
|
||||
|
||||
impl BaseAddr for [u64; 512] {
|
||||
fn base_addr(&self) -> u64 {
|
||||
self as *const u64 as u64
|
||||
}
|
||||
}
|
||||
|
||||
const NUM_ENTRIES_4KIB: usize = 512;
|
||||
|
||||
// We need a wrapper struct here so that we can make use of the align attribute.
|
||||
#[repr(C)]
|
||||
#[repr(align(4096))]
|
||||
struct PageTable {
|
||||
entries: [u64; NUM_ENTRIES_4KIB],
|
||||
}
|
||||
|
||||
static mut LVL2_TABLE: PageTable = PageTable {
|
||||
entries: [0; NUM_ENTRIES_4KIB],
|
||||
};
|
||||
static mut SINGLE_LVL3_TABLE: PageTable = PageTable {
|
||||
entries: [0; NUM_ENTRIES_4KIB],
|
||||
};
|
||||
|
||||
/// Set up identity mapped page tables for the first 1 GiB of address space.
|
||||
pub unsafe fn init() {
|
||||
// First, define the three memory types that we will map. Cacheable and
|
||||
// non-cacheable normal DRAM, and device.
|
||||
MAIR_EL1.write(
|
||||
// Attribute 2
|
||||
MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable
|
||||
+ MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable
|
||||
|
||||
// Attribute 1
|
||||
+ MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
|
||||
+ MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc
|
||||
|
||||
// Attribute 0
|
||||
+ MAIR_EL1::Attr0_HIGH::Device
|
||||
+ MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE,
|
||||
);
|
||||
|
||||
// Descriptive consts for indexing into the correct MAIR_EL1 attributes.
|
||||
mod mair {
|
||||
pub const DEVICE: u64 = 0;
|
||||
pub const NORMAL: u64 = 1;
|
||||
pub const NORMAL_NON_CACHEABLE: u64 = 2;
|
||||
}
|
||||
|
||||
// The first 2 MiB.
|
||||
//
|
||||
// Set up the first LVL2 entry, pointing to the base address of a follow-up
|
||||
// table containing 4 KiB pages.
|
||||
//
|
||||
// 0x0000_0000_0000_0000 |
|
||||
// |> 2 MiB
|
||||
// 0x0000_0000_001F_FFFF |
|
||||
let lvl3_base: u64 = SINGLE_LVL3_TABLE.entries.base_addr() >> 12;
|
||||
LVL2_TABLE.entries[0] = (STAGE1_DESCRIPTOR::VALID::True
|
||||
+ STAGE1_DESCRIPTOR::TYPE::Table
|
||||
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(lvl3_base))
|
||||
.value;
|
||||
|
||||
// The second 2 MiB as block entry.
|
||||
//
|
||||
// Mapped as non-cacheable.
|
||||
//
|
||||
// 0x0000_0000_0020_0000 |
|
||||
// |> 2 MiB
|
||||
// 0x0000_0000_003F_FFFF |
|
||||
LVL2_TABLE.entries[1] = (STAGE1_DESCRIPTOR::VALID::True
|
||||
+ STAGE1_DESCRIPTOR::TYPE::Block
|
||||
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE)
|
||||
+ STAGE1_DESCRIPTOR::AP::RW_EL1
|
||||
+ STAGE1_DESCRIPTOR::SH::OuterShareable
|
||||
+ STAGE1_DESCRIPTOR::AF::True
|
||||
// This translation is accessed for virtual 0x200000. Point to physical
|
||||
// 0x400000, aka the third phyiscal 2 MiB DRAM block (third block == 2,
|
||||
// because we start counting at 0).
|
||||
//
|
||||
// Here, we configure it non-cacheable.
|
||||
+ STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(2)
|
||||
+ STAGE1_DESCRIPTOR::XN::True)
|
||||
.value;
|
||||
|
||||
// Fill the rest of the LVL2 (2 MiB) entries as block descriptors.
|
||||
//
|
||||
// Differentiate between
|
||||
// - cacheable DRAM
|
||||
// - device memory
|
||||
//
|
||||
// Ranges are stored in memory.rs.
|
||||
//
|
||||
// 0x0000_0000_0040_0000 |
|
||||
// |> 1004 MiB cacheable DRAM
|
||||
// 0x0000_0000_3EFF_FFFF |
|
||||
// 0x0000_0000_3F00_0000 |
|
||||
// |> 16 MiB device (MMIO)
|
||||
// 0x0000_0000_4000_0000 |
|
||||
let mmio_first_block_index: u64 = (super::MMIO_BASE >> 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). Start at the third 2 MiB DRAM block, which will point
|
||||
// virtual 0x400000 to physical 0x400000, configured as cacheable memory.
|
||||
for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(2) {
|
||||
let j: u64 = i as u64;
|
||||
|
||||
let mem_attr = if j >= mmio_first_block_index {
|
||||
STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE)
|
||||
} else {
|
||||
STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL)
|
||||
};
|
||||
|
||||
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(j)).value;
|
||||
}
|
||||
|
||||
// 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
|
||||
// KiB aligned, and we export the boundaries via symbols.
|
||||
extern "C" {
|
||||
// The inclusive start of the read-only area, aka the address of the
|
||||
// first byte of the area.
|
||||
static mut __ro_start: u64;
|
||||
|
||||
// The non-inclusive end of the read-only area, aka the address of the
|
||||
// first byte _after_ the RO area.
|
||||
static mut __ro_end: u64;
|
||||
}
|
||||
|
||||
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 (ro_first_page_index..=ro_last_page_index).contains(&j) {
|
||||
STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False
|
||||
} else {
|
||||
STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True
|
||||
};
|
||||
|
||||
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value;
|
||||
}
|
||||
|
||||
// Point to the LVL2 table base address in TTBR0.
|
||||
TTBR0_EL1.set_baddr(LVL2_TABLE.entries.base_addr());
|
||||
|
||||
// Configure various settings of stage 1 of the EL1 translation regime.
|
||||
let ips = ID_AA64MMFR0_EL1.read(ID_AA64MMFR0_EL1::PARange);
|
||||
TCR_EL1.write(
|
||||
TCR_EL1::TBI0::Ignored
|
||||
+ TCR_EL1::IPS.val(ips)
|
||||
+ TCR_EL1::TG0::KiB_4 // 4 KiB granule
|
||||
+ TCR_EL1::SH0::Inner
|
||||
+ TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
|
||||
+ TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable
|
||||
+ TCR_EL1::EPD0::EnableTTBR0Walks
|
||||
+ TCR_EL1::T0SZ.val(34), // Start walks at level 2
|
||||
);
|
||||
|
||||
// Switch the MMU on.
|
||||
//
|
||||
// First, force all previous changes to be seen before the MMU is enabled.
|
||||
barrier::isb(barrier::SY);
|
||||
|
||||
// Enable the MMU and turn on data and instruction caching.
|
||||
SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable);
|
||||
|
||||
// Force MMU init to complete before next instruction
|
||||
barrier::isb(barrier::SY);
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue