Rewrite the kernel's static virtual memory mapping.

pull/15/head
Andre Richter 5 years ago
parent 5011d5e507
commit 47996b4b78
No known key found for this signature in database
GPG Key ID: 2116C1AB102F615E

@ -65,3 +65,18 @@ kernel8::mmu::init::h53df3fab6e51e098:
80778: 0d a2 18 d5 msr MAIR_EL1, x13 80778: 0d a2 18 d5 msr MAIR_EL1, x13
... ...
``` ```
## Output
```console
ferris@box:~$ make raspboot
[0] UART is live!
[1] Press a key to continue booting... Greetings fellow Rustacean!
[i] MMU: 4 KiB granule supported!
[i] MMU: Up to 40 Bit physical address range supported!
[2] MMU online.
Writing through the virtual mapping at 0x00000000001FF000.
```

Binary file not shown.

Binary file not shown.

@ -1,7 +1,7 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2018 Andre Richter <andre.o.richter@gmail.com> * Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -22,7 +22,6 @@
* SOFTWARE. * SOFTWARE.
*/ */
use super::MMIO_BASE;
use core::ops; use core::ops;
use register::{mmio::ReadWrite, register_bitfields}; use register::{mmio::ReadWrite, register_bitfields};
@ -67,8 +66,6 @@ register_bitfields! {
] ]
} }
const GPIO_BASE: u32 = MMIO_BASE + 0x200_000;
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[repr(C)] #[repr(C)]
pub struct RegisterBlock { pub struct RegisterBlock {
@ -99,23 +96,25 @@ pub struct RegisterBlock {
} }
/// Public interface to the GPIO MMIO area /// Public interface to the GPIO MMIO area
pub struct GPIO; pub struct GPIO {
base_addr: usize,
}
impl ops::Deref for GPIO { impl ops::Deref for GPIO {
type Target = RegisterBlock; type Target = RegisterBlock;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
unsafe { &*Self::ptr() } unsafe { &*self.ptr() }
} }
} }
impl GPIO { impl GPIO {
pub fn new() -> GPIO { pub fn new(base_addr: usize) -> GPIO {
GPIO GPIO { base_addr }
} }
/// Returns a pointer to the register block /// Returns a pointer to the register block
fn ptr() -> *const RegisterBlock { fn ptr(&self) -> *const RegisterBlock {
GPIO_BASE as *const _ self.base_addr as *const _
} }
} }

@ -26,21 +26,19 @@
#![no_main] #![no_main]
#![feature(range_contains)] #![feature(range_contains)]
const MMIO_BASE: u32 = 0x3F00_0000;
mod delays; mod delays;
mod gpio; mod gpio;
mod mbox; mod mbox;
mod mmu; mod memory;
mod uart; mod uart;
fn kernel_entry() -> ! { fn kernel_entry() -> ! {
let gpio = gpio::GPIO::new(); let gpio = gpio::GPIO::new(memory::map::physical::GPIO_BASE);
let mut mbox = mbox::Mbox::new(); let mut mbox = mbox::Mbox::new(memory::map::physical::VIDEOCORE_MBOX_BASE);
{ {
// Before the MMU is live, instantiate a UART driver with the physical address // Before the MMU is live, instantiate a UART driver with the physical address
let uart = uart::Uart::new(uart::UART_PHYS_BASE); let uart = uart::Uart::new(memory::map::physical::UART_BASE);
// set up serial console // set up serial console
match uart.init(&mut mbox, &gpio) { match uart.init(&mut mbox, &gpio) {
@ -54,20 +52,24 @@ fn kernel_entry() -> ! {
uart.getc(); uart.getc();
uart.puts("Greetings fellow Rustacean!\n"); uart.puts("Greetings fellow Rustacean!\n");
mmu::print_features(&uart); memory::mmu::print_features(&uart);
uart.puts("[2] Switching MMU on now... "); match unsafe { memory::mmu::init() } {
Err(s) => {
uart.puts("[2][Error] MMU: ");
uart.puts(s);
uart.puts("\n");
}
Ok(()) => uart.puts("[2] MMU online.\n"),
}
} // After this closure, the UART instance is not valid anymore. } // After this closure, the UART instance is not valid anymore.
unsafe { mmu::init() }; // Instantiate a new UART using the remapped address. No need to init()
// again, though.
// Instantiate a new UART using the virtual mapping in the second 2 MiB let uart = uart::Uart::new(memory::map::virt::REMAPPED_UART_BASE);
// block. No need to init() again, though.
const UART_VIRT_BASE: u32 = 2 * 1024 * 1024 + 0x1000;
let uart = uart::Uart::new(UART_VIRT_BASE);
uart.puts("MMU is live \\o/\n\nWriting through the virtual mapping at 0x"); uart.puts("\nWriting through the virtual mapping at 0x");
uart.hex(u64::from(UART_VIRT_BASE)); uart.hex(memory::map::virt::REMAPPED_UART_BASE as u64);
uart.puts(".\n"); uart.puts(".\n");
// echo everything back // echo everything back

@ -1,7 +1,7 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2018 Andre Richter <andre.o.richter@gmail.com> * Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -22,7 +22,6 @@
* SOFTWARE. * SOFTWARE.
*/ */
use super::MMIO_BASE;
use core::ops; use core::ops;
use cortex_a::asm; use cortex_a::asm;
use register::{ use register::{
@ -39,8 +38,6 @@ register_bitfields! {
] ]
} }
const VIDEOCORE_MBOX: u32 = MMIO_BASE + 0xB880;
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[repr(C)] #[repr(C)]
pub struct RegisterBlock { pub struct RegisterBlock {
@ -89,6 +86,7 @@ pub struct Mbox {
// The address for buffer needs to be 16-byte aligned so that the // The address for buffer needs to be 16-byte aligned so that the
// Videcore can handle it properly. // Videcore can handle it properly.
pub buffer: [u32; 36], pub buffer: [u32; 36],
base_addr: usize,
} }
/// Deref to RegisterBlock /// Deref to RegisterBlock
@ -105,18 +103,21 @@ impl ops::Deref for Mbox {
type Target = RegisterBlock; type Target = RegisterBlock;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
unsafe { &*Self::ptr() } unsafe { &*self.ptr() }
} }
} }
impl Mbox { impl Mbox {
pub fn new() -> Mbox { pub fn new(base_addr: usize) -> Mbox {
Mbox { buffer: [0; 36] } Mbox {
buffer: [0; 36],
base_addr,
}
} }
/// Returns a pointer to the register block /// Returns a pointer to the register block
fn ptr() -> *const RegisterBlock { fn ptr(&self) -> *const RegisterBlock {
VIDEOCORE_MBOX as *const _ self.base_addr as *const _
} }
/// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success /// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success

@ -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);
}

@ -1,7 +1,7 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2018 Andre Richter <andre.o.richter@gmail.com> * Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -22,7 +22,6 @@
* SOFTWARE. * SOFTWARE.
*/ */
use super::MMIO_BASE;
use crate::delays; use crate::delays;
use crate::gpio; use crate::gpio;
use crate::mbox; use crate::mbox;
@ -120,8 +119,6 @@ register_bitfields! {
] ]
} }
pub const UART_PHYS_BASE: u32 = MMIO_BASE + 0x20_1000;
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[repr(C)] #[repr(C)]
pub struct RegisterBlock { pub struct RegisterBlock {
@ -143,7 +140,7 @@ pub enum UartError {
pub type Result<T> = ::core::result::Result<T, UartError>; pub type Result<T> = ::core::result::Result<T, UartError>;
pub struct Uart { pub struct Uart {
uart_base: u32, base_addr: usize,
} }
impl ops::Deref for Uart { impl ops::Deref for Uart {
@ -155,13 +152,13 @@ impl ops::Deref for Uart {
} }
impl Uart { impl Uart {
pub fn new(uart_base: u32) -> Uart { pub fn new(base_addr: usize) -> Uart {
Uart { uart_base } Uart { base_addr }
} }
/// Returns a pointer to the register block /// Returns a pointer to the register block
fn ptr(&self) -> *const RegisterBlock { fn ptr(&self) -> *const RegisterBlock {
self.uart_base as *const _ self.base_addr as *const _
} }
///Set baud rate and characteristics (115200 8N1) and map to GPIO ///Set baud rate and characteristics (115200 8N1) and map to GPIO

@ -18,13 +18,12 @@ operating with data on the same DRAM with caching enabled and disabled.
### mmu.rs ### mmu.rs
Therefore, we will map the same physical memory via two different virtual Therefore, we will map the same physical memory via two different virtual
addresses. We set up our pagetables such that the virtual address `0x200000` addresses. We set up our pagetables such that the virtual address `0x400000`
points to the physical DRAM at `0x400000`, and we configure it as points to the physical DRAM at `0x200000`, and we configure it as
`non-cacheable` in the page tables. `non-cacheable` in the page tables.
We are still using a `2 MiB` granule, and set up the next block, which starts at There is also an identity mapped block, which starts at virtual `0x200000` and
virtual `0x400000`, to point at physical `0x400000` (this is an identity mapped points at physical `0x200000`. This time, the block is configured as cacheable.
block). This time, the block is configured as cacheable.
### benchmark.rs ### benchmark.rs
@ -38,15 +37,20 @@ non-cacheable virtual addresses. Remember that both virtual addresses point to
the _same_ physical DRAM, so the difference in time that we will see will the _same_ physical DRAM, so the difference in time that we will see will
showcase how much faster it is to operate on DRAM with caching enabled. showcase how much faster it is to operate on DRAM with caching enabled.
## Results ## Output
On my Raspberry, I get the following results: On my Raspberry, I get the following results:
```text ```console
Benchmarking non-cacheable DRAM modifications at virtual 0x0000000000200000, physical 0x0000000000400000: ferris@box:~$ make raspboot
[0] UART is live!
[1] Press a key to continue booting... Greetings fellow Rustacean!
[2] MMU online.
Benchmarking non-cacheable DRAM modifications at virtual 0x0000000000400000, physical 0x0000000000200000:
1040 miliseconds. 1040 miliseconds.
Benchmarking cacheable DRAM modifications at virtual 0x0000000000400000, physical 0x0000000000400000: Benchmarking cacheable DRAM modifications at virtual 0x0000000000200000, physical 0x0000000000200000:
53 miliseconds. 53 miliseconds.
With caching, the function is 1800% faster! With caching, the function is 1800% faster!

Binary file not shown.

Binary file not shown.

@ -22,26 +22,26 @@
* SOFTWARE. * SOFTWARE.
*/ */
use super::uart; use crate::uart;
use core::sync::atomic::{compiler_fence, Ordering}; use core::sync::atomic::{compiler_fence, Ordering};
use cortex_a::{barrier, regs::*}; use cortex_a::{barrier, regs::*};
/// We assume that addr is cacheline aligned /// We assume that addr is cacheline aligned
fn batch_modify_time(addr: u64) -> Option<u64> { fn batch_modify_time(addr: usize) -> Option<u64> {
const CACHELINE_SIZE_BYTES: usize = 64; // TODO: retrieve this from a system register const CACHELINE_SIZE_BYTES: usize = 64; // TODO: retrieve this from a system register
const NUM_CACHELINES_TOUCHED: usize = 5; const NUM_CACHELINES_TOUCHED: usize = 5;
const NUM_BENCH_ITERATIONS: usize = 20_000; const NUM_BENCH_ITERATIONS: usize = 20_000;
const NUM_BYTES_TOUCHED: usize = CACHELINE_SIZE_BYTES * NUM_CACHELINES_TOUCHED; const NUM_BYTES_TOUCHED: usize = CACHELINE_SIZE_BYTES * NUM_CACHELINES_TOUCHED;
let mem = unsafe { core::slice::from_raw_parts_mut(addr as *mut u64, NUM_BYTES_TOUCHED) }; let mem = unsafe { core::slice::from_raw_parts_mut(addr as *mut usize, NUM_BYTES_TOUCHED) };
// Benchmark starts here // Benchmark starts here
let t1 = CNTPCT_EL0.get(); let t1 = CNTPCT_EL0.get();
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
let mut temp: u64; let mut temp: usize;
for _ in 0..NUM_BENCH_ITERATIONS { for _ in 0..NUM_BENCH_ITERATIONS {
for qword in mem.iter_mut() { for qword in mem.iter_mut() {
unsafe { unsafe {
@ -65,24 +65,17 @@ fn batch_modify_time(addr: u64) -> Option<u64> {
} }
pub fn run(uart: &uart::Uart) { pub fn run(uart: &uart::Uart) {
const SIZE_2MIB: u64 = 2 * 1024 * 1024; use crate::memory::map;
const ERROR_STRING: &str = "Something went wrong!";
// Start of the __SECOND__ virtual 2 MiB block (counting starts at zero).
// NON-cacheable DRAM memory.
let non_cacheable_addr: u64 = SIZE_2MIB;
// Start of the __THIRD__ virtual 2 MiB block. const ERROR_STRING: &str = "Something went wrong!";
// Cacheable DRAM memory
let cacheable_addr: u64 = 2 * SIZE_2MIB;
uart.puts("Benchmarking non-cacheable DRAM modifications at virtual 0x"); uart.puts("Benchmarking non-cacheable DRAM modifications at virtual 0x");
uart.hex(non_cacheable_addr); uart.hex(map::virt::NON_CACHEABLE_START as u64);
uart.puts(", physical 0x"); uart.puts(", physical 0x");
uart.hex(2 * SIZE_2MIB); uart.hex(map::virt::CACHEABLE_START as u64);
uart.puts(":\n"); uart.puts(":\n");
let result_nc = match batch_modify_time(non_cacheable_addr) { let result_nc = match batch_modify_time(map::virt::NON_CACHEABLE_START) {
Some(t) => { Some(t) => {
uart.dec(t as u32); uart.dec(t as u32);
uart.puts(" miliseconds.\n\n"); uart.puts(" miliseconds.\n\n");
@ -95,12 +88,12 @@ pub fn run(uart: &uart::Uart) {
}; };
uart.puts("Benchmarking cacheable DRAM modifications at virtual 0x"); uart.puts("Benchmarking cacheable DRAM modifications at virtual 0x");
uart.hex(cacheable_addr); uart.hex(map::virt::CACHEABLE_START as u64);
uart.puts(", physical 0x"); uart.puts(", physical 0x");
uart.hex(2 * SIZE_2MIB); uart.hex(map::virt::CACHEABLE_START as u64);
uart.puts(":\n"); uart.puts(":\n");
let result_c = match batch_modify_time(cacheable_addr) { let result_c = match batch_modify_time(map::virt::CACHEABLE_START) {
Some(t) => { Some(t) => {
uart.dec(t as u32); uart.dec(t as u32);
uart.puts(" miliseconds.\n\n"); uart.puts(" miliseconds.\n\n");

@ -1,7 +1,7 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2018 Andre Richter <andre.o.richter@gmail.com> * Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -22,7 +22,6 @@
* SOFTWARE. * SOFTWARE.
*/ */
use super::MMIO_BASE;
use core::ops; use core::ops;
use register::{mmio::ReadWrite, register_bitfields}; use register::{mmio::ReadWrite, register_bitfields};
@ -67,8 +66,6 @@ register_bitfields! {
] ]
} }
const GPIO_BASE: u32 = MMIO_BASE + 0x200_000;
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[repr(C)] #[repr(C)]
pub struct RegisterBlock { pub struct RegisterBlock {
@ -99,23 +96,25 @@ pub struct RegisterBlock {
} }
/// Public interface to the GPIO MMIO area /// Public interface to the GPIO MMIO area
pub struct GPIO; pub struct GPIO {
base_addr: usize,
}
impl ops::Deref for GPIO { impl ops::Deref for GPIO {
type Target = RegisterBlock; type Target = RegisterBlock;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
unsafe { &*Self::ptr() } unsafe { &*self.ptr() }
} }
} }
impl GPIO { impl GPIO {
pub fn new() -> GPIO { pub fn new(base_addr: usize) -> GPIO {
GPIO GPIO { base_addr }
} }
/// Returns a pointer to the register block /// Returns a pointer to the register block
fn ptr() -> *const RegisterBlock { fn ptr(&self) -> *const RegisterBlock {
GPIO_BASE as *const _ self.base_addr as *const _
} }
} }

@ -26,19 +26,17 @@
#![no_main] #![no_main]
#![feature(range_contains)] #![feature(range_contains)]
const MMIO_BASE: u32 = 0x3F00_0000;
mod benchmark; mod benchmark;
mod delays; mod delays;
mod gpio; mod gpio;
mod mbox; mod mbox;
mod mmu; mod memory;
mod uart; mod uart;
fn kernel_entry() -> ! { fn kernel_entry() -> ! {
let gpio = gpio::GPIO::new(); let gpio = gpio::GPIO::new(memory::map::physical::GPIO_BASE);
let mut mbox = mbox::Mbox::new(); let mut mbox = mbox::Mbox::new(memory::map::physical::VIDEOCORE_MBOX_BASE);
let uart = uart::Uart::new(uart::UART_PHYS_BASE); let uart = uart::Uart::new(memory::map::physical::UART_BASE);
// set up serial console // set up serial console
match uart.init(&mut mbox, &gpio) { match uart.init(&mut mbox, &gpio) {
@ -52,11 +50,14 @@ fn kernel_entry() -> ! {
uart.getc(); uart.getc();
uart.puts("Greetings fellow Rustacean!\n"); uart.puts("Greetings fellow Rustacean!\n");
uart.puts("[2] Switching MMU on now... "); match unsafe { memory::mmu::init() } {
Err(s) => {
unsafe { mmu::init() }; uart.puts("[2][Error] MMU: ");
uart.puts(s);
uart.puts("MMU is live \\o/\n\n"); uart.puts("\n");
}
Ok(()) => uart.puts("[2] MMU online.\n"),
}
benchmark::run(&uart); benchmark::run(&uart);

@ -1,7 +1,7 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2018 Andre Richter <andre.o.richter@gmail.com> * Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -22,7 +22,6 @@
* SOFTWARE. * SOFTWARE.
*/ */
use super::MMIO_BASE;
use core::ops; use core::ops;
use cortex_a::asm; use cortex_a::asm;
use register::{ use register::{
@ -39,8 +38,6 @@ register_bitfields! {
] ]
} }
const VIDEOCORE_MBOX: u32 = MMIO_BASE + 0xB880;
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[repr(C)] #[repr(C)]
pub struct RegisterBlock { pub struct RegisterBlock {
@ -89,6 +86,7 @@ pub struct Mbox {
// The address for buffer needs to be 16-byte aligned so that the // The address for buffer needs to be 16-byte aligned so that the
// Videcore can handle it properly. // Videcore can handle it properly.
pub buffer: [u32; 36], pub buffer: [u32; 36],
base_addr: usize,
} }
/// Deref to RegisterBlock /// Deref to RegisterBlock
@ -105,18 +103,21 @@ impl ops::Deref for Mbox {
type Target = RegisterBlock; type Target = RegisterBlock;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
unsafe { &*Self::ptr() } unsafe { &*self.ptr() }
} }
} }
impl Mbox { impl Mbox {
pub fn new() -> Mbox { pub fn new(base_addr: usize) -> Mbox {
Mbox { buffer: [0; 36] } Mbox {
buffer: [0; 36],
base_addr,
}
} }
/// Returns a pointer to the register block /// Returns a pointer to the register block
fn ptr() -> *const RegisterBlock { fn ptr(&self) -> *const RegisterBlock {
VIDEOCORE_MBOX as *const _ self.base_addr as *const _
} }
/// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success /// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success

@ -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);
}

@ -1,7 +1,7 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2018 Andre Richter <andre.o.richter@gmail.com> * Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -22,7 +22,6 @@
* SOFTWARE. * SOFTWARE.
*/ */
use super::MMIO_BASE;
use crate::delays; use crate::delays;
use crate::gpio; use crate::gpio;
use crate::mbox; use crate::mbox;
@ -120,8 +119,6 @@ register_bitfields! {
] ]
} }
pub const UART_PHYS_BASE: u32 = MMIO_BASE + 0x20_1000;
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[repr(C)] #[repr(C)]
pub struct RegisterBlock { pub struct RegisterBlock {
@ -143,7 +140,7 @@ pub enum UartError {
pub type Result<T> = ::core::result::Result<T, UartError>; pub type Result<T> = ::core::result::Result<T, UartError>;
pub struct Uart { pub struct Uart {
uart_base: u32, base_addr: usize,
} }
impl ops::Deref for Uart { impl ops::Deref for Uart {
@ -155,13 +152,13 @@ impl ops::Deref for Uart {
} }
impl Uart { impl Uart {
pub fn new(uart_base: u32) -> Uart { pub fn new(base_addr: usize) -> Uart {
Uart { uart_base } Uart { base_addr }
} }
/// Returns a pointer to the register block /// Returns a pointer to the register block
fn ptr(&self) -> *const RegisterBlock { fn ptr(&self) -> *const RegisterBlock {
self.uart_base as *const _ self.base_addr as *const _
} }
///Set baud rate and characteristics (115200 8N1) and map to GPIO ///Set baud rate and characteristics (115200 8N1) and map to GPIO

Binary file not shown.

Binary file not shown.

@ -23,9 +23,9 @@
*/ */
mod gpio; mod gpio;
mod pl011_uart; mod uart;
mod videocore_mbox; mod videocore_mbox;
pub use gpio::GPIO; pub use gpio::GPIO;
pub use pl011_uart::PL011Uart; pub use uart::Uart;
pub use videocore_mbox::VideocoreMbox; pub use videocore_mbox::VideocoreMbox;

@ -97,7 +97,7 @@ pub struct RegisterBlock {
/// Public interface to the GPIO MMIO area /// Public interface to the GPIO MMIO area
pub struct GPIO { pub struct GPIO {
base_addr: u32, base_addr: usize,
} }
impl ops::Deref for GPIO { impl ops::Deref for GPIO {
@ -109,7 +109,7 @@ impl ops::Deref for GPIO {
} }
impl GPIO { impl GPIO {
pub fn new(base_addr: u32) -> GPIO { pub fn new(base_addr: usize) -> GPIO {
GPIO { base_addr } GPIO { base_addr }
} }

@ -135,16 +135,16 @@ pub struct RegisterBlock {
ICR: WriteOnly<u32, ICR::Register>, // 0x44 ICR: WriteOnly<u32, ICR::Register>, // 0x44
} }
pub enum PL011UartError { pub enum UartError {
MailboxError, MailboxError,
} }
pub type Result<T> = ::core::result::Result<T, PL011UartError>; pub type Result<T> = ::core::result::Result<T, UartError>;
pub struct PL011Uart { pub struct Uart {
base_addr: u32, base_addr: usize,
} }
impl ops::Deref for PL011Uart { impl ops::Deref for Uart {
type Target = RegisterBlock; type Target = RegisterBlock;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@ -152,9 +152,9 @@ impl ops::Deref for PL011Uart {
} }
} }
impl PL011Uart { impl Uart {
pub fn new(base_addr: u32) -> PL011Uart { pub fn new(base_addr: usize) -> Uart {
PL011Uart { base_addr } Uart { base_addr }
} }
/// Returns a pointer to the register block /// Returns a pointer to the register block
@ -188,7 +188,7 @@ impl PL011Uart {
compiler_fence(Ordering::Release); compiler_fence(Ordering::Release);
if v_mbox.call(videocore_mbox::channel::PROP).is_err() { if v_mbox.call(videocore_mbox::channel::PROP).is_err() {
return Err(PL011UartError::MailboxError); // Abort if UART clocks couldn't be set return Err(UartError::MailboxError); // Abort if UART clocks couldn't be set
}; };
// map UART0 to GPIO pins // map UART0 to GPIO pins
@ -217,14 +217,14 @@ impl PL011Uart {
} }
} }
impl Drop for PL011Uart { impl Drop for Uart {
fn drop(&mut self) { fn drop(&mut self) {
self.CR self.CR
.write(CR::UARTEN::Disabled + CR::TXE::Disabled + CR::RXE::Disabled); .write(CR::UARTEN::Disabled + CR::TXE::Disabled + CR::RXE::Disabled);
} }
} }
impl ConsoleOps for PL011Uart { impl ConsoleOps for Uart {
/// Send a character /// Send a character
fn putc(&self, c: char) { fn putc(&self, c: char) {
// wait until we can send // wait until we can send

@ -87,7 +87,7 @@ pub struct VideocoreMbox {
// can handle it properly. Hence, put it at the start of the struct so that // can handle it properly. Hence, put it at the start of the struct so that
// the align attribute is effective. // the align attribute is effective.
pub buffer: [u32; 36], pub buffer: [u32; 36],
base_addr: u32, base_addr: usize,
} }
/// Deref to RegisterBlock /// Deref to RegisterBlock
@ -109,7 +109,7 @@ impl ops::Deref for VideocoreMbox {
} }
impl VideocoreMbox { impl VideocoreMbox {
pub fn new(base_addr: u32) -> VideocoreMbox { pub fn new(base_addr: usize) -> VideocoreMbox {
VideocoreMbox { VideocoreMbox {
buffer: [0; 36], buffer: [0; 36],
base_addr, base_addr,

@ -47,12 +47,12 @@ impl ConsoleOps for NullConsole {}
/// Possible outputs which the console can store. /// Possible outputs which the console can store.
pub enum Output { pub enum Output {
None(NullConsole), None(NullConsole),
PL011Uart(hw::PL011Uart), Uart(hw::Uart),
} }
impl From<hw::PL011Uart> for Output { impl From<hw::Uart> for Output {
fn from(instance: hw::PL011Uart) -> Self { fn from(instance: hw::Uart) -> Self {
Output::PL011Uart(instance) Output::Uart(instance)
} }
} }
@ -71,7 +71,7 @@ impl Console {
fn current_ptr(&self) -> &dyn ConsoleOps { fn current_ptr(&self) -> &dyn ConsoleOps {
match &self.output { match &self.output {
Output::None(i) => i, Output::None(i) => i,
Output::PL011Uart(i) => i, Output::Uart(i) => i,
} }
} }

@ -50,17 +50,17 @@ fn kernel_entry() -> ! {
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate GPIO device // Instantiate GPIO device
//------------------------------------------------------------ //------------------------------------------------------------
let gpio = hw::GPIO::new(memory::map::GPIO_BASE); let gpio = hw::GPIO::new(memory::map::physical::GPIO_BASE);
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate Videocore Mailbox // Instantiate Videocore Mailbox
//------------------------------------------------------------ //------------------------------------------------------------
let mut v_mbox = hw::VideocoreMbox::new(memory::map::VIDEOCORE_MBOX_BASE); let mut v_mbox = hw::VideocoreMbox::new(memory::map::physical::VIDEOCORE_MBOX_BASE);
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate PL011 UART and put it in CONSOLE // Instantiate PL011 UART and put it in CONSOLE
//------------------------------------------------------------ //------------------------------------------------------------
let uart = hw::PL011Uart::new(memory::map::PL011_UART_BASE); let uart = hw::Uart::new(memory::map::physical::UART_BASE);
match uart.init(&mut v_mbox, &gpio) { match uart.init(&mut v_mbox, &gpio) {
Ok(_) => { Ok(_) => {
@ -89,9 +89,11 @@ fn kernel_entry() -> ! {
//------------------------------------------------------------ //------------------------------------------------------------
// Bring up memory subsystem // Bring up memory subsystem
//------------------------------------------------------------ //------------------------------------------------------------
print!("[2] Switching MMU on now... "); if unsafe { memory::mmu::init() }.is_err() {
unsafe { memory::mmu::init() }; println!("[2][Error] Could not set up MMU. Aborting.");
println!("MMU online."); } else {
println!("[2] MMU online.");
}
memory::print_layout(); memory::print_layout();

@ -23,91 +23,246 @@
*/ */
use crate::println; use crate::println;
use core::fmt;
use core::ops::RangeInclusive;
pub mod mmu; pub mod mmu;
/// The system memory map. /// System memory map.
#[rustfmt::skip] #[rustfmt::skip]
pub mod map { pub mod map {
pub const KERN_STACK_BOT: u32 = 0x0000_0000; pub const START: usize = 0x0000_0000;
pub const KERN_STACK_TOP: u32 = 0x0007_FFFF; pub const END: usize = 0x3FFF_FFFF;
pub const MMIO_BASE: u32 = 0x3F00_0000; pub mod physical {
pub const VIDEOCORE_MBOX_BASE: u32 = MMIO_BASE + 0x0000_B880; pub const MMIO_BASE: usize = 0x3F00_0000;
pub const GPIO_BASE: u32 = MMIO_BASE + 0x0020_0000; pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880;
pub const PL011_UART_BASE: u32 = MMIO_BASE + 0x0020_1000; 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 const PHYS_ADDR_MAX: u32 = 0x3FFF_FFFF; pub mod virt {
pub const KERN_STACK_START: usize = super::START;
pub const KERN_STACK_END: usize = 0x0007_FFFF;
}
} }
const PAGESIZE: u64 = 4096; /// Types used for compiling the virtual memory layout of the kernel using
/// address ranges.
pub mod kernel_mem_range {
use core::ops::RangeInclusive;
fn get_ro_start_end() -> (u64, u64) { #[allow(dead_code)]
// Using the linker script, we ensure that the RO area is consecutive and 4 #[derive(Copy, Clone)]
// KiB aligned, and we export the boundaries via symbols. pub enum MemAttributes {
extern "C" { CacheableDRAM,
// The inclusive start of the read-only area, aka the address of the NonCacheableDRAM,
// first byte of the area. Device,
static __ro_start: u64; }
// The non-inclusive end of the read-only area, aka the address of the #[derive(Copy, Clone)]
// first byte _after_ the RO area. pub enum AccessPermissions {
static __ro_end: u64; ReadOnly,
ReadWrite,
} }
unsafe { #[allow(dead_code)]
// Notice the subtraction to calculate the last page index of the RO #[derive(Copy, Clone)]
// area and not the first page index after the RO area. pub enum Translation {
( Identity,
&__ro_start as *const _ as u64, Offset(usize),
&__ro_end as *const _ as u64 - 1, }
)
#[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 name: &'static str,
pub virtual_range: fn() -> RangeInclusive<usize>,
pub translation: Translation,
pub attribute_fields: AttributeFields,
} }
} }
pub fn print_layout() { use kernel_mem_range::*;
use crate::memory::map::*;
// log2(1024) /// A virtual memory layout that is agnostic of the paging granularity that the
const KIB_RSHIFT: u32 = 10; /// hardware MMU will use.
///
/// Contains only special ranges, aka anything that is _not_ normal cacheable
/// DRAM.
static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 4] = [
Descriptor {
name: "Kernel stack",
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,
},
},
Descriptor {
name: "Kernel code and RO data",
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;
// log2(1024 * 1024) // The exclusive end of the read-only area, aka the address of
const MIB_RSHIFT: u32 = 20; // the first byte _after_ the RO area.
static __ro_end: u64;
}
println!("[i] Memory layout:"); 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,
},
},
Descriptor {
name: "Kernel data and BSS",
virtual_range: || {
extern "C" {
static __ro_end: u64;
static __bss_end: u64;
}
println!( unsafe {
" {:#010X} - {:#010X} | {: >4} KiB | Kernel stack", RangeInclusive::new(
KERN_STACK_BOT, &__ro_end as *const _ as usize,
KERN_STACK_TOP, &__bss_end as *const _ as usize - 1,
(KERN_STACK_TOP - KERN_STACK_BOT + 1) >> KIB_RSHIFT )
); }
},
translation: Translation::Identity,
attribute_fields: AttributeFields {
mem_attributes: MemAttributes::CacheableDRAM,
acc_perms: AccessPermissions::ReadWrite,
execute_never: true,
},
},
Descriptor {
name: "Device MMIO",
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,
},
},
];
let (ro_start, ro_end) = get_ro_start_end(); /// For a given virtual address, find and return the output address and
println!( /// according attributes.
" {:#010X} - {:#010X} | {: >4} KiB | Kernel code and RO data", ///
ro_start, /// If the address is not covered in VIRTUAL_LAYOUT, return a default for normal
ro_end, /// cacheable DRAM.
(ro_end - ro_start + 1) >> KIB_RSHIFT fn get_virt_addr_properties(virt_addr: usize) -> Result<(usize, AttributeFields), &'static str> {
); if virt_addr > map::END {
return Err("Address out of range.");
}
extern "C" { for i in KERNEL_VIRTUAL_LAYOUT.iter() {
static __bss_end: u64; 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));
}
} }
let start = ro_end + 1; Ok((virt_addr, AttributeFields::default()))
let end = unsafe { &__bss_end as *const _ as u64 } - 1; }
println!(
" {:#010X} - {:#010X} | {: >4} KiB | Kernel data and BSS", /// Human-readable output of a Descriptor.
start, impl fmt::Display for Descriptor {
end, fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(end - start + 1) >> KIB_RSHIFT // Call the function to which self.range points, and dereference the
); // result, which causes Rust to copy the value.
let start = *(self.virtual_range)().start();
println!( let end = *(self.virtual_range)().end();
" {:#010X} - {:#010X} | {: >4} MiB | Device MMIO", let size = end - start + 1;
MMIO_BASE,
PHYS_ADDR_MAX, // log2(1024)
(PHYS_ADDR_MAX - MMIO_BASE + 1) >> MIB_RSHIFT const KIB_RSHIFT: u32 = 10;
);
// log2(1024 * 1024)
const MIB_RSHIFT: u32 = 20;
let (size, unit) = if (size >> MIB_RSHIFT) > 0 {
(size >> MIB_RSHIFT, "MiB")
} else if (size >> KIB_RSHIFT) > 0 {
(size >> KIB_RSHIFT, "KiB")
} else {
(size, "Byte")
};
let attr = match self.attribute_fields.mem_attributes {
MemAttributes::CacheableDRAM => "C",
MemAttributes::NonCacheableDRAM => "NC",
MemAttributes::Device => "Dev",
};
let acc_p = match self.attribute_fields.acc_perms {
AccessPermissions::ReadOnly => "RO",
AccessPermissions::ReadWrite => "RW",
};
let xn = if self.attribute_fields.execute_never {
"PXN"
} else {
"PX"
};
write!(
f,
" {:#010X} - {:#010X} | {: >3} {} | {: <3} {} {: <3} | {}",
start, end, size, unit, attr, acc_p, xn, self.name
)
}
}
/// Print the kernel memory layout.
pub fn print_layout() {
println!("[i] Kernel memory layout:");
for i in KERNEL_VIRTUAL_LAYOUT.iter() {
println!("{}", i);
}
} }

@ -22,14 +22,15 @@
* SOFTWARE. * SOFTWARE.
*/ */
use crate::memory::{get_virt_addr_properties, AttributeFields};
use cortex_a::{barrier, regs::*}; use cortex_a::{barrier, regs::*};
use register::register_bitfields; use register::register_bitfields;
register_bitfields! {u64, register_bitfields! {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
], ],
@ -73,42 +74,150 @@ register_bitfields! {u64,
] ]
} }
trait BaseAddr { const FOUR_KIB: usize = 4 * 1024;
fn base_addr(&self) -> u64; 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
}
} }
impl BaseAddr for [u64; 512] { /// A function that maps the generic memory range attributes to HW-specific
fn base_addr(&self) -> u64 { /// attributes of the MMU.
self as *const u64 as u64 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
} }
} }
const NUM_ENTRIES_4KIB: usize = 512; /// 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),
))
}
// We need a wrapper struct here so that we can make use of the align attribute. fn value(&self) -> u64 {
#[repr(C)] self.0.value
#[repr(align(4096))] }
struct PageTable {
entries: [u64; NUM_ENTRIES_4KIB],
} }
static mut LVL2_TABLE: PageTable = PageTable { /// Constants for indexing the MAIR_EL1.
entries: [0; NUM_ENTRIES_4KIB], #[allow(dead_code)]
}; mod mair {
static mut SINGLE_LVL3_TABLE: PageTable = PageTable { pub const DEVICE: u64 = 0;
entries: [0; NUM_ENTRIES_4KIB], pub const NORMAL: u64 = 1;
}; pub const NORMAL_NON_CACHEABLE: u64 = 2;
}
/// Set up identity mapped page tables for the first 1 GiB of address space. /// Setup function for the MAIR_EL1 register.
pub unsafe fn init() { fn set_up_mair() {
// First, define the three memory types that we will map. Cacheable and // Define the three memory types that we will map. Cacheable and
// non-cacheable normal DRAM, and device. // non-cacheable normal DRAM, and device.
MAIR_EL1.write( MAIR_EL1.write(
// Attribute 2 // Attribute 2
MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable
+ MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable + MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable
// Attribute 1 // Attribute 1
+ MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc + MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
+ MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc + MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc
@ -116,92 +225,97 @@ pub unsafe fn init() {
+ MAIR_EL1::Attr0_HIGH::Device + MAIR_EL1::Attr0_HIGH::Device
+ MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE, + MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE,
); );
}
trait BaseAddr {
fn base_addr_u64(&self) -> u64;
fn base_addr_usize(&self) -> usize;
}
// Descriptive consts for indexing into the correct MAIR_EL1 attributes. impl BaseAddr for [u64; 512] {
#[allow(dead_code)] fn base_addr_u64(&self) -> u64 {
mod mair { self as *const u64 as u64
pub const DEVICE: u64 = 0;
pub const NORMAL: u64 = 1;
pub const NORMAL_NON_CACHEABLE: u64 = 2;
} }
// The first 2 MiB. fn base_addr_usize(&self) -> usize {
// self as *const u64 as usize
// Set up the first LVL2 entry, pointing to the base address of a follow-up }
// table containing 4 KiB pages. }
//
// 0x0000_0000_0000_0000 | const NUM_ENTRIES_4KIB: usize = 512;
// |> 2 MiB
// 0x0000_0000_001F_FFFF | // A wrapper struct is needed here so that the align attribute can be used.
let lvl3_base: u64 = SINGLE_LVL3_TABLE.entries.base_addr() >> 12; #[repr(C)]
LVL2_TABLE.entries[0] = (STAGE1_DESCRIPTOR::VALID::True #[repr(align(4096))]
+ STAGE1_DESCRIPTOR::TYPE::Table struct PageTable {
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(lvl3_base)) entries: [u64; NUM_ENTRIES_4KIB],
.value; }
/// 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. // 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_0020_0000 |
// |> 1006 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 = (crate::memory::map::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(1) which makes the iteration start at the second 2 MiB // Notice the skip(1) which makes the iteration start at the second 2 MiB
// block (0x20_0000). // block (0x20_0000).
for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) { for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) {
let j: u64 = i as u64; let virt_addr = block_descriptor_nr << TWO_MIB_SHIFT;
let mem_attr = if j >= mmio_first_block_index { let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE) Err(s) => return Err(s),
} else { Ok((a, b)) => (a, b),
STAGE1_DESCRIPTOR::SH::InnerShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL)
}; };
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(j)).value; let block_desc = match Lvl2BlockDescriptor::new(output_addr, attribute_fields) {
} Err(s) => return Err(s),
Ok(desc) => desc,
// Finally, fill the single LVL3 table (4 KiB granule). Differentiate };
// between code+RO and RW pages.
let (ro_start_addr, ro_end_addr) = crate::memory::get_ro_start_end();
let ro_first_page_index = ro_start_addr / crate::memory::PAGESIZE; *entry = block_desc.value();
let ro_last_page_index = ro_end_addr / crate::memory::PAGESIZE; }
let common = STAGE1_DESCRIPTOR::VALID::True // Finally, fill the single LVL3 table (4 KiB granule).
+ STAGE1_DESCRIPTOR::TYPE::Table for (page_descriptor_nr, entry) in LVL3_TABLE.entries.iter_mut().enumerate() {
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) let virt_addr = page_descriptor_nr << FOUR_KIB_SHIFT;
+ STAGE1_DESCRIPTOR::SH::InnerShareable
+ STAGE1_DESCRIPTOR::AF::True;
for (i, entry) in SINGLE_LVL3_TABLE.entries.iter_mut().enumerate() { let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
let j: u64 = i as u64; Err(s) => return Err(s),
Ok((a, b)) => (a, b),
};
let mem_attr = if (ro_first_page_index..=ro_last_page_index).contains(&j) { let page_desc = match PageDescriptor::new(output_addr, attribute_fields) {
STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False Err(s) => return Err(s),
} else { Ok(desc) => desc,
STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True
}; };
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value; *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);
@ -226,4 +340,6 @@ pub unsafe fn init() {
// Force MMU init to complete before next instruction // Force MMU init to complete before next instruction
barrier::isb(barrier::SY); barrier::isb(barrier::SY);
Ok(())
} }

@ -8,16 +8,21 @@ This lesson will teach about:
Uart later (which now needs the memory allocator that theoretically could fail Uart later (which now needs the memory allocator that theoretically could fail
- which the MiniUart could then print). - which the MiniUart could then print).
## Output
```console ```console
ferris@box:~$ make raspboot
[0] MiniUart online. [0] MiniUart online.
[1] Press a key to continue booting... Greetings fellow Rustacean! [1] Press a key to continue booting... Greetings fellow Rustacean!
[2] Switching MMU on now... MMU online. [2] MMU online.
[i] Memory layout: [i] Kernel memory layout:
0x00000000 - 0x0007FFFF | 512 KiB | Kernel stack 0x00000000 - 0x0007FFFF | 512 KiB | C RW PXN | Kernel stack
0x00080000 - 0x00083FFF | 16 KiB | Kernel code and RO data 0x00080000 - 0x00083FFF | 16 KiB | C RO PX | Kernel code and RO data
0x00084000 - 0x00087007 | 12 KiB | Kernel data and BSS 0x00084000 - 0x0008700F | 12 KiB | C RW PXN | Kernel data and BSS
0x00200000 - 0x005FFFFF | 4 MiB | DMA heap pool 0x00200000 - 0x005FFFFF | 4 MiB | NC RW PXN | DMA heap pool
0x3F000000 - 0x3FFFFFFF | 16 MiB | Device MMIO 0x3F000000 - 0x3FFFFFFF | 16 MiB | Dev RW PXN | Device MMIO
[i] Global DMA Allocator: [i] Global DMA Allocator:
Allocated Addr 0x00200000 Size 0x90 Allocated Addr 0x00200000 Size 0x90
[3] Videocore Mailbox set up (DMA mem heap allocation successful). [3] Videocore Mailbox set up (DMA mem heap allocation successful).

Binary file not shown.

Binary file not shown.

@ -97,7 +97,7 @@ pub struct RegisterBlock {
/// Public interface to the GPIO MMIO area /// Public interface to the GPIO MMIO area
pub struct GPIO { pub struct GPIO {
base_addr: u32, base_addr: usize,
} }
impl ops::Deref for GPIO { impl ops::Deref for GPIO {
@ -109,7 +109,7 @@ impl ops::Deref for GPIO {
} }
impl GPIO { impl GPIO {
pub fn new(base_addr: u32) -> GPIO { pub fn new(base_addr: usize) -> GPIO {
GPIO { base_addr } GPIO { base_addr }
} }

@ -123,7 +123,7 @@ pub struct RegisterBlock {
} }
pub struct MiniUart { pub struct MiniUart {
base_addr: u32, base_addr: usize,
} }
/// Deref to RegisterBlock /// Deref to RegisterBlock
@ -145,7 +145,7 @@ impl ops::Deref for MiniUart {
} }
impl MiniUart { impl MiniUart {
pub fn new(base_addr: u32) -> MiniUart { pub fn new(base_addr: usize) -> MiniUart {
MiniUart { base_addr } MiniUart { base_addr }
} }

@ -141,7 +141,7 @@ pub enum PL011UartError {
pub type Result<T> = ::core::result::Result<T, PL011UartError>; pub type Result<T> = ::core::result::Result<T, PL011UartError>;
pub struct PL011Uart { pub struct PL011Uart {
base_addr: u32, base_addr: usize,
} }
impl ops::Deref for PL011Uart { impl ops::Deref for PL011Uart {
@ -153,7 +153,7 @@ impl ops::Deref for PL011Uart {
} }
impl PL011Uart { impl PL011Uart {
pub fn new(base_addr: u32) -> PL011Uart { pub fn new(base_addr: usize) -> PL011Uart {
PL011Uart { base_addr } PL011Uart { base_addr }
} }

@ -87,7 +87,7 @@ const MBOX_SIZE: usize = 36;
// Public interface to the mailbox // Public interface to the mailbox
pub struct VideocoreMbox<'a> { pub struct VideocoreMbox<'a> {
pub buffer: &'a mut [u32], pub buffer: &'a mut [u32],
base_addr: u32, base_addr: usize,
} }
/// Deref to RegisterBlock /// Deref to RegisterBlock
@ -109,7 +109,7 @@ impl<'a> ops::Deref for VideocoreMbox<'a> {
} }
impl<'a> VideocoreMbox<'a> { impl<'a> VideocoreMbox<'a> {
pub fn new(base_addr: u32) -> ::core::result::Result<VideocoreMbox<'a>, ()> { pub fn new(base_addr: usize) -> ::core::result::Result<VideocoreMbox<'a>, ()> {
let ret = crate::DMA_ALLOCATOR.lock(|d| d.alloc_slice_zeroed(MBOX_SIZE, MBOX_ALIGNMENT)); let ret = crate::DMA_ALLOCATOR.lock(|d| d.alloc_slice_zeroed(MBOX_SIZE, MBOX_ALIGNMENT));
if ret.is_err() { if ret.is_err() {

@ -45,8 +45,8 @@ static CONSOLE: sync::NullLock<devices::virt::Console> =
/// non-cacheable in the page tables. /// non-cacheable in the page tables.
static DMA_ALLOCATOR: sync::NullLock<memory::BumpAllocator> = static DMA_ALLOCATOR: sync::NullLock<memory::BumpAllocator> =
sync::NullLock::new(memory::BumpAllocator::new( sync::NullLock::new(memory::BumpAllocator::new(
memory::map::DMA_HEAP_START as usize, memory::map::virt::DMA_HEAP_START as usize,
memory::map::DMA_HEAP_END as usize, memory::map::virt::DMA_HEAP_END as usize,
"Global DMA Allocator", "Global DMA Allocator",
// Try the following arguments instead to see the PL011 UART init // Try the following arguments instead to see the PL011 UART init
// fail. It will cause the allocator to use memory that are marked // fail. It will cause the allocator to use memory that are marked
@ -66,12 +66,12 @@ fn kernel_entry() -> ! {
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate GPIO device // Instantiate GPIO device
//------------------------------------------------------------ //------------------------------------------------------------
let gpio = hw::GPIO::new(memory::map::GPIO_BASE); let gpio = hw::GPIO::new(memory::map::physical::GPIO_BASE);
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate MiniUart // Instantiate MiniUart
//------------------------------------------------------------ //------------------------------------------------------------
let mini_uart = hw::MiniUart::new(memory::map::MINI_UART_BASE); let mini_uart = hw::MiniUart::new(memory::map::physical::MINI_UART_BASE);
mini_uart.init(&gpio); mini_uart.init(&gpio);
CONSOLE.lock(|c| { CONSOLE.lock(|c| {
@ -90,24 +90,26 @@ fn kernel_entry() -> ! {
}); });
println!("Greetings fellow Rustacean!"); println!("Greetings fellow Rustacean!");
//------------------------------------------------------------
// Bring up memory subsystem
//------------------------------------------------------------
print!("[2] Switching MMU on now... ");
unsafe { memory::mmu::init() };
println!("MMU online.");
memory::print_layout();
// We are now in a state where every next step can fail, but we can handle // We are now in a state where every next step can fail, but we can handle
// the error with feedback for the user and fall through to our UART // the error with feedback for the user and fall through to our UART
// loopback. // loopback.
'init: { 'init: {
//------------------------------------------------------------
// Bring up memory subsystem
//------------------------------------------------------------
if unsafe { memory::mmu::init() }.is_err() {
println!("[2][Error] Could not set up MMU. Aborting.");
break 'init;
};
println!("[2] MMU online.");
memory::print_layout();
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate Videocore Mailbox // Instantiate Videocore Mailbox
//------------------------------------------------------------ //------------------------------------------------------------
let mut v_mbox; let mut v_mbox;
match hw::VideocoreMbox::new(memory::map::VIDEOCORE_MBOX_BASE) { match hw::VideocoreMbox::new(memory::map::physical::VIDEOCORE_MBOX_BASE) {
Ok(i) => { Ok(i) => {
println!("[3] Videocore Mailbox set up (DMA mem heap allocation successful)."); println!("[3] Videocore Mailbox set up (DMA mem heap allocation successful).");
v_mbox = i; v_mbox = i;
@ -122,7 +124,7 @@ fn kernel_entry() -> ! {
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate PL011 UART and replace MiniUart with it in CONSOLE // Instantiate PL011 UART and replace MiniUart with it in CONSOLE
//------------------------------------------------------------ //------------------------------------------------------------
let pl011_uart = hw::PL011Uart::new(memory::map::PL011_UART_BASE); let pl011_uart = hw::PL011Uart::new(memory::map::physical::PL011_UART_BASE);
// uart.init() will reconfigure the GPIO, which causes a race against // uart.init() will reconfigure the GPIO, which causes a race against
// the MiniUart that is still putting out characters on the physical // the MiniUart that is still putting out characters on the physical

@ -23,111 +23,270 @@
*/ */
use crate::println; use crate::println;
use core::fmt;
use core::ops::RangeInclusive;
mod bump_allocator; mod bump_allocator;
pub use bump_allocator::BumpAllocator; pub use bump_allocator::BumpAllocator;
pub mod mmu; pub mod mmu;
/// The system memory map. /// System memory map.
#[rustfmt::skip] #[rustfmt::skip]
pub mod map { pub mod map {
pub const KERN_STACK_BOT: u32 = 0x0000_0000; pub const START: usize = 0x0000_0000;
pub const KERN_STACK_TOP: u32 = 0x0007_FFFF; pub const END: usize = 0x3FFF_FFFF;
/// The second 2 MiB block. pub mod physical {
pub const DMA_HEAP_START: u32 = 0x0020_0000; pub const MMIO_BASE: usize = 0x3F00_0000;
pub const DMA_HEAP_END: u32 = 0x005F_FFFF; pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880;
pub const GPIO_BASE: usize = MMIO_BASE + 0x0020_0000;
pub const PL011_UART_BASE: usize = MMIO_BASE + 0x0020_1000;
pub const MINI_UART_BASE: usize = MMIO_BASE + 0x0021_5000;
pub const MMIO_END: usize = super::END;
}
pub const MMIO_BASE: u32 = 0x3F00_0000; pub mod virt {
pub const VIDEOCORE_MBOX_BASE: u32 = MMIO_BASE + 0x0000_B880; pub const KERN_STACK_START: usize = super::START;
pub const GPIO_BASE: u32 = MMIO_BASE + 0x0020_0000; pub const KERN_STACK_END: usize = 0x0007_FFFF;
pub const PL011_UART_BASE: u32 = MMIO_BASE + 0x0020_1000;
pub const MINI_UART_BASE: u32 = MMIO_BASE + 0x0021_5000;
pub const PHYS_ADDR_MAX: u32 = 0x3FFF_FFFF; // The second 2 MiB block.
pub const DMA_HEAP_START: usize = 0x0020_0000;
pub const DMA_HEAP_END: usize = 0x005F_FFFF;
}
} }
const PAGESIZE: u64 = 4096; /// Types used for compiling the virtual memory layout of the kernel using
/// address ranges.
pub mod kernel_mem_range {
use core::ops::RangeInclusive;
#[inline] #[derive(Copy, Clone)]
fn aligned_addr_unchecked(addr: usize, alignment: usize) -> usize { pub enum MemAttributes {
(addr + (alignment - 1)) & !(alignment - 1) CacheableDRAM,
NonCacheableDRAM,
Device,
}
#[derive(Copy, Clone)]
pub enum AccessPermissions {
ReadOnly,
ReadWrite,
}
#[allow(dead_code)]
#[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 name: &'static str,
pub virtual_range: fn() -> RangeInclusive<usize>,
pub translation: Translation,
pub attribute_fields: AttributeFields,
}
} }
fn get_ro_start_end() -> (u64, u64) { use kernel_mem_range::*;
// Using the linker script, we ensure that the RO area is consecutive and 4
// KiB aligned, and we export the boundaries via symbols. /// A virtual memory layout that is agnostic of the paging granularity that the
extern "C" { /// hardware MMU will use.
// The inclusive start of the read-only area, aka the address of the ///
// first byte of the area. /// Contains only special ranges, aka anything that is _not_ normal cacheable
static __ro_start: u64; /// DRAM.
static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 5] = [
// The non-inclusive end of the read-only area, aka the address of the Descriptor {
// first byte _after_ the RO area. name: "Kernel stack",
static __ro_end: u64; 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,
},
},
Descriptor {
name: "Kernel code and RO data",
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,
},
},
Descriptor {
name: "Kernel data and BSS",
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,
},
},
Descriptor {
name: "DMA heap pool",
virtual_range: || RangeInclusive::new(map::virt::DMA_HEAP_START, map::virt::DMA_HEAP_END),
translation: Translation::Identity,
attribute_fields: AttributeFields {
mem_attributes: MemAttributes::NonCacheableDRAM,
acc_perms: AccessPermissions::ReadWrite,
execute_never: true,
},
},
Descriptor {
name: "Device MMIO",
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.");
} }
unsafe { for i in KERNEL_VIRTUAL_LAYOUT.iter() {
// Notice the subtraction to calculate the last page index of the RO if (i.virtual_range)().contains(&virt_addr) {
// area and not the first page index after the RO area. let output_addr = match i.translation {
( Translation::Identity => virt_addr,
&__ro_start as *const _ as u64, Translation::Offset(a) => a + (virt_addr - (i.virtual_range)().start()),
&__ro_end as *const _ as u64 - 1, };
)
return Ok((output_addr, i.attribute_fields));
}
} }
Ok((virt_addr, AttributeFields::default()))
} }
pub fn print_layout() { /// Human-readable output of a Descriptor.
use crate::memory::map::*; impl fmt::Display for Descriptor {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Call the function to which self.range points, and dereference the
// result, which causes Rust to copy the value.
let start = *(self.virtual_range)().start();
let end = *(self.virtual_range)().end();
let size = end - start + 1;
// log2(1024)
const KIB_RSHIFT: u32 = 10;
// log2(1024 * 1024)
const MIB_RSHIFT: u32 = 20;
// log2(1024) let (size, unit) = if (size >> MIB_RSHIFT) > 0 {
const KIB_RSHIFT: u32 = 10; (size >> MIB_RSHIFT, "MiB")
} else if (size >> KIB_RSHIFT) > 0 {
(size >> KIB_RSHIFT, "KiB")
} else {
(size, "Byte")
};
// log2(1024 * 1024) let attr = match self.attribute_fields.mem_attributes {
const MIB_RSHIFT: u32 = 20; MemAttributes::CacheableDRAM => "C",
MemAttributes::NonCacheableDRAM => "NC",
MemAttributes::Device => "Dev",
};
println!("[i] Memory layout:"); let acc_p = match self.attribute_fields.acc_perms {
AccessPermissions::ReadOnly => "RO",
AccessPermissions::ReadWrite => "RW",
};
println!( let xn = if self.attribute_fields.execute_never {
" {:#010X} - {:#010X} | {: >4} KiB | Kernel stack", "PXN"
KERN_STACK_BOT, } else {
KERN_STACK_TOP, "PX"
(KERN_STACK_TOP - KERN_STACK_BOT + 1) >> KIB_RSHIFT };
);
let (ro_start, ro_end) = get_ro_start_end(); write!(
println!( f,
" {:#010X} - {:#010X} | {: >4} KiB | Kernel code and RO data", " {:#010X} - {:#010X} | {: >3} {} | {: <3} {} {: <3} | {}",
ro_start, start, end, size, unit, attr, acc_p, xn, self.name
ro_end, )
(ro_end - ro_start + 1) >> KIB_RSHIFT }
); }
/// Print the kernel memory layout.
pub fn print_layout() {
println!("[i] Kernel memory layout:");
extern "C" { for i in KERNEL_VIRTUAL_LAYOUT.iter() {
static __bss_end: u64; println!("{}", i);
} }
}
let start = ro_end + 1; /// Calculate the next possible aligned address without sanity checking the
let end = unsafe { &__bss_end as *const _ as u64 } - 1; /// input parameters.
println!( #[inline]
" {:#010X} - {:#010X} | {: >4} KiB | Kernel data and BSS", fn aligned_addr_unchecked(addr: usize, alignment: usize) -> usize {
start, (addr + (alignment - 1)) & !(alignment - 1)
end,
(end - start + 1) >> KIB_RSHIFT
);
println!(
" {:#010X} - {:#010X} | {: >4} MiB | DMA heap pool",
DMA_HEAP_START,
DMA_HEAP_END,
(DMA_HEAP_END - DMA_HEAP_START + 1) >> MIB_RSHIFT
);
println!(
" {:#010X} - {:#010X} | {: >4} MiB | Device MMIO",
MMIO_BASE,
PHYS_ADDR_MAX,
(PHYS_ADDR_MAX - MMIO_BASE + 1) >> MIB_RSHIFT
);
} }

@ -22,14 +22,15 @@
* SOFTWARE. * SOFTWARE.
*/ */
use crate::memory::{get_virt_addr_properties, AttributeFields};
use cortex_a::{barrier, regs::*}; use cortex_a::{barrier, regs::*};
use register::register_bitfields; use register::register_bitfields;
register_bitfields! {u64, register_bitfields! {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
], ],
@ -73,42 +74,150 @@ register_bitfields! {u64,
] ]
} }
trait BaseAddr { const FOUR_KIB: usize = 4 * 1024;
fn base_addr(&self) -> u64; 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
}
} }
impl BaseAddr for [u64; 512] { /// A function that maps the generic memory range attributes to HW-specific
fn base_addr(&self) -> u64 { /// attributes of the MMU.
self as *const u64 as u64 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
} }
} }
const NUM_ENTRIES_4KIB: usize = 512; /// 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),
))
}
// We need a wrapper struct here so that we can make use of the align attribute. fn value(&self) -> u64 {
#[repr(C)] self.0.value
#[repr(align(4096))] }
struct PageTable {
entries: [u64; NUM_ENTRIES_4KIB],
} }
static mut LVL2_TABLE: PageTable = PageTable { /// Constants for indexing the MAIR_EL1.
entries: [0; NUM_ENTRIES_4KIB], #[allow(dead_code)]
}; mod mair {
static mut SINGLE_LVL3_TABLE: PageTable = PageTable { pub const DEVICE: u64 = 0;
entries: [0; NUM_ENTRIES_4KIB], pub const NORMAL: u64 = 1;
}; pub const NORMAL_NON_CACHEABLE: u64 = 2;
}
/// Set up identity mapped page tables for the first 1 GiB of address space. /// Setup function for the MAIR_EL1 register.
pub unsafe fn init() { fn set_up_mair() {
// First, define the three memory types that we will map. Cacheable and // Define the three memory types that we will map. Cacheable and
// non-cacheable normal DRAM, and device. // non-cacheable normal DRAM, and device.
MAIR_EL1.write( MAIR_EL1.write(
// Attribute 2 // Attribute 2
MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable
+ MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable + MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable
// Attribute 1 // Attribute 1
+ MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc + MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
+ MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc + MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc
@ -116,101 +225,97 @@ pub unsafe fn init() {
+ MAIR_EL1::Attr0_HIGH::Device + MAIR_EL1::Attr0_HIGH::Device
+ MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE, + MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE,
); );
}
trait BaseAddr {
fn base_addr_u64(&self) -> u64;
fn base_addr_usize(&self) -> usize;
}
// Descriptive consts for indexing into the correct MAIR_EL1 attributes. impl BaseAddr for [u64; 512] {
#[allow(dead_code)] fn base_addr_u64(&self) -> u64 {
mod mair { self as *const u64 as u64
pub const DEVICE: u64 = 0;
pub const NORMAL: u64 = 1;
pub const NORMAL_NON_CACHEABLE: u64 = 2;
} }
// The first 2 MiB. fn base_addr_usize(&self) -> usize {
// self as *const u64 as usize
// Set up the first LVL2 entry, pointing to the base address of a follow-up }
// table containing 4 KiB pages. }
//
// 0x0000_0000_0000_0000 | const NUM_ENTRIES_4KIB: usize = 512;
// |> 2 MiB
// 0x0000_0000_001F_FFFF | // A wrapper struct is needed here so that the align attribute can be used.
let lvl3_base: u64 = SINGLE_LVL3_TABLE.entries.base_addr() >> 12; #[repr(C)]
LVL2_TABLE.entries[0] = (STAGE1_DESCRIPTOR::VALID::True #[repr(align(4096))]
+ STAGE1_DESCRIPTOR::TYPE::Table struct PageTable {
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(lvl3_base)) entries: [u64; NUM_ENTRIES_4KIB],
.value; }
/// 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. // Fill the rest of the LVL2 (2 MiB) entries as block descriptors.
// //
// Differentiate between
// - non-cacheable DRAM
// - cacheable DRAM
// - device memory
//
// Ranges are stored in memory.rs.
//
// 0x0000_0000_0020_0000 |
// |> 4 MiB non-cacheable DRAM
// 0x0000_0000_005F_FFFF |
// 0x0000_0000_0060_0000 |
// |> 1002 MiB cacheable DRAM
// 0x0000_0000_3EFF_FFFF |
// 0x0000_0000_3F00_0000 |
// |> 16 MiB device (MMIO)
// 0x0000_0000_4000_0000 |
let dma_first_block_index: u64 = (crate::memory::map::DMA_HEAP_START >> 21).into();
let dma_last_block_index: u64 = (crate::memory::map::DMA_HEAP_END >> 21).into();
let mmio_first_block_index: u64 = (crate::memory::map::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(1) which makes the iteration start at the second 2 MiB // Notice the skip(1) which makes the iteration start at the second 2 MiB
// block (0x20_0000). // block (0x20_0000).
for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) { for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) {
let j: u64 = i as u64; let virt_addr = block_descriptor_nr << TWO_MIB_SHIFT;
let mem_attr = if (dma_first_block_index..=dma_last_block_index).contains(&j) { let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
STAGE1_DESCRIPTOR::SH::InnerShareable Err(s) => return Err(s),
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE) Ok((a, b)) => (a, b),
} else 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; let block_desc = match Lvl2BlockDescriptor::new(output_addr, attribute_fields) {
} Err(s) => return Err(s),
Ok(desc) => desc,
// Finally, fill the single LVL3 table (4 KiB granule). Differentiate };
// between code+RO and RW pages.
let (ro_start_addr, ro_end_addr) = crate::memory::get_ro_start_end();
let ro_first_page_index = ro_start_addr / crate::memory::PAGESIZE; *entry = block_desc.value();
let ro_last_page_index = ro_end_addr / crate::memory::PAGESIZE; }
let common = STAGE1_DESCRIPTOR::VALID::True // Finally, fill the single LVL3 table (4 KiB granule).
+ STAGE1_DESCRIPTOR::TYPE::Table for (page_descriptor_nr, entry) in LVL3_TABLE.entries.iter_mut().enumerate() {
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) let virt_addr = page_descriptor_nr << FOUR_KIB_SHIFT;
+ STAGE1_DESCRIPTOR::SH::InnerShareable
+ STAGE1_DESCRIPTOR::AF::True;
for (i, entry) in SINGLE_LVL3_TABLE.entries.iter_mut().enumerate() { let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
let j: u64 = i as u64; Err(s) => return Err(s),
Ok((a, b)) => (a, b),
};
let mem_attr = if (ro_first_page_index..=ro_last_page_index).contains(&j) { let page_desc = match PageDescriptor::new(output_addr, attribute_fields) {
STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False Err(s) => return Err(s),
} else { Ok(desc) => desc,
STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True
}; };
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value; *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);
@ -235,4 +340,6 @@ pub unsafe fn init() {
// Force MMU init to complete before next instruction // Force MMU init to complete before next instruction
barrier::isb(barrier::SY); barrier::isb(barrier::SY);
Ok(())
} }

@ -258,29 +258,35 @@ unsafe { core::ptr::read_volatile(big_addr as *mut u64) };
``` ```
Finally, this triggers our exception code, because we try to read from a virtual address for which no address translations have been installed. Remember, we only installed identity-mapped page tables for the first 1 GiB of address space in lesson `0C`. Finally, this triggers our exception code, because we try to read from a virtual address for which no address translations have been installed. Remember, we only installed identity-mapped page tables for the first 1 GiB of address space in lesson `0C`.
After the exception handler is finished, it returns to the first instruction after the memory read that caused the exception: After the exception handler is finished, it returns to the first instruction
after the memory read that caused the exception.
## Output
```console ```console
ferris@box:~$ make raspboot ferris@box:~$ make raspboot
[0] MiniUart online. [0] MiniUart online.
[1] Press a key to continue booting... Greetings fellow Rustacean! [1] Press a key to continue booting... Greetings fellow Rustacean!
[2] Switching MMU on now... MMU online. [2] MMU online.
[i] Memory layout: [i] Kernel memory layout:
0x00000000 - 0x0007FFFF | 512 KiB | Kernel stack 0x00000000 - 0x0007FFFF | 512 KiB | C RW PXN | Kernel stack
0x00080000 - 0x00084FFF | 20 KiB | Kernel code and RO data 0x00080000 - 0x00084FFF | 20 KiB | C RO PX | Kernel code and RO data
0x00085000 - 0x00088007 | 12 KiB | Kernel data and BSS 0x00085000 - 0x0008800F | 12 KiB | C RW PXN | Kernel data and BSS
0x00200000 - 0x005FFFFF | 4 MiB | DMA heap pool 0x00200000 - 0x005FFFFF | 4 MiB | NC RW PXN | DMA heap pool
0x3F000000 - 0x3FFFFFFF | 16 MiB | Device MMIO 0x3F000000 - 0x3FFFFFFF | 16 MiB | Dev RW PXN | Device MMIO
[i] Global DMA Allocator: [i] Global DMA Allocator:
Allocated Addr 0x00200000 Size 0x90 Allocated Addr 0x00200000 Size 0x90
[3] Videocore Mailbox set up (DMA mem heap allocation successful). [3] Videocore Mailbox set up (DMA mem heap allocation successful).
[4] PL011 UART online. Output switched to it. [4] PL011 UART online. Output switched to it.
[5] Exception vectors are set up. [5] Exception vectors are set up.
[!] A synchronous exception happened. [!] A synchronous exception happened.
ELR_EL1: 0x000809C0 ELR_EL1: 0x00080C20
Incrementing ELR_EL1 by 4 now to continue with the first instruction after the exception! Incrementing ELR_EL1 by 4 now to continue with the first instruction after the exception!
ELR_EL1 modified: 0x000809C4 ELR_EL1 modified: 0x00080C24
Returning from exception... Returning from exception...
[i] Whoa! We recovered from an exception. [i] Whoa! We recovered from an exception.
$>
``` ```

Binary file not shown.

@ -97,7 +97,7 @@ pub struct RegisterBlock {
/// Public interface to the GPIO MMIO area /// Public interface to the GPIO MMIO area
pub struct GPIO { pub struct GPIO {
base_addr: u32, base_addr: usize,
} }
impl ops::Deref for GPIO { impl ops::Deref for GPIO {
@ -109,7 +109,7 @@ impl ops::Deref for GPIO {
} }
impl GPIO { impl GPIO {
pub fn new(base_addr: u32) -> GPIO { pub fn new(base_addr: usize) -> GPIO {
GPIO { base_addr } GPIO { base_addr }
} }

@ -123,7 +123,7 @@ pub struct RegisterBlock {
} }
pub struct MiniUart { pub struct MiniUart {
base_addr: u32, base_addr: usize,
} }
/// Deref to RegisterBlock /// Deref to RegisterBlock
@ -145,7 +145,7 @@ impl ops::Deref for MiniUart {
} }
impl MiniUart { impl MiniUart {
pub fn new(base_addr: u32) -> MiniUart { pub fn new(base_addr: usize) -> MiniUart {
MiniUart { base_addr } MiniUart { base_addr }
} }

@ -141,7 +141,7 @@ pub enum PL011UartError {
pub type Result<T> = ::core::result::Result<T, PL011UartError>; pub type Result<T> = ::core::result::Result<T, PL011UartError>;
pub struct PL011Uart { pub struct PL011Uart {
base_addr: u32, base_addr: usize,
} }
impl ops::Deref for PL011Uart { impl ops::Deref for PL011Uart {
@ -153,7 +153,7 @@ impl ops::Deref for PL011Uart {
} }
impl PL011Uart { impl PL011Uart {
pub fn new(base_addr: u32) -> PL011Uart { pub fn new(base_addr: usize) -> PL011Uart {
PL011Uart { base_addr } PL011Uart { base_addr }
} }

@ -87,7 +87,7 @@ const MBOX_SIZE: usize = 36;
// Public interface to the mailbox // Public interface to the mailbox
pub struct VideocoreMbox<'a> { pub struct VideocoreMbox<'a> {
pub buffer: &'a mut [u32], pub buffer: &'a mut [u32],
base_addr: u32, base_addr: usize,
} }
/// Deref to RegisterBlock /// Deref to RegisterBlock
@ -109,7 +109,7 @@ impl<'a> ops::Deref for VideocoreMbox<'a> {
} }
impl<'a> VideocoreMbox<'a> { impl<'a> VideocoreMbox<'a> {
pub fn new(base_addr: u32) -> ::core::result::Result<VideocoreMbox<'a>, ()> { pub fn new(base_addr: usize) -> ::core::result::Result<VideocoreMbox<'a>, ()> {
let ret = crate::DMA_ALLOCATOR.lock(|d| d.alloc_slice_zeroed(MBOX_SIZE, MBOX_ALIGNMENT)); let ret = crate::DMA_ALLOCATOR.lock(|d| d.alloc_slice_zeroed(MBOX_SIZE, MBOX_ALIGNMENT));
if ret.is_err() { if ret.is_err() {

@ -47,8 +47,8 @@ static CONSOLE: sync::NullLock<devices::virt::Console> =
/// non-cacheable in the page tables. /// non-cacheable in the page tables.
static DMA_ALLOCATOR: sync::NullLock<memory::BumpAllocator> = static DMA_ALLOCATOR: sync::NullLock<memory::BumpAllocator> =
sync::NullLock::new(memory::BumpAllocator::new( sync::NullLock::new(memory::BumpAllocator::new(
memory::map::DMA_HEAP_START as usize, memory::map::virt::DMA_HEAP_START as usize,
memory::map::DMA_HEAP_END as usize, memory::map::virt::DMA_HEAP_END as usize,
"Global DMA Allocator", "Global DMA Allocator",
)); ));
@ -63,12 +63,12 @@ fn kernel_entry() -> ! {
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate GPIO device // Instantiate GPIO device
//------------------------------------------------------------ //------------------------------------------------------------
let gpio = hw::GPIO::new(memory::map::GPIO_BASE); let gpio = hw::GPIO::new(memory::map::physical::GPIO_BASE);
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate MiniUart // Instantiate MiniUart
//------------------------------------------------------------ //------------------------------------------------------------
let mini_uart = hw::MiniUart::new(memory::map::MINI_UART_BASE); let mini_uart = hw::MiniUart::new(memory::map::physical::MINI_UART_BASE);
mini_uart.init(&gpio); mini_uart.init(&gpio);
CONSOLE.lock(|c| { CONSOLE.lock(|c| {
@ -87,24 +87,26 @@ fn kernel_entry() -> ! {
}); });
println!("Greetings fellow Rustacean!"); println!("Greetings fellow Rustacean!");
//------------------------------------------------------------
// Bring up memory subsystem
//------------------------------------------------------------
print!("[2] Switching MMU on now... ");
unsafe { memory::mmu::init() };
println!("MMU online.");
memory::print_layout();
// We are now in a state where every next step can fail, but we can handle // We are now in a state where every next step can fail, but we can handle
// the error with feedback for the user and fall through to our UART // the error with feedback for the user and fall through to our UART
// loopback. // loopback.
'init: { 'init: {
//------------------------------------------------------------
// Bring up memory subsystem
//------------------------------------------------------------
if unsafe { memory::mmu::init() }.is_err() {
println!("[2][Error] Could not set up MMU. Aborting.");
break 'init;
};
println!("[2] MMU online.");
memory::print_layout();
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate Videocore Mailbox // Instantiate Videocore Mailbox
//------------------------------------------------------------ //------------------------------------------------------------
let mut v_mbox; let mut v_mbox;
match hw::VideocoreMbox::new(memory::map::VIDEOCORE_MBOX_BASE) { match hw::VideocoreMbox::new(memory::map::physical::VIDEOCORE_MBOX_BASE) {
Ok(i) => { Ok(i) => {
println!("[3] Videocore Mailbox set up (DMA mem heap allocation successful)."); println!("[3] Videocore Mailbox set up (DMA mem heap allocation successful).");
v_mbox = i; v_mbox = i;
@ -119,7 +121,7 @@ fn kernel_entry() -> ! {
//------------------------------------------------------------ //------------------------------------------------------------
// Instantiate PL011 UART and replace MiniUart with it in CONSOLE // Instantiate PL011 UART and replace MiniUart with it in CONSOLE
//------------------------------------------------------------ //------------------------------------------------------------
let pl011_uart = hw::PL011Uart::new(memory::map::PL011_UART_BASE); let pl011_uart = hw::PL011Uart::new(memory::map::physical::PL011_UART_BASE);
// uart.init() will reconfigure the GPIO, which causes a race against // uart.init() will reconfigure the GPIO, which causes a race against
// the MiniUart that is still putting out characters on the physical // the MiniUart that is still putting out characters on the physical

@ -23,111 +23,270 @@
*/ */
use crate::println; use crate::println;
use core::fmt;
use core::ops::RangeInclusive;
mod bump_allocator; mod bump_allocator;
pub use bump_allocator::BumpAllocator; pub use bump_allocator::BumpAllocator;
pub mod mmu; pub mod mmu;
/// The system memory map. /// System memory map.
#[rustfmt::skip] #[rustfmt::skip]
pub mod map { pub mod map {
pub const KERN_STACK_BOT: u32 = 0x0000_0000; pub const START: usize = 0x0000_0000;
pub const KERN_STACK_TOP: u32 = 0x0007_FFFF; pub const END: usize = 0x3FFF_FFFF;
/// The second 2 MiB block. pub mod physical {
pub const DMA_HEAP_START: u32 = 0x0020_0000; pub const MMIO_BASE: usize = 0x3F00_0000;
pub const DMA_HEAP_END: u32 = 0x005F_FFFF; pub const VIDEOCORE_MBOX_BASE: usize = MMIO_BASE + 0x0000_B880;
pub const GPIO_BASE: usize = MMIO_BASE + 0x0020_0000;
pub const PL011_UART_BASE: usize = MMIO_BASE + 0x0020_1000;
pub const MINI_UART_BASE: usize = MMIO_BASE + 0x0021_5000;
pub const MMIO_END: usize = super::END;
}
pub const MMIO_BASE: u32 = 0x3F00_0000; pub mod virt {
pub const VIDEOCORE_MBOX_BASE: u32 = MMIO_BASE + 0x0000_B880; pub const KERN_STACK_START: usize = super::START;
pub const GPIO_BASE: u32 = MMIO_BASE + 0x0020_0000; pub const KERN_STACK_END: usize = 0x0007_FFFF;
pub const PL011_UART_BASE: u32 = MMIO_BASE + 0x0020_1000;
pub const MINI_UART_BASE: u32 = MMIO_BASE + 0x0021_5000;
pub const PHYS_ADDR_MAX: u32 = 0x3FFF_FFFF; // The second 2 MiB block.
pub const DMA_HEAP_START: usize = 0x0020_0000;
pub const DMA_HEAP_END: usize = 0x005F_FFFF;
}
} }
const PAGESIZE: u64 = 4096; /// Types used for compiling the virtual memory layout of the kernel using
/// address ranges.
pub mod kernel_mem_range {
use core::ops::RangeInclusive;
#[inline] #[derive(Copy, Clone)]
fn aligned_addr_unchecked(addr: usize, alignment: usize) -> usize { pub enum MemAttributes {
(addr + (alignment - 1)) & !(alignment - 1) CacheableDRAM,
NonCacheableDRAM,
Device,
}
#[derive(Copy, Clone)]
pub enum AccessPermissions {
ReadOnly,
ReadWrite,
}
#[allow(dead_code)]
#[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 name: &'static str,
pub virtual_range: fn() -> RangeInclusive<usize>,
pub translation: Translation,
pub attribute_fields: AttributeFields,
}
} }
fn get_ro_start_end() -> (u64, u64) { use kernel_mem_range::*;
// Using the linker script, we ensure that the RO area is consecutive and 4
// KiB aligned, and we export the boundaries via symbols. /// A virtual memory layout that is agnostic of the paging granularity that the
extern "C" { /// hardware MMU will use.
// The inclusive start of the read-only area, aka the address of the ///
// first byte of the area. /// Contains only special ranges, aka anything that is _not_ normal cacheable
static __ro_start: u64; /// DRAM.
static KERNEL_VIRTUAL_LAYOUT: [Descriptor; 5] = [
// The non-inclusive end of the read-only area, aka the address of the Descriptor {
// first byte _after_ the RO area. name: "Kernel stack",
static __ro_end: u64; 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,
},
},
Descriptor {
name: "Kernel code and RO data",
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,
},
},
Descriptor {
name: "Kernel data and BSS",
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,
},
},
Descriptor {
name: "DMA heap pool",
virtual_range: || RangeInclusive::new(map::virt::DMA_HEAP_START, map::virt::DMA_HEAP_END),
translation: Translation::Identity,
attribute_fields: AttributeFields {
mem_attributes: MemAttributes::NonCacheableDRAM,
acc_perms: AccessPermissions::ReadWrite,
execute_never: true,
},
},
Descriptor {
name: "Device MMIO",
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.");
} }
unsafe { for i in KERNEL_VIRTUAL_LAYOUT.iter() {
// Notice the subtraction to calculate the last page index of the RO if (i.virtual_range)().contains(&virt_addr) {
// area and not the first page index after the RO area. let output_addr = match i.translation {
( Translation::Identity => virt_addr,
&__ro_start as *const _ as u64, Translation::Offset(a) => a + (virt_addr - (i.virtual_range)().start()),
&__ro_end as *const _ as u64 - 1, };
)
return Ok((output_addr, i.attribute_fields));
}
} }
Ok((virt_addr, AttributeFields::default()))
} }
pub fn print_layout() { /// Human-readable output of a Descriptor.
use crate::memory::map::*; impl fmt::Display for Descriptor {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Call the function to which self.range points, and dereference the
// result, which causes Rust to copy the value.
let start = *(self.virtual_range)().start();
let end = *(self.virtual_range)().end();
let size = end - start + 1;
// log2(1024)
const KIB_RSHIFT: u32 = 10;
// log2(1024 * 1024)
const MIB_RSHIFT: u32 = 20;
// log2(1024) let (size, unit) = if (size >> MIB_RSHIFT) > 0 {
const KIB_RSHIFT: u32 = 10; (size >> MIB_RSHIFT, "MiB")
} else if (size >> KIB_RSHIFT) > 0 {
(size >> KIB_RSHIFT, "KiB")
} else {
(size, "Byte")
};
// log2(1024 * 1024) let attr = match self.attribute_fields.mem_attributes {
const MIB_RSHIFT: u32 = 20; MemAttributes::CacheableDRAM => "C",
MemAttributes::NonCacheableDRAM => "NC",
MemAttributes::Device => "Dev",
};
println!("[i] Memory layout:"); let acc_p = match self.attribute_fields.acc_perms {
AccessPermissions::ReadOnly => "RO",
AccessPermissions::ReadWrite => "RW",
};
println!( let xn = if self.attribute_fields.execute_never {
" {:#010X} - {:#010X} | {: >4} KiB | Kernel stack", "PXN"
KERN_STACK_BOT, } else {
KERN_STACK_TOP, "PX"
(KERN_STACK_TOP - KERN_STACK_BOT + 1) >> KIB_RSHIFT };
);
let (ro_start, ro_end) = get_ro_start_end(); write!(
println!( f,
" {:#010X} - {:#010X} | {: >4} KiB | Kernel code and RO data", " {:#010X} - {:#010X} | {: >3} {} | {: <3} {} {: <3} | {}",
ro_start, start, end, size, unit, attr, acc_p, xn, self.name
ro_end, )
(ro_end - ro_start + 1) >> KIB_RSHIFT }
); }
/// Print the kernel memory layout.
pub fn print_layout() {
println!("[i] Kernel memory layout:");
extern "C" { for i in KERNEL_VIRTUAL_LAYOUT.iter() {
static __bss_end: u64; println!("{}", i);
} }
}
let start = ro_end + 1; /// Calculate the next possible aligned address without sanity checking the
let end = unsafe { &__bss_end as *const _ as u64 } - 1; /// input parameters.
println!( #[inline]
" {:#010X} - {:#010X} | {: >4} KiB | Kernel data and BSS", fn aligned_addr_unchecked(addr: usize, alignment: usize) -> usize {
start, (addr + (alignment - 1)) & !(alignment - 1)
end,
(end - start + 1) >> KIB_RSHIFT
);
println!(
" {:#010X} - {:#010X} | {: >4} MiB | DMA heap pool",
DMA_HEAP_START,
DMA_HEAP_END,
(DMA_HEAP_END - DMA_HEAP_START + 1) >> MIB_RSHIFT
);
println!(
" {:#010X} - {:#010X} | {: >4} MiB | Device MMIO",
MMIO_BASE,
PHYS_ADDR_MAX,
(PHYS_ADDR_MAX - MMIO_BASE + 1) >> MIB_RSHIFT
);
} }

@ -22,14 +22,15 @@
* SOFTWARE. * SOFTWARE.
*/ */
use crate::memory::{get_virt_addr_properties, AttributeFields};
use cortex_a::{barrier, regs::*}; use cortex_a::{barrier, regs::*};
use register::register_bitfields; use register::register_bitfields;
register_bitfields! {u64, register_bitfields! {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
], ],
@ -73,42 +74,150 @@ register_bitfields! {u64,
] ]
} }
trait BaseAddr { const FOUR_KIB: usize = 4 * 1024;
fn base_addr(&self) -> u64; 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
}
} }
impl BaseAddr for [u64; 512] { /// A function that maps the generic memory range attributes to HW-specific
fn base_addr(&self) -> u64 { /// attributes of the MMU.
self as *const u64 as u64 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
} }
} }
const NUM_ENTRIES_4KIB: usize = 512; /// 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),
))
}
// We need a wrapper struct here so that we can make use of the align attribute. fn value(&self) -> u64 {
#[repr(C)] self.0.value
#[repr(align(4096))] }
struct PageTable {
entries: [u64; NUM_ENTRIES_4KIB],
} }
static mut LVL2_TABLE: PageTable = PageTable { /// Constants for indexing the MAIR_EL1.
entries: [0; NUM_ENTRIES_4KIB], #[allow(dead_code)]
}; mod mair {
static mut SINGLE_LVL3_TABLE: PageTable = PageTable { pub const DEVICE: u64 = 0;
entries: [0; NUM_ENTRIES_4KIB], pub const NORMAL: u64 = 1;
}; pub const NORMAL_NON_CACHEABLE: u64 = 2;
}
/// Set up identity mapped page tables for the first 1 GiB of address space. /// Setup function for the MAIR_EL1 register.
pub unsafe fn init() { fn set_up_mair() {
// First, define the three memory types that we will map. Cacheable and // Define the three memory types that we will map. Cacheable and
// non-cacheable normal DRAM, and device. // non-cacheable normal DRAM, and device.
MAIR_EL1.write( MAIR_EL1.write(
// Attribute 2 // Attribute 2
MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable MAIR_EL1::Attr2_HIGH::Memory_OuterNonCacheable
+ MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable + MAIR_EL1::Attr2_LOW_MEMORY::InnerNonCacheable
// Attribute 1 // Attribute 1
+ MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc + MAIR_EL1::Attr1_HIGH::Memory_OuterWriteBack_NonTransient_ReadAlloc_WriteAlloc
+ MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc + MAIR_EL1::Attr1_LOW_MEMORY::InnerWriteBack_NonTransient_ReadAlloc_WriteAlloc
@ -116,101 +225,97 @@ pub unsafe fn init() {
+ MAIR_EL1::Attr0_HIGH::Device + MAIR_EL1::Attr0_HIGH::Device
+ MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE, + MAIR_EL1::Attr0_LOW_DEVICE::Device_nGnRE,
); );
}
trait BaseAddr {
fn base_addr_u64(&self) -> u64;
fn base_addr_usize(&self) -> usize;
}
// Descriptive consts for indexing into the correct MAIR_EL1 attributes. impl BaseAddr for [u64; 512] {
#[allow(dead_code)] fn base_addr_u64(&self) -> u64 {
mod mair { self as *const u64 as u64
pub const DEVICE: u64 = 0;
pub const NORMAL: u64 = 1;
pub const NORMAL_NON_CACHEABLE: u64 = 2;
} }
// The first 2 MiB. fn base_addr_usize(&self) -> usize {
// self as *const u64 as usize
// Set up the first LVL2 entry, pointing to the base address of a follow-up }
// table containing 4 KiB pages. }
//
// 0x0000_0000_0000_0000 | const NUM_ENTRIES_4KIB: usize = 512;
// |> 2 MiB
// 0x0000_0000_001F_FFFF | // A wrapper struct is needed here so that the align attribute can be used.
let lvl3_base: u64 = SINGLE_LVL3_TABLE.entries.base_addr() >> 12; #[repr(C)]
LVL2_TABLE.entries[0] = (STAGE1_DESCRIPTOR::VALID::True #[repr(align(4096))]
+ STAGE1_DESCRIPTOR::TYPE::Table struct PageTable {
+ STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(lvl3_base)) entries: [u64; NUM_ENTRIES_4KIB],
.value; }
/// 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. // Fill the rest of the LVL2 (2 MiB) entries as block descriptors.
// //
// Differentiate between
// - non-cacheable DRAM
// - cacheable DRAM
// - device memory
//
// Ranges are stored in memory.rs.
//
// 0x0000_0000_0020_0000 |
// |> 4 MiB non-cacheable DRAM
// 0x0000_0000_005F_FFFF |
// 0x0000_0000_0060_0000 |
// |> 1002 MiB cacheable DRAM
// 0x0000_0000_3EFF_FFFF |
// 0x0000_0000_3F00_0000 |
// |> 16 MiB device (MMIO)
// 0x0000_0000_4000_0000 |
let dma_first_block_index: u64 = (crate::memory::map::DMA_HEAP_START >> 21).into();
let dma_last_block_index: u64 = (crate::memory::map::DMA_HEAP_END >> 21).into();
let mmio_first_block_index: u64 = (crate::memory::map::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(1) which makes the iteration start at the second 2 MiB // Notice the skip(1) which makes the iteration start at the second 2 MiB
// block (0x20_0000). // block (0x20_0000).
for (i, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) { for (block_descriptor_nr, entry) in LVL2_TABLE.entries.iter_mut().enumerate().skip(1) {
let j: u64 = i as u64; let virt_addr = block_descriptor_nr << TWO_MIB_SHIFT;
let mem_attr = if (dma_first_block_index..=dma_last_block_index).contains(&j) { let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
STAGE1_DESCRIPTOR::SH::InnerShareable Err(s) => return Err(s),
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL_NON_CACHEABLE) Ok((a, b)) => (a, b),
} else 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; let block_desc = match Lvl2BlockDescriptor::new(output_addr, attribute_fields) {
} Err(s) => return Err(s),
Ok(desc) => desc,
// Finally, fill the single LVL3 table (4 KiB granule). Differentiate };
// between code+RO and RW pages.
let (ro_start_addr, ro_end_addr) = crate::memory::get_ro_start_end();
let ro_first_page_index = ro_start_addr / crate::memory::PAGESIZE; *entry = block_desc.value();
let ro_last_page_index = ro_end_addr / crate::memory::PAGESIZE; }
let common = STAGE1_DESCRIPTOR::VALID::True // Finally, fill the single LVL3 table (4 KiB granule).
+ STAGE1_DESCRIPTOR::TYPE::Table for (page_descriptor_nr, entry) in LVL3_TABLE.entries.iter_mut().enumerate() {
+ STAGE1_DESCRIPTOR::AttrIndx.val(mair::NORMAL) let virt_addr = page_descriptor_nr << FOUR_KIB_SHIFT;
+ STAGE1_DESCRIPTOR::SH::InnerShareable
+ STAGE1_DESCRIPTOR::AF::True;
for (i, entry) in SINGLE_LVL3_TABLE.entries.iter_mut().enumerate() { let (output_addr, attribute_fields) = match get_virt_addr_properties(virt_addr) {
let j: u64 = i as u64; Err(s) => return Err(s),
Ok((a, b)) => (a, b),
};
let mem_attr = if (ro_first_page_index..=ro_last_page_index).contains(&j) { let page_desc = match PageDescriptor::new(output_addr, attribute_fields) {
STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False Err(s) => return Err(s),
} else { Ok(desc) => desc,
STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True
}; };
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value; *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);
@ -235,4 +340,6 @@ pub unsafe fn init() {
// Force MMU init to complete before next instruction // Force MMU init to complete before next instruction
barrier::isb(barrier::SY); barrier::isb(barrier::SY);
Ok(())
} }

Loading…
Cancel
Save