feat: ✨ Do a Rust-only chainloader!
This commit is contained in:
parent
0cc683a50f
commit
12f51399df
2
Justfile
2
Justfile
|
@ -59,7 +59,7 @@ cb-eject:
|
|||
|
||||
# Build default hw kernel
|
||||
build:
|
||||
cargo make build
|
||||
cargo make build-device
|
||||
cargo make kernel-binary
|
||||
|
||||
# Clean project
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -5,6 +5,9 @@ args = ["build"]
|
|||
[tasks.chainofcommand]
|
||||
dependencies = ["build"]
|
||||
|
||||
[tasks.build-device]
|
||||
disabled = true
|
||||
|
||||
[tasks.test]
|
||||
command = "cargo"
|
||||
args = ["test"]
|
||||
|
|
Loading…
Reference in New Issue