1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-03-21 22:49:44 +02:00

Add pages about entry point and exception handling on APs ()

* Add page about entry point before Rust code.

* Convert tabs to spaces.

mdbook doesn't seem to handle tabs in code properly.

* Add page about handling exceptions.

* More nuanced discussion of Rust Raspberry Pi OS tutorial.

* Add note about EL1 to entry point page too.
This commit is contained in:
Andrew Walbran 2023-06-14 19:27:07 +01:00 committed by GitHub
parent 9127253adf
commit 54fd2578d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 146 additions and 58 deletions

@ -228,6 +228,7 @@
# Bare Metal: Afternoon
- [Application Processors](bare-metal/aps.md)
- [Getting Ready to Rust](bare-metal/aps/entry-point.md)
- [Inline Assembly](bare-metal/aps/inline-assembly.md)
- [MMIO](bare-metal/aps/mmio.md)
- [Let's Write a UART Driver](bare-metal/aps/uart.md)
@ -239,6 +240,7 @@
- [Using It](bare-metal/aps/better-uart/using.md)
- [Logging](bare-metal/aps/logging.md)
- [Using It](bare-metal/aps/logging/using.md)
- [Exceptions](bare-metal/aps/exceptions.md)
- [Other Projects](bare-metal/aps/other-projects.md)
- [Useful Crates](bare-metal/useful-crates.md)
- [zerocopy](bare-metal/useful-crates/zerocopy.md)

@ -0,0 +1,35 @@
# Getting Ready to Rust
Before we can start running Rust code, we need to do some initialisation.
```armasm
{{#include examples/entry.S:entry}}
```
<details>
* This is the same as it would be for C: initialising the processor state, zeroing the BSS, and
setting up the stack pointer.
* The BSS (block starting symbol, for historical reasons) is the part of the object file which
containing statically allocated variables which are initialised to zero. They are omitted from
the image, to avoid wasting space on zeroes. The compiler assumes that the loader will take care
of zeroing them.
* The BSS may already be zeroed, depending on how memory is initialised and the image is loaded, but
we zero it to be sure.
* We need to enable the MMU and cache before reading or writing any memory. If we don't:
* Unaligned accesses will fault. We build the Rust code for the `aarch64-unknown-none` target
which sets `+strict-align` to prevent the compiler generating unaligned accesses, so it should
be fine in this case, but this is not necessarily the case in general.
* If it were running in a VM, this can lead to cache coherency issues. The problem is that the VM
is accessing memory directly with the cache disabled, while the host has cachable aliases to the
same memory. Even if the host doesn't explicitly access the memory, speculative accesses can
lead to cache fills, and then changes from one or the other will get lost when the cache is
cleaned or the VM enables the cache. (Cache is keyed by physical address, not VA or IPA.)
* For simplicity, we just use a hardcoded pagetable (see `idmap.S`) which identity maps the first 1
GiB of address space for devices, the next 1 GiB for DRAM, and another 1 GiB higher up for more
devices. This matches the memory layout that QEMU uses.
* We also set up the exception vector (`vbar_el1`), which we'll see more about later.
* All examples this afternoon assume we will be running at exception level 1 (EL1). If you need to
run at a different exception level you'll need to modify `entry.S` accordingly.
</details>

@ -15,19 +15,19 @@
*/
.macro adr_l, reg:req, sym:req
adrp \reg, \sym
add \reg, \reg, :lo12:\sym
adrp \reg, \sym
add \reg, \reg, :lo12:\sym
.endm
.macro mov_i, reg:req, imm:req
movz \reg, :abs_g3:\imm
movk \reg, :abs_g2_nc:\imm
movk \reg, :abs_g1_nc:\imm
movk \reg, :abs_g0_nc:\imm
movz \reg, :abs_g3:\imm
movk \reg, :abs_g2_nc:\imm
movk \reg, :abs_g1_nc:\imm
movk \reg, :abs_g0_nc:\imm
.endm
.set .L_MAIR_DEV_nGnRE, 0x04
.set .L_MAIR_MEM_WBWA, 0xff
.set .L_MAIR_DEV_nGnRE, 0x04
.set .L_MAIR_MEM_WBWA, 0xff
.set .Lmairval, .L_MAIR_DEV_nGnRE | (.L_MAIR_MEM_WBWA << 8)
/* 4 KiB granule size for TTBR0_EL1. */
@ -77,66 +77,71 @@
* prepares the stack, enables floating point, and sets up the exception vector. It preserves x0-x3
* for the Rust entry point, as these may contain boot parameters.
*/
// ANCHOR: entry
.section .init.entry, "ax"
.global entry
entry:
/* Load and apply the memory management configuration, ready to enable MMU and caches. */
adrp x30, idmap
msr ttbr0_el1, x30
/*
* Load and apply the memory management configuration, ready to enable MMU and
* caches.
*/
adrp x30, idmap
msr ttbr0_el1, x30
mov_i x30, .Lmairval
msr mair_el1, x30
mov_i x30, .Lmairval
msr mair_el1, x30
mov_i x30, .Ltcrval
/* Copy the supported PA range into TCR_EL1.IPS. */
mrs x29, id_aa64mmfr0_el1
bfi x30, x29, #32, #4
mov_i x30, .Ltcrval
/* Copy the supported PA range into TCR_EL1.IPS. */
mrs x29, id_aa64mmfr0_el1
bfi x30, x29, #32, #4
msr tcr_el1, x30
msr tcr_el1, x30
mov_i x30, .Lsctlrval
mov_i x30, .Lsctlrval
/*
* Ensure everything before this point has completed, then invalidate any potentially stale
* local TLB entries before they start being used.
*/
isb
tlbi vmalle1
ic iallu
dsb nsh
isb
/*
* Ensure everything before this point has completed, then invalidate any
* potentially stale local TLB entries before they start being used.
*/
isb
tlbi vmalle1
ic iallu
dsb nsh
isb
/*
* Configure sctlr_el1 to enable MMU and cache and don't proceed until this has completed.
*/
msr sctlr_el1, x30
isb
/*
* Configure sctlr_el1 to enable MMU and cache and don't proceed until this
* has completed.
*/
msr sctlr_el1, x30
isb
/* Disable trapping floating point access in EL1. */
mrs x30, cpacr_el1
orr x30, x30, #(0x3 << 20)
msr cpacr_el1, x30
isb
/* Disable trapping floating point access in EL1. */
mrs x30, cpacr_el1
orr x30, x30, #(0x3 << 20)
msr cpacr_el1, x30
isb
/* Zero out the bss section. */
adr_l x29, bss_begin
adr_l x30, bss_end
0: cmp x29, x30
b.hs 1f
stp xzr, xzr, [x29], #16
b 0b
/* Zero out the bss section. */
adr_l x29, bss_begin
adr_l x30, bss_end
0: cmp x29, x30
b.hs 1f
stp xzr, xzr, [x29], #16
b 0b
1: /* Prepare the stack. */
adr_l x30, boot_stack_end
mov sp, x30
1: /* Prepare the stack. */
adr_l x30, boot_stack_end
mov sp, x30
/* Set up exception vector. */
adr x30, vector_table_el1
msr vbar_el1, x30
/* Set up exception vector. */
adr x30, vector_table_el1
msr vbar_el1, x30
/* Call into Rust code. */
bl main
/* Call into Rust code. */
bl main
/* Loop forever waiting for interrupts. */
2: wfi
b 2b
/* Loop forever waiting for interrupts. */
2: wfi
b 2b

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// ANCHOR: exceptions
use log::error;
use smccc::psci::system_off;
use smccc::Hvc;

@ -0,0 +1,27 @@
# Exceptions
AArch64 defines an exception vector table with 16 entries, for 4 types of exceptions (synchronous,
IRQ, FIQ, SError) from 4 states (current EL with SP0, current EL with SPx, lower EL using AArch64,
lower EL using AArch32). We implement this in assembly to save volatile registers to the stack
before calling into Rust code:
```rust,editable,compile_fail
{{#include examples/src/exceptions.rs:exceptions}}
```
<details>
* EL is exception level; all our examples this afternoon run in EL1.
* For simplicity we aren't distinguishing between SP0 and SPx for the current EL exceptions, or
between AArch32 and AArch64 for the lower EL exceptions.
* For this example we just log the exception and power down, as we don't expect any of them to
actually happen.
* We can think of exception handlers and our main execution context more or less like different
threads. [`Send` and `Sync`][1] will control what we can share between them, just like with threads.
For example, if we want to share some value between exception handlers and the rest of the
program, and it's `Send` but not `Sync`, then we'll need to wrap it in something like a `Mutex`
and put it in a static.
</details>
[1]: ../../concurrency/send-sync.md

@ -5,7 +5,25 @@
* Supports x86, aarch64 and RISC-V.
* Relies on LinuxBoot rather than having many drivers itself.
* [Rust RaspberryPi OS tutorial](https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials)
* Initialisation, UART driver, simple bootloader, JTAG, exception levels, exception handling, page tables
* Not all very well written, so beware.
* Initialisation, UART driver, simple bootloader, JTAG, exception levels, exception handling,
page tables
* Some dodginess around cache maintenance and initialisation in Rust, not necessarily a good
example to copy for production code.
* [`cargo-call-stack`](https://crates.io/crates/cargo-call-stack)
* Static analysis to determine maximum stack usage.
<details>
* The RaspberryPi OS tutorial runs Rust code before the MMU and caches are enabled. This will read
and write memory (e.g. the stack). However:
* Without the MMU and cache, unaligned accesses will fault. It builds with `aarch64-unknown-none`
which sets `+strict-align` to prevent the compiler generating unaligned accesses so it should be
alright, but this is not necessarily the case in general.
* If it were running in a VM, this can lead to cache coherency issues. The problem is that the VM
is accessing memory directly with the cache disabled, while the host has cachable aliases to the
same memory. Even if the host doesn't explicitly access the memory, speculative accesses can
lead to cache fills, and then changes from one or the other will get lost. Again this is alright
in this particular case (running directly on the hardware with no hypervisor), but isn't a good
pattern in general.
</details>