Merge pull request #88 from alistair23/alistair/riscv32

riscv32: Initial support of RISC-V
diff --git a/.cargo/config b/.cargo/config
index 10d0ff2..973dfb5 100644
--- a/.cargo/config
+++ b/.cargo/config
@@ -5,3 +5,10 @@
   "-C", "relocation-model=static",
   "-D", "warnings",
 ]
+
+[target.riscv32imac-unknown-none-elf]
+rustflags = [
+  "-C", "link-arg=-Triscv32_layout.ld",
+  "-C", "relocation-model=static",
+  "-D", "warnings",
+]
diff --git a/riscv32_layout.ld b/riscv32_layout.ld
new file mode 100644
index 0000000..6a37365
--- /dev/null
+++ b/riscv32_layout.ld
@@ -0,0 +1,16 @@
+/* Layout for the RISC-V 32 boards, used by the examples in this repository. */
+
+MEMORY {
+  FLASH (rx) : ORIGIN = 0x20430000, LENGTH = 32M
+  SRAM (rwx) : ORIGIN = 0x80000000, LENGTH = 512K
+}
+
+/*
+ * Any change to STACK_SIZE should be accompanied by a corresponding change to
+ * `elf2tab`'s `--stack` option
+ */
+STACK_SIZE = 2048;
+
+MPU_MIN_ALIGN = 1K;
+
+INCLUDE layout.ld
diff --git a/src/debug.rs b/src/debug.rs
index 24b2188..c35a3b7 100644
--- a/src/debug.rs
+++ b/src/debug.rs
@@ -13,6 +13,7 @@
     Console::new().write(buffer);
 }
 
+#[cfg(target_arch = "arm")]
 pub fn print_stack_pointer() {
     let stack_pointer;
     unsafe { asm!("mov $0, sp" : "=r"(stack_pointer) : : : "volatile") };
@@ -23,6 +24,9 @@
     Console::new().write(buffer);
 }
 
+#[cfg(target_arch = "riscv32")]
+pub fn print_stack_pointer() {}
+
 #[inline(always)] // Initial stack size is too small (128 bytes currently)
 pub fn dump_address(address: *const usize) {
     let mut buffer = [b' '; 28];
diff --git a/src/entry_point.rs b/src/entry_point.rs
index 6a96eb1..9942b19 100644
--- a/src/entry_point.rs
+++ b/src/entry_point.rs
@@ -58,6 +58,7 @@
 
 /// 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]
@@ -164,6 +165,123 @@
     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)]
diff --git a/src/lib.rs b/src/lib.rs
index 9b0888d..8047028 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -22,19 +22,19 @@
 pub mod timer;
 pub mod unwind_symbols;
 
-#[cfg(target_arch = "arm")]
+#[cfg(any(target_arch = "arm", target_arch = "riscv32"))]
 pub mod entry_point;
 
-#[cfg(target_arch = "arm")]
+#[cfg(any(target_arch = "arm", target_arch = "riscv32"))]
 mod lang_items;
 
-#[cfg(target_arch = "arm")]
+#[cfg(any(target_arch = "arm", target_arch = "riscv32"))]
 pub mod syscalls;
 
-#[cfg(not(target_arch = "arm"))]
+#[cfg(not(any(target_arch = "arm", target_arch = "riscv32")))]
 #[path = "syscalls_mock.rs"]
 mod syscalls;
 
-#[cfg(target_arch = "arm")]
+#[cfg(any(target_arch = "arm", target_arch = "riscv32"))]
 #[global_allocator]
 static ALLOCATOR: linked_list_allocator::LockedHeap = linked_list_allocator::LockedHeap::empty();
diff --git a/src/syscalls.rs b/src/syscalls.rs
index fa0362a..049d102 100644
--- a/src/syscalls.rs
+++ b/src/syscalls.rs
@@ -2,6 +2,7 @@
 use crate::callback::SubscribableCallback;
 use crate::shared_memory::SharedMemory;
 
+#[cfg(target_arch = "arm")]
 pub fn yieldk() {
     // Note: A process stops yielding when there is a callback ready to run,
     // which the kernel executes by modifying the stack frame pushed by the
@@ -35,6 +36,21 @@
     }
 }
 
+#[cfg(target_arch = "riscv32")]
+pub fn yieldk() {
+    /* TODO: Stop yielding */
+    unsafe {
+        asm! (
+            "li    a0, 0
+            ecall"
+            :
+            :
+            : "memory", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
+            "t0", "t1", "t2", "t3", "t4", "t5", "t6", "ra"
+            : "volatile");
+    }
+}
+
 pub fn yieldk_for<F: Fn() -> bool>(cond: F) {
     while !cond() {
         yieldk();
@@ -72,6 +88,7 @@
     callback.call_rust(arg0, arg1, arg2);
 }
 
+#[cfg(target_arch = "arm")]
 pub unsafe fn subscribe_ptr(
     major: usize,
     minor: usize,
@@ -86,6 +103,24 @@
     res
 }
 
+#[cfg(target_arch = "riscv32")]
+pub unsafe fn subscribe_ptr(
+    major: usize,
+    minor: usize,
+    cb: *const unsafe extern "C" fn(usize, usize, usize, usize),
+    ud: usize,
+) -> isize {
+    let res;
+    asm!("li    a0, 1
+          ecall"
+         : "=r" (res)
+         : "r" (major), "r" (minor), "r" (cb), "r" (ud)
+         : "memory"
+         : "volatile" );
+    res
+}
+
+#[cfg(target_arch = "arm")]
 pub unsafe fn command(major: usize, minor: usize, arg1: usize, arg2: usize) -> isize {
     let res;
     asm!("svc 2" : "={r0}"(res)
@@ -95,6 +130,18 @@
     res
 }
 
+#[cfg(target_arch = "riscv32")]
+pub unsafe fn command(major: usize, minor: usize, arg1: usize, arg2: usize) -> isize {
+    let res;
+    asm!("li    a0, 2
+          ecall"
+         : "=r" (res)
+         : "r" (major), "r" (minor), "r" (arg1), "r" (arg2)
+         : "memory"
+         : "volatile");
+    res
+}
+
 pub fn allow(
     driver_number: usize,
     allow_number: usize,
@@ -120,6 +167,7 @@
     }
 }
 
+#[cfg(target_arch = "arm")]
 pub unsafe fn allow_ptr(major: usize, minor: usize, slice: *mut u8, len: usize) -> isize {
     let res;
     asm!("svc 3" : "={r0}"(res)
@@ -129,6 +177,19 @@
     res
 }
 
+#[cfg(target_arch = "riscv32")]
+pub unsafe fn allow_ptr(major: usize, minor: usize, slice: *mut u8, len: usize) -> isize {
+    let res;
+    asm!("li    a0, 3
+          ecall"
+         : "=r" (res)
+         : "r" (major), "r" (minor), "r" (slice), "r" (len)
+         : "memory"
+         : "volatile");
+    res
+}
+
+#[cfg(target_arch = "arm")]
 pub unsafe fn memop(major: u32, arg1: usize) -> isize {
     let res;
     asm!("svc 4" : "={r0}"(res)
@@ -137,3 +198,15 @@
                  : "volatile");
     res
 }
+
+#[cfg(target_arch = "riscv32")]
+pub unsafe fn memop(major: u32, arg1: usize) -> isize {
+    let res;
+    asm!("li    a0, 4
+          ecall"
+         : "=r" (res)
+         : "r" (major), "r" (arg1)
+         : "memory"
+         : "volatile");
+    res
+}