First part of tutorial: 0C_virtual_memory

pull/9/head
Andre Richter 6 years ago
parent 60db699637
commit bf2a1fff7e
No known key found for this signature in database
GPG Key ID: 2116C1AB102F615E

@ -0,0 +1,6 @@
[target.aarch64-unknown-none]
rustflags = [
"-C", "link-arg=-Tlink.ld",
"-C", "target-feature=-fp-armv8",
"-C", "target-cpu=cortex-a53",
]

@ -0,0 +1,55 @@
[[package]]
name = "cortex-a"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel8"
version = "0.1.0"
dependencies = [
"cortex-a 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"raspi3_boot 0.1.0",
"register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "panic-abort"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "r0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "raspi3_boot"
version = "0.1.0"
dependencies = [
"cortex-a 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"panic-abort 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "register"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"tock-registers 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tock-registers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum cortex-a 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e24491e2ca61ecf806cd212431910cd621277240b22a41a1a551c647cb9ca2c"
"checksum panic-abort 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6bc796c620f27056d4ffe7c558533fd67ae5af0fd8e919fbe38de803368af73e"
"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f"
"checksum register 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "157a11ac0b1882ff4a527a92f911dd288df17367faaaa0c36f188cd61ec36fc1"
"checksum tock-registers 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3a385d94f3f62e60445a0adb9ff8d9621faa272234530d4c0f848ec98f88e316"

@ -0,0 +1,12 @@
[package]
name = "kernel8"
version = "0.1.0"
authors = ["Andre Richter <andre.o.richter@gmail.com>"]
[dependencies]
raspi3_boot = { path = "raspi3_boot" }
cortex-a = "2.2.0"
register = "0.2.0"
[package.metadata.cargo-xbuild]
sysroot_path = "../xbuild_sysroot"

@ -0,0 +1,66 @@
#
# MIT License
#
# Copyright (c) 2018 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.
#
TARGET = aarch64-unknown-none
OBJCOPY = cargo objcopy --
OBJCOPY_PARAMS = --strip-all -O binary
UTILS_CONTAINER = andrerichter/raspi3-utils
DOCKER_CMD = docker run -it --rm -v $(shell pwd):/work -w /work
DOCKER_TTY = --privileged -v /dev:/dev
QEMU_CMD = qemu-system-aarch64 -M raspi3 -kernel kernel8.img
RASPBOOT_CMD = raspbootcom /dev/ttyUSB0 kernel8.img
all: clean kernel8.img
target/$(TARGET)/debug/kernel8: src/main.rs
cargo xbuild --target=$(TARGET)
cp $@ .
target/$(TARGET)/release/kernel8: src/main.rs
cargo xbuild --target=$(TARGET) --release
cp $@ .
ifeq ($(DEBUG),1)
kernel8: target/$(TARGET)/debug/kernel8
else
kernel8: target/$(TARGET)/release/kernel8
endif
kernel8.img: kernel8
$(OBJCOPY) $(OBJCOPY_PARAMS) $< kernel8.img
qemu: all
$(DOCKER_CMD) $(UTILS_CONTAINER) $(QEMU_CMD) -serial stdio
raspboot: all
$(DOCKER_CMD) $(DOCKER_TTY) $(UTILS_CONTAINER) $(RASPBOOT_CMD)
clippy:
cargo xclippy --target=$(TARGET)
clean:
cargo clean
rm -f kernel8

@ -0,0 +1,67 @@
# Tutorial 0C - Virtual Memory
**This is a stub**
TODO: Write rest of tutorial.
Virtual memory is an immensely complex, but exciting topic. In this first
lesson, we start slow and switch on the MMU using handcrafted page tables for
the first `1 GiB` of memory. That is the amount of `DRAM` the usual Raspberry Pi
3 has. As we already know, the upper `16 MiB` of this gigabyte-window are
occupied by the Raspberry's peripherals such as the UART.
The page tables we install alternate between `2 MiB` blocks and `4 KiB` blocks.
The first `2 MiB` of memory are identity mapped, and therefore contain our code
and the stack. We use a single table with a `4 KiB` granule to differentiate
between code, RO-data and RW-data. The linker script was adapted to adhere to
the pagetable sizes.
Next, we map the UART into the second `2 MiB` block to show the effects of
virtual memory.
Everyting else is, for reasons of convenience, again identity mapped using `2
MiB` blocks.
Hopefully, in a later tutorial, we will write or use (e.g. from the `cortex-a`
crate) proper modules for page table handling, that, among others, cover topics
such as using recursive mapping for maintenace.
## Zero-cost abstraction
The MMU init code is a good example to see the great potential of Rust's
zero-cost abstractions[[1]](https://blog.rust-lang.org/2015/05/11/traits.html)[[2]](https://ruudvanasseldonk.com/2016/11/30/zero-cost-abstractions) for embedded programming.
Take this piece of code for setting up the `MAIR_EL1` register using the
[cortex-a](https://crates.io/crates/cortex-a) crate:
```rust
// First, define the two memory types that we will map. Normal DRAM type 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,
);
```
This piece of code is super expressive, and it makes us of `traits`, different
`types` and `constants` to provide type-safe register manipulation.
In the end, this code sets the first four bytes of the register to certain
values according to the data sheet. Looking at the generated code, we can see
that despite all the type-safety and abstractions, we get super lean code:
```text
kernel8::mmu::init::h53df3fab6e51e098:
...
80768: ed 9f 80 52 mov w13, #0x4ff
...
80778: 0d a2 18 d5 msr MAIR_EL1, x13
...
```

Binary file not shown.

@ -0,0 +1,57 @@
/*
* MIT License
*
* Copyright (c) 2018 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.
*/
ENTRY(_boot_cores);
SECTIONS
{
. = 0x80000; /* This is already 4KiB aligned */
__ro_start = .;
.text :
{
KEEP(*(.text.boot)) *(.text .text.*)
}
.rodata :
{
*(.rodata .rodata.*)
}
. = ALIGN(4096); /* Fill up to 4KiB */
__ro_end = .;
.data :
{
*(.data .data.*)
}
.bss ALIGN(8):
{
__bss_start = .;
*(.bss .bss.*)
*(COMMON)
__bss_end = .;
}
/DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}

@ -0,0 +1,9 @@
[package]
name = "raspi3_boot"
version = "0.1.0"
authors = ["Andre Richter <andre.o.richter@gmail.com>"]
[dependencies]
cortex-a = "2.2.0"
panic-abort = "0.2.0"
r0 = "0.2.2"

@ -0,0 +1,131 @@
/*
* MIT License
*
* Copyright (c) 2018 Jorge Aparicio
* Copyright (c) 2018 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.
*/
#![deny(missing_docs)]
#![deny(warnings)]
#![no_std]
//! Low-level boot of the Raspberry's processor
extern crate cortex_a;
extern crate panic_abort;
extern crate r0;
#[macro_export]
macro_rules! entry {
($path:path) => {
#[export_name = "main"]
pub unsafe fn __main() -> ! {
// type check the given path
let f: fn() -> ! = $path;
f()
}
};
}
/// Reset function.
///
/// Initializes the bss section before calling into the user's `main()`.
unsafe fn reset() -> ! {
extern "C" {
// Boundaries of the .bss section, provided by the linker script
static mut __bss_start: u64;
static mut __bss_end: u64;
}
// Zeroes the .bss section
r0::zero_bss(&mut __bss_start, &mut __bss_end);
extern "Rust" {
fn main() -> !;
}
main()
}
/// Prepare and execute transition from EL2 to EL1.
#[inline]
fn setup_and_enter_el1_from_el2() -> ! {
use cortex_a::{asm, regs::*};
// Enable timer counter registers for EL1
CNTHCTL_EL2.write(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET);
// No offset for reading the counters
CNTVOFF_EL2.set(0);
// Set EL1 execution state to AArch64
// TODO: Explain the SWIO bit
HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64 + HCR_EL2::SWIO::SET);
// Set up a simulated exception return.
//
// First, fake a saved program status, where all interrupts were
// masked and SP_EL0 was used as a stack pointer.
SPSR_EL2.write(
SPSR_EL2::D::Masked
+ SPSR_EL2::A::Masked
+ SPSR_EL2::I::Masked
+ SPSR_EL2::F::Masked
+ SPSR_EL2::M::EL1t,
);
// Second, let the link register point to reset().
ELR_EL2.set(reset as *const () as u64);
// Set up SP_EL0 (stack pointer), which will be used by EL1 once
// we "return" to it.
SP_EL0.set(0x80_000);
// Use `eret` to "return" to EL1. This will result in execution of
// `reset()` in EL1.
asm::eret()
}
/// Entrypoint of the processor.
///
/// Parks all cores except core0 and checks if we started in EL2. If
/// so, proceeds with setting up EL1.
#[link_section = ".text.boot"]
#[no_mangle]
pub unsafe extern "C" fn _boot_cores() -> ! {
use cortex_a::{asm, regs::*};
const CORE_0: u64 = 0;
const CORE_MASK: u64 = 0x3;
const EL2: u32 = CurrentEL::EL::EL2.value;
if let CORE_0 = MPIDR_EL1.get() & CORE_MASK {
if let EL2 = CurrentEL.get() {
setup_and_enter_el1_from_el2()
}
}
// if not core0 or EL != 2, infinitely wait for events
loop {
asm::wfe();
}
}

@ -0,0 +1,75 @@
/*
* MIT License
*
* Copyright (c) 2018 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::MMIO_BASE;
use register::mmio::ReadWrite;
// Descriptions taken from
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
register_bitfields! {
u32,
/// GPIO Function Select 1
GPFSEL1 [
/// Pin 15
FSEL15 OFFSET(15) NUMBITS(3) [
Input = 0b000,
Output = 0b001,
RXD0 = 0b100, // UART0 - Alternate function 0
RXD1 = 0b010 // Mini UART - Alternate function 5
],
/// Pin 14
FSEL14 OFFSET(12) NUMBITS(3) [
Input = 0b000,
Output = 0b001,
TXD0 = 0b100, // UART0 - Alternate function 0
TXD1 = 0b010 // Mini UART - Alternate function 5
]
],
/// GPIO Pull-up/down Clock Register 0
GPPUDCLK0 [
/// Pin 15
PUDCLK15 OFFSET(15) NUMBITS(1) [
NoEffect = 0,
AssertClock = 1
],
/// Pin 14
PUDCLK14 OFFSET(14) NUMBITS(1) [
NoEffect = 0,
AssertClock = 1
]
]
}
pub const GPFSEL1: *const ReadWrite<u32, GPFSEL1::Register> =
(MMIO_BASE + 0x0020_0004) as *const ReadWrite<u32, GPFSEL1::Register>;
pub const GPPUD: *const ReadWrite<u32> = (MMIO_BASE + 0x0020_0094) as *const ReadWrite<u32>;
pub const GPPUDCLK0: *const ReadWrite<u32, GPPUDCLK0::Register> =
(MMIO_BASE + 0x0020_0098) as *const ReadWrite<u32, GPPUDCLK0::Register>;

@ -0,0 +1,84 @@
/*
* MIT License
*
* Copyright (c) 2018 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.
*/
#![no_std]
#![no_main]
#![feature(asm)]
#![feature(const_fn)]
extern crate cortex_a;
#[macro_use]
extern crate raspi3_boot;
#[macro_use]
extern crate register;
const MMIO_BASE: u32 = 0x3F00_0000;
mod gpio;
mod mbox;
mod mmu;
mod uart;
entry!(kernel_entry);
fn kernel_entry() -> ! {
let mut mbox = mbox::Mbox::new();
{
// Before the MMU is live, instantiate a UART driver with the physical address
let uart = uart::Uart::new(uart::UART_PHYS_BASE);
// set up serial console
if uart.init(&mut mbox).is_err() {
loop {
cortex_a::asm::wfe() // If UART fails, abort early
}
}
uart.getc(); // Press a key first before being greeted
uart.puts("Hello Rustacean!\n\n");
mmu::print_features(&uart);
uart.puts("\nSwitching MMU on now...");
} // After this closure, the UART instance is not valid anymore.
unsafe { mmu::init() };
// Instantiate a new UART using the virtual mapping in the second 2 MiB
// 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.hex(UART_VIRT_BASE);
uart.puts(".\n");
// echo everything back
loop {
uart.send(uart.getc());
}
}

@ -0,0 +1,159 @@
/*
* MIT License
*
* Copyright (c) 2018 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::MMIO_BASE;
use core::ops;
use cortex_a::asm;
use register::mmio::{ReadOnly, WriteOnly};
register_bitfields! {
u32,
STATUS [
FULL OFFSET(31) NUMBITS(1) [],
EMPTY OFFSET(30) NUMBITS(1) []
]
}
const VIDEOCORE_MBOX: u32 = MMIO_BASE + 0xB880;
#[allow(non_snake_case)]
#[repr(C)]
pub struct RegisterBlock {
READ: ReadOnly<u32>, // 0x00
__reserved_0: [u32; 5], // 0x04
STATUS: ReadOnly<u32, STATUS::Register>, // 0x18
__reserved_1: u32, // 0x1C
WRITE: WriteOnly<u32>, // 0x20
}
// Custom errors
pub enum MboxError {
ResponseError,
UnknownError,
}
pub type Result<T> = ::core::result::Result<T, MboxError>;
// Channels
pub mod channel {
pub const PROP: u32 = 8;
}
// Tags
pub mod tag {
pub const SETCLKRATE: u32 = 0x38002;
pub const LAST: u32 = 0;
}
// Clocks
pub mod clock {
pub const UART: u32 = 0x0_0000_0002;
}
// Responses
mod response {
pub const SUCCESS: u32 = 0x8000_0000;
pub const ERROR: u32 = 0x8000_0001; // error parsing request buffer (partial response)
}
pub const REQUEST: u32 = 0;
// Public interface to the mailbox
#[repr(C)]
#[repr(align(16))]
pub struct Mbox {
// The address for buffer needs to be 16-byte aligned so that the
// Videcore can handle it properly.
pub buffer: [u32; 36],
}
/// Deref to RegisterBlock
///
/// Allows writing
/// ```
/// self.STATUS.read()
/// ```
/// instead of something along the lines of
/// ```
/// unsafe { (*Mbox::ptr()).STATUS.read() }
/// ```
impl ops::Deref for Mbox {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*Self::ptr() }
}
}
impl Mbox {
pub fn new() -> Mbox {
Mbox { buffer: [0; 36] }
}
/// Returns a pointer to the register block
fn ptr() -> *const RegisterBlock {
VIDEOCORE_MBOX as *const _
}
/// Make a mailbox call. Returns Err(MboxError) on failure, Ok(()) success
pub fn call(&self, channel: u32) -> Result<()> {
// wait until we can write to the mailbox
loop {
if !self.STATUS.is_set(STATUS::FULL) {
break;
}
asm::nop();
}
let buf_ptr = self.buffer.as_ptr() as u32;
// write the address of our message to the mailbox with channel identifier
self.WRITE.set((buf_ptr & !0xF) | (channel & 0xF));
// now wait for the response
loop {
// is there a response?
loop {
if !self.STATUS.is_set(STATUS::EMPTY) {
break;
}
asm::nop();
}
let resp: u32 = self.READ.get();
// is it a response to our message?
if ((resp & 0xF) == channel) && ((resp & !0xF) == buf_ptr) {
// is it a valid successful response?
return match self.buffer[1] {
response::SUCCESS => Ok(()),
response::ERROR => Err(MboxError::ResponseError),
_ => Err(MboxError::UnknownError),
};
}
}
}
}

@ -0,0 +1,227 @@
/*
* MIT License
*
* Copyright (c) 2018 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::*};
/// 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("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("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;
static mut LVL2_TABLE: [u64; NUM_ENTRIES_4KIB] = [0; NUM_ENTRIES_4KIB];
static mut SINGLE_LVL3_TABLE: [u64; NUM_ENTRIES_4KIB] = [0; NUM_ENTRIES_4KIB];
/// Set up identity mapped page tables for the first 1 gigabyte of address
/// space.
pub unsafe fn init() {
// First, define the two memory types that we will map. 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,
);
// Two descriptive consts for indexing into the correct MAIR_EL1 attributes.
mod mair {
pub const NORMAL: u64 = 0;
pub const DEVICE: u64 = 1;
}
// Set up the first LVL2 entry, pointing to a 4KiB table base address.
let lvl3_base: u64 = SINGLE_LVL3_TABLE.base_addr() >> 12;
LVL2_TABLE[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.
let uart_phys_base: u64 = (uart::UART_PHYS_BASE >> 21).into();
LVL2_TABLE[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 (2MiB) entries as block
// descriptors. Differentiate between normal and device mem.
let mmio_base: 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)
for (i, entry) in LVL2_TABLE.iter_mut().enumerate().skip(2) {
let j: u64 = i as u64;
let mem_attr = if j >= mmio_base {
STAGE1_DESCRIPTOR::SH::OuterShareable + STAGE1_DESCRIPTOR::AttrIndx.val(mair::DEVICE)
} 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 (4KiB granule). Differentiate between
// code/RO and RW sections.
//
// Using the linker script, we ensure that the RO sections are 4KiB aligned,
// and we export their boundaries via symbols.
extern "C" {
static mut __ro_start: u64;
static mut __ro_end: u64;
}
const PAGESIZE: u64 = 4096;
let ro_start: u64 = &__ro_start as *const _ as u64 / PAGESIZE;
let ro_end: u64 = &__ro_end as *const _ as u64 / PAGESIZE;
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.iter_mut().enumerate() {
let j: u64 = i as u64;
let mem_attr = if j < ro_start || j > ro_end {
STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True
} else {
STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False
};
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value;
}
// Point to the LVL2 table base address in TTBR0.
TTBR0_EL1.set_baddr(LVL2_TABLE.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
SCTLR_EL1.modify(SCTLR_EL1::M::Enable);
// Force MMU init to complete before next instruction
barrier::isb(barrier::SY);
}

@ -0,0 +1,288 @@
/*
* MIT License
*
* Copyright (c) 2018 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::MMIO_BASE;
use core::{
ops,
sync::atomic::{compiler_fence, Ordering},
};
use cortex_a::asm;
use gpio;
use mbox;
use register::mmio::*;
// PL011 UART registers.
//
// Descriptions taken from
// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
register_bitfields! {
u32,
/// Flag Register
FR [
/// Transmit FIFO full. The meaning of this bit depends on the
/// state of the FEN bit in the UARTLCR_ LCRH Register. If the
/// FIFO is disabled, this bit is set when the transmit
/// holding register is full. If the FIFO is enabled, the TXFF
/// bit is set when the transmit FIFO is full.
TXFF OFFSET(5) NUMBITS(1) [],
/// Receive FIFO empty. The meaning of this bit depends on the
/// state of the FEN bit in the UARTLCR_H Register. If the
/// FIFO is disabled, this bit is set when the receive holding
/// register is empty. If the FIFO is enabled, the RXFE bit is
/// set when the receive FIFO is empty.
RXFE OFFSET(4) NUMBITS(1) []
],
/// Integer Baud rate divisor
IBRD [
/// Integer Baud rate divisor
IBRD OFFSET(0) NUMBITS(16) []
],
/// Fractional Baud rate divisor
FBRD [
/// Fractional Baud rate divisor
FBRD OFFSET(0) NUMBITS(6) []
],
/// Line Control register
LCRH [
/// Word length. These bits indicate the number of data bits
/// transmitted or received in a frame.
WLEN OFFSET(5) NUMBITS(2) [
FiveBit = 0b00,
SixBit = 0b01,
SevenBit = 0b10,
EightBit = 0b11
]
],
/// Control Register
CR [
/// Receive enable. If this bit is set to 1, the receive
/// section of the UART is enabled. Data reception occurs for
/// UART signals. When the UART is disabled in the middle of
/// reception, it completes the current character before
/// stopping.
RXE OFFSET(9) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// Transmit enable. If this bit is set to 1, the transmit
/// section of the UART is enabled. Data transmission occurs
/// for UART signals. When the UART is disabled in the middle
/// of transmission, it completes the current character before
/// stopping.
TXE OFFSET(8) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// UART enable
UARTEN OFFSET(0) NUMBITS(1) [
/// If the UART is disabled in the middle of transmission
/// or reception, it completes the current character
/// before stopping.
Disabled = 0,
Enabled = 1
]
],
/// Interupt Clear Register
ICR [
/// Meta field for all pending interrupts
ALL OFFSET(0) NUMBITS(11) []
]
}
pub const UART_PHYS_BASE: u32 = MMIO_BASE + 0x20_1000;
#[allow(non_snake_case)]
#[repr(C)]
pub struct RegisterBlock {
DR: ReadWrite<u32>, // 0x00
__reserved_0: [u32; 5], // 0x04
FR: ReadOnly<u32, FR::Register>, // 0x18
__reserved_1: [u32; 2], // 0x1c
IBRD: WriteOnly<u32, IBRD::Register>, // 0x24
FBRD: WriteOnly<u32, FBRD::Register>, // 0x28
LCRH: WriteOnly<u32, LCRH::Register>, // 0x2C
CR: WriteOnly<u32, CR::Register>, // 0x30
__reserved_2: [u32; 4], // 0x34
ICR: WriteOnly<u32, ICR::Register>, // 0x44
}
pub enum UartError {
MailboxError,
}
pub type Result<T> = ::core::result::Result<T, UartError>;
pub struct Uart {
uart_base: u32,
}
impl ops::Deref for Uart {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr() }
}
}
impl Uart {
pub fn new(uart_base: u32) -> Uart {
Uart { uart_base }
}
/// Returns a pointer to the register block
fn ptr(&self) -> *const RegisterBlock {
self.uart_base as *const _
}
///Set baud rate and characteristics (115200 8N1) and map to GPIO
pub fn init(&self, mbox: &mut mbox::Mbox) -> Result<()> {
// turn off UART0
self.CR.set(0);
// set up clock for consistent divisor values
mbox.buffer[0] = 9 * 4;
mbox.buffer[1] = mbox::REQUEST;
mbox.buffer[2] = mbox::tag::SETCLKRATE;
mbox.buffer[3] = 12;
mbox.buffer[4] = 8;
mbox.buffer[5] = mbox::clock::UART; // UART clock
mbox.buffer[6] = 4_000_000; // 4Mhz
mbox.buffer[7] = 0; // skip turbo setting
mbox.buffer[8] = mbox::tag::LAST;
// Insert a compiler fence that ensures that all stores to the
// mbox buffer are finished before the GPU is signaled (which
// is done by a store operation as well).
compiler_fence(Ordering::Release);
if mbox.call(mbox::channel::PROP).is_err() {
return Err(UartError::MailboxError); // Abort if UART clocks couldn't be set
};
// map UART0 to GPIO pins
unsafe {
(*gpio::GPFSEL1).modify(gpio::GPFSEL1::FSEL14::TXD0 + gpio::GPFSEL1::FSEL15::RXD0);
(*gpio::GPPUD).set(0); // enable pins 14 and 15
for _ in 0..150 {
asm::nop();
}
(*gpio::GPPUDCLK0).modify(
gpio::GPPUDCLK0::PUDCLK14::AssertClock + gpio::GPPUDCLK0::PUDCLK15::AssertClock,
);
for _ in 0..150 {
asm::nop();
}
(*gpio::GPPUDCLK0).set(0);
}
self.ICR.write(ICR::ALL::CLEAR);
self.IBRD.write(IBRD::IBRD.val(2)); // Results in 115200 baud
self.FBRD.write(FBRD::FBRD.val(0xB));
self.LCRH.write(LCRH::WLEN::EightBit); // 8N1
self.CR
.write(CR::UARTEN::Enabled + CR::TXE::Enabled + CR::RXE::Enabled);
Ok(())
}
/// Send a character
pub fn send(&self, c: char) {
// wait until we can send
loop {
if !self.FR.is_set(FR::TXFF) {
break;
}
asm::nop();
}
// write the character to the buffer
self.DR.set(c as u32);
}
/// Receive a character
pub fn getc(&self) -> char {
// wait until something is in the buffer
loop {
if !self.FR.is_set(FR::RXFE) {
break;
}
asm::nop();
}
// read it and return
let mut ret = self.DR.get() as u8 as char;
// convert carrige return to newline
if ret == '\r' {
ret = '\n'
}
ret
}
/// Display a string
pub fn puts(&self, string: &str) {
for c in string.chars() {
// convert newline to carrige return + newline
if c == '\n' {
self.send('\r')
}
self.send(c);
}
}
/// Display a binary value in hexadecimal
pub fn hex(&self, d: u32) {
let mut n;
for i in 0..8 {
// get highest tetrad
n = d.wrapping_shr(28 - i * 4) & 0xF;
// 0-9 => '0'-'9', 10-15 => 'A'-'F'
// Add proper offset for ASCII table
if n > 9 {
n += 0x37;
} else {
n += 0x30;
}
self.send(n as u8 as char);
}
}
}
Loading…
Cancel
Save