From 6800f4e356ab6398c9a3ec51538a95948f7989c8 Mon Sep 17 00:00:00 2001 From: Fritz Stracke Date: Thu, 22 May 2025 14:28:22 +0200 Subject: [PATCH 1/3] add linux header to entry.s identify the binary as bootable by inserting a linux boot header. --- src/arch/aarch64/entry.s | 12 ++++++++++++ src/arch/aarch64/link.ld | 1 + 2 files changed, 13 insertions(+) diff --git a/src/arch/aarch64/entry.s b/src/arch/aarch64/entry.s index 29a9cd8e..f1f15a39 100644 --- a/src/arch/aarch64/entry.s +++ b/src/arch/aarch64/entry.s @@ -4,6 +4,18 @@ .section .text +_linux: // Let's fake being linux! https://www.kernel.org/doc/Documentation/arm64/booting.txt + mov x0, #1 // code0: Do nothing here (no uefi) + b _start // code1: Branch to real stuff.... + .quad 0 // text_offset: linux needs none, neither do we + .quad prog_size + .quad 2 // flags: 4k pagesize. might need adjustment. Page size currently undefined. + .quad 0 // res2: reserved + .quad 0 // res3: reserved + .quad 0 // res4: reserved + .long 0x644d5241 // magic: "ARMx64" + .long 0 // res5: header size for efi boot. Not needed. + .align 8 _start: // Only proceed on the boot core. Park it otherwise. mrs x1, mpidr_el1 diff --git a/src/arch/aarch64/link.ld b/src/arch/aarch64/link.ld index 23a2a78a..5b783e58 100644 --- a/src/arch/aarch64/link.ld +++ b/src/arch/aarch64/link.ld @@ -48,4 +48,5 @@ SECTIONS /* | direction */ __boot_core_stack_end_exclusive = .; /* | */ loader_end = .; + prog_size = loader_end - loader_start; } From ae6983219b4501b152398fc44573bdf783f097fa Mon Sep 17 00:00:00 2001 From: Fritz Stracke Date: Thu, 31 Jul 2025 14:36:22 +0200 Subject: [PATCH 2/3] boot independently of current exception level when booting, this commit adds a check to determine the machines current exception level. If it is lower than 1, it changes the EL accordingly. Signed-off-by: Fritz Stracke --- src/arch/aarch64/entry.s | 51 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/arch/aarch64/entry.s b/src/arch/aarch64/entry.s index f1f15a39..d03af8d3 100644 --- a/src/arch/aarch64/entry.s +++ b/src/arch/aarch64/entry.s @@ -31,8 +31,57 @@ _start: adrp x4, __boot_core_stack_end_exclusive add x4, x4, #:lo12:__boot_core_stack_end_exclusive mov sp, x4 - + + // Set correct Exception level! + + mrs x0, CurrentEL + cmp x0, #4 // EL = 1 + b.eq el_1_entry + + // Test if EL2 + cmp x0, #8 + b.eq el_2_entry + + //EL3 + msr SP_EL2, x4 + msr SP_EL1, x4 + msr SCTLR_EL2, xzr + msr HCR_EL2, xzr + + mrs x0, SCR_EL3 + and x0, x0, #(~(1 << 3)) + and x0, x0, #(~(1 << 2)) + and x0, x0, #(~(1 << 1)) + orr x0, x0, #(1<<10) + orr x0, x0, #(1<<0) + msr SCR_EL3, x0 + + mov x0, #0b1111001001 // D-Flag, I-FLAG, A-FLAG, F-FLAG, EL2h + msr SPSR_EL3, x0 + + adr x0, el_2_entry + msr ELR_EL3, x0 + + eret + +// EL2 +el_2_entry: + msr SP_EL1, x4 + msr SCTLR_EL1, xzr + mrs x0, HCR_EL2 + orr x0, x0, #(1<<31) + msr HCR_EL2, x0 + + mov x0, #0b1111000101 // D-Flag, I-FLAG, A-FLAG, F-FLAG, EL1h + msr SPSR_EL2, x0 + + adr x0, el_1_entry + msr ELR_EL2, x0 + + eret + // Jump to Rust code. +el_1_entry: b {start_rust} // Infinitely wait for events (aka "park the core"). From 3b177c2d789dc83b4f0373f3c4ad4829a2320a3f Mon Sep 17 00:00:00 2001 From: Fritz Stracke Date: Thu, 7 Aug 2025 14:36:09 +0200 Subject: [PATCH 3/3] add driver module add drivers for serial devices. recognize drivers based on dtb tag. use appropriate driver in console module. Signed-off-by: Fritz Stracke --- Cargo.lock | 36 +++++++- Cargo.toml | 2 + src/arch/aarch64/console.rs | 57 +++++++------ src/arch/aarch64/drivers/mod.rs | 47 +++++++++++ src/arch/aarch64/drivers/qemu_serial.rs | 46 ++++++++++ src/arch/aarch64/drivers/xlnx_serial.rs | 108 ++++++++++++++++++++++++ src/arch/aarch64/mod.rs | 28 +++--- 7 files changed, 287 insertions(+), 37 deletions(-) create mode 100644 src/arch/aarch64/drivers/mod.rs create mode 100644 src/arch/aarch64/drivers/qemu_serial.rs create mode 100644 src/arch/aarch64/drivers/xlnx_serial.rs diff --git a/Cargo.lock b/Cargo.lock index b8ecb738..2bb4a9fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,6 +289,18 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "errno" version = "0.3.12" @@ -413,6 +425,7 @@ dependencies = [ "allocator-api2 0.3.0", "anstyle", "cfg-if", + "enum_dispatch", "fdt", "goblin 0.10.0", "hermit-entry", @@ -426,6 +439,7 @@ dependencies = [ "uart_16550", "uefi", "vm-fdt", + "volatile 0.6.1", "x86_64", ] @@ -1071,6 +1085,26 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" +[[package]] +name = "volatile" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8ca9a5d4debca0633e697c88269395493cebf2e10db21ca2dbde37c1356452" +dependencies = [ + "volatile-macro", +] + +[[package]] +name = "volatile-macro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c67ce935f3b4329e473ecaff7bab444fcdc3d1d19f8bae61fabfa90b84f93e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1330,7 +1364,7 @@ dependencies = [ "bit_field", "bitflags 2.9.1", "rustversion", - "volatile", + "volatile 0.4.6", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 138270b4..4534b9f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,10 @@ x86_64 = { version = "0.15", default-features = false, features = ["instructions [target.'cfg(target_arch = "aarch64")'.dependencies] aarch64-cpu = "10" +enum_dispatch = "0.3" fdt = { version = "0.1" } goblin = { version = "0.10", default-features = false, features = ["elf64"] } +volatile = { version = "0.6", features = ["derive"] } [target.'cfg(target_os = "uefi")'.dependencies] uefi = { version = "0.35", features = ["alloc", "panic_handler", "qemu"] } diff --git a/src/arch/aarch64/console.rs b/src/arch/aarch64/console.rs index 910a5bbc..fd7f1496 100644 --- a/src/arch/aarch64/console.rs +++ b/src/arch/aarch64/console.rs @@ -1,12 +1,17 @@ -use core::ptr::NonNull; +use core::num::NonZeroU32; use fdt::Fdt; +use crate::arch::aarch64::drivers::SerialDriver; +use crate::arch::drivers::qemu_serial::QemuSerial; +use crate::arch::drivers::xlnx_serial::XlnxSerial; +use crate::arch::drivers::{SerialPort, get_device}; + pub struct Console { - stdout: NonNull, + stdout: SerialPort, } -fn stdout() -> u32 { +fn stdout() -> SerialPort { /// Physical address of UART0 at Qemu's virt emulation const SERIAL_PORT_ADDRESS: u32 = 0x09000000; @@ -16,40 +21,44 @@ fn stdout() -> u32 { }; let property = dtb.chosen().stdout(); - if let Some(stdout) = property { - let stdout = stdout.name.trim_matches(char::from(0)); - if let Some(pos) = stdout.find('@') { - let len = stdout.len(); - u32::from_str_radix(&stdout[pos + 1..len], 16).unwrap_or(SERIAL_PORT_ADDRESS) - } else { - SERIAL_PORT_ADDRESS - } - } else { - SERIAL_PORT_ADDRESS - } + property + .and_then(|node| get_device(node)) + .unwrap_or(SerialPort::Qemu(QemuSerial::from_addr( + NonZeroU32::new(SERIAL_PORT_ADDRESS).unwrap(), + ))) } impl Console { pub fn write_bytes(&mut self, bytes: &[u8]) { - for byte in bytes.iter().copied() { - unsafe { - self.stdout.as_ptr().write_volatile(byte); - } - } + self.stdout.putstr(bytes); } - pub(super) fn get_stdout(&self) -> NonNull { - self.stdout + pub(super) fn get_stdout(&self) -> u32 { + self.stdout.get_addr() + } + + pub(crate) fn set_stdout(&mut self, stdout: u32) { + match self.stdout { + SerialPort::Qemu(_) => { + self.stdout = + SerialPort::Qemu(QemuSerial::from_addr(NonZeroU32::new(stdout).unwrap())) + } + SerialPort::Xlnx(_) => { + self.stdout = + SerialPort::Xlnx(XlnxSerial::from_addr(NonZeroU32::new(stdout).unwrap())) + } + } + self.stdout.init(); } - pub(super) fn set_stdout(&mut self, stdout: NonNull) { - self.stdout = stdout; + pub(crate) fn wait_empty(&mut self) { + self.stdout.wait_empty(); } } impl Default for Console { fn default() -> Self { - let stdout = NonNull::new(stdout() as *mut u8).unwrap(); + let stdout = stdout(); Self { stdout } } } diff --git a/src/arch/aarch64/drivers/mod.rs b/src/arch/aarch64/drivers/mod.rs new file mode 100644 index 00000000..7dc0b51f --- /dev/null +++ b/src/arch/aarch64/drivers/mod.rs @@ -0,0 +1,47 @@ +use core::num::NonZeroU32; + +use enum_dispatch::enum_dispatch; +use fdt::node::FdtNode; +use qemu_serial::QemuSerial; +use xlnx_serial::XlnxSerial; + +pub mod qemu_serial; +pub mod xlnx_serial; + +pub enum SerialSuccess { + Success(T), + ERetry, +} + +#[enum_dispatch] +pub trait SerialDriver { + fn init(&mut self); + fn putc(&mut self, c: u8) -> SerialSuccess; + fn putstr(&mut self, s: &[u8]); + fn get_addr(&self) -> u32; + fn wait_empty(&mut self); +} + +#[enum_dispatch(SerialDriver)] +pub enum SerialPort { + Qemu(QemuSerial), + Xlnx(XlnxSerial), +} + +pub fn get_device<'a>(node: FdtNode<'_, 'a>) -> Option { + let compat = node.compatible()?; + let reg = node.reg()?.next()?; + + for id in compat.all() { + if id == "arm,pl011" { + return Some(SerialPort::Qemu(QemuSerial::from_addr( + NonZeroU32::new(reg.starting_address as u32).unwrap(), + ))); + } else if id == "xlnx,xuartlite" { + return Some(SerialPort::Xlnx(XlnxSerial::from_addr( + NonZeroU32::new(reg.starting_address as u32).unwrap(), + ))); + } + } + None +} diff --git a/src/arch/aarch64/drivers/qemu_serial.rs b/src/arch/aarch64/drivers/qemu_serial.rs new file mode 100644 index 00000000..62380b9a --- /dev/null +++ b/src/arch/aarch64/drivers/qemu_serial.rs @@ -0,0 +1,46 @@ +use core::num::NonZeroU32; +use core::ptr::NonNull; + +use volatile::{VolatileFieldAccess, VolatileRef}; + +use crate::arch::drivers::SerialSuccess::Success; +use crate::arch::drivers::{SerialDriver, SerialSuccess}; + +#[repr(C)] +#[derive(VolatileFieldAccess)] +struct QemuPort { + out: u8, +} + +pub struct QemuSerial { + regs: VolatileRef<'static, QemuPort>, +} + +impl QemuSerial { + pub fn from_addr(base_addr: NonZeroU32) -> QemuSerial { + Self { + regs: unsafe { + VolatileRef::new(NonNull::new_unchecked(base_addr.get() as *mut QemuPort)) + }, + } + } +} + +impl SerialDriver for QemuSerial { + fn init(&mut self) {} + fn putc(&mut self, c: u8) -> SerialSuccess { + self.regs.as_mut_ptr().out().write(c); + Success(c) + } + + fn putstr(&mut self, s: &[u8]) { + for c in s.iter().copied() { + let _ = self.putc(c); + } + } + fn get_addr(&self) -> u32 { + self.regs.as_ptr().as_raw_ptr().as_ptr() as u32 + } + + fn wait_empty(&mut self) {} +} diff --git a/src/arch/aarch64/drivers/xlnx_serial.rs b/src/arch/aarch64/drivers/xlnx_serial.rs new file mode 100644 index 00000000..e046f8fa --- /dev/null +++ b/src/arch/aarch64/drivers/xlnx_serial.rs @@ -0,0 +1,108 @@ +use core::hint; +use core::num::NonZeroU32; +use core::ptr::NonNull; + +use aarch64_cpu::asm::barrier; +use aarch64_cpu::asm::barrier::SY; +use volatile::{VolatileFieldAccess, VolatileRef}; + +use crate::arch::drivers::SerialSuccess::{ERetry, Success}; +use crate::arch::drivers::{SerialDriver, SerialSuccess}; + +const ZYNQ_UART_SR_TXACTIVE: u32 = 1 << 11; +const ZYNQ_UART_SR_TXFULL: u32 = 1 << 4; +const ZYNQ_UART_SR_TXEMPTY: u32 = 1 << 3; + +const ZYNQ_UART_CR_TX_EN: u32 = 1 << 4; +const ZYNQ_UART_CR_RX_EN: u32 = 1 << 2; +const ZYNQ_UART_CR_TXRST: u32 = 1 << 1; +const ZYNQ_UART_CR_RXRST: u32 = 1 << 0; + +const ZYNQ_UART_MR_PARITY_NONE: u32 = 0x00000020; + +#[repr(C)] +#[derive(VolatileFieldAccess)] +pub struct XlnxRegisters { + control: u32, + mode: u32, + reserved1: [u32; 4], + baud_rate_gen: u32, + reserved2: [u32; 4], + channel_sts: u32, + tx_rx_fifo: u32, + baud_rate_divider: u32, +} + +pub struct XlnxSerial { + regs: VolatileRef<'static, XlnxRegisters>, +} + +impl XlnxSerial { + pub fn from_addr(base_addr: NonZeroU32) -> XlnxSerial { + Self { + regs: unsafe { + VolatileRef::new(NonNull::new_unchecked(base_addr.get() as *mut XlnxRegisters)) + }, + } + } +} + +impl SerialDriver for XlnxSerial { + fn init(&mut self) { + self.regs.as_mut_ptr().control().write( + ZYNQ_UART_CR_TX_EN | ZYNQ_UART_CR_RX_EN | ZYNQ_UART_CR_TXRST | ZYNQ_UART_CR_RXRST, + ); + self.regs + .as_mut_ptr() + .mode() + .write(ZYNQ_UART_MR_PARITY_NONE); + } + + fn putc(&mut self, c: u8) -> SerialSuccess { + barrier::dmb(SY); + barrier::dsb(SY); + if self.regs.as_mut_ptr().channel_sts().read() & ZYNQ_UART_SR_TXFULL != 0 { + return ERetry; + } + + self.regs.as_mut_ptr().tx_rx_fifo().write(c as u32); + Success(c) + } + + fn putstr(&mut self, s: &[u8]) { + 'foo: for c in s.iter().copied() { + if c == b'\n' { + for _ in 0..1000 { + match self.putc(b'\r') { + ERetry => continue, + Success(_) => break, + } + } + } + for _ in 0..1000 { + hint::spin_loop(); + match self.putc(c) { + ERetry => continue, + Success(_) => continue 'foo, + } + } + self.init(); + while self.regs.as_mut_ptr().channel_sts().read() & ZYNQ_UART_SR_TXEMPTY != 0 { + hint::spin_loop(); + } + } + } + + fn get_addr(&self) -> u32 { + self.regs.as_ptr().as_raw_ptr().as_ptr() as u32 + } + + fn wait_empty(&mut self) { + while self.regs.as_mut_ptr().channel_sts().read() & ZYNQ_UART_SR_TXACTIVE != 0 { + hint::spin_loop(); + } + while self.regs.as_mut_ptr().channel_sts().read() & ZYNQ_UART_SR_TXEMPTY != 0 { + hint::spin_loop(); + } + } +} diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs index 8272ec57..d40e0613 100644 --- a/src/arch/aarch64/mod.rs +++ b/src/arch/aarch64/mod.rs @@ -1,11 +1,14 @@ mod console; + pub use self::console::Console; +pub mod drivers; pub mod entry; pub mod paging; use core::arch::asm; -use core::ptr::{self, NonNull}; +use core::ptr::{self}; +use aarch64_cpu::asm::barrier::{NSH, SY, dmb, dsb, isb}; use align_address::Align; use fdt::Fdt; use goblin::elf::header::header64::{EI_DATA, ELFDATA2LSB, ELFMAG, Header, SELFMAG}; @@ -54,7 +57,6 @@ pub fn find_kernel() -> &'static [u8] { Fdt::from_ptr(sptr::from_exposed_addr(DEVICE_TREE as usize)) .expect(".dtb file has invalid header") }; - let module_start = dtb .find_node("/chosen") .unwrap() @@ -77,7 +79,7 @@ pub fn find_kernel() -> &'static [u8] { }; if header.e_ident[0..SELFMAG] != ELFMAG[..] { - panic!("Don't found valid ELF file!"); + panic!("Didn't find valid ELF file!"); } #[cfg(target_endian = "little")] @@ -116,7 +118,7 @@ pub unsafe fn boot_kernel(kernel_info: LoadedKernel) -> ! { let cpus = dtb.cpus().count(); info!("Detect {cpus} CPU(s)"); - let uart_address: u32 = CONSOLE.lock().get().get_stdout().as_ptr() as u32; + let uart_address: u32 = CONSOLE.lock().get().get_stdout(); info!("Detect UART at {uart_address:#x}"); let pgt_slice = unsafe { core::slice::from_raw_parts_mut(ptr::addr_of_mut!(l0_pgtable), 512) }; @@ -162,10 +164,7 @@ pub unsafe fn boot_kernel(kernel_info: LoadedKernel) -> ! { *entry = RAM_START + (i * BasePageSize::SIZE) as u64 + PT_MEM; } - CONSOLE - .lock() - .get() - .set_stdout(NonNull::new(0x1000 as *mut u8).unwrap()); + CONSOLE.lock().get().set_stdout(0x1000); // Load TTBRx unsafe { @@ -192,6 +191,8 @@ pub unsafe fn boot_kernel(kernel_info: LoadedKernel) -> ! { ); } + info!("Successfully set up paging."); + let dtb = unsafe { Fdt::from_ptr(sptr::from_exposed_addr(DEVICE_TREE as usize)) .expect(".dtb file has invalid header") @@ -206,11 +207,12 @@ pub unsafe fn boot_kernel(kernel_info: LoadedKernel) -> ! { .trim_matches(char::from(0)); assert!(device_type == "memory"); } - + info!("Memory found!"); let regions = dtb.memory().regions().next().unwrap(); let ram_start = regions.starting_address as u64; let ram_size = regions.size.unwrap() as u64; + info!("ram_start: {ram_start:#x}, ram_size: {ram_size:#x}. Trying to jump into kernel soon."); let boot_info = BootInfo { hardware_info: HardwareInfo { phys_addr_range: ram_start..ram_start + ram_size, @@ -240,9 +242,11 @@ unsafe fn enter_kernel(stack: *mut u8, entry: *const (), raw_boot_info: &'static info!("Entering kernel at {entry:p}, stack at {stack:p}, raw_boot_info at {raw_boot_info:p}"); // Memory barrier - unsafe { - asm!("dsb sy", options(nostack)); - } + CONSOLE.lock().get().wait_empty(); + dsb(SY); + isb(SY); + dmb(SY); + dsb(NSH); unsafe { asm!(