From: ardb@kernel.org
To: linux-efi@vger.kernel.org
Cc: Ard Biesheuvel <ardb@google.com>, Marc Zyngier <maz@kernel.org>,
Will Deacon <will@kernel.org>,
Quentin Perret <qperret@google.com>,
David Brazdil <dbrazdil@google.com>,
Fuad Tabba <tabba@google.com>, Kees Cook <keescook@chromium.org>
Subject: [RFC PATCH v0 1/6] Implement a bare metal Rust runtime on top of QEMU's mach-virt
Date: Mon, 14 Mar 2022 09:26:39 +0100 [thread overview]
Message-ID: <20220314082644.3436071-2-ardb@kernel.org> (raw)
In-Reply-To: <20220314082644.3436071-1-ardb@kernel.org>
From: Ard Biesheuvel <ardb@google.com>
Implement the startup sequence to set up a runtime for Rust code, and
populate it with a logger wired to the QEMU emulated PL011 UART, and a
heap allocator.
The .text and .rodata parts are in emulated NOR flash, and the
executable pieces execute in place. The .data and .bss sections as well
as the stack are disjoint from the flash image, and reside in DRAM. The
assembler startup code sets up the stack pointer and initializes the
writable sections.
The startup code programs the MMU with a set of translation tables in
NOR flash, which describe a 2M R-X region in flash, a 2 M R-- region
covering the base of DRAM (which is where QEMU's mach-virt puts the
device tree), and 2M RW- region used for the stack, .data/.bss and the
heap.
---
.cargo/config | 5 +
.gitignore | 2 +
Cargo.lock | 73 ++++++++++++
Cargo.toml | 10 ++
efilite.lds | 61 ++++++++++
src/console.rs | 57 +++++++++
src/cstring.rs | 9 ++
src/head.S | 121 ++++++++++++++++++++
src/main.rs | 49 +++++++-
src/ttable.S | 37 ++++++
10 files changed, 422 insertions(+), 2 deletions(-)
diff --git a/.cargo/config b/.cargo/config
new file mode 100644
index 000000000000..584568a162de
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,5 @@
+[target.aarch64-unknown-linux-gnu]
+rustflags = ["-C", "relocation-model=static", "-C", "link-arg=-Wl,-Tefilite.lds,--orphan-handling=warn", "-C", "link-arg=-nostartfiles"]
+
+[build]
+target = "aarch64-unknown-linux-gnu"
diff --git a/.gitignore b/.gitignore
index ea8c4bf7f35f..c5a7561a896d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
/target
+*.bin
+.*.swp
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 000000000000..617acc9c6086
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,73 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "efilite"
+version = "0.1.0"
+dependencies = [
+ "linked_list_allocator",
+ "log",
+ "mmio",
+ "rlibc",
+]
+
+[[package]]
+name = "linked_list_allocator"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "549ce1740e46b291953c4340adcd74c59bcf4308f4cac050fd33ba91b7168f4a"
+dependencies = [
+ "spinning_top",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "mmio"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee857bfd0b37394f3507d78ee7bd4b712a2179a2ce50e47d36bbb481672f5408"
+
+[[package]]
+name = "rlibc"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "spinning_top"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75adad84ee84b521fb2cca2d4fd0f1dab1d8d026bda3c5bea4ca63b5f9f9293c"
+dependencies = [
+ "lock_api",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 07cf0efb7baf..9bc2b39f6e9b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,3 +6,13 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+rlibc = "1.0.0"
+linked_list_allocator = "0.9.1"
+log = "0.4.14"
+mmio = "2.1.0"
+
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
diff --git a/efilite.lds b/efilite.lds
new file mode 100644
index 000000000000..0632cbaf8e4e
--- /dev/null
+++ b/efilite.lds
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+MEMORY
+{
+ flash : ORIGIN = 0, LENGTH = 2M
+ ram : ORIGIN = 0x40000000, LENGTH = 4M
+}
+
+PROVIDE(_init_base = 0x40000000);
+PROVIDE(_init_size = 0x400000);
+
+ENTRY(__init)
+
+SECTIONS
+{
+ .text : {
+ *(.head)
+ *(.text .text*)
+ *(.rodata .rodata*)
+ *(.got .got.plt)
+ } >flash
+
+ /*
+ * QEMU passes the DT blob by storing it at the base of DRAM
+ * before starting the guest
+ */
+ .dtb (NOLOAD) : {
+ _dtb = .;
+ . += 0x200000;
+ } >ram
+
+ /*
+ * put the stack first so we will notice if we overrun and
+ * hit the R/O mapping of the DT blob
+ */
+ .stack (NOLOAD) : {
+ . += 0x4000;
+ _stack_end = .;
+ } >ram
+
+ .data : ALIGN(32) {
+ _data = .;
+ *(.data .data*)
+ . = ALIGN(32);
+ _edata = .;
+ } >ram AT >flash
+
+ data_lma = LOADADDR(.data);
+
+ .bss : ALIGN (32) {
+ _bss_start = .;
+ *(.bss .bss*)
+ . = ALIGN(32);
+ _bss_end = .;
+ _end = .;
+ } >ram
+
+ /DISCARD/ : {
+ *(.note*)
+ }
+}
diff --git a/src/console.rs b/src/console.rs
new file mode 100644
index 000000000000..3841c6cb2dd0
--- /dev/null
+++ b/src/console.rs
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+use core::fmt::Write;
+use log::{Level, Metadata, Record};
+use mmio::{Allow, Deny, VolBox};
+
+pub struct QemuSerialConsole {
+ base: u64,
+}
+
+struct QemuSerialConsoleWriter<'a> {
+ console: &'a QemuSerialConsole,
+}
+
+impl QemuSerialConsole {
+ fn puts(&self, s: &str) {
+ //
+ // This is technically racy, as nothing is preventing concurrent accesses to the UART if we
+ // model it this way. However, this is a debug tool only, and we never read back the
+ // register value so any races cannot have any observeable side effects to the program
+ // itself.
+ //
+ let mut out = unsafe { VolBox::<u32, Deny, Allow>::new(self.base as *mut u32) };
+
+ for b in s.as_bytes().iter() {
+ if *b == b'\n' {
+ out.write(b'\r' as u32);
+ }
+ out.write(*b as u32)
+ }
+ }
+}
+
+impl Write for QemuSerialConsoleWriter<'_> {
+ fn write_str(&mut self, s: &str) -> core::fmt::Result {
+ self.console.puts(s);
+ Ok(())
+ }
+}
+
+impl log::Log for QemuSerialConsole {
+ fn enabled(&self, metadata: &Metadata) -> bool {
+ metadata.level() <= Level::Info
+ }
+
+ fn log(&self, record: &Record) {
+ if self.enabled(record.metadata()) {
+ let mut out = QemuSerialConsoleWriter { console: &self };
+ write!(&mut out, "{} - {}", record.level(), record.args()).unwrap();
+ }
+ }
+
+ fn flush(&self) {}
+}
+
+// The primary UART of QEMU's mach-virt
+pub static OUT: QemuSerialConsole = QemuSerialConsole { base: 0x900_0000 };
diff --git a/src/cstring.rs b/src/cstring.rs
new file mode 100644
index 000000000000..b6a5c4308067
--- /dev/null
+++ b/src/cstring.rs
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+extern crate rlibc;
+use rlibc::memcmp;
+
+#[no_mangle]
+pub unsafe extern "C" fn bcmp(s1: *const u8, s2: *const u8, len: usize) -> i32 {
+ memcmp(s1, s2, len)
+}
diff --git a/src/head.S b/src/head.S
new file mode 100644
index 000000000000..b82dca4325fa
--- /dev/null
+++ b/src/head.S
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+ .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_g2:\imm
+ movk \reg, :abs_g1_nc:\imm
+ movk \reg, :abs_g0_nc:\imm
+ .endm
+
+ .section ".head", "ax", %progbits
+ .globl __init
+__init:
+ mov_i x0, mairval
+ mov_i x1, tcrval
+ adrp x2, idmap
+ mov_i x3, sctlrval
+ mov_i x4, cpacrval
+ adr_l x5, vector_table
+
+ msr mair_el1, x0 // set up the 1:1 mapping
+ msr tcr_el1, x1
+ msr ttbr0_el1, x2
+ isb
+
+ tlbi vmalle1 // invalidate any cached translations
+ ic iallu // invalidate the I-cache
+ dsb nsh
+ isb
+
+ msr sctlr_el1, x3 // enable MMU and caches
+ msr cpacr_el1, x4 // enable FP/SIMD
+ msr vbar_el1, x5 // enable exception handling
+ isb
+
+ adr_l x0, _data // initialize the .data section
+ adr_l x1, _edata
+ adr_l x2, data_lma
+0: cmp x0, x1
+ b.ge 1f
+ ldp q0, q1, [x2], #32
+ stp q0, q1, [x0], #32
+ b 0b
+
+1: adr_l x0, _bss_start // wipe the .bss section
+ adr_l x1, _bss_end
+ movi v0.16b, #0
+2: cmp x0, x1
+ b.ge 3f
+ stp q0, q0, [x0], #32
+ b 2b
+
+3: mov x29, xzr // initialize the frame pointer
+ adrp x0, _stack_end
+ mov sp, x0
+ adrp x0, _init_base // initial DRAM base address
+ movz x1, :abs_g1:_init_size // initially mapped area
+ adr_l x2, _end // statically allocated by program
+ sub x2, x2, x0
+ bl efilite_main
+
+4: mov_i x0, 0x84000008 // PSCI SYSTEM OFF
+ hvc #0
+ wfi
+ b 4b
+
+ .macro vector_entry
+ adrp x0, idmap
+ adrp x1, _stack_end
+ msr ttbr0_el1, x0 // switch back to the initial ID map
+ isb
+ mov sp, x1 // reset the stack pointer
+ mov x29, xzr
+ mrs x0, esr_el1
+ mrs x1, elr_el1
+ mrs x2, far_el1
+ bl handle_exception
+ .endm
+
+ .section ".text", "ax", %progbits
+ .align 11
+vector_table:
+ vector_entry
+ .org vector_table + 0x200
+ vector_entry
+ .org vector_table + 0x400
+ vector_entry
+ .org vector_table + 0x600
+ vector_entry
+
+ .set .L_MAIR_DEV_nGnRE, 0x04
+ .set .L_MAIR_MEM_WBWA, 0xff
+ .set mairval, .L_MAIR_DEV_nGnRE | (.L_MAIR_MEM_WBWA << 8)
+
+ .set .L_TCR_TG0_4KB, 0x0 << 14
+ .set .L_TCR_TG1_4KB, 0x2 << 30
+ .set .L_TCR_IPS_64GB, 0x1 << 32
+ .set .L_TCR_EPD1, 0x1 << 23
+ .set .L_TCR_SH_INNER, 0x3 << 12
+ .set .L_TCR_RGN_OWB, 0x1 << 10
+ .set .L_TCR_RGN_IWB, 0x1 << 8
+ .set tcrval, .L_TCR_TG0_4KB | .L_TCR_TG1_4KB | .L_TCR_EPD1 | .L_TCR_IPS_64GB | .L_TCR_RGN_OWB
+ .set tcrval, tcrval | .L_TCR_RGN_IWB | .L_TCR_SH_INNER | (64 - 36) // TCR_T0SZ
+
+ .set .L_SCTLR_ELx_I, 0x1 << 12
+ .set .L_SCTLR_ELx_SA, 0x1 << 3
+ .set .L_SCTLR_ELx_C, 0x1 << 2
+ .set .L_SCTLR_ELx_M, 0x1 << 0
+ .set .L_SCTLR_EL1_SPAN, 0x1 << 23
+ .set .L_SCTLR_EL1_WXN, 0x1 << 19
+ .set .L_SCTLR_EL1_SED, 0x1 << 8
+ .set .L_SCTLR_EL1_ITD, 0x1 << 7
+ .set .L_SCTLR_EL1_RES1, (0x1 << 11) | (0x1 << 20) | (0x1 << 22) | (0x1 << 28) | (0x1 << 29)
+ .set sctlrval, .L_SCTLR_ELx_M | .L_SCTLR_ELx_C | .L_SCTLR_ELx_SA | .L_SCTLR_EL1_ITD | .L_SCTLR_EL1_SED
+ .set sctlrval, sctlrval | .L_SCTLR_ELx_I | .L_SCTLR_EL1_WXN | .L_SCTLR_EL1_SPAN | .L_SCTLR_EL1_RES1
+
+ .set .L_CPACR_EL1_FPEN, 0x3 << 20
+ .set cpacrval, .L_CPACR_EL1_FPEN
diff --git a/src/main.rs b/src/main.rs
index e7a11a969c03..698e9c5724bf 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,48 @@
-fn main() {
- println!("Hello, world!");
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#![no_std]
+#![no_main]
+
+mod console;
+mod cstring;
+
+use core::{arch::global_asm, panic::PanicInfo};
+use linked_list_allocator::LockedHeap;
+use log::{error, info};
+
+#[global_allocator]
+pub static ALLOCATOR: LockedHeap = LockedHeap::empty();
+
+#[no_mangle]
+extern "C" fn efilite_main(base: usize, mapped: usize, used: usize) {
+ #[cfg(debug_assertions)]
+ log::set_logger(&console::OUT)
+ .map(|()| log::set_max_level(log::LevelFilter::Info))
+ .unwrap();
+
+ // Give the mapped but unused memory to the heap allocator
+ info!(
+ "Heap allocator with {} KB of memory\n",
+ (mapped - used) / 1024
+ );
+ unsafe {
+ ALLOCATOR.lock().init(base + used, mapped - used);
+ }
}
+
+#[no_mangle]
+extern "C" fn handle_exception(esr: u64, elr: u64, far: u64) -> ! {
+ panic!(
+ "Unhandled exception: ESR = 0x{:X}, ELR = 0x{:X}, FAR = 0x{:X}.",
+ esr, elr, far
+ );
+}
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ error!("{}\n", info);
+ loop {}
+}
+
+global_asm!(include_str!("head.S"));
+global_asm!(include_str!("ttable.S"));
diff --git a/src/ttable.S b/src/ttable.S
new file mode 100644
index 000000000000..b6fdadb6dbdc
--- /dev/null
+++ b/src/ttable.S
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+ .set .L_TT_TYPE_BLOCK, 0x1
+ .set .L_TT_TYPE_TABLE, 0x3
+
+ .set .L_TT_AF, 0x1 << 10
+ .set .L_TT_NG, 0x3 << 11
+ .set .L_TT_RO, 0x2 << 6
+ .set .L_TT_XN, 0x3 << 53
+
+ .set .L_TT_MT_DEV, 0x0 << 2 // MAIR #0
+ .set .L_TT_MT_MEM, (0x1 << 2) | (0x3 << 8) // MAIR #1
+
+ .set BLOCK_XIP, .L_TT_TYPE_BLOCK | .L_TT_MT_MEM | .L_TT_AF | .L_TT_RO
+ .set BLOCK_DEV, .L_TT_TYPE_BLOCK | .L_TT_MT_DEV | .L_TT_AF | .L_TT_XN
+ .set BLOCK_MEM, .L_TT_TYPE_BLOCK | .L_TT_MT_MEM | .L_TT_AF | .L_TT_XN | .L_TT_NG
+
+ .section ".rodata", "a", %progbits
+ .align 12
+ /* level 2 */
+0: .quad BLOCK_XIP // 2 MB of R-X flash
+ .fill 63, 8, 0x0 // 126 MB of unused flash
+ .set idx, 64
+ .rept 448
+ .quad BLOCK_DEV | (idx << 21) // 896 MB of RW- device mappings
+ .set idx, idx + 1
+ .endr
+1: .quad BLOCK_XIP | 0x40000000 // DT provided by VMM
+ .quad BLOCK_MEM | 0x40200000 // 2 MB of DRAM
+ .fill 510, 8, 0x0
+
+ .globl idmap
+idmap:
+ /* level 1 */
+ .quad 0b + .L_TT_TYPE_TABLE // flash and device mappings
+ .quad 1b + .L_TT_TYPE_TABLE // up to 1 GB of DRAM
+ .fill 62, 8, 0x0 // 62 GB of remaining VA space
--
2.30.2
next prev parent reply other threads:[~2022-03-14 8:27 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-03-14 8:26 [RFC PATCH v0 0/6] Minimal Linux/arm64 VM firmware (written in Rust) ardb
2022-03-14 8:26 ` ardb [this message]
2022-03-14 8:26 ` [RFC PATCH v0 2/6] Add DTB processing ardb
2022-03-14 8:26 ` [RFC PATCH v0 3/6] Add paging code to manage the full ID map ardb
2022-03-14 8:26 ` [RFC PATCH v0 4/6] Discover QEMU fwcfg device and use it to load the kernel ardb
2022-03-14 8:26 ` [RFC PATCH v0 5/6] Remap code section of loaded kernel and boot it ardb
2022-03-14 8:26 ` [RFC PATCH v0 6/6] Temporarily pass the kaslr seed via register X1 ardb
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220314082644.3436071-2-ardb@kernel.org \
--to=ardb@kernel.org \
--cc=ardb@google.com \
--cc=dbrazdil@google.com \
--cc=keescook@chromium.org \
--cc=linux-efi@vger.kernel.org \
--cc=maz@kernel.org \
--cc=qperret@google.com \
--cc=tabba@google.com \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).