From 773fc4902041bbcffeed31459b45d58ba544b84a Mon Sep 17 00:00:00 2001 From: Kit Rhett Aultman Date: Mon, 24 Jun 2024 00:06:17 -0400 Subject: [PATCH] Hello world for qemu serial This commit establishes the overall project structure needed for doing a bare metal executive on RISC-V. This currently supports the qemu risc64 emulator because that is a target I can develop across multiple environments as I have the free time. This contains: * Basic project structure * rustc cross-compilation to riscv64-unknown-none-elf target * Cargo integration for running qemu * linking scripts for the qemu riscv64 virt "board" memory layout * do-nothing panic handler * _start function that contains the minimal RISC-V initialization: setting up the global pointer (gp), reserving a stack, and clearing the bss region * Very simple use of the uart_16550 crate to print a "Hello world" message This all came together mostly in an afternoon's work and wouldn't have been possible without these exceptional resources: "Running Rust code on RISC-V in Qemu" https://meyerzinn.tech/posts/2023/03/05/running-rust-code-on-risc-v-in-qemu/ "RISC-V from scratch 2: Hardware layouts, linker scripts, and C runtimes" https://twilco.github.io/riscv-from-scratch/2019/04/27/riscv-from-scratch-2.html --- .cargo/config.toml | 16 +++++++++++ .gitignore | 1 + Cargo.lock | 59 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 19 +++++++++++++ rust-toolchain.toml | 2 ++ src/linker.ld | 46 ++++++++++++++++++++++++++++++ src/main.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 211 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 rust-toolchain.toml create mode 100644 src/linker.ld create mode 100644 src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..f69d0bd --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,16 @@ +[target.riscv64gc-unknown-none-elf] +rustflags = ["-C", "link-args=-Tsrc/linker.ld --omagic"] +# Add -S to wait for the debugger +runner = """ qemu-system-riscv64 + -machine virt + -cpu rv64 + -m 128M + -s + -nographic + -serial mon:stdio + -bios """ + +[build] +target = "riscv64gc-unknown-none-elf" + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c0b5a87 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,59 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags", +] + +[[package]] +name = "riscv" +version = "0.1.0" +dependencies = [ + "uart_16550", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "uart_16550" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dc00444796f6c71f47c85397a35e9c4dbf9901902ac02386940d178e2b78687" +dependencies = [ + "bitflags", + "rustversion", + "x86", +] + +[[package]] +name = "x86" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" +dependencies = [ + "bit_field", + "bitflags", + "raw-cpuid", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..56aec2c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "riscv" +version = "0.1.0" +edition = "2021" + +[build] +target = "riscv64gc-unknown-none-elf" + +[dependencies] +uart_16550 = "0.3.0" + +# We need to turn off panic unwinding +# so we set the panic behavior to abort + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/linker.ld b/src/linker.ld new file mode 100644 index 0000000..21ec60b --- /dev/null +++ b/src/linker.ld @@ -0,0 +1,46 @@ +OUTPUT_ARCH(riscv) + +MEMORY { + RAM (wxa) : ORIGIN = 0x80000000, LENGTH = 128M +} + +PHDRS { + text PT_LOAD; + data PT_LOAD; + bss PT_LOAD; +} + +SECTIONS { + . = ORIGIN(RAM); # start at 0x8000_0000 + + .text : { # put code first + *(.text.init) # start with anything in the .text.init section + *(.text .text.*) # then put anything else in .text + } >RAM AT>RAM :text # put this section into the text segment + + # we have to provide a pointer at the start of the rodata/data/bss area + # in order to facilitate linker relaxation. This is a process whereby the + # linker can use simpler instructions for variable accesses that exist within + # a 12-bit offset from the global pointer + PROVIDE(_global_pointer = .); # we have to provide the global_pointer at the beg + + .rodata : { # next, read-only data + *(.rodata .rodata.*) + } >RAM AT>RAM :text # goes into the text segment as well (since instructions are generally read-only) + + .data : { # and the data section + *(.sdata .sdata.*) *(.data .data.*) + } >RAM AT>RAM :data # this will go into the data segment + + .bss :{ # finally, the BSS + PROVIDE(_bss_start = .); # define a variable for the start of this section + *(.sbss .sbss.*) *(.bss .bss.*) + PROVIDE(_bss_end = .); # ... and one at the end + } >RAM AT>RAM :bss # and this goes into the bss segment + + # start the stack at the end of memory and let it grow downward + PROVIDE(_stack_top = ORIGIN(RAM) + LENGTH(RAM)); +} + +# make the linker error unless we provide a _start +ENTRY(_start) diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..da4170b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] +#![feature(naked_functions)] // enable functions that are pure inline assembly + +use core::panic::PanicInfo; + +// cfg not test is set here because the analyzer and the test system +// complained about the panic handler being doubly-defined +#[cfg(not(test))] +#[panic_handler] +fn on_panic(_info: &PanicInfo) -> ! { + loop {} +} + +/* + * Global entry point, specified in the linker script. + * Requires C-style linkage in order for the linker to identify the name + * of the function and place it at the entry point address. + * + * The main purpose here is to "initialize the runtime". In this case, this + * means setting up the global pointer for linker relaxation, establishing + * the stack, and zeroing-out the bss section + */ +#[naked] +#[no_mangle] // C-style linkage needs an unmangled function +#[link_section = ".text.init"] +unsafe extern "C" fn _start() -> ! { + use core::arch::asm; + asm!( + // The linker will try to emit the `la` pseudo-instruction when it believes + // the referenced variable will be within a 12-bit offset of the _global_pointer, + // so we must initialize the `gp` register to this at runtime. + // (see the linker script file for more details) + ".option push", + ".option norelax", + "la gp, _global_pointer", + ".option pop", + + // set the stack pointer + "la sp, _stack_top", + + // clear the BSS section + "la t0, _bss_start", + "la t1, _bss_end", + "bgeu t0, t1, 2f", +"1:", + "sb zero, 0(t0)", + "addi t0, t0, 1", + "bne t0, t1, 1b", +"2:", + + // "tail-call" to {entry} (call without saving a return address) + "tail {entry}", + entry = sym entry, // {entry} refers to the function [entry] below + options(noreturn) // we must handle "returning" from assembly + ); +} + +#[no_mangle] // Again, being mindful of the C calling convention +extern "C" fn entry() -> ! { + use uart_16550::MmioSerialPort; + const SERIAL_BASE: usize = 0x1000_0000; + let mut serial_port = unsafe { MmioSerialPort::new(SERIAL_BASE) }; + for byte in "KIT -- Hello World!\n".bytes() { + serial_port.send(byte) + } + loop {} +} -- 2.34.1