diff mbox series

[RFC,v0,1/6] Implement a bare metal Rust runtime on top of QEMU's mach-virt

Message ID 20220314082644.3436071-2-ardb@kernel.org
State New
Headers show
Series Minimal Linux/arm64 VM firmware (written in Rust) | expand

Commit Message

Ard Biesheuvel March 14, 2022, 8:26 a.m. UTC
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 mbox series

Patch

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