Add tutorial 0E - Exceptions: Groundwork

pull/15/head
Andre Richter 5 years ago
parent 90d88f65b6
commit 4ecddb20de
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.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"register 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel8"
version = "0.1.0"
dependencies = [
"cortex-a 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"raspi3_boot 0.1.0",
"register 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "panic-abort"
version = "0.3.1"
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.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"panic-abort 0.3.1 (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.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"tock-registers 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tock-registers"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum cortex-a 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97867bac786c0a2912f7df981bdb8b6ea109a2422c22b37faf651d558a054453"
"checksum panic-abort 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c14a66511ed17b6a8b4256b868d7fd207836d891db15eea5195dbcaf87e630f"
"checksum r0 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f"
"checksum register 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a0f44a6dc9a98359515541a0c46ef4e3630a30879c1d7a4038f31dd533570bfb"
"checksum tock-registers 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c758f5195a2e0df9d9fecf6f506506b2766ff74cf64db1e995c87e2761a5c3e2"

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

@ -0,0 +1,65 @@
#
# 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
SOURCES = $(wildcard **/*.rs) $(wildcard **/*.S) link.ld
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
.PHONY: all qemu raspboot clippy clean objdump nm
all: clean kernel8.img
target/$(TARGET)/release/kernel8: $(SOURCES)
cargo xbuild --target=$(TARGET) --release
kernel8.img: target/$(TARGET)/release/kernel8
cp $< .
$(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
objdump:
cargo objdump --target $(TARGET) -- -disassemble -print-imm-hex kernel8
nm:
cargo nm --target $(TARGET) -- kernel8 | sort

@ -0,0 +1,301 @@
# Tutorial 0E - Exceptions: Groundwork
In this tutorial, we lay the groundwork for taking exceptions, and write a very bare-bones handler for a synchronous exception that happens in `EL1`.
More tutorials on exceptions will follow, implementing and introducing various other aspects of this rather huge topic.
## Exception Types
In `AArch64`, it is differentiated between four types of exceptions. These are:
- Synchronous
- For example, a `data abort` or a `system call`. They happen in direct consequence of executing a certain instruction, hence _synchronously_.
- Interrupt Request (`IRQ`)
- For example, an external device, like a timer, is asserting a physical interrupt line. IRQs happen _asynchronously_.
- Fast Interrupt Request (`FIQ`)
- These are basically interrupts that take priority over normal IRQs and have some more traits that make them suitable to implement super-fast processing. However, this is out of scope for this tutorial. For the sake of keeping these tutorials compact and concise, we will more or less ignore FIQs and only implement a dummy handler that would halt the CPU core.
- System Error (`SError`)
- Like IRQs, SErrors happen asynchronously and are technically more or less the same. They are intended to signal rather fatal errors in the system, e.g. if a transaction times out on the `SoC` interconnect. They are highly implementation specific and it is up to the SoC designer to decide which events are delivered as SErrors instead of normal IRQs.
## Exception entry
We recommend to read pages 1874-1876 of the [ARMv8 Architecture Reference Manual][ARMv8_Manual] to understand the mechanisms of taking an exception.
Here's an excerpt of important features for this tutorial:
- Exception entry moves the processor to the same or a higher `Exception Level`, but never to a lower `EL`.
- The program status is saved in the `SPSR_ELx` register at the target `EL`.
- The preferred return address is saved in the `ELR_ELx` register.
- "Preferred" here means that `ELR_ELx` may hold the instruction address of the instructions that caused the exception (`synchronous case`) or the first instruction that did not complete due to an `asynchronous` exception. Details in Chapter D1.10.1 of the [ARMv8 Architecture Reference Manual][ARMv8_Manual].
- All kinds of exceptions are turned off upon taking an exception, so that by default exception handlers can not get interrupted themselves.
- Taking an exception will select the dedicated stack pointer of the target `EL`.
- For example, if an exception in `EL0` is taken, the Stack Pointer Select register `SPSel` will switch from `0` to `1`, meaning that `SP_EL1` will be used by the exception vector code unless you explicitly change it back to `SP_EL0`.
### Exception Vectors
`AArch64` has a total of 16 exception vectors. There is one for each of the four kinds that were introduced already, and additionally, it is taken into account _where_ the exception was taken from and what the circumstances were.
Here is a copy of the decision table as shown in Chapter D1.10.2 of the [ARMv8 Architecture Reference Manual][ARMv8_Manual]:
[ARMv8_Manual]: https://developer.arm.com/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile
<table>
<thead>
<tr>
<th rowspan=2>Exception taken from </th>
<th colspan=4>Offset for exception type</th>
</tr>
<tr>
<th>Synchronous</th>
<th>IRQ or vIRQ</th>
<th>FIQ or vFIQ</th>
<th>SError or vSError</th>
</tr>
</thead>
<tbody>
<tr>
<td width="40%">Current Exception level with SP_EL0.</td>
<td align="center">0x000</td>
<td align="center">0x080</td>
<td align="center">0x100</td>
<td align="center">0x180</td>
</tr>
<tr>
<td>Current Exception level with SP_ELx, x>0.</td>
<td align="center">0x200</td>
<td align="center">0x280</td>
<td align="center">0x300</td>
<td align="center">0x380</td>
</tr>
<tr>
<td>Lower Exception level, where the implemented level immediately lower than the target level is using AArch64.</td>
<td align="center">0x400</td>
<td align="center">0x480</td>
<td align="center">0x500</td>
<td align="center">0x580</td>
</tr>
<tr>
<td>Lower Exception level, where the implemented level immediately lower than the target level is using AArch32.</td>
<td align="center">0x600</td>
<td align="center">0x680</td>
<td align="center">0x700</td>
<td align="center">0x780</td>
</tr>
</tbody>
</table>
Since our bare-metal Raspberry code operates in `EL1` using `SP_EL1`, if we'd cause a synchronous exception, the exception vector at offset `0x200` would be executed. But what does that even mean?
## Handler Code and Offsets
In many architectures, Operating Systems register their exception handlers (aka vectors) by compiling an architecturally defined data structure that stores function pointers to the different handlers. This can be as simple as an ordinary array of function pointers. The `base address` of this data structure is then stored into a special purpose register so that the CPU can branch to the respective handler function upon taking an exception. The famous `x86_64` architecture follows this principle, for example.
In `AArch64`, it is a bit different. Here, we have the special purpose register as well, called `VBAR_EL1`: Vector Base Address Register.
However, it does not store the base address of an array of function pointers, but the base address of a **memory location that contains code** for the 16 handlers, one handler back-to-back after the other. Each handler can take a maximum space of `0x80` bytes, aka 128 bytes. That's why the table above shows `offsets`: To indicate at which offset a certain handler starts.
Of course, you are not obliged to cram all your handler code into only 128 bytes. You are free to branch off to any other functions at any time. Actually, that is needed in most cases anyways, because the context-saving code alone would take up most of the available space (You'll learn about what context saving is shortly).
Additionally, there is a requirement that the `Vector Base Address` is aligned to `0x800` aka 2048 bytes.
## Rust Implementation
We start by adding a new section to the `link.ld` script, which will contain the exception vector code:
```rust
SECTIONS
{
.vectors ALIGN(2048):
{
*(.vectors)
}
```
### Context Save and Restore
Exception vectors, just like any other code, use a bunch of commonly shared processor resources. Most of all, the set of `General Purpose Registers` (GPRs) that each core in `AArch64` provides (`X0`-`X30`).
In order to not taint these registers when executing exception vector code, it is general practice to save these shared resources in memory (the stack, to be precise) as the very first action. This is commonly described as *saving the context*. Exception vector code can then use the shared resources in its own code without bothering, and as a last action before returning from exception handling code, restore the context, so that the processor can continue where it left off before taking the exception.
Context save and restore is one of the few places in system software where it is strongly advised to to use some hand-crafted assembly. Introducing `vectors.S`:
```asm
.macro SAVE_CONTEXT_AND_CALL_HANDLER handler
.balign 0x80
sub sp, sp, #16 * 17
stp x0, x1, [sp, #16 * 0]
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp x18, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
mrs x1, SPSR_EL1
mrs x2, ELR_EL1
stp x30, x1, [sp, #16 * 15]
str x2, [sp, #16 * 16]
mov x0, sp
b \handler
.endm
```
First, a macro for saving the context and branching to follow-up handler code. In advance, we reserve space on the stack for the context. That is, the 30 `GPRs` as well as the `saved program status` and the `exception link register` (holding the preferred return address). Afterwards, we store those registers, save the current stack address in `X0` and branch off to follow-up handler-code, whose function name is supplied as an argument to the macro.
The handler code will be written in Rust, but use the platform's `C` ABI. This way, we can define a function signature that has a pointer to the context-data on the stack as its first argument, and know that this argument is expected to be in the `X0` register. We need to use the `C` ABI here because `Rust` has no stable convention ([yet](https://github.com/rust-lang/rfcs/issues/600)).
Also note the `.balign 0x80` which ensure that the code is aligned properly according to the table shown earlier.
### Exception Vector Code
Next, we populate the `.vectors` section of the linker script using our macro (except for the FIQ vectors, for which we insert code that halts the CPU (via another macro):
```asm
.section .vectors, "ax"
.global __exception_vectors_start
__exception_vectors_start:
SAVE_CONTEXT_AND_CALL_HANDLER current_el0_synchronous // 0x000
SAVE_CONTEXT_AND_CALL_HANDLER current_el0_irq // 0x080
FIQ_DUMMY // 0x100
SAVE_CONTEXT_AND_CALL_HANDLER current_el0_serror // 0x180
SAVE_CONTEXT_AND_CALL_HANDLER current_elx_synchronous // 0x200
SAVE_CONTEXT_AND_CALL_HANDLER current_elx_irq // 0x280
FIQ_DUMMY // 0x300
SAVE_CONTEXT_AND_CALL_HANDLER current_elx_serror // 0x380
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch64_synchronous // 0x400
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch64_irq // 0x480
FIQ_DUMMY // 0x500
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch64_serror // 0x580
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch32_synchronous // 0x600
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch32_irq // 0x680
FIQ_DUMMY // 0x700
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch32_serror // 0x780
```
This part introduces various handler function names, which we can now implement using `Rust`.
### Implementing a handler
In `exception.rs`, we implement a handler for a synchronous exception that that will happen in `EL1` using `SP_EL1`:
```rust
#[naked]
#[no_mangle]
unsafe extern "C" fn current_elx_synchronous(e: &mut ExceptionContext) {
UART.puts("A synchronous exception happened.\n");
UART.puts(" ELR_EL1: 0x");
UART.hex(e.elr_el1);
UART.puts(
"\n Incrementing ELR_EL1 by 4 now to continue with the first \
instruction after the exception!\n",
);
e.elr_el1 += 4;
UART.puts(" ELR_EL1 modified: 0x");
UART.hex(e.elr_el1);
UART.puts("\n");
UART.puts(" Returning from exception...\n\n");
exception_return!();
}
```
First of all, we make use of the `#[naked]` attribute to define a [naked function](https://github.com/nox/rust-rfcs/blob/master/tex), which prevents emission of a function prologue and epilogue, which would potentially further modify our already set-up stack.
The function takes a `struct ExceptionContext` argument, which resembles what we put on the stack before branching to the handler:
```rust
#[repr(C)]
pub struct GPR {
x: [u64; 31],
}
#[repr(C)]
pub struct ExceptionContext {
// General Purpose Registers
gpr: GPR,
spsr_el1: u64,
elr_el1: u64,
}
```
Inside the function, for demo purposes, we advance the copy of the `ELR` by 4, which makes it point to the next instruction after the instruction that caused the exception.
Finally, we call the `exception_return!()` Rust macro, which plays back our saved context before finally executing `eret` to return from the exception.
#### Default handler
In order to spare the work of implementing each and every handler, we define an `extern "C" fn default_exception_handler()`. Using the linker script, we take a shortcut and make all the other handlers point to this function code if it is not implemented explicitly anywhere else:
```rust
PROVIDE(current_el0_synchronous = default_exception_handler);
PROVIDE(current_el0_irq = default_exception_handler);
PROVIDE(current_el0_serror = default_exception_handler);
...(Many more omitted)
```
## Causing an Exception - Testing the Code
In `mmu.rs`, we set the `Access Flag` of the second 2 MiB block to `False`:
```rust
// The second 2 MiB block.
LVL2_TABLE[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
// Set the Access Flag to false. This will cause a synchronous exception
// when code tries to read from this virtual address.
+ STAGE1_DESCRIPTOR::AF::False
+ STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(1)
+ STAGE1_DESCRIPTOR::XN::True)
.value;
```
After pointing `VBAR_EL1` to our vector code,
```rust
exception::set_vbar_el1_checked(exception_vectors_start)
```
we cause a data abort exception by reading from said 2 MiB block:
```rusta
unsafe { core::ptr::read_volatile((2 * 1024 * 1024) as *const u64) };
```
Finally, this triggers our exception code, and returns to the first instruction afterwards:
```console
ferris@box:~$ make raspboot
[0] UART is live!
[1] Press a key to continue booting... Greetings fellow Rustacean!
[2] Switching MMU on now... MMU is live \o/
[3] Exception vectors are set up.
A synchronous exception happened.
ELR_EL1: 0x00000000000807A4
Incrementing ELR_EL1 by 4 now to continue with the first instruction after the exception!
ELR_EL1 modified: 0x00000000000807A8
Returning from exception...
Whoa! We recovered from an exception.
```

Binary file not shown.

@ -0,0 +1,78 @@
/*
* 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.*)
}
.vectors ALIGN(2048):
{
*(.vectors)
}
.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*) }
}
PROVIDE(current_el0_synchronous = default_exception_handler);
PROVIDE(current_el0_irq = default_exception_handler);
PROVIDE(current_el0_serror = default_exception_handler);
PROVIDE(current_elx_synchronous = default_exception_handler);
PROVIDE(current_elx_irq = default_exception_handler);
PROVIDE(current_elx_serror = default_exception_handler);
PROVIDE(lower_aarch64_synchronous = default_exception_handler);
PROVIDE(lower_aarch64_irq = default_exception_handler);
PROVIDE(lower_aarch64_serror = default_exception_handler);
PROVIDE(lower_aarch32_synchronous = default_exception_handler);
PROVIDE(lower_aarch32_irq = default_exception_handler);
PROVIDE(lower_aarch32_serror = default_exception_handler);

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

@ -0,0 +1,129 @@
/*
* 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 panic_abort;
#[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::*};
const STACK_START: u64 = 0x80_000;
// 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_EL1 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::EL1h,
);
// Second, let the link register point to reset().
ELR_EL2.set(reset as *const () as u64);
// Set up SP_EL1 (stack pointer), which will be used by EL1 once
// we "return" to it.
SP_EL1.set(STACK_START);
// 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 (CORE_0 == MPIDR_EL1.get() & CORE_MASK) && (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,37 @@
/*
* 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 cortex_a::asm;
/*
*
* Using the CPU's cycles
*
*/
/// Wait N CPU cycles (ARM CPU only)
pub fn wait_cycles(cyc: u32) {
for _ in 0..cyc {
asm::nop();
}
}

@ -0,0 +1,119 @@
/*
* 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::{asm, barrier, regs::*};
global_asm!(include_str!("vectors.S"));
pub unsafe fn set_vbar_el1_checked(vec_base_addr: u64) -> bool {
if vec_base_addr.trailing_zeros() < 11 {
false
} else {
cortex_a::regs::VBAR_EL1.set(vec_base_addr);
// Force VBAR update to complete before next instruction.
barrier::isb(barrier::SY);
true
}
}
#[repr(C)]
pub struct GPR {
x: [u64; 31],
}
#[repr(C)]
pub struct ExceptionContext {
// General Purpose Registers
gpr: GPR,
spsr_el1: u64,
elr_el1: u64,
}
macro_rules! exception_return {
() => {
asm! {"RESTORE_CONTEXT"}
asm::eret();
};
}
/// The default exception, invoked for every exception type unless the handler
/// is overwritten.
#[naked]
#[no_mangle]
extern "C" fn default_exception_handler() {
UART.puts("Unexpected exception. Halting CPU.\n");
loop {
cortex_a::asm::wfe()
}
}
// To implement an exception handler, overwrite it by defining the respective
// function below.
// Don't forget:
// - The #[naked] attribute
// - The #[no_mangle] attribute
// - The exception_return! macro if suitable.
//
// unsafe extern "C" fn current_el0_synchronous(e: &mut ExceptionContext);
// unsafe extern "C" fn current_el0_irq(e: &mut ExceptionContext);
// unsafe extern "C" fn current_el0_serror(e: &mut ExceptionContext);
// unsafe extern "C" fn current_elx_synchronous(e: &mut ExceptionContext);
// unsafe extern "C" fn current_elx_irq(e: &mut ExceptionContext);
// unsafe extern "C" fn current_elx_serror(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch64_synchronous(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch64_irq(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch64_serror(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch32_synchronous(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch32_irq(e: &mut ExceptionContext);
// unsafe extern "C" fn lower_aarch32_serror(e: &mut ExceptionContext);
#[naked]
#[no_mangle]
unsafe extern "C" fn current_elx_synchronous(e: &mut ExceptionContext) {
UART.puts("A synchronous exception happened.\n");
UART.puts(" ELR_EL1: 0x");
UART.hex(e.elr_el1);
UART.puts(
"\n Incrementing ELR_EL1 by 4 now to continue with the first \
instruction after the exception!\n",
);
e.elr_el1 += 4;
UART.puts(" ELR_EL1 modified: 0x");
UART.hex(e.elr_el1);
UART.puts("\n");
UART.puts(" Returning from exception...\n\n");
exception_return!();
}

@ -0,0 +1,121 @@
/*
* 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 register::{mmio::ReadWrite, register_bitfields};
// 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
]
]
}
const GPIO_BASE: u32 = MMIO_BASE + 0x200_000;
#[allow(non_snake_case)]
#[repr(C)]
pub struct RegisterBlock {
pub GPFSEL0: ReadWrite<u32>, // 0x00
pub GPFSEL1: ReadWrite<u32, GPFSEL1::Register>, // 0x04
pub GPFSEL2: ReadWrite<u32>, // 0x08
pub GPFSEL3: ReadWrite<u32>, // 0x0C
pub GPFSEL4: ReadWrite<u32>, // 0x10
pub GPFSEL5: ReadWrite<u32>, // 0x14
__reserved_0: u32, // 0x18
GPSET0: ReadWrite<u32>, // 0x1C
GPSET1: ReadWrite<u32>, // 0x20
__reserved_1: u32, //
GPCLR0: ReadWrite<u32>, // 0x28
__reserved_2: [u32; 2], //
GPLEV0: ReadWrite<u32>, // 0x34
GPLEV1: ReadWrite<u32>, // 0x38
__reserved_3: u32, //
GPEDS0: ReadWrite<u32>, // 0x40
GPEDS1: ReadWrite<u32>, // 0x44
__reserved_4: [u32; 7], //
GPHEN0: ReadWrite<u32>, // 0x64
GPHEN1: ReadWrite<u32>, // 0x68
__reserved_5: [u32; 10], //
pub GPPUD: ReadWrite<u32>, // 0x94
pub GPPUDCLK0: ReadWrite<u32, GPPUDCLK0::Register>, // 0x98
pub GPPUDCLK1: ReadWrite<u32>, // 0x9C
}
/// Public interface to the GPIO MMIO area
pub struct GPIO;
impl ops::Deref for GPIO {
type Target = RegisterBlock;
fn deref(&self) -> &Self::Target {
unsafe { &*Self::ptr() }
}
}
impl GPIO {
pub fn new() -> GPIO {
GPIO
}
/// Returns a pointer to the register block
fn ptr() -> *const RegisterBlock {
GPIO_BASE as *const _
}
}

@ -0,0 +1,95 @@
/*
* 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)]
#![feature(global_asm)]
#![feature(label_break_value)]
#![feature(naked_functions)]
const MMIO_BASE: u32 = 0x3F00_0000;
mod delays;
mod exception;
mod gpio;
mod mbox;
mod mmu;
mod uart;
static UART: uart::Uart = uart::Uart::new(uart::UART_PHYS_BASE);
fn kernel_entry() -> ! {
extern "C" {
static __exception_vectors_start: u64;
}
let gpio = gpio::GPIO::new();
let mut mbox = mbox::Mbox::new();
// set up serial console
match UART.init(&mut mbox, &gpio) {
Ok(_) => UART.puts("\n[0] UART is live!\n"),
Err(_) => loop {
cortex_a::asm::wfe() // If UART fails, abort early
},
}
UART.puts("[1] Press a key to continue booting... ");
UART.getc();
UART.puts("Greetings fellow Rustacean!\n");
UART.puts("[2] Switching MMU on now... ");
unsafe { mmu::init() };
UART.puts("MMU is live \\o/\n");
'init: {
if unsafe {
let exception_vectors_start: u64 = &__exception_vectors_start as *const _ as u64;
exception::set_vbar_el1_checked(exception_vectors_start)
} {
UART.puts("[3] Exception vectors are set up.\n\n");
} else {
UART.puts("[3] Error setting exception vectors. Aborting early.\n");
break 'init;
}
// Cause an exception by accessing a virtual address for which we set
// the "Access Flag" to zero in the page tables.
unsafe { core::ptr::read_volatile((2 * 1024 * 1024) as *const u64) };
UART.puts("Whoa! We recovered from an exception.\n")
}
// echo everything back
loop {
UART.send(UART.getc());
}
}
raspi3_boot::entry!(kernel_entry);

@ -0,0 +1,162 @@
/*
* 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,
};
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,226 @@
/*
* 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 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;
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 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;
}
// 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;
// The second 2 MiB block.
LVL2_TABLE[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
// Set the Access Flag to false. This will cause a synchronous exception
// when code tries to read from this virtual address.
+ STAGE1_DESCRIPTOR::AF::False
+ STAGE1_DESCRIPTOR::LVL2_OUTPUT_ADDR_4KiB.val(1)
+ 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). 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.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 (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.iter_mut().enumerate() {
let j: u64 = i as u64;
let mem_attr = if j < ro_first_page_index || j > ro_last_page_index {
STAGE1_DESCRIPTOR::AP::RW_EL1 + STAGE1_DESCRIPTOR::XN::True
} else {
STAGE1_DESCRIPTOR::AP::RO_EL1 + STAGE1_DESCRIPTOR::XN::False
};
*entry = (common + mem_attr + STAGE1_DESCRIPTOR::NEXT_LVL_TABLE_ADDR_4KiB.val(j)).value;
}
// 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 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);
}

@ -0,0 +1,284 @@
/*
* 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 crate::delays;
use crate::gpio;
use crate::mbox;
use core::{
ops,
sync::atomic::{compiler_fence, Ordering},
};
use cortex_a::asm;
use register::{mmio::*, register_bitfields};
// 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 const 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, gpio: &gpio::GPIO) -> 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
gpio.GPFSEL1
.modify(gpio::GPFSEL1::FSEL14::TXD0 + gpio::GPFSEL1::FSEL15::RXD0);
gpio.GPPUD.set(0); // enable pins 14 and 15
delays::wait_cycles(150);
gpio.GPPUDCLK0.modify(
gpio::GPPUDCLK0::PUDCLK14::AssertClock + gpio::GPPUDCLK0::PUDCLK15::AssertClock,
);
delays::wait_cycles(150);
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: u64) {
let mut n;
for i in 0..16 {
// get highest tetrad
n = d.wrapping_shr(60 - 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);
}
}
}

@ -0,0 +1,109 @@
//
// 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.
//
.macro SAVE_CONTEXT_AND_CALL_HANDLER handler
.balign 0x80
sub sp, sp, #16 * 17
stp x0, x1, [sp, #16 * 0]
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp x18, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
mrs x1, SPSR_EL1
mrs x2, ELR_EL1
stp x30, x1, [sp, #16 * 15]
str x2, [sp, #16 * 16]
mov x0, sp
b \handler
.endm
.macro RESTORE_CONTEXT
ldr x19, [sp, #16 * 16]
ldp x30, x20, [sp, #16 * 15]
msr ELR_EL1, x19
msr SPSR_EL1, x20
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]
ldp x4, x5, [sp, #16 * 2]
ldp x6, x7, [sp, #16 * 3]
ldp x8, x9, [sp, #16 * 4]
ldp x10, x11, [sp, #16 * 5]
ldp x12, x13, [sp, #16 * 6]
ldp x14, x15, [sp, #16 * 7]
ldp x16, x17, [sp, #16 * 8]
ldp x18, x19, [sp, #16 * 9]
ldp x20, x21, [sp, #16 * 10]
ldp x22, x23, [sp, #16 * 11]
ldp x24, x25, [sp, #16 * 12]
ldp x26, x27, [sp, #16 * 13]
ldp x28, x29, [sp, #16 * 14]
add sp, sp, #16 * 17
.endm
.macro FIQ_DUMMY
.balign 0x80
1: wfe
b 1b
.endm
.section .vectors, "ax"
.global __exception_vectors_start
__exception_vectors_start:
SAVE_CONTEXT_AND_CALL_HANDLER current_el0_synchronous // 0x000
SAVE_CONTEXT_AND_CALL_HANDLER current_el0_irq // 0x080
FIQ_DUMMY // 0x100
SAVE_CONTEXT_AND_CALL_HANDLER current_el0_serror // 0x180
SAVE_CONTEXT_AND_CALL_HANDLER current_elx_synchronous // 0x200
SAVE_CONTEXT_AND_CALL_HANDLER current_elx_irq // 0x280
FIQ_DUMMY // 0x300
SAVE_CONTEXT_AND_CALL_HANDLER current_elx_serror // 0x380
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch64_synchronous // 0x400
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch64_irq // 0x480
FIQ_DUMMY // 0x500
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch64_serror // 0x580
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch32_synchronous // 0x600
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch32_irq // 0x680
FIQ_DUMMY // 0x700
SAVE_CONTEXT_AND_CALL_HANDLER lower_aarch32_serror // 0x780
Loading…
Cancel
Save