Add start_prototype, a tool used for writing entry point assembly.
This is a Rust version of the entry point. Using it directly is undefined behavior for multiple reasons (Rust assumes the stack is setup and the globals are initialized), but it can be a useful reference while working on a real (assembly) entry point.
diff --git a/core/runtime/src/start_prototype.rs b/core/runtime/src/start_prototype.rs
new file mode 100644
index 0000000..ed0206b
--- /dev/null
+++ b/core/runtime/src/start_prototype.rs
@@ -0,0 +1,92 @@
+// The `start` symbol must be written purely in assembly, because it has an ABI
+// that the Rust compiler doesn't know (e.g. it does not expect the stack to be
+// set up). One way to write a correct `start` implementation is to write it in
+// Rust using the C ABI, compile that implementation, then tweak the assembly by
+// hand. This is a Rust version of `start` for developers who are working on
+// `start`.
+//
+// This file is not compiled by default; it must be included manually by adding
+// `mod start_prototype;` to `lib.rs`.
+
+#[repr(C)]
+struct RtHeader {
+ start: usize,
+ initial_break: usize,
+ stack_top: usize,
+ data_size: usize,
+ data_flash_start: *const u32,
+ data_ram_start: *mut u32,
+ bss_size: usize,
+ bss_start: *mut u8,
+}
+
+#[link_section = ".start"]
+#[no_mangle]
+extern fn start_prototype(
+ rt_header: &RtHeader,
+ _memory_start: usize,
+ _memory_len: usize,
+ _app_break: usize,
+) -> ! {
+ use crate::TockSyscalls;
+ use libtock_platform::{OneArgMemop, RawSyscalls, YieldType};
+
+ let pc: usize;
+ #[cfg(target_arch = "riscv32")]
+ unsafe {
+ asm!("auipc {}, 0", lateout(reg) pc, options(nomem, nostack, preserves_flags));
+ }
+ if pc != rt_header.start {
+ // Binary is in an incorrect location: report an error via
+ // LowLevelDebug.
+ unsafe {
+ TockSyscalls::four_arg_syscall(8, 1, 2, 0, 2);
+ }
+ // TODO: Replace with an Exit call when exit is implemented.
+ loop {
+ TockSyscalls::raw_yield(YieldType::Wait);
+ }
+ }
+
+ // Set the app break.
+ // TODO: Replace with Syscalls::memop_brk() when that is implemented.
+ TockSyscalls::one_arg_memop(OneArgMemop::Brk, rt_header.initial_break);
+
+ // Set the stack pointer.
+ #[cfg(target_arch = "riscv32")]
+ unsafe {
+ asm!("mv sp, {}", in(reg) rt_header.stack_top, options(nomem, preserves_flags));
+ }
+
+ // Copy .data into place. Uses a manual loop rather than
+ // `core::ptr::copy*()` to avoid relying on `memcopy` or `memmove`.
+ let mut remaining = rt_header.data_size;
+ let mut src = rt_header.data_flash_start;
+ let mut dest = rt_header.data_ram_start;
+ while remaining > 0 {
+ unsafe {
+ core::ptr::write(dest, *(src));
+ src = src.add(1);
+ dest = dest.add(1);
+ }
+ remaining -= 4;
+ }
+
+ // Zero .bss. Uses a manual loop and volatile write to avoid relying on
+ // `memset`.
+ let mut remaining = rt_header.bss_size;
+ let mut dest = rt_header.bss_start;
+ while remaining > 0 {
+ unsafe {
+ core::ptr::write_volatile(dest, 0);
+ dest = dest.add(1);
+ }
+ remaining -= 1;
+ }
+
+ extern {
+ fn rust_start() -> !;
+ }
+
+ unsafe { rust_start(); }
+}