blob: e485badb1d13f25a61b08a70b965107c2465dc57 [file] [log] [blame]
use crate::syscalls;
use crate::ALLOCATOR;
use core::intrinsics;
use core::ptr;
// _start and rust_start are the first two procedures executed when a Tock
// application starts. _start is invoked directly by the Tock kernel; it
// performs stack setup then calls rust_start. rust_start performs data
// relocation and sets up the heap before calling the rustc-generated main.
// rust_start and _start are tightly coupled.
//
// The memory layout is controlled by the linker script.
//
// When the kernel gives control to us, we get r0-r3 values that is as follows.
//
// +--------------+ <- (r2) mem.len()
// | Grant |
// +--------------+
// | Unused |
// S +--------------+ <- (r3) app_heap_break
// R | Heap | (hardcoded to mem_start + 3072 in
// A +--------------| Processs::create which could be lesser than
// M | .bss | mem_start + stack + .data + .bss)
// +--------------|
// | .data |
// +--------------+
// | Stack |
// +--------------+ <- (r1) mem_start
//
// +--------------+
// | .text |
// F +--------------+
// L | .crt0_header |
// A +--------------+ <- (r0) app_start
// S | Protected |
// H | Region |
// +--------------+
//
// We want to organize the memory as follows.
//
// +--------------+ <- app_heap_break
// | Heap |
// +--------------| <- heap_start
// | .bss |
// +--------------|
// | .data |
// +--------------+ <- stack_start (stacktop)
// | Stack |
// | (grows down) |
// +--------------+ <- mem_start
//
// app_heap_break and mem_start are given to us by the kernel. The stack size is
// determined using pointer app_start, and is used with mem_start to compute
// stack_start (stacktop). The placement of .data and .bss are given to us by
// the linker script; the heap is located between the end of .bss and
// app_heap_break. This requires that .bss is the last (highest-address) section
// placed by the linker script.
/// Tock programs' entry point. Called by the kernel at program start. Sets up
/// the stack then calls rust_start() for the remainder of setup.
#[cfg(target_arch = "arm")]
#[doc(hidden)]
#[no_mangle]
#[naked]
#[link_section = ".start"]
pub unsafe extern "C" fn _start(
app_start: usize,
mem_start: usize,
_memory_len: usize,
app_heap_break: usize,
) -> ! {
asm!("
// Because ROPI-RWPI support in LLVM/rustc is incomplete, Rust
// applications must be statically linked. An offset between the
// location the program is linked at and its actual location in flash
// would cause references in .data and .rodata to point to the wrong
// data. To mitigate this, this section checks that .text (and .start)
// are loaded at the correct location. If the application was linked and
// loaded correctly, the location of the first instruction (read using
// the Program Counter) will match the intended location of .start. We
// don't have an easy way to signal an error, so for now we just yield
// if the location is wrong.
sub r4, pc, #4 // r4 = pc
ldr r5, =.start // r5 = address of .start
cmp r4, r5
beq .Lstack_init // Jump to stack initialization if pc was correct
.Lyield_loop:
svc 0 // yield() syscall
b .Lyield_loop
.Lstack_init:
// Compute the stacktop (stack_start). The stacktop is computed as
// stack_size + mem_start plus padding to align the stack to a multiple
// of 8 bytes. The 8 byte alignment is to follow ARM AAPCS:
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka4127.html
ldr r4, [r0, #36] // r4 = app_start->stack_size
add r4, r4, r1 // r4 = app_start->stack_size + mem_start
add r4, #7 // r4 = app_start->stack_size + mem_start + 7
bic r4, r4, #7 // r4 = (app_start->stack_size + mem_start + 7) & ~0x7
mov sp, r4 // sp = r4
// We need to pass app_start, stacktop and app_heap_break to rust_start.
// Temporarily store them in r6, r7 and r8
mov r6, r0
mov r7, sp
// Debug support, tell the kernel the stack location
//
// memop(10, stacktop)
// r7 contains stacktop
mov r0, #10
mov r1, r7
svc 4
// Debug support, tell the kernel the heap_start location
mov r0, r6
ldr r4, [r0, #24] // r4 = app_start->bss_start
ldr r5, [r0, #28] // r5 = app_start->bss_size
add r4, r4, r5 // r4 = bss_start + bss_size
//
// memop(11, r4)
mov r0, #11
mov r1, r4
svc 4
// Store heap_start (and soon to be app_heap_break) in r8
mov r8, r4
// There is a possibility that stack + .data + .bss is greater than
// 3072. Therefore setup the initial app_heap_break to heap_start (that
// is zero initial heap) and let rust_start determine where the actual
// app_heap_break should go.
//
// Also, because app_heap_break is where the unprivileged MPU region
// ends, in case mem_start + stack + .data + .bss is greater than
// initial app_heap_break (mem_start + 3072), we will get a memory fault
// in rust_start when initializing .data and .bss. Setting
// app_heap_break to heap_start avoids that.
// memop(0, r8)
mov r0, #0
mov r1, r8
svc 4
// NOTE: If there is a hard-fault before this point, then
// process_detail_fmt in kernel/src/process.rs panics which
// will result in us losing the PC of the instruction
// generating the hard-fault. Therefore any code before
// this point is critical code
// Setup parameters needed by rust_start
// r6 (app_start), r7 (stacktop), r8 (app_heap_break)
mov r0, r6
mov r1, r7
mov r2, r8
// Call rust_start
bl rust_start"
: // No output operands
: "{r0}"(app_start), "{r1}"(mem_start), "{r3}"(app_heap_break) // Input operands
: "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r12",
"cc", "memory" // Clobbers
: "volatile" // Options
);
intrinsics::unreachable();
}
/// Tock programs' entry point. Called by the kernel at program start. Sets up
/// the stack then calls rust_start() for the remainder of setup.
#[cfg(target_arch = "riscv32")]
#[doc(hidden)]
#[naked]
#[no_mangle]
#[link_section = ".start"]
// The args for this function are:
// app_start: usize,
// mem_start: usize,
// memory_len: usize,
// app_heap_break: usize,
// Due to Rust issue: https://github.com/rust-lang/rust/issues/42779 we can't have
// args to the function
pub unsafe extern "C" fn _start() -> ! {
asm!(
// Compute the stack top.
//
// struct hdr* myhdr = (struct hdr*) app_start;
// uint32_t stacktop = (((uint32_t) mem_start + myhdr->stack_size + 7) & 0xfffffff8);
"lw t0, 36(a0) // t0 = myhdr->stack_size
addi t0, t0, 7 // t0 = myhdr->stack_size + 7
add t0, t0, a1 // t0 = mem_start + myhdr->stack_size + 7
li t1, 7 // t1 = 7
not t1, t1 // t1 = ~0x7
and t0, t0, t1 // t0 = (mem_start + myhdr->stack_size + 7) & ~0x7
//
// Compute the app data size and where initial app brk should go.
// This includes the GOT, data, and BSS sections. However, we can't be sure
// the linker puts them back-to-back, but we do assume that BSS is last
// (i.e. myhdr->got_start < myhdr->bss_start && myhdr->data_start <
// myhdr->bss_start). With all of that true, then the size is equivalent
// to the end of the BSS section.
//
// uint32_t appdata_size = myhdr->bss_start + myhdr->bss_size;
lw t1, 24(a0) // t1 = myhdr->bss_start
lw t2, 28(a0) // t2 = myhdr->bss_size
lw t3, 4(a0) // t3 = myhdr->got_start
add t1, t1, t2 // t1 = bss_start + bss_size
//
// Move arguments we need to keep over to callee-saved locations.
mv s0, a0 // s0 = void* app_start
mv s1, t0 // s1 = stack_top
//
// Now we may want to move the stack pointer. If the kernel set the
// `app_heap_break` larger than we need (and we are going to call `brk()`
// to reduce it) then our stack pointer will fit and we can move it now.
// Otherwise after the first syscall (the memop to set the brk), the return
// will use a stack that is outside of the process accessible memory.
//
add t2, t0, t1 // t2 = stacktop + appdata_size
bgt t2, a3, skip_set_sp // Compare `app_heap_break` with new brk.
// If our current `app_heap_break` is larger
// then we need to move the stack pointer
// before we call the `brk` syscall.
mv sp, t0 // Update the stack pointer
skip_set_sp: // Back to regularly scheduled programming.
// Call `brk` to set to requested memory
// memop(0, stacktop + appdata_size);
li a0, 4 // a0 = 4 // memop syscall
li a1, 0 // a1 = 0
mv a2, t2 // a2 = stacktop + appdata_size
ecall // memop
//
// Debug support, tell the kernel the stack location
//
// memop(10, stacktop);
li a0, 4 // a0 = 4 // memop syscall
li a1, 10 // a1 = 10
mv a2, s1 // a2 = stacktop
ecall // memop
//
// Debug support, tell the kernel the heap location
//
// memop(11, stacktop + appdata_size);
li a0, 4 // a0 = 4 // memop syscall
li a1, 11 // a1 = 10
mv a2, t2 // a2 = stacktop + appdata_size
ecall // memop
//
// Setup initial stack pointer for normal execution
// Call into the rest of startup. This should never return.
mv sp, s1 // sp = stacktop
mv a0, s0 // first arg is app_start
mv s0, sp // Set the frame pointer to sp.
mv a1, s1 // second arg is stacktop
mv a1, s1 // second arg is stackto
jal rust_start"
: // No output operands
:
: "memory", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
"t0", "t1", "t2", "t3", "t4", "t5", "t6", "ra" // Clobbers
: "volatile" // Options
);
intrinsics::unreachable();
}
/// Ensure an abort symbol exists.
#[cfg(target_arch = "riscv32")]
#[link_section = ".start"]
#[export_name = "abort"]
pub extern "C" fn abort() {
unsafe {
asm! ("
// Simply go back to the start as if we had just booted.
j _start
"
:
:
:
: "volatile");
}
}
/// The header encoded at the beginning of .text by the linker script. It is
/// accessed by rust_start() using its app_start parameter.
#[repr(C)]
struct LayoutHeader {
got_sym_start: usize,
got_start: usize,
got_size: usize,
data_sym_start: usize,
data_start: usize,
data_size: usize,
bss_start: usize,
bss_size: usize,
reldata_start: usize,
stack_size: usize,
}
/// Rust setup, called by _start. Uses the extern "C" calling convention so that
/// the assembly in _start knows how to call it (the Rust ABI is not defined).
/// Sets up the data segment (including relocations) and the heap, then calls
/// into the rustc-generated main(). This cannot use mutable global variables or
/// global references to globals until it is done setting up the data segment.
#[no_mangle]
pub unsafe extern "C" fn rust_start(app_start: usize, stacktop: usize, app_heap_break: usize) -> ! {
extern "C" {
// This function is created internally by `rustc`. See
// `src/lang_items.rs` for more details.
fn main(argc: isize, argv: *const *const u8) -> isize;
}
// Copy .data into its final location in RAM (determined by the linker
// script -- should be immediately above the stack).
let layout_header: &LayoutHeader = core::mem::transmute(app_start);
let data_flash_start_addr = app_start + layout_header.data_sym_start;
intrinsics::copy_nonoverlapping(
data_flash_start_addr as *const u8,
stacktop as *mut u8,
layout_header.data_size,
);
// Zero .bss (specified by the linker script).
let bss_end = layout_header.bss_start + layout_header.bss_size; // 1 past the end of .bss
for i in layout_header.bss_start..bss_end {
core::ptr::write(i as *mut u8, 0);
}
// TODO: Wait for rustc to have working ROPI-RWPI relocation support, then
// implement dynamic relocations here. At the moment, rustc does not have
// working ROPI-RWPI support, and it is not clear what that support would
// look like at the LLVM level. Once we know what the relocation strategy
// looks like we can write the dynamic linker.
// Initialize the heap. Unlike libtock-c's newlib allocator, which can use
// `sbrk` system call to dynamically request heap memory from the kernel, we
// need to tell `linked_list_allocator` where the heap starts and ends.
//
// Heap size is set using `elf2tab` with `--app-heap` option, which is
// currently at 1024. If you change the `elf2tab` heap size, make sure to
// make the corresponding change here.
const HEAP_SIZE: usize = 1024;
// we could have also bss_end for app_heap_start
let app_heap_start = app_heap_break;
let app_heap_end = app_heap_break + HEAP_SIZE;
// tell the kernel the new app heap break
syscalls::memop(0, app_heap_end);
ALLOCATOR.lock().init(app_heap_start, HEAP_SIZE);
main(0, ptr::null());
loop {
syscalls::yieldk();
}
}