From fb3fd3979cef7ae76803d044352f53e178117a03 Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Tue, 21 Mar 2023 13:36:55 +0000 Subject: [PATCH] Add RTC exercise. --- src/SUMMARY.md | 3 + src/exercises/bare-metal/afternoon.md | 11 ++ src/exercises/bare-metal/rtc.md | 78 ++++++++ .../bare-metal/rtc/.cargo/config.toml | 3 + src/exercises/bare-metal/rtc/Cargo.lock | 108 +++++++++++ src/exercises/bare-metal/rtc/Cargo.toml | 16 ++ src/exercises/bare-metal/rtc/Makefile | 30 +++ src/exercises/bare-metal/rtc/build.rs | 25 +++ src/exercises/bare-metal/rtc/entry.S | 142 ++++++++++++++ src/exercises/bare-metal/rtc/exceptions.S | 178 ++++++++++++++++++ src/exercises/bare-metal/rtc/idmap.S | 42 +++++ src/exercises/bare-metal/rtc/image.ld | 104 ++++++++++ .../bare-metal/rtc/src/exceptions.rs | 64 +++++++ src/exercises/bare-metal/rtc/src/logger.rs | 54 ++++++ src/exercises/bare-metal/rtc/src/main.rs | 67 +++++++ src/exercises/bare-metal/rtc/src/pl011.rs | 168 +++++++++++++++++ src/exercises/bare-metal/rtc/src/pl031.rs | 70 +++++++ .../bare-metal/solutions-afternoon.md | 9 + 18 files changed, 1172 insertions(+) create mode 100644 src/exercises/bare-metal/afternoon.md create mode 100644 src/exercises/bare-metal/rtc.md create mode 100644 src/exercises/bare-metal/rtc/.cargo/config.toml create mode 100644 src/exercises/bare-metal/rtc/Cargo.lock create mode 100644 src/exercises/bare-metal/rtc/Cargo.toml create mode 100644 src/exercises/bare-metal/rtc/Makefile create mode 100644 src/exercises/bare-metal/rtc/build.rs create mode 100644 src/exercises/bare-metal/rtc/entry.S create mode 100644 src/exercises/bare-metal/rtc/exceptions.S create mode 100644 src/exercises/bare-metal/rtc/idmap.S create mode 100644 src/exercises/bare-metal/rtc/image.ld create mode 100644 src/exercises/bare-metal/rtc/src/exceptions.rs create mode 100644 src/exercises/bare-metal/rtc/src/logger.rs create mode 100644 src/exercises/bare-metal/rtc/src/main.rs create mode 100644 src/exercises/bare-metal/rtc/src/pl011.rs create mode 100644 src/exercises/bare-metal/rtc/src/pl031.rs create mode 100644 src/exercises/bare-metal/solutions-afternoon.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 1296e4ab..620780c4 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -276,6 +276,8 @@ - [spin and once_cell]() - [Android]() - [vmbase]() +- [Exercises](exercises/bare-metal/afternoon.md) + - [RTC driver](exercises/bare-metal/rtc.md) --- @@ -292,3 +294,4 @@ - [Day 3 Afternoon](exercises/day-3/solutions-afternoon.md) - [Day 4 Morning](exercises/day-4/solutions-morning.md) - [Bare Metal Rust Morning](exercises/bare-metal/solutions-morning.md) + - [Bare Metal Rust Afternoon](exercises/bare-metal/solutions-afternoon.md) diff --git a/src/exercises/bare-metal/afternoon.md b/src/exercises/bare-metal/afternoon.md new file mode 100644 index 00000000..c69d1062 --- /dev/null +++ b/src/exercises/bare-metal/afternoon.md @@ -0,0 +1,11 @@ +# Exercises + +We will write a driver for the PL031 real-time clock device. + +
+ +After looking at the exercises, you can look at the [solutions] provided. + +[solutions]: solutions-afternoon.md + +
diff --git a/src/exercises/bare-metal/rtc.md b/src/exercises/bare-metal/rtc.md new file mode 100644 index 00000000..d4e79e3c --- /dev/null +++ b/src/exercises/bare-metal/rtc.md @@ -0,0 +1,78 @@ +# RTC driver + +The QEMU aarch64 virt machine has a [PL031][1] real-time clock at 0x9010000. For this exercise, you +should write a driver for it and use it to print the current time to the serial console. You can use +the [`chrono`][2] crate for date/time formatting. + +`src/main.rs`: +```rust,compile_fail +{{#include rtc/src/main.rs:top}} + +{{#include rtc/src/main.rs:imports}} + +{{#include rtc/src/main.rs:main}} + + // TODO: Initialise RTC and print value. + +{{#include rtc/src/main.rs:main_end}} +``` + +`src/exceptions.rs` (you shouldn't need to change this): +```rust,compile_fail +{{#include rtc/src/exceptions.rs}} +``` + +`src/logger.rs` (you shouldn't need to change this): +```rust,compile_fail +{{#include rtc/src/logger.rs}} +``` + +`src/pl011.rs` (you shouldn't need to change this): +```rust,compile_fail +{{#include rtc/src/pl011.rs}} +``` + +`Cargo.toml` (you shouldn't need to change this): +```toml +{{#include rtc/Cargo.toml}} +``` + +`build.rs` (you shouldn't need to change this): +```rust,compile_fail +{{#include rtc/build.rs}} +``` + +`entry.S` (you shouldn't need to change this): +```armasm +{{#include rtc/entry.S}} +``` + +`exceptions.S` (you shouldn't need to change this): +```armasm +{{#include rtc/exceptions.S}} +``` + +`idmap.S` (you shouldn't need to change this): +```armasm +{{#include rtc/idmap.S}} +``` + +`image.ld` (you shouldn't need to change this): +```ld +{{#include rtc/image.ld}} +``` + +`Makefile` (you shouldn't need to change this): +```makefile +{{#include rtc/Makefile}} +``` + +`.cargo/config.toml` (you shouldn't need to change this): +```toml +{{#include rtc/.cargo/config.toml}} +``` + +Run the code in QEMU with `make qemu`. + +[1]: https://developer.arm.com/documentation/ddi0224/c +[2]: https://crates.io/crates/chrono diff --git a/src/exercises/bare-metal/rtc/.cargo/config.toml b/src/exercises/bare-metal/rtc/.cargo/config.toml new file mode 100644 index 00000000..ffecab4e --- /dev/null +++ b/src/exercises/bare-metal/rtc/.cargo/config.toml @@ -0,0 +1,3 @@ +[build] +target = "aarch64-unknown-none" +rustflags = ["-C", "link-arg=-Timage.ld"] diff --git a/src/exercises/bare-metal/rtc/Cargo.lock b/src/exercises/bare-metal/rtc/Cargo.lock new file mode 100644 index 00000000..755d5b97 --- /dev/null +++ b/src/exercises/bare-metal/rtc/Cargo.lock @@ -0,0 +1,108 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "psci" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3374e3ae47f134467227a48be93b929e5d304efcd25ce5d176006403ca1d9bab" + +[[package]] +name = "rtc" +version = "0.1.0" +dependencies = [ + "bitflags", + "cc", + "chrono", + "log", + "psci", + "spin", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "spin" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5d6e0250b93c8427a177b849d144a96d5acc57006149479403d7861ab721e34" +dependencies = [ + "lock_api", +] diff --git a/src/exercises/bare-metal/rtc/Cargo.toml b/src/exercises/bare-metal/rtc/Cargo.toml new file mode 100644 index 00000000..77a78fe4 --- /dev/null +++ b/src/exercises/bare-metal/rtc/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] + +[package] +name = "rtc" +version = "0.1.0" +edition = "2021" + +[dependencies] +bitflags = "2.0.0" +chrono = { version = "0.4.24", default-features = false } +log = "0.4.17" +psci = "0.1.1" +spin = "0.9.4" + +[build-dependencies] +cc = "1.0.73" diff --git a/src/exercises/bare-metal/rtc/Makefile b/src/exercises/bare-metal/rtc/Makefile new file mode 100644 index 00000000..26d4a563 --- /dev/null +++ b/src/exercises/bare-metal/rtc/Makefile @@ -0,0 +1,30 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.PHONY: build qemu_minimal qemu qemu_logger + +all: rtc.bin + +build: + cargo build + +rtc.bin: build + aarch64-linux-gnu-objcopy -O binary target/aarch64-unknown-none/debug/rtc $@ + +qemu: rtc.bin + qemu-system-aarch64 -machine virt -cpu max -serial mon:stdio -display none -kernel $< -s + +clean: + cargo clean + rm -f *.bin diff --git a/src/exercises/bare-metal/rtc/build.rs b/src/exercises/bare-metal/rtc/build.rs new file mode 100644 index 00000000..d1d4e645 --- /dev/null +++ b/src/exercises/bare-metal/rtc/build.rs @@ -0,0 +1,25 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use cc::Build; +use std::env; + +fn main() { + env::set_var("CROSS_COMPILE", "aarch64-linux-gnu"); + Build::new() + .file("entry.S") + .file("exceptions.S") + .file("idmap.S") + .compile("empty") +} diff --git a/src/exercises/bare-metal/rtc/entry.S b/src/exercises/bare-metal/rtc/entry.S new file mode 100644 index 00000000..cd554f28 --- /dev/null +++ b/src/exercises/bare-metal/rtc/entry.S @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.macro adr_l, reg:req, sym:req + 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 +.endm + +.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. */ +.set .L_TCR_TG0_4KB, 0x0 << 14 +/* 4 KiB granule size for TTBR1_EL1. */ +.set .L_TCR_TG1_4KB, 0x2 << 30 +/* Disable translation table walk for TTBR1_EL1, generating a translation fault instead. */ +.set .L_TCR_EPD1, 0x1 << 23 +/* Translation table walks for TTBR0_EL1 are inner sharable. */ +.set .L_TCR_SH_INNER, 0x3 << 12 +/* + * Translation table walks for TTBR0_EL1 are outer write-back read-allocate write-allocate + * cacheable. + */ +.set .L_TCR_RGN_OWB, 0x1 << 10 +/* + * Translation table walks for TTBR0_EL1 are inner write-back read-allocate write-allocate + * cacheable. + */ +.set .L_TCR_RGN_IWB, 0x1 << 8 +/* Size offset for TTBR0_EL1 is 2**39 bytes (512 GiB). */ +.set .L_TCR_T0SZ_512, 64 - 39 +.set .Ltcrval, .L_TCR_TG0_4KB | .L_TCR_TG1_4KB | .L_TCR_EPD1 | .L_TCR_RGN_OWB +.set .Ltcrval, .Ltcrval | .L_TCR_RGN_IWB | .L_TCR_SH_INNER | .L_TCR_T0SZ_512 + +/* Stage 1 instruction access cacheability is unaffected. */ +.set .L_SCTLR_ELx_I, 0x1 << 12 +/* SP alignment fault if SP is not aligned to a 16 byte boundary. */ +.set .L_SCTLR_ELx_SA, 0x1 << 3 +/* Stage 1 data access cacheability is unaffected. */ +.set .L_SCTLR_ELx_C, 0x1 << 2 +/* EL0 and EL1 stage 1 MMU enabled. */ +.set .L_SCTLR_ELx_M, 0x1 << 0 +/* Privileged Access Never is unchanged on taking an exception to EL1. */ +.set .L_SCTLR_EL1_SPAN, 0x1 << 23 +/* SETEND instruction disabled at EL0 in aarch32 mode. */ +.set .L_SCTLR_EL1_SED, 0x1 << 8 +/* Various IT instructions are disabled at EL0 in aarch32 mode. */ +.set .L_SCTLR_EL1_ITD, 0x1 << 7 +.set .L_SCTLR_EL1_RES1, (0x1 << 11) | (0x1 << 20) | (0x1 << 22) | (0x1 << 28) | (0x1 << 29) +.set .Lsctlrval, .L_SCTLR_ELx_M | .L_SCTLR_ELx_C | .L_SCTLR_ELx_SA | .L_SCTLR_EL1_ITD | .L_SCTLR_EL1_SED +.set .Lsctlrval, .Lsctlrval | .L_SCTLR_ELx_I | .L_SCTLR_EL1_SPAN | .L_SCTLR_EL1_RES1 + +/** + * This is a generic entry point for an image. It carries out the operations required to prepare the + * loaded image to be run. Specifically, it zeroes the bss section using registers x25 and above, + * 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. + */ +.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 + + 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 + + msr tcr_el1, x30 + + 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 + + /* + * 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 + + /* 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 + + /* Set up exception vector. */ + adr x30, vector_table_el1 + msr vbar_el1, x30 + + /* Call into Rust code. */ + bl main + + /* Loop forever waiting for interrupts. */ +2: wfi + b 2b diff --git a/src/exercises/bare-metal/rtc/exceptions.S b/src/exercises/bare-metal/rtc/exceptions.S new file mode 100644 index 00000000..434b0503 --- /dev/null +++ b/src/exercises/bare-metal/rtc/exceptions.S @@ -0,0 +1,178 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Saves the volatile registers onto the stack. This currently takes 14 + * instructions, so it can be used in exception handlers with 18 instructions + * left. + * + * On return, x0 and x1 are initialised to elr_el2 and spsr_el2 respectively, + * which can be used as the first and second arguments of a subsequent call. + */ +.macro save_volatile_to_stack + /* Reserve stack space and save registers x0-x18, x29 & x30. */ + stp x0, x1, [sp, #-(8 * 24)]! + stp x2, x3, [sp, #8 * 2] + stp x4, x5, [sp, #8 * 4] + stp x6, x7, [sp, #8 * 6] + stp x8, x9, [sp, #8 * 8] + stp x10, x11, [sp, #8 * 10] + stp x12, x13, [sp, #8 * 12] + stp x14, x15, [sp, #8 * 14] + stp x16, x17, [sp, #8 * 16] + str x18, [sp, #8 * 18] + stp x29, x30, [sp, #8 * 20] + + /* + * Save elr_el1 & spsr_el1. This such that we can take nested exception + * and still be able to unwind. + */ + mrs x0, elr_el1 + mrs x1, spsr_el1 + stp x0, x1, [sp, #8 * 22] +.endm + +/** + * Restores the volatile registers from the stack. This currently takes 14 + * instructions, so it can be used in exception handlers while still leaving 18 + * instructions left; if paired with save_volatile_to_stack, there are 4 + * instructions to spare. + */ +.macro restore_volatile_from_stack + /* Restore registers x2-x18, x29 & x30. */ + ldp x2, x3, [sp, #8 * 2] + ldp x4, x5, [sp, #8 * 4] + ldp x6, x7, [sp, #8 * 6] + ldp x8, x9, [sp, #8 * 8] + ldp x10, x11, [sp, #8 * 10] + ldp x12, x13, [sp, #8 * 12] + ldp x14, x15, [sp, #8 * 14] + ldp x16, x17, [sp, #8 * 16] + ldr x18, [sp, #8 * 18] + ldp x29, x30, [sp, #8 * 20] + + /* Restore registers elr_el1 & spsr_el1, using x0 & x1 as scratch. */ + ldp x0, x1, [sp, #8 * 22] + msr elr_el1, x0 + msr spsr_el1, x1 + + /* Restore x0 & x1, and release stack space. */ + ldp x0, x1, [sp], #8 * 24 +.endm + +/** + * This is a generic handler for exceptions taken at the current EL while using + * SP0. It behaves similarly to the SPx case by first switching to SPx, doing + * the work, then switching back to SP0 before returning. + * + * Switching to SPx and calling the Rust handler takes 16 instructions. To + * restore and return we need an additional 16 instructions, so we can implement + * the whole handler within the allotted 32 instructions. + */ +.macro current_exception_sp0 handler:req + msr spsel, #1 + save_volatile_to_stack + bl \handler + restore_volatile_from_stack + msr spsel, #0 + eret +.endm + +/** + * This is a generic handler for exceptions taken at the current EL while using + * SPx. It saves volatile registers, calls the Rust handler, restores volatile + * registers, then returns. + * + * This also works for exceptions taken from EL0, if we don't care about + * non-volatile registers. + * + * Saving state and jumping to the Rust handler takes 15 instructions, and + * restoring and returning also takes 15 instructions, so we can fit the whole + * handler in 30 instructions, under the limit of 32. + */ +.macro current_exception_spx handler:req + save_volatile_to_stack + bl \handler + restore_volatile_from_stack + eret +.endm + +.section .text.vector_table_el1, "ax" +.global vector_table_el1 +.balign 0x800 +vector_table_el1: +sync_cur_sp0: + current_exception_sp0 sync_exception_current + +.balign 0x80 +irq_cur_sp0: + current_exception_sp0 irq_current + +.balign 0x80 +fiq_cur_sp0: + current_exception_sp0 fiq_current + +.balign 0x80 +serr_cur_sp0: + current_exception_sp0 serr_current + +.balign 0x80 +sync_cur_spx: + current_exception_spx sync_exception_current + +.balign 0x80 +irq_cur_spx: + current_exception_spx irq_current + +.balign 0x80 +fiq_cur_spx: + current_exception_spx fiq_current + +.balign 0x80 +serr_cur_spx: + current_exception_spx serr_current + +.balign 0x80 +sync_lower_64: + current_exception_spx sync_lower + +.balign 0x80 +irq_lower_64: + current_exception_spx irq_lower + +.balign 0x80 +fiq_lower_64: + current_exception_spx fiq_lower + +.balign 0x80 +serr_lower_64: + current_exception_spx serr_lower + +.balign 0x80 +sync_lower_32: + current_exception_spx sync_lower + +.balign 0x80 +irq_lower_32: + current_exception_spx irq_lower + +.balign 0x80 +fiq_lower_32: + current_exception_spx fiq_lower + +.balign 0x80 +serr_lower_32: + current_exception_spx serr_lower diff --git a/src/exercises/bare-metal/rtc/idmap.S b/src/exercises/bare-metal/rtc/idmap.S new file mode 100644 index 00000000..a79f3af2 --- /dev/null +++ b/src/exercises/bare-metal/rtc/idmap.S @@ -0,0 +1,42 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.set .L_TT_TYPE_BLOCK, 0x1 +.set .L_TT_TYPE_PAGE, 0x3 +.set .L_TT_TYPE_TABLE, 0x3 + +/* Access flag. */ +.set .L_TT_AF, 0x1 << 10 +/* Not global. */ +.set .L_TT_NG, 0x1 << 11 +.set .L_TT_XN, 0x3 << 53 + +.set .L_TT_MT_DEV, 0x0 << 2 // MAIR #0 (DEV_nGnRE) +.set .L_TT_MT_MEM, (0x1 << 2) | (0x3 << 8) // MAIR #1 (MEM_WBWA), inner shareable + +.set .L_BLOCK_DEV, .L_TT_TYPE_BLOCK | .L_TT_MT_DEV | .L_TT_AF | .L_TT_XN +.set .L_BLOCK_MEM, .L_TT_TYPE_BLOCK | .L_TT_MT_MEM | .L_TT_AF | .L_TT_NG + +.section ".rodata.idmap", "a", %progbits +.global idmap +.align 12 +idmap: + /* level 1 */ + .quad .L_BLOCK_DEV | 0x0 // 1 GiB of device mappings + .quad .L_BLOCK_MEM | 0x40000000 // 1 GiB of DRAM + .fill 254, 8, 0x0 // 254 GiB of unmapped VA space + .quad .L_BLOCK_DEV | 0x4000000000 // 1 GiB of device mappings + .fill 255, 8, 0x0 // 255 GiB of remaining VA space diff --git a/src/exercises/bare-metal/rtc/image.ld b/src/exercises/bare-metal/rtc/image.ld new file mode 100644 index 00000000..59c77527 --- /dev/null +++ b/src/exercises/bare-metal/rtc/image.ld @@ -0,0 +1,104 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Code will start running at this symbol which is placed at the start of the + * image. + */ +ENTRY(entry) + +MEMORY +{ + image : ORIGIN = 0x40080000, LENGTH = 2M +} + +SECTIONS +{ + /* + * Collect together the code. + */ + .init : ALIGN(4096) { + text_begin = .; + *(.init.entry) + *(.init.*) + } >image + .text : { + *(.text.*) + } >image + text_end = .; + + /* + * Collect together read-only data. + */ + .rodata : ALIGN(4096) { + rodata_begin = .; + *(.rodata.*) + } >image + .got : { + *(.got) + } >image + rodata_end = .; + + /* + * Collect together the read-write data including .bss at the end which + * will be zero'd by the entry code. + */ + .data : ALIGN(4096) { + data_begin = .; + *(.data.*) + /* + * The entry point code assumes that .data is a multiple of 32 + * bytes long. + */ + . = ALIGN(32); + data_end = .; + } >image + + /* Everything beyond this point will not be included in the binary. */ + bin_end = .; + + /* The entry point code assumes that .bss is 16-byte aligned. */ + .bss : ALIGN(16) { + bss_begin = .; + *(.bss.*) + *(COMMON) + . = ALIGN(16); + bss_end = .; + } >image + + .stack (NOLOAD) : ALIGN(4096) { + boot_stack_begin = .; + . += 40 * 4096; + . = ALIGN(4096); + boot_stack_end = .; + } >image + + . = ALIGN(4K); + PROVIDE(dma_region = .); + + /* + * Remove unused sections from the image. + */ + /DISCARD/ : { + /* The image loads itself so doesn't need these sections. */ + *(.gnu.hash) + *(.hash) + *(.interp) + *(.eh_frame_hdr) + *(.eh_frame) + *(.note.gnu.build-id) + } +} diff --git a/src/exercises/bare-metal/rtc/src/exceptions.rs b/src/exercises/bare-metal/rtc/src/exceptions.rs new file mode 100644 index 00000000..18d7b846 --- /dev/null +++ b/src/exercises/bare-metal/rtc/src/exceptions.rs @@ -0,0 +1,64 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::error; +use psci::system_off; + +#[no_mangle] +extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) { + error!("sync_exception_current"); + system_off().unwrap(); +} + +#[no_mangle] +extern "C" fn irq_current(_elr: u64, _spsr: u64) { + error!("irq_current"); + system_off().unwrap(); +} + +#[no_mangle] +extern "C" fn fiq_current(_elr: u64, _spsr: u64) { + error!("fiq_current"); + system_off().unwrap(); +} + +#[no_mangle] +extern "C" fn serr_current(_elr: u64, _spsr: u64) { + error!("serr_current"); + system_off().unwrap(); +} + +#[no_mangle] +extern "C" fn sync_lower(_elr: u64, _spsr: u64) { + error!("sync_lower"); + system_off().unwrap(); +} + +#[no_mangle] +extern "C" fn irq_lower(_elr: u64, _spsr: u64) { + error!("irq_lower"); + system_off().unwrap(); +} + +#[no_mangle] +extern "C" fn fiq_lower(_elr: u64, _spsr: u64) { + error!("fiq_lower"); + system_off().unwrap(); +} + +#[no_mangle] +extern "C" fn serr_lower(_elr: u64, _spsr: u64) { + error!("serr_lower"); + system_off().unwrap(); +} diff --git a/src/exercises/bare-metal/rtc/src/logger.rs b/src/exercises/bare-metal/rtc/src/logger.rs new file mode 100644 index 00000000..81519493 --- /dev/null +++ b/src/exercises/bare-metal/rtc/src/logger.rs @@ -0,0 +1,54 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: main +use crate::pl011::Uart; +use core::fmt::Write; +use log::{LevelFilter, Log, Metadata, Record, SetLoggerError}; +use spin::mutex::SpinMutex; + +static LOGGER: Logger = Logger { + uart: SpinMutex::new(None), +}; + +struct Logger { + uart: SpinMutex>, +} + +impl Log for Logger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + writeln!( + self.uart.lock().as_mut().unwrap(), + "[{}] {}", + record.level(), + record.args() + ) + .unwrap(); + } + + fn flush(&self) {} +} + +/// Initialises UART logger. +pub fn init(uart: Uart, max_level: LevelFilter) -> Result<(), SetLoggerError> { + LOGGER.uart.lock().replace(uart); + + log::set_logger(&LOGGER)?; + log::set_max_level(max_level); + Ok(()) +} diff --git a/src/exercises/bare-metal/rtc/src/main.rs b/src/exercises/bare-metal/rtc/src/main.rs new file mode 100644 index 00000000..ad40a92a --- /dev/null +++ b/src/exercises/bare-metal/rtc/src/main.rs @@ -0,0 +1,67 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ANCHOR: top +#![no_main] +#![no_std] + +mod exceptions; +mod logger; +mod pl011; +// ANCHOR_END: top +mod pl031; + +use crate::pl031::Rtc; +use chrono::{TimeZone, Utc}; +// ANCHOR: imports +use crate::pl011::Uart; +use core::panic::PanicInfo; +use log::{error, info, LevelFilter}; +use psci::system_off; + +/// Base address of the primary PL011 UART. +pub const PL011_BASE_ADDRESS: usize = 0x900_0000; +// ANCHOR_END: imports + +/// Base address of the PL031 RTC. +pub const PL031_BASE_ADDRESS: usize = 0x901_0000; + +// ANCHOR: main +#[no_mangle] +extern "C" fn main(x0: u64, x1: u64, x2: u64, x3: u64) { + // Safe because `PL011_BASE_ADDRESS` is the base address of a PL011 device, + // and nothing else accesses that address range. + let uart = unsafe { Uart::new(PL011_BASE_ADDRESS as *mut u32) }; + logger::init(uart, LevelFilter::Trace).unwrap(); + + info!("main({:#x}, {:#x}, {:#x}, {:#x})", x0, x1, x2, x3); + // ANCHOR_END: main + + // Safe because `PL031_BASE_ADDRESS` is the base address of a PL031 device, + // and nothing else accesses that address range. + let rtc = unsafe { Rtc::new(PL031_BASE_ADDRESS as *mut u32) }; + let time = Utc.timestamp_opt(rtc.read().into(), 0).unwrap(); + info!("RTC: {}", time); + + // ANCHOR: main_end + system_off().unwrap(); +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + error!("{}", info); + system_off().unwrap(); + loop {} +} +// ANCHOR_END: main_end diff --git a/src/exercises/bare-metal/rtc/src/pl011.rs b/src/exercises/bare-metal/rtc/src/pl011.rs new file mode 100644 index 00000000..1ca80f87 --- /dev/null +++ b/src/exercises/bare-metal/rtc/src/pl011.rs @@ -0,0 +1,168 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::{ + fmt::{self, Write}, + ptr::{addr_of, addr_of_mut}, +}; + +// ANCHOR: Flags +use bitflags::bitflags; + +bitflags! { + /// Flags from the UART flag register. + #[repr(transparent)] + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + struct Flags: u16 { + /// Clear to send. + const CTS = 1 << 0; + /// Data set ready. + const DSR = 1 << 1; + /// Data carrier detect. + const DCD = 1 << 2; + /// UART busy transmitting data. + const BUSY = 1 << 3; + /// Receive FIFO is empty. + const RXFE = 1 << 4; + /// Transmit FIFO is full. + const TXFF = 1 << 5; + /// Receive FIFO is full. + const RXFF = 1 << 6; + /// Transmit FIFO is empty. + const TXFE = 1 << 7; + /// Ring indicator. + const RI = 1 << 8; + } +} +// ANCHOR_END: Flags + +bitflags! { + /// Flags from the UART Receive Status Register / Error Clear Register. + #[repr(transparent)] + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + struct ReceiveStatus: u16 { + /// Framing error. + const FE = 1 << 0; + /// Parity error. + const PE = 1 << 1; + /// Break error. + const BE = 1 << 2; + /// Overrun error. + const OE = 1 << 3; + } +} + +// ANCHOR: Registers +#[repr(C, align(4))] +struct Registers { + dr: u16, + _reserved0: [u8; 2], + rsr: ReceiveStatus, + _reserved1: [u8; 19], + fr: Flags, + _reserved2: [u8; 6], + ilpr: u8, + _reserved3: [u8; 3], + ibrd: u16, + _reserved4: [u8; 2], + fbrd: u8, + _reserved5: [u8; 3], + lcr_h: u8, + _reserved6: [u8; 3], + cr: u16, + _reserved7: [u8; 3], + ifls: u8, + _reserved8: [u8; 3], + imsc: u16, + _reserved9: [u8; 2], + ris: u16, + _reserved10: [u8; 2], + mis: u16, + _reserved11: [u8; 2], + icr: u16, + _reserved12: [u8; 2], + dmacr: u8, + _reserved13: [u8; 3], +} +// ANCHOR_END: Registers + +// ANCHOR: Uart +/// Driver for a PL011 UART. +#[derive(Debug)] +pub struct Uart { + registers: *mut Registers, +} + +impl Uart { + /// Constructs a new instance of the UART driver for a PL011 device at the + /// given base address. + /// + /// # Safety + /// + /// The given base address must point to the MMIO control registers of a + /// PL011 device, which must be mapped into the address space of the process + /// as device memory and not have any other aliases. + pub unsafe fn new(base_address: *mut u32) -> Self { + Self { + registers: base_address as *mut Registers, + } + } + + /// Writes a single byte to the UART. + pub fn write_byte(&self, byte: u8) { + // Wait until there is room in the TX buffer. + while self.read_flag_register().contains(Flags::TXFF) {} + + // Safe because we know that self.registers points to the control + // registers of a PL011 device which is appropriately mapped. + unsafe { + // Write to the TX buffer. + addr_of_mut!((*self.registers).dr).write_volatile(byte.into()); + } + + // Wait until the UART is no longer busy. + while self.read_flag_register().contains(Flags::BUSY) {} + } + + /// Reads and returns a pending byte, or `None` if nothing has been received. + pub fn read_byte(&self) -> Option { + if self.read_flag_register().contains(Flags::RXFE) { + None + } else { + let data = unsafe { addr_of!((*self.registers).dr).read_volatile() }; + // TODO: Check for error conditions in bits 8-11. + Some(data as u8) + } + } + + fn read_flag_register(&self) -> Flags { + // Safe because we know that self.registers points to the control + // registers of a PL011 device which is appropriately mapped. + unsafe { addr_of!((*self.registers).fr).read_volatile() } + } +} +// ANCHOR_END: Uart + +impl Write for Uart { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.as_bytes() { + self.write_byte(*c); + } + Ok(()) + } +} + +// Safe because it just contains a pointer to device memory, which can be +// accessed from any context. +unsafe impl Send for Uart {} diff --git a/src/exercises/bare-metal/rtc/src/pl031.rs b/src/exercises/bare-metal/rtc/src/pl031.rs new file mode 100644 index 00000000..7b8d13da --- /dev/null +++ b/src/exercises/bare-metal/rtc/src/pl031.rs @@ -0,0 +1,70 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::ptr::addr_of; + +#[repr(C, align(4))] +struct Registers { + /// Data register + dr: u32, + /// Match register + mr: u32, + /// Load register + lr: u32, + /// Control register + cr: u8, + _reserved0: [u8; 3], + /// Interrupt Mask Set or Clear register + imsc: u8, + _reserved1: [u8; 3], + /// Raw Interrupt Status + ris: u8, + _reserved2: [u8; 3], + /// Masked Interrupt Status + mis: u8, + _reserved3: [u8; 3], + /// Interrupt Clear Register + icr: u8, + _reserved4: [u8; 3], +} + +#[derive(Debug)] +pub struct Rtc { + registers: *mut Registers, +} + +impl Rtc { + /// Constructs a new instance of the RTC driver for a PL031 device at the + /// given base address. + /// + /// # Safety + /// + /// The given base address must point to the MMIO control registers of a + /// PL031 device, which must be mapped into the address space of the process + /// as device memory and not have any other aliases. + pub unsafe fn new(base_address: *mut u32) -> Self { + Self { + registers: base_address as *mut Registers, + } + } + + /// Reads the current RTC value. + pub fn read(&self) -> u32 { + unsafe { addr_of!((*self.registers).dr).read_volatile() } + } +} + +// Safe because it just contains a pointer to device memory, which can be +// accessed from any context. +unsafe impl Send for Rtc {} diff --git a/src/exercises/bare-metal/solutions-afternoon.md b/src/exercises/bare-metal/solutions-afternoon.md new file mode 100644 index 00000000..e57863f2 --- /dev/null +++ b/src/exercises/bare-metal/solutions-afternoon.md @@ -0,0 +1,9 @@ +# Bare Metal Rust Afternoon + +## RTC driver + +([back to exercise](rtc.md)) + +```rust,compile_fail +{{#include rtc/src/main.rs}} +```