feat: Do a Rust-only chainloader!

This commit is contained in:
Berkus Decker 2022-10-15 02:51:34 +03:00 committed by Berkus Decker
parent 0cc683a50f
commit 12f51399df
8 changed files with 86 additions and 138 deletions

View File

@ -59,7 +59,7 @@ cb-eject:
# Build default hw kernel
build:
cargo make build
cargo make build-device
cargo make kernel-binary
# Clean project

View File

@ -42,9 +42,10 @@ VOLUME = { value = "/Volumes/BOOT", condition = { env_not_set = ["VOLUME"] } }
#
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
RUST_LIBS = "-Z build-std=compiler_builtins,core,alloc -Z build-std-features=compiler-builtins-mem"
RUST_STD = "-Zbuild-std=compiler_builtins,core,alloc"
RUST_STD_FEATURES = "-Zbuild-std-features=compiler-builtins-mem"
TARGET_JSON = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/targets/${TARGET}.json"
PLATFORM_TARGET="--target=${TARGET_JSON} --features=${TARGET_FEATURES} ${RUST_LIBS}"
PLATFORM_TARGET="--target=${TARGET_JSON} --features=${TARGET_FEATURES}"
DEVICE_FEATURES = "noserial"
QEMU_FEATURES = "qemu,rpi3"
@ -88,15 +89,20 @@ dependencies = ["kernel-binary"]
command = "cargo"
args = ["modules", "tree"]
[tasks.build]
env = { "TARGET_FEATURES" = "${TARGET_BOARD}" }
[tasks.do-build]
command = "cargo"
args = ["build", "@@split(PLATFORM_TARGET, )", "--release"]
args = ["build", "@@split(PLATFORM_TARGET, )", "@@remove-empty(RUST_STD)", "@@remove-empty(RUST_STD_FEATURES)", "--release"]
[tasks.build]
disabled = true
[tasks.build-device]
env = { "TARGET_FEATURES" = "${TARGET_BOARD}" }
run_task = "do-build"
[tasks.build-qemu]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
command = "cargo"
args = ["build", "@@split(PLATFORM_TARGET, )", "--release"]
run_task = "do-build"
[tasks.qemu-runner]
dependencies = ["build-qemu", "kernel-binary"]
@ -115,7 +121,7 @@ args = ["expand", "@@split(PLATFORM_TARGET, )", "--release"]
[tasks.test]
env = { "TARGET_FEATURES" = "${QEMU_FEATURES}" }
command = "cargo"
args = ["test", "@@split(PLATFORM_TARGET, )"]
args = ["test", "@@split(PLATFORM_TARGET, )", "@@remove-empty(RUST_STD)", "@@remove-empty(RUST_STD_FEATURES)"]
[tasks.docs]
env = { "TARGET_FEATURES" = "" }
@ -128,6 +134,8 @@ command = "cargo"
args = ["clippy", "@@split(PLATFORM_TARGET, )", "@@remove-empty(CLIPPY_FEATURES)", "--", "--deny", "warnings", "--allow", "deprecated"]
# These tasks are written in cargo-make's own script to make it portable across platforms (no `basename` on Windows)
## Copy and prepare a given ELF file. Convert to binary output format.
[tasks.custom-binary]
env = { "BINARY_FILE" = "${BINARY_FILE}" }
script_runner = "@duckscript"
@ -144,10 +152,12 @@ script = [
]
install_crate = { crate_name = "cargo-binutils", binary = "rust-objcopy", test_arg = ["--help"] }
## Copy and prepare binary with tests.
[tasks.test-binary]
env = { "BINARY_FILE" = "${CARGO_MAKE_TASK_ARGS}" }
run_task = "custom-binary"
## Run binary with tests in QEMU.
[tasks.test-runner]
dependencies = ["test-binary"]
script_runner = "@duckscript"
@ -159,6 +169,7 @@ script = [
'''
]
## Generate GDB startup configuration file.
[tasks.gdb-config]
script_runner = "@duckscript"
script = [
@ -168,6 +179,7 @@ script = [
'''
]
## Generate zellij configuration file.
[tasks.zellij-config]
dependencies = ["build-qemu", "kernel-binary"]
script_runner = "@duckscript"
@ -196,6 +208,6 @@ script = [
]
[tasks.chainboot]
dependencies = ["build", "kernel-binary"]
dependencies = ["build-device", "kernel-binary"]
command = "echo"
args = ["\n***===***\n", "Run the following command in your terminal:\n", " ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/target/debug/chainofcommand ${CHAINBOOT_SERIAL} ${CHAINBOOT_BAUD} --kernel ${KERNEL_BIN}\n", "***===***\n\n"]

View File

@ -12,15 +12,13 @@ edition = "2021"
maintenance = { status = "experimental" }
[features]
default = ["asm"]
default = []
# Build for running under QEMU with semihosting, so various halt/reboot options would for example quit QEMU instead.
qemu = ["machine/qemu"]
# Build for debugging it over JTAG/SWD connection - halts on first non-startup function start.
jtag = ["machine/jtag"]
# Dummy feature, ignored in this crate.
noserial = []
# Startup relocation code is implemented in assembly
asm = []
# Mutually exclusive features to choose a target board
rpi3 = ["machine/rpi3"]
rpi4 = ["machine/rpi4"]

View File

@ -1,12 +1,7 @@
// Assembly counterpart to this file.
#[cfg(feature = "asm")]
core::arch::global_asm!(include_str!("boot.s"));
// This is quite impossible - the linker constants are resolved to fully constant offsets in asm
// version, but are image-relative symbols in rust, and I see no way to force it otherwise.
// Make first function small enough so that compiler doesn't try
// to crate a huge stack frame before we have a chance to set SP.
#[no_mangle]
#[link_section = ".text._start"]
#[cfg(not(feature = "asm"))]
pub unsafe extern "C" fn _start() -> ! {
use {
core::cell::UnsafeCell,
@ -18,11 +13,29 @@ pub unsafe extern "C" fn _start() -> ! {
const CORE_0: u64 = 0;
const CORE_MASK: u64 = 0x3;
if CORE_0 == MPIDR_EL1.get() & CORE_MASK {
if CORE_0 != MPIDR_EL1.get() & CORE_MASK {
// if not core0, infinitely wait for events
endless_sleep()
}
extern "Rust" {
// Stack top
static __boot_core_stack_end_exclusive: UnsafeCell<()>;
}
// Set stack pointer.
SP.set(__boot_core_stack_end_exclusive.get() as u64);
reset();
}
#[no_mangle]
#[link_section = ".text._start"]
pub unsafe extern "C" fn reset() -> ! {
use core::{
cell::UnsafeCell,
sync::{atomic, atomic::Ordering},
};
// These are a problem, because they are not interpreted as constants here.
// Subsequently, this code tries to read values from not-yet-existing data locations.
extern "Rust" {
@ -38,35 +51,52 @@ pub unsafe extern "C" fn _start() -> ! {
static __boot_core_stack_end_exclusive: UnsafeCell<()>;
}
// Set stack pointer.
SP.set(__boot_core_stack_end_exclusive.get() as u64);
// This tries to call memcpy() at a wrong linked address - the function is in relocated area!
// Relocate the code.
// Emulate
// core::ptr::copy_nonoverlapping(
// __binary_nonzero_lma.get() as *const u64,
// __binary_nonzero_vma.get() as *mut u64,
// __binary_nonzero_vma_end_exclusive.get() as usize - __binary_nonzero_vma.get() as usize,
// );
let binary_size =
__binary_nonzero_vma_end_exclusive.get() as usize - __binary_nonzero_vma.get() as usize;
local_memcpy(
__binary_nonzero_vma.get() as *mut u8,
__binary_nonzero_lma.get() as *const u8,
binary_size,
);
// This tries to call memset() at a wrong linked address - the function is in relocated area!
// Zeroes the .bss section
// Emulate
// crate::stdmem::local_memset(__bss_start.get() as *mut u8, 0u8, __bss_size.get() as usize);
let bss =
core::slice::from_raw_parts_mut(__bss_start.get() as *mut u8, __bss_size.get() as usize);
for i in bss {
*i = 0;
}
// Relocate the code
core::ptr::copy_nonoverlapping(
__binary_nonzero_lma.get() as *const u64,
__binary_nonzero_vma.get() as *mut u64,
(__binary_nonzero_vma_end_exclusive.get() as usize - __binary_nonzero_vma.get() as usize),
);
// Don't cross this line with loads and stores. The initializations
// done above could be "invisible" to the compiler, because we write to the
// same memory location that is used by statics after this point.
// Additionally, we assume that no statics are accessed before this point.
atomic::compiler_fence(Ordering::SeqCst);
_start_rust();
}
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
/// The Rust entry of the `kernel` binary.
///
/// The function is called from the assembly `_start` function, keep it to support "asm" feature.
#[no_mangle]
#[inline(always)]
pub unsafe fn _start_rust(max_kernel_size: u64) -> ! {
let max_kernel_size =
__binary_nonzero_vma.get() as u64 - __boot_core_stack_end_exclusive.get() as u64;
crate::kernel_init(max_kernel_size)
}
#[inline(always)]
#[link_section = ".text.boot"]
unsafe fn local_memcpy(mut dest: *mut u8, mut src: *const u8, n: usize) {
let dest_end = dest.add(n);
while dest < dest_end {
*dest = *src;
dest = dest.add(1);
src = src.add(1);
}
}

View File

@ -1,94 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2021 Andre Richter <andre.o.richter@gmail.com>
// Modifications
// Copyright (c) 2021- Berkus <berkus+github@metta.systems>
//--------------------------------------------------------------------------------------------------
// Definitions
//--------------------------------------------------------------------------------------------------
// Load the address of a symbol into a register, PC-relative.
//
// The symbol must lie within +/- 4 GiB of the Program Counter.
//
// # Resources
//
// - https://sourceware.org/binutils/docs-2.36/as/AArch64_002dRelocations.html
.macro ADR_REL register, symbol
adrp \register, \symbol
add \register, \register, #:lo12:\symbol
.endm
// Load the address of a symbol into a register, absolute.
//
// # Resources
//
// - https://sourceware.org/binutils/docs-2.36/as/AArch64_002dRelocations.html
.macro ADR_ABS register, symbol
movz \register, #:abs_g2:\symbol
movk \register, #:abs_g1_nc:\symbol
movk \register, #:abs_g0_nc:\symbol
.endm
//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
.section .text._start
//------------------------------------------------------------------------------
// fn _start()
//------------------------------------------------------------------------------
_start:
// Only proceed on the boot core. Park it otherwise.
mrs x1, MPIDR_EL1
and x1, x1, 0b11 // core id mask
cmp x1, 0 // boot core id
b.ne .L_parking_loop
// If execution reaches here, it is the boot core.
// Initialize bss.
ADR_ABS x0, __bss_start
ADR_ABS x1, __bss_size
add x1, x0, x1
.L_bss_init_loop:
cmp x0, x1
b.eq .L_relocate_binary
stp xzr, xzr, [x0], #16
b .L_bss_init_loop
// Next, relocate the binary.
.L_relocate_binary:
ADR_REL x0, __binary_nonzero_lma // The address the binary got loaded to.
ADR_ABS x1, __binary_nonzero_vma // The address the binary was linked to.
ADR_ABS x2, __binary_nonzero_vma_end_exclusive
sub x4, x1, x0 // Get difference between vma and lma as max size
.L_copy_loop:
ldr x3, [x0], #8
str x3, [x1], #8
cmp x1, x2
b.lo .L_copy_loop
// Prepare the jump to Rust code.
// Set the stack pointer.
ADR_ABS x0, __rpi_phys_binary_load_addr
mov sp, x0
// Pass maximum kernel size as an argument to Rust init function.
mov x0, x4
// Jump to the relocated Rust code.
ADR_ABS x1, _start_rust
br x1
// Infinitely wait for events (aka "park the core").
.L_parking_loop:
wfe
b .L_parking_loop
.size _start, . - _start
.type _start, function
.global _start

View File

@ -52,7 +52,7 @@ SECTIONS
.text :
{
KEEP(*(.text._start))
/* *(text.memcpy) -- only relevant for Rust relocator impl which is currently impossible */
*(.text.stdmem)
} :segment_start_code
/* Align to 8 bytes, b/c relocating the binary is done in u64 chunks */

View File

@ -25,7 +25,6 @@ mod boot;
///
/// - Only a single core must be active and running this function.
/// - The init calls in this function must appear in the correct order.
#[inline(always)]
unsafe fn kernel_init(max_kernel_size: u64) -> ! {
#[cfg(feature = "jtag")]
machine::arch::jtag::wait_debugger();

View File

@ -5,6 +5,9 @@ args = ["build"]
[tasks.chainofcommand]
dependencies = ["build"]
[tasks.build-device]
disabled = true
[tasks.test]
command = "cargo"
args = ["test"]