From 6b0f825ee97b16e51b173d8cc9e22667cb6bed94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Thu, 10 Aug 2023 16:19:54 +0200 Subject: [PATCH 1/3] refactor: make `loader_main` public MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Kröning --- src/none.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/none.rs b/src/none.rs index 1b5e8cf4..37b4cc6f 100644 --- a/src/none.rs +++ b/src/none.rs @@ -15,7 +15,7 @@ extern "C" { /// Entry Point of the BIOS Loader /// (called from entry.asm or entry.rs) #[no_mangle] -unsafe extern "C" fn loader_main() -> ! { +pub(crate) unsafe extern "C" fn loader_main() -> ! { arch::message_output_init(); crate::log::init(); From 910c1256259a813a4b6abc99119925571d8a9cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kr=C3=B6ning?= Date: Fri, 11 Aug 2023 11:57:53 +0200 Subject: [PATCH 2/3] build(deps): bump hermit-entry to 0.9.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Martin Kröning --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91c96e39..0f4451e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,9 +77,9 @@ checksum = "89e0de208fa9b99664812350b33072d0d9b3a63caaebb03eeb23316eeedfb8d0" [[package]] name = "hermit-entry" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b60fa4b9d132da6f91ffabe4eaa399d96e812f8285c69ff4d3de9190b83a7b" +checksum = "f82940ef008865ffdba8630108f499596c36cce8e302b127a1b7badf938c424f" dependencies = [ "align-address", "goblin", From 3c7a8a1c50e266e8bc72b46a2f1b80cb183147c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sch=C3=B6ning?= Date: Mon, 28 Feb 2022 17:48:42 +0100 Subject: [PATCH 3/3] feat: add 64-bit RISC-V support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usage with QEMU is similar to the x86 version: The loader is passed via "-kernel" and the application via "-initrd". Co-authored-by: Martin Kröning Signed-off-by: Martin Kröning --- .github/workflows/ci.yml | 26 +++++- Cargo.lock | 92 ++++++++++++++++++++ Cargo.toml | 7 ++ data/riscv64/hello_world | 3 + src/arch/mod.rs | 5 ++ src/arch/riscv64/address_range.rs | 87 +++++++++++++++++++ src/arch/riscv64/link.ld | 11 +++ src/arch/riscv64/mod.rs | 140 ++++++++++++++++++++++++++++++ src/arch/riscv64/start.rs | 68 +++++++++++++++ src/main.rs | 1 + xtask/src/main.rs | 2 +- xtask/src/target.rs | 5 ++ 12 files changed, 444 insertions(+), 3 deletions(-) create mode 100755 data/riscv64/hello_world create mode 100644 src/arch/riscv64/address_range.rs create mode 100644 src/arch/riscv64/link.ld create mode 100644 src/arch/riscv64/mod.rs create mode 100644 src/arch/riscv64/start.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fe18827..2d98bd89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: name: Run strategy: matrix: - target: [x86_64, x86_64-uefi, x86_64-fc, aarch64] + target: [x86_64, x86_64-uefi, x86_64-fc, aarch64, riscv64] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -51,7 +51,7 @@ jobs: if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update - sudo apt-get install qemu-system-x86 qemu-system-arm nasm + sudo apt-get install qemu-system-x86 qemu-system-arm qemu-system-misc nasm - name: Install QEMU, NASM (macos) if: matrix.os == 'macos-latest' run: | @@ -96,6 +96,17 @@ jobs: -display none -serial stdio -semihosting \ -kernel target/aarch64/debug/rusty-loader \ -device guest-loader,addr=0x48000000,initrd=data/aarch64/hello_world + - name: Run loader (riscv64) + if: matrix.target == 'riscv64' + run: | + qemu-system-riscv64 \ + -machine virt \ + -cpu rv64 \ + -smp 1 \ + -m 32M \ + -display none -serial stdio \ + -kernel target/riscv64/debug/rusty-loader \ + -initrd data/riscv64/hello_world - name: Build (release) run: cargo xtask build --target ${{ matrix.target }} --release - name: Run loader (release, x86_64) @@ -123,3 +134,14 @@ jobs: -display none -serial stdio -semihosting \ -kernel target/aarch64/release/rusty-loader \ -device guest-loader,addr=0x48000000,initrd=data/aarch64/hello_world + - name: Run loader (release, riscv64) + if: matrix.target == 'riscv64' + run: | + qemu-system-riscv64 \ + -machine virt \ + -cpu rv64 \ + -smp 1 \ + -m 32M \ + -display none -serial stdio \ + -kernel target/riscv64/release/rusty-loader \ + -initrd data/riscv64/hello_world diff --git a/Cargo.lock b/Cargo.lock index 0f4451e3..78cd0fee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,12 +53,34 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "deranged" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "fdt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" + [[package]] name = "goblin" version = "0.7.1" @@ -115,12 +137,48 @@ dependencies = [ "paste", ] +[[package]] +name = "naked-function" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b8d5fca6ab1e6215b010aefd3b9ac5aae369dae0faea3a7f34f296cc9f719ac" +dependencies = [ + "cfg-if", + "naked-function-macro", +] + +[[package]] +name = "naked-function-macro" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b4123e70df5fe0bb370cff166ae453b9c5324a2cfc932c0f7e55498147a0475" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "nasm-rs" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4d98d0065f4b1daf164b3eafb11974c94662e5e2396cf03f32d0bb5c17da51" +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + [[package]] name = "paste" version = "1.0.14" @@ -180,6 +238,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "riscv" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa3145d2fae3778b1e31ec2e827b228bdc6abd9b74bb5705ba46dcb82069bc4f" +dependencies = [ + "bit_field", + "critical-section", + "embedded-hal", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -193,24 +262,41 @@ dependencies = [ "align-address", "align-data", "cc", + "fdt", "goblin", "hermit-dtb", "hermit-entry", "log", "multiboot", + "naked-function", "nasm-rs", + "riscv", + "sbi", + "sptr", "uart_16550", "uefi", "uefi-services", "x86_64", ] +[[package]] +name = "sbi" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29cb0870400aca7e4487e8ec1e93f9d4288da763cb1da2cedc5102e62b6522ad" + [[package]] name = "scroll" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "syn" version = "1.0.109" @@ -329,6 +415,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "volatile" version = "0.4.6" diff --git a/Cargo.toml b/Cargo.toml index b6b61547..c46a49cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,13 @@ goblin = { version = "0.7", default-features = false, features = ["elf64"] } uefi = "0.24" uefi-services = "0.21" +[target.'cfg(target_arch = "riscv64")'.dependencies] +fdt = "0.1" +naked-function = "0.1" +riscv = "0.10" +sbi = "0.2" +sptr = "0.3" + [build-dependencies] cc = "1.0" nasm-rs = "0.2" diff --git a/data/riscv64/hello_world b/data/riscv64/hello_world new file mode 100755 index 00000000..1d6d3046 --- /dev/null +++ b/data/riscv64/hello_world @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eced9ae473fa1ffc91f210c038b1abf3552256657645355c0d40fce2445ed687 +size 856776 diff --git a/src/arch/mod.rs b/src/arch/mod.rs index ac8f8dd5..f4e5f885 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -1,10 +1,15 @@ #[cfg(target_arch = "aarch64")] pub use crate::arch::aarch64::*; +#[cfg(target_arch = "riscv64")] +pub use crate::arch::riscv64::*; #[cfg(target_arch = "x86_64")] pub use crate::arch::x86_64::*; #[cfg(target_arch = "aarch64")] pub mod aarch64; +#[cfg(target_arch = "riscv64")] +pub mod riscv64; + #[cfg(target_arch = "x86_64")] pub mod x86_64; diff --git a/src/arch/riscv64/address_range.rs b/src/arch/riscv64/address_range.rs new file mode 100644 index 00000000..bd712a32 --- /dev/null +++ b/src/arch/riscv64/address_range.rs @@ -0,0 +1,87 @@ +// TODO: Move this into its own crate. +#![allow(dead_code)] + +use core::cmp::Ordering; +use core::fmt; +use core::ops::Range; + +use align_address::Align; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct AddressRange { + start: usize, + end: usize, +} + +impl AddressRange { + pub fn new(start: usize, end: usize) -> Option { + (start <= end).then_some(Self { start, end }) + } + + pub fn from_start_len(start: usize, len: usize) -> Self { + Self { + start, + end: start + len, + } + } + + pub fn overlaps(self, other: Self) -> bool { + self.partial_cmp(&other).is_none() + } + + pub fn next(self, len: usize) -> Self { + Self::from_start_len(self.end, len) + } + + pub fn align_to(self, align: usize) -> Self { + Self { + start: self.start.align_down(align), + end: self.end.align_up(align), + } + } + + pub fn start(self) -> usize { + self.start + } + + pub fn end(self) -> usize { + self.end + } + + pub fn len(self) -> usize { + self.end - self.start + } +} + +#[derive(Debug)] +pub struct TryFromRangeError(()); + +impl TryFrom> for AddressRange { + type Error = TryFromRangeError; + + fn try_from(value: Range<*const T>) -> Result { + Self::new(value.start as usize, value.end as usize).ok_or(TryFromRangeError(())) + } +} + +impl fmt::Display for AddressRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { start, end } = self; + let len = self.len(); + write!(f, "{start:#x}..{end:#x} (len = {len:#10x})") + } +} + +impl PartialOrd for AddressRange { + fn partial_cmp(&self, other: &Self) -> Option { + if self.end <= other.start { + Some(Ordering::Less) + } else if self.start >= other.end { + Some(Ordering::Greater) + } else if self == other { + Some(Ordering::Equal) + } else { + None + } + } +} diff --git a/src/arch/riscv64/link.ld b/src/arch/riscv64/link.ld new file mode 100644 index 00000000..2210e676 --- /dev/null +++ b/src/arch/riscv64/link.ld @@ -0,0 +1,11 @@ +SECTIONS { + kernel_start = ADDR (.text.start); + + .text.start 0x80200000 : { *(.text._start) } + .text : { *(.text.*) } + .rodata : { *(.rodata.*) } + .data : { *(.data.*) } + .bss : { *(.bss.*) } + + kernel_end = .; +} diff --git a/src/arch/riscv64/mod.rs b/src/arch/riscv64/mod.rs new file mode 100644 index 00000000..51fd5cf3 --- /dev/null +++ b/src/arch/riscv64/mod.rs @@ -0,0 +1,140 @@ +mod address_range; +mod start; + +use core::arch::asm; +use core::slice; + +use address_range::AddressRange; +use hermit_entry::boot_info::{ + BootInfo, DeviceTreeAddress, HardwareInfo, PlatformInfo, RawBootInfo, +}; +use hermit_entry::elf::LoadedKernel; +use hermit_entry::Entry; +use log::info; +use sptr::Strict; + +pub fn message_output_init() {} + +pub use sbi::legacy::console_putchar as output_message_byte; + +pub fn find_kernel() -> &'static [u8] { + let fdt = start::get_fdt(); + let chosen = fdt.find_node("/chosen").unwrap(); + + let initrd_start = chosen + .property("linux,initrd-start") + .unwrap() + .as_usize() + .unwrap(); + let initrd_start = sptr::from_exposed_addr_mut::(initrd_start); + let initrd_end = chosen + .property("linux,initrd-end") + .unwrap() + .as_usize() + .unwrap(); + let initrd_end = sptr::from_exposed_addr_mut::(initrd_end); + // SAFETY: We trust the raw pointer from the firmware + let initrd_len = unsafe { initrd_end.offset_from(initrd_start).try_into().unwrap() }; + + // SAFETY: We trust the raw pointer from the firmware + unsafe { slice::from_raw_parts(initrd_start, initrd_len) } +} + +pub unsafe fn get_memory(memory_size: u64) -> u64 { + let memory_size = usize::try_from(memory_size).unwrap(); + + let initrd = AddressRange::try_from(find_kernel().as_ptr_range()).unwrap(); + let fdt = { + let start = start::get_fdt_ptr(); + AddressRange::try_from(start..start.add(start::get_fdt().total_size())).unwrap() + }; + + info!("initrd = {initrd}"); + info!("fdt = {fdt}"); + + const SUPERPAGE_SIZE: usize = 2 * 1024 * 1024; + let initrd = initrd.align_to(SUPERPAGE_SIZE); + let fdt = fdt.align_to(SUPERPAGE_SIZE); + + let [first, second] = if initrd < fdt { + [initrd, fdt] + } else { + [fdt, initrd] + }; + + let start_address = if first.next(memory_size).overlaps(second) { + second.end() + } else { + first.end() + }; + + u64::try_from(start_address).unwrap() +} + +pub unsafe fn boot_kernel(kernel_info: LoadedKernel) -> ! { + let LoadedKernel { + load_info, + entry_point, + } = kernel_info; + + let fdt = start::get_fdt(); + + info!("hart_id = {}", start::get_hart_id()); + + static mut BOOT_INFO: Option = None; + + BOOT_INFO = { + let phys_addr_range = { + let memory = fdt.memory(); + let mut regions = memory.regions(); + + let mem_region = regions.next().unwrap(); + assert!( + regions.next().is_none(), + "hermit-loader can only handle one memory region yet" + ); + + let mem_base = u64::try_from(mem_region.starting_address.addr()).unwrap(); + let mem_size = u64::try_from(mem_region.size.unwrap()).unwrap(); + mem_base..mem_base + mem_size + }; + + let device_tree = { + let fdt_addr = start::get_fdt_ptr().expose_addr(); + DeviceTreeAddress::new(fdt_addr.try_into().unwrap()) + }; + + let boot_info = BootInfo { + hardware_info: HardwareInfo { + phys_addr_range, + serial_port_base: None, + device_tree, + }, + load_info, + platform_info: PlatformInfo::LinuxBoot, + }; + + info!("boot_info = {boot_info:#?}"); + + Some(RawBootInfo::from(boot_info)) + }; + + // Check expected signature of entry function + let entry: Entry = { + let entry: unsafe extern "C" fn(hart_id: usize, boot_info: &'static RawBootInfo) -> ! = + core::mem::transmute(entry_point); + entry + }; + + info!("Jumping into kernel at {entry:p}"); + + asm!( + "mv sp, {stack}", + "jr {entry}", + entry = in(reg) entry, + stack = in(reg) start::get_stack_ptr(), + in("a0") start::get_hart_id(), + in("a1") BOOT_INFO.as_ref().unwrap(), + options(noreturn) + ) +} diff --git a/src/arch/riscv64/start.rs b/src/arch/riscv64/start.rs new file mode 100644 index 00000000..d0060d34 --- /dev/null +++ b/src/arch/riscv64/start.rs @@ -0,0 +1,68 @@ +use core::ptr; +use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; + +use fdt::Fdt; + +static mut STACK: Stack = Stack::new(); +static HART_ID: AtomicUsize = AtomicUsize::new(0); +static FDT: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + +pub fn get_hart_id() -> usize { + HART_ID.load(Ordering::Relaxed) +} + +pub fn get_fdt_ptr() -> *const u8 { + FDT.load(Ordering::Relaxed).cast_const() +} + +pub fn get_fdt() -> Fdt<'static> { + // SAFETY: We trust the FDT pointer provided by the firmware + unsafe { Fdt::from_ptr(get_fdt_ptr()).unwrap() } +} + +pub fn get_stack_ptr() -> *mut u8 { + // SAFETY: We only create a pointer here + let stack_top = unsafe { ptr::addr_of_mut!(STACK) }; + // SAFETY: Pointing directly past the object is allowed + let stack_bottom = unsafe { stack_top.add(1) }; + stack_bottom.cast::() +} + +// TODO: Migrate to Constrained Naked Functions once stabilized +// https://github.com/rust-lang/rust/issues/90957 +// TODO: Migrate to asm_const for Stack::SIZE once stabilized +// https://github.com/rust-lang/rust/issues/93332 +#[no_mangle] +#[naked_function::naked] +pub unsafe extern "C" fn _start(hart_id: usize, fdt: *const u8) -> ! { + asm!( + // Initialize stack + "la sp, {BOOT_STACK}", + "li t0, 0x8000", + "add sp, sp, t0", + + "j {start}", + + BOOT_STACK = sym STACK, + start = sym start, + ) +} + +extern "C" fn start(hart_id: usize, fdt: *const u8) -> ! { + HART_ID.store(hart_id, Ordering::Relaxed); + FDT.store(fdt.cast_mut(), Ordering::Relaxed); + + unsafe { crate::none::loader_main() } +} + +// Align to page size +#[repr(C, align(0x1000))] +pub struct Stack([u8; Self::SIZE]); + +impl Stack { + const SIZE: usize = 0x8000; + + pub const fn new() -> Self { + Self([0; Self::SIZE]) + } +} diff --git a/src/main.rs b/src/main.rs index b98159ad..db0db570 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![no_std] #![no_main] #![warn(rust_2018_idioms)] +#![cfg_attr(target_arch = "riscv64", allow(unstable_name_collisions))] #![allow(clippy::missing_safety_doc)] #[macro_use] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 72923353..25b9cee4 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -156,7 +156,7 @@ impl flags::Clippy { // TODO: Enable clippy for x86_64-uefi // https://github.com/hermitcore/rusty-loader/issues/122 #[allow(clippy::single_element_loop)] - for target in [Target::X86_64] { + for target in [Target::X86_64, Target::Riscv64] { target.install()?; let triple = target.triple(); cmd!(sh, "cargo clippy --target={triple}").run()?; diff --git a/xtask/src/target.rs b/xtask/src/target.rs index 4fd34d41..095aaa74 100644 --- a/xtask/src/target.rs +++ b/xtask/src/target.rs @@ -9,6 +9,7 @@ pub enum Target { X86_64Fc, X86_64Uefi, AArch64, + Riscv64, } impl Target { @@ -31,6 +32,7 @@ impl Target { Self::X86_64Fc => "x86_64-fc", Self::X86_64Uefi => "x86_64-uefi", Self::AArch64 => "aarch64", + Self::Riscv64 => "riscv64", } } @@ -40,6 +42,7 @@ impl Target { Self::X86_64Fc => "x86_64-unknown-none", Self::X86_64Uefi => "x86_64-unknown-uefi", Self::AArch64 => "aarch64-unknown-none-softfloat", + Self::Riscv64 => "riscv64imac-unknown-none-elf", } } @@ -55,6 +58,7 @@ impl Target { ], Self::X86_64Uefi => &[], Self::AArch64 => &["-Clink-arg=-Tsrc/arch/aarch64/link.ld"], + Self::Riscv64 => &["-Clink-arg=-Tsrc/arch/riscv64/link.ld"], } } @@ -82,6 +86,7 @@ impl FromStr for Target { "x86_64-fc" => Ok(Self::X86_64Fc), "x86_64-uefi" => Ok(Self::X86_64Uefi), "aarch64" => Ok(Self::AArch64), + "riscv64" => Ok(Self::Riscv64), s => Err(anyhow!("Unsupported target: {s}")), } }