Merge #275 #276

275: Add `libtock_runtime` startup code. r=alistair23 a=jrvanwhy

This includes:

1. `rust_start`: The first Rust code to run in a process
1. `set_main!`: Used to specify `main` in a process binary
1. `stack_size!`: Used to specify the stack size in a process binary
1. Documentation of the startup sequence, including the 3 above steps.

The documentation refers to a two things that haven't been implemented yet:
1. Debug syscalls performed by `rust_start` -- unimplemented because `memop` is
   still unimplemented.
1. The `exit` system call.

276: Add a tock2 submodule with a tock-2.0-dev kernel. r=alistair23 a=jrvanwhy

I intend to use this kernel in integration tests for the Tock 2.0 crates. When we are ready to remove Tock 1.x support, I will remove the duplicate submodule.

Co-authored-by: Johnathan Van Why <jrvanwhy@google.com>
diff --git a/.cargo/config b/.cargo/config
index 96fe275..8292c1c 100644
--- a/.cargo/config
+++ b/.cargo/config
@@ -6,10 +6,6 @@
 rthumbv7em = "run --release --target=thumbv7em-none-eabi --example"
 rtv7em = "rthumbv7em"
 
-# Deny warnings on all architectures. build.rustflags cannot be used here as the lower section would override its effect.
-[target.'cfg(all())']
-rustflags = ["-D", "warnings"]
-
 # Common settings for all embedded targets
 [target.'cfg(any(target_arch = "arm", target_arch = "riscv32"))']
 rustflags = [
diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml
index 94a8b10..d394011 100644
--- a/.github/workflows/artifacts.yml
+++ b/.github/workflows/artifacts.yml
@@ -12,6 +12,7 @@
 
       - name: Install dependencies
         run: |
+          sudo apt-get install binutils-riscv64-unknown-elf
           cargo install elf2tab --version 0.4.0
 
       - name: Build Hello World
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5bdae5f..e9b448b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,9 +16,9 @@
     # Using ubuntu-latest can cause breakage when ubuntu-latest is updated to
     # point at a new Ubuntu version. Instead, explicitly specify the version, so
     # we can update when we need to. This *could* break if we don't update it
-    # until support for 18.04 is dropped, but it is likely we'll have a reason
+    # until support for 20.04 is dropped, but it is likely we'll have a reason
     # to update to a newer Ubuntu before then anyway.
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
 
     steps:
       # Clones a single commit from the libtock-rs repository. The commit cloned
@@ -35,8 +35,16 @@
       # makefile can be tested locally. We experimentally determined that -j2 is
       # optimal for the Azure Standard_DS2_v2 VM, which is the VM type used by
       # GitHub Actions at the time of this writing.
+      #
+      # We have to append the "-D warnings" flag to .cargo/config rather than
+      # using the RUSTFLAGS environment variable because if we set RUSTFLAGS
+      # cargo will ignore the rustflags config in .cargo/config, breaking
+      # relocation.
       - name: Build and Test
         run: |
+          sudo apt-get install binutils-riscv64-unknown-elf
           cd "${GITHUB_WORKSPACE}"
+          echo "[target.'cfg(all())']" >> .cargo/config
+          echo 'rustflags = ["-D", "warnings"]' >> .cargo/config
           make -j2 setup
           make -j2 test
diff --git a/.github/workflows/mac-os.yml b/.github/workflows/mac-os.yml
new file mode 100644
index 0000000..c813ace
--- /dev/null
+++ b/.github/workflows/mac-os.yml
@@ -0,0 +1,27 @@
+# This workflow verifies libtock-rs is usable on Mac OS.
+
+name: ci-mac-os
+
+# We run this workflow during pull request review, but not for Bors merges, as
+# it takes over an hour to run.
+on: pull_request
+
+jobs:
+  ci-mac-os:
+    runs-on: macos-10.15
+
+    steps:
+      # Clones a single commit from the libtock-rs repository. The commit cloned
+      # is a merge commit between the PR's target branch and the PR's source.
+      - name: Clone repository
+        uses: actions/checkout@v2.3.0
+
+      # Install the toolchains we need, then run `cargo build`.
+      - name: Build and Test
+        run: |
+          brew tap riscv/riscv
+          brew update
+          brew install riscv-gnu-toolchain --with-multilib
+          cd "${GITHUB_WORKSPACE}"
+          LIBTOCK_PLATFORM=hifive1 cargo build -p libtock_runtime \
+            --target=riscv32imac-unknown-none-elf
diff --git a/.github/workflows/size-diff.yml b/.github/workflows/size-diff.yml
index cbf2939..33a0870 100644
--- a/.github/workflows/size-diff.yml
+++ b/.github/workflows/size-diff.yml
@@ -17,9 +17,9 @@
     # Using ubuntu-latest can cause breakage when ubuntu-latest is updated to
     # point at a new Ubuntu version. Instead, explicitly specify the version, so
     # we can update when we need to. This *could* break if we don't update it
-    # until support for 18.04 is dropped, but it is likely we'll have a reason
+    # until support for 20.04 is dropped, but it is likely we'll have a reason
     # to update to a newer Ubuntu before then anyway.
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
 
     steps:
       # Clones a single commit from the libtock-rs repository. The commit cloned
@@ -39,6 +39,7 @@
       # master.
       - name: Compute sizes
         run: |
+          sudo apt-get install binutils-riscv64-unknown-elf
           UPSTREAM_REMOTE_NAME="${UPSTREAM_REMOTE_NAME:-origin}"
           GITHUB_BASE_REF="${GITHUB_BASE_REF:-master}"
           cd "${GITHUB_WORKSPACE}"
diff --git a/.gitmodules b/.gitmodules
index 45ddcde..d21db5e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,6 @@
 [submodule "tock"]
 	path = tock
 	url = https://github.com/tock/tock.git
+[submodule "tock2"]
+	path = tock2
+	url = https://github.com/tock/tock.git
diff --git a/Makefile b/Makefile
index da40d28..a37e40c 100644
--- a/Makefile
+++ b/Makefile
@@ -54,6 +54,14 @@
 	$(MAKE) -C tock/boards/hifive1 \
 		$(CURDIR)/tock/target/riscv32imac-unknown-none-elf/release/hifive1.elf
 
+# Builds a Tock 2.0 kernel for the HiFive board for use by QEMU tests.
+# TODO: After Tock 2.0 is released, we should merge the tock/ and tock2/
+# submodules and only build Tock 2.0 kernels.
+.PHONY: kernel-hifive-2
+kernel-hifive-2:
+	$(MAKE) -C tock2/boards/hifive1 \
+		$(CURDIR)/tock2/target/riscv32imac-unknown-none-elf/release/hifive1.elf
+
 # Prints out the sizes of the example binaries.
 .PHONY: print-sizes
 print-sizes: examples
@@ -79,8 +87,9 @@
 .PHONY: test
 test: examples test-qemu-hifive
 	LIBTOCK_PLATFORM=nrf52 PLATFORM=nrf52 cargo fmt --all -- --check
-	LIBTOCK_PLATFORM=nrf52 PLATFORM=nrf52 cargo clippy --workspace --all-targets
-	LIBTOCK_PLATFORM=nrf52 PLATFORM=nrf52 cargo miri test --workspace
+	PLATFORM=nrf52 cargo clippy --all-targets --exclude libtock_runtime --workspace
+	LIBTOCK_PLATFORM=hifive1 cargo clippy --target=riscv32imac-unknown-none-elf -p libtock_runtime
+	PLATFORM=nrf52 cargo miri test --exclude libtock_runtime --workspace
 	echo '[ SUCCESS ] libtock-rs tests pass'
 
 .PHONY: analyse-stack-sizes
diff --git a/core/platform/src/command_return.rs b/core/platform/src/command_return.rs
index 7fa7302..9d84572 100644
--- a/core/platform/src/command_return.rs
+++ b/core/platform/src/command_return.rs
@@ -1,22 +1,43 @@
 use crate::{return_variant, ErrorCode, ReturnVariant};
 
+use core::mem::transmute;
+
 /// The response type from `command`. Can represent a successful value or a
 /// failure.
 #[derive(Clone, Copy)]
 pub struct CommandReturn {
-    pub(crate) return_variant: ReturnVariant,
+    return_variant: ReturnVariant,
     // r1, r2, and r3 should only contain 32-bit values. However, these are
     // converted directly from usizes returned by RawSyscalls::four_arg_syscall.
     // To avoid casting twice (both when converting to a Command Return and when
     // calling a get_*() function), we store the usizes directly. Then using the
     // CommandReturn only involves one conversion for each of r1, r2, and r3,
     // performed in the get_*() functions.
-    pub(crate) r1: usize,
-    pub(crate) r2: usize,
-    pub(crate) r3: usize,
+
+    // Safety invariant on r1: If return_variant is failure variant, r1 must be
+    // a valid ErrorCode.
+    r1: usize,
+    r2: usize,
+    r3: usize,
 }
 
 impl CommandReturn {
+    /// # Safety
+    /// If return_variant is a failure variant, r1 must be a valid ErrorCode.
+    #[cfg(test)] // Will be removed when command() is implemented.
+    pub(crate) unsafe fn new(
+        return_variant: ReturnVariant,
+        r1: usize,
+        r2: usize,
+        r3: usize,
+    ) -> Self {
+        CommandReturn {
+            return_variant,
+            r1,
+            r2,
+            r3,
+        }
+    }
     // I generally expect CommandReturn to be used with pattern matching, e.g.:
     //
     //     let command_return = Syscalls::command(314, 1, 1, 2);
@@ -85,7 +106,7 @@
         if !self.is_failure() {
             return None;
         }
-        Some(self.r1.into())
+        Some(unsafe { transmute(self.r1 as u16) })
     }
 
     /// Returns the error code and value if this CommandReturn is of type
@@ -94,7 +115,7 @@
         if !self.is_failure_u32() {
             return None;
         }
-        Some((self.r1.into(), self.r2 as u32))
+        Some((unsafe { transmute(self.r1 as u16) }, self.r2 as u32))
     }
 
     /// Returns the error code and return values if this CommandReturn is of
@@ -103,7 +124,11 @@
         if !self.is_failure_2_u32() {
             return None;
         }
-        Some((self.r1.into(), self.r2 as u32, self.r3 as u32))
+        Some((
+            unsafe { transmute(self.r1 as u16) },
+            self.r2 as u32,
+            self.r3 as u32,
+        ))
     }
 
     /// Returns the error code and return value if this CommandReturn is of type
@@ -112,7 +137,10 @@
         if !self.is_failure_u64() {
             return None;
         }
-        Some((self.r1.into(), self.r2 as u64 + ((self.r3 as u64) << 32)))
+        Some((
+            unsafe { transmute(self.r1 as u16) },
+            self.r2 as u64 + ((self.r3 as u64) << 32),
+        ))
     }
 
     /// Returns the value if this CommandReturn is of type Success with u32.
diff --git a/core/platform/src/command_return_tests.rs b/core/platform/src/command_return_tests.rs
index 1712f71..439f9e7 100644
--- a/core/platform/src/command_return_tests.rs
+++ b/core/platform/src/command_return_tests.rs
@@ -1,12 +1,14 @@
-use crate::{error_code, return_variant, CommandReturn};
+use crate::{return_variant, CommandReturn, ErrorCode};
 
 #[test]
 fn failure() {
-    let command_return = CommandReturn {
-        return_variant: return_variant::FAILURE,
-        r1: error_code::RESERVE.into(),
-        r2: 1002,
-        r3: 1003,
+    let command_return = unsafe {
+        CommandReturn::new(
+            return_variant::FAILURE,
+            ErrorCode::Reserve as usize,
+            1002,
+            1003,
+        )
     };
     assert_eq!(command_return.is_failure(), true);
     assert_eq!(command_return.is_failure_u32(), false);
@@ -18,7 +20,7 @@
     assert_eq!(command_return.is_success_u64(), false);
     assert_eq!(command_return.is_success_3_u32(), false);
     assert_eq!(command_return.is_success_u32_u64(), false);
-    assert_eq!(command_return.get_failure(), Some(error_code::RESERVE));
+    assert_eq!(command_return.get_failure(), Some(ErrorCode::Reserve));
     assert_eq!(command_return.get_failure_u32(), None);
     assert_eq!(command_return.get_failure_2_u32(), None);
     assert_eq!(command_return.get_failure_u64(), None);
@@ -32,11 +34,13 @@
 
 #[test]
 fn failure_u32() {
-    let command_return = CommandReturn {
-        return_variant: return_variant::FAILURE_U32,
-        r1: error_code::OFF.into(),
-        r2: 1002,
-        r3: 1003,
+    let command_return = unsafe {
+        CommandReturn::new(
+            return_variant::FAILURE_U32,
+            ErrorCode::Off as usize,
+            1002,
+            1003,
+        )
     };
     assert_eq!(command_return.is_failure(), false);
     assert_eq!(command_return.is_failure_u32(), true);
@@ -51,7 +55,7 @@
     assert_eq!(command_return.get_failure(), None);
     assert_eq!(
         command_return.get_failure_u32(),
-        Some((error_code::OFF, 1002))
+        Some((ErrorCode::Off, 1002))
     );
     assert_eq!(command_return.get_failure_2_u32(), None);
     assert_eq!(command_return.get_failure_u64(), None);
@@ -65,11 +69,13 @@
 
 #[test]
 fn failure_2_u32() {
-    let command_return = CommandReturn {
-        return_variant: return_variant::FAILURE_2_U32,
-        r1: error_code::ALREADY.into(),
-        r2: 1002,
-        r3: 1003,
+    let command_return = unsafe {
+        CommandReturn::new(
+            return_variant::FAILURE_2_U32,
+            ErrorCode::Already as usize,
+            1002,
+            1003,
+        )
     };
     assert_eq!(command_return.is_failure(), false);
     assert_eq!(command_return.is_failure_u32(), false);
@@ -85,7 +91,7 @@
     assert_eq!(command_return.get_failure_u32(), None);
     assert_eq!(
         command_return.get_failure_2_u32(),
-        Some((error_code::ALREADY, 1002, 1003))
+        Some((ErrorCode::Already, 1002, 1003))
     );
     assert_eq!(command_return.get_failure_u64(), None);
     assert_eq!(command_return.get_success_u32(), None);
@@ -101,11 +107,13 @@
 
 #[test]
 fn failure_u64() {
-    let command_return = CommandReturn {
-        return_variant: return_variant::FAILURE_U64,
-        r1: error_code::BUSY.into(),
-        r2: 0x00001002,
-        r3: 0x00001003,
+    let command_return = unsafe {
+        CommandReturn::new(
+            return_variant::FAILURE_U64,
+            ErrorCode::Busy as usize,
+            0x1002,
+            0x1003,
+        )
     };
     assert_eq!(command_return.is_failure(), false);
     assert_eq!(command_return.is_failure_u32(), false);
@@ -122,7 +130,7 @@
     assert_eq!(command_return.get_failure_2_u32(), None);
     assert_eq!(
         command_return.get_failure_u64(),
-        Some((error_code::BUSY, 0x00001003_00001002))
+        Some((ErrorCode::Busy, 0x00001003_00001002))
     );
     assert_eq!(command_return.get_success_u32(), None);
     assert_eq!(command_return.get_success_2_u32(), None);
@@ -134,12 +142,7 @@
 
 #[test]
 fn success() {
-    let command_return = CommandReturn {
-        return_variant: return_variant::SUCCESS,
-        r1: 1001,
-        r2: 1002,
-        r3: 1003,
-    };
+    let command_return = unsafe { CommandReturn::new(return_variant::SUCCESS, 1001, 1002, 1003) };
     assert_eq!(command_return.is_failure(), false);
     assert_eq!(command_return.is_failure_u32(), false);
     assert_eq!(command_return.is_failure_2_u32(), false);
@@ -164,12 +167,8 @@
 
 #[test]
 fn success_u32() {
-    let command_return = CommandReturn {
-        return_variant: return_variant::SUCCESS_U32,
-        r1: 1001,
-        r2: 1002,
-        r3: 1003,
-    };
+    let command_return =
+        unsafe { CommandReturn::new(return_variant::SUCCESS_U32, 1001, 1002, 1003) };
     assert_eq!(command_return.is_failure(), false);
     assert_eq!(command_return.is_failure_u32(), false);
     assert_eq!(command_return.is_failure_2_u32(), false);
@@ -194,12 +193,8 @@
 
 #[test]
 fn success_2_u32() {
-    let command_return = CommandReturn {
-        return_variant: return_variant::SUCCESS_2_U32,
-        r1: 1001,
-        r2: 1002,
-        r3: 1003,
-    };
+    let command_return =
+        unsafe { CommandReturn::new(return_variant::SUCCESS_2_U32, 1001, 1002, 1003) };
     assert_eq!(command_return.is_failure(), false);
     assert_eq!(command_return.is_failure_u32(), false);
     assert_eq!(command_return.is_failure_2_u32(), false);
@@ -227,12 +222,8 @@
 
 #[test]
 fn success_u64() {
-    let command_return = CommandReturn {
-        return_variant: return_variant::SUCCESS_U64,
-        r1: 0x00001001,
-        r2: 0x00001002,
-        r3: 1003,
-    };
+    let command_return =
+        unsafe { CommandReturn::new(return_variant::SUCCESS_U64, 0x1001, 0x1002, 1003) };
     assert_eq!(command_return.is_failure(), false);
     assert_eq!(command_return.is_failure_u32(), false);
     assert_eq!(command_return.is_failure_2_u32(), false);
@@ -257,12 +248,8 @@
 
 #[test]
 fn success_3_u32() {
-    let command_return = CommandReturn {
-        return_variant: return_variant::SUCCESS_3_U32,
-        r1: 1001,
-        r2: 1002,
-        r3: 1003,
-    };
+    let command_return =
+        unsafe { CommandReturn::new(return_variant::SUCCESS_3_U32, 1001, 1002, 1003) };
     assert_eq!(command_return.is_failure(), false);
     assert_eq!(command_return.is_failure_u32(), false);
     assert_eq!(command_return.is_failure_2_u32(), false);
@@ -290,12 +277,8 @@
 
 #[test]
 fn success_u32_u64() {
-    let command_return = CommandReturn {
-        return_variant: return_variant::SUCCESS_U32_U64,
-        r1: 1001,
-        r2: 0x00001002,
-        r3: 0x00001003,
-    };
+    let command_return =
+        unsafe { CommandReturn::new(return_variant::SUCCESS_U32_U64, 1001, 0x1002, 0x1003) };
     assert_eq!(command_return.is_failure(), false);
     assert_eq!(command_return.is_failure_u32(), false);
     assert_eq!(command_return.is_failure_2_u32(), false);
diff --git a/core/platform/src/error_code.rs b/core/platform/src/error_code.rs
index 63cc34e..276e703 100644
--- a/core/platform/src/error_code.rs
+++ b/core/platform/src/error_code.rs
@@ -1,39 +1,232 @@
-/// A system call error code. This can either be an error code returned by the
-/// kernel or BADRVAL, which indicates the kernel returned the wrong type of
-/// response to a system call.
-// ErrorCode is not an enum so that conversion from the kernel's return value (a
-// `usize` in a register) is free.
+/// An error code returned by the kernel.
 // TODO: derive(Debug) is currently only enabled for test builds, which is
 // necessary so it can be used in assert_eq!. We should develop a lighter-weight
 // Debug implementation and see if it is small enough to enable on non-Debug
 // builds.
 #[cfg_attr(test, derive(Debug))]
 #[derive(Clone, Copy, PartialEq, Eq)]
-pub struct ErrorCode(usize);
+#[repr(u16)]  // To facilitate use with transmute() in CommandReturn
+#[rustfmt::skip]
+pub enum ErrorCode {
+    Fail = 1,
+    Busy = 2,
+    Already = 3,
+    Off = 4,
+    Reserve = 5,
+    Invalid = 6,
+    Size = 7,
+    Cancel = 8,
+    NoMem = 9,
+    NoSupport = 10,
+    NoDevice = 11,
+    Uninstalled = 12,
+    NoAck = 13,
 
-impl From<usize> for ErrorCode {
-    fn from(value: usize) -> ErrorCode {
-        ErrorCode(value)
-    }
+    // Error codes reserved for future use. We have to include these for future
+    // compatibility -- this allows process binaries compiled with this version
+    // of libtock-rs to run on future kernel versions that may return a larger
+    // variety of error codes.
+                                                    N00014 =    14, N00015 =    15,
+    N00016 =    16, N00017 =    17, N00018 =    18, N00019 =    19, N00020 =    20,
+    N00021 =    21, N00022 =    22, N00023 =    23, N00024 =    24, N00025 =    25,
+    N00026 =    26, N00027 =    27, N00028 =    28, N00029 =    29, N00030 =    30,
+    N00031 =    31, N00032 =    32, N00033 =    33, N00034 =    34, N00035 =    35,
+    N00036 =    36, N00037 =    37, N00038 =    38, N00039 =    39, N00040 =    40,
+    N00041 =    41, N00042 =    42, N00043 =    43, N00044 =    44, N00045 =    45,
+    N00046 =    46, N00047 =    47, N00048 =    48, N00049 =    49, N00050 =    50,
+    N00051 =    51, N00052 =    52, N00053 =    53, N00054 =    54, N00055 =    55,
+    N00056 =    56, N00057 =    57, N00058 =    58, N00059 =    59, N00060 =    60,
+    N00061 =    61, N00062 =    62, N00063 =    63, N00064 =    64, N00065 =    65,
+    N00066 =    66, N00067 =    67, N00068 =    68, N00069 =    69, N00070 =    70,
+    N00071 =    71, N00072 =    72, N00073 =    73, N00074 =    74, N00075 =    75,
+    N00076 =    76, N00077 =    77, N00078 =    78, N00079 =    79, N00080 =    80,
+    N00081 =    81, N00082 =    82, N00083 =    83, N00084 =    84, N00085 =    85,
+    N00086 =    86, N00087 =    87, N00088 =    88, N00089 =    89, N00090 =    90,
+    N00091 =    91, N00092 =    92, N00093 =    93, N00094 =    94, N00095 =    95,
+    N00096 =    96, N00097 =    97, N00098 =    98, N00099 =    99, N00100 =   100,
+    N00101 =   101, N00102 =   102, N00103 =   103, N00104 =   104, N00105 =   105,
+    N00106 =   106, N00107 =   107, N00108 =   108, N00109 =   109, N00110 =   110,
+    N00111 =   111, N00112 =   112, N00113 =   113, N00114 =   114, N00115 =   115,
+    N00116 =   116, N00117 =   117, N00118 =   118, N00119 =   119, N00120 =   120,
+    N00121 =   121, N00122 =   122, N00123 =   123, N00124 =   124, N00125 =   125,
+    N00126 =   126, N00127 =   127, N00128 =   128, N00129 =   129, N00130 =   130,
+    N00131 =   131, N00132 =   132, N00133 =   133, N00134 =   134, N00135 =   135,
+    N00136 =   136, N00137 =   137, N00138 =   138, N00139 =   139, N00140 =   140,
+    N00141 =   141, N00142 =   142, N00143 =   143, N00144 =   144, N00145 =   145,
+    N00146 =   146, N00147 =   147, N00148 =   148, N00149 =   149, N00150 =   150,
+    N00151 =   151, N00152 =   152, N00153 =   153, N00154 =   154, N00155 =   155,
+    N00156 =   156, N00157 =   157, N00158 =   158, N00159 =   159, N00160 =   160,
+    N00161 =   161, N00162 =   162, N00163 =   163, N00164 =   164, N00165 =   165,
+    N00166 =   166, N00167 =   167, N00168 =   168, N00169 =   169, N00170 =   170,
+    N00171 =   171, N00172 =   172, N00173 =   173, N00174 =   174, N00175 =   175,
+    N00176 =   176, N00177 =   177, N00178 =   178, N00179 =   179, N00180 =   180,
+    N00181 =   181, N00182 =   182, N00183 =   183, N00184 =   184, N00185 =   185,
+    N00186 =   186, N00187 =   187, N00188 =   188, N00189 =   189, N00190 =   190,
+    N00191 =   191, N00192 =   192, N00193 =   193, N00194 =   194, N00195 =   195,
+    N00196 =   196, N00197 =   197, N00198 =   198, N00199 =   199, N00200 =   200,
+    N00201 =   201, N00202 =   202, N00203 =   203, N00204 =   204, N00205 =   205,
+    N00206 =   206, N00207 =   207, N00208 =   208, N00209 =   209, N00210 =   210,
+    N00211 =   211, N00212 =   212, N00213 =   213, N00214 =   214, N00215 =   215,
+    N00216 =   216, N00217 =   217, N00218 =   218, N00219 =   219, N00220 =   220,
+    N00221 =   221, N00222 =   222, N00223 =   223, N00224 =   224, N00225 =   225,
+    N00226 =   226, N00227 =   227, N00228 =   228, N00229 =   229, N00230 =   230,
+    N00231 =   231, N00232 =   232, N00233 =   233, N00234 =   234, N00235 =   235,
+    N00236 =   236, N00237 =   237, N00238 =   238, N00239 =   239, N00240 =   240,
+    N00241 =   241, N00242 =   242, N00243 =   243, N00244 =   244, N00245 =   245,
+    N00246 =   246, N00247 =   247, N00248 =   248, N00249 =   249, N00250 =   250,
+    N00251 =   251, N00252 =   252, N00253 =   253, N00254 =   254, N00255 =   255,
+    N00256 =   256, N00257 =   257, N00258 =   258, N00259 =   259, N00260 =   260,
+    N00261 =   261, N00262 =   262, N00263 =   263, N00264 =   264, N00265 =   265,
+    N00266 =   266, N00267 =   267, N00268 =   268, N00269 =   269, N00270 =   270,
+    N00271 =   271, N00272 =   272, N00273 =   273, N00274 =   274, N00275 =   275,
+    N00276 =   276, N00277 =   277, N00278 =   278, N00279 =   279, N00280 =   280,
+    N00281 =   281, N00282 =   282, N00283 =   283, N00284 =   284, N00285 =   285,
+    N00286 =   286, N00287 =   287, N00288 =   288, N00289 =   289, N00290 =   290,
+    N00291 =   291, N00292 =   292, N00293 =   293, N00294 =   294, N00295 =   295,
+    N00296 =   296, N00297 =   297, N00298 =   298, N00299 =   299, N00300 =   300,
+    N00301 =   301, N00302 =   302, N00303 =   303, N00304 =   304, N00305 =   305,
+    N00306 =   306, N00307 =   307, N00308 =   308, N00309 =   309, N00310 =   310,
+    N00311 =   311, N00312 =   312, N00313 =   313, N00314 =   314, N00315 =   315,
+    N00316 =   316, N00317 =   317, N00318 =   318, N00319 =   319, N00320 =   320,
+    N00321 =   321, N00322 =   322, N00323 =   323, N00324 =   324, N00325 =   325,
+    N00326 =   326, N00327 =   327, N00328 =   328, N00329 =   329, N00330 =   330,
+    N00331 =   331, N00332 =   332, N00333 =   333, N00334 =   334, N00335 =   335,
+    N00336 =   336, N00337 =   337, N00338 =   338, N00339 =   339, N00340 =   340,
+    N00341 =   341, N00342 =   342, N00343 =   343, N00344 =   344, N00345 =   345,
+    N00346 =   346, N00347 =   347, N00348 =   348, N00349 =   349, N00350 =   350,
+    N00351 =   351, N00352 =   352, N00353 =   353, N00354 =   354, N00355 =   355,
+    N00356 =   356, N00357 =   357, N00358 =   358, N00359 =   359, N00360 =   360,
+    N00361 =   361, N00362 =   362, N00363 =   363, N00364 =   364, N00365 =   365,
+    N00366 =   366, N00367 =   367, N00368 =   368, N00369 =   369, N00370 =   370,
+    N00371 =   371, N00372 =   372, N00373 =   373, N00374 =   374, N00375 =   375,
+    N00376 =   376, N00377 =   377, N00378 =   378, N00379 =   379, N00380 =   380,
+    N00381 =   381, N00382 =   382, N00383 =   383, N00384 =   384, N00385 =   385,
+    N00386 =   386, N00387 =   387, N00388 =   388, N00389 =   389, N00390 =   390,
+    N00391 =   391, N00392 =   392, N00393 =   393, N00394 =   394, N00395 =   395,
+    N00396 =   396, N00397 =   397, N00398 =   398, N00399 =   399, N00400 =   400,
+    N00401 =   401, N00402 =   402, N00403 =   403, N00404 =   404, N00405 =   405,
+    N00406 =   406, N00407 =   407, N00408 =   408, N00409 =   409, N00410 =   410,
+    N00411 =   411, N00412 =   412, N00413 =   413, N00414 =   414, N00415 =   415,
+    N00416 =   416, N00417 =   417, N00418 =   418, N00419 =   419, N00420 =   420,
+    N00421 =   421, N00422 =   422, N00423 =   423, N00424 =   424, N00425 =   425,
+    N00426 =   426, N00427 =   427, N00428 =   428, N00429 =   429, N00430 =   430,
+    N00431 =   431, N00432 =   432, N00433 =   433, N00434 =   434, N00435 =   435,
+    N00436 =   436, N00437 =   437, N00438 =   438, N00439 =   439, N00440 =   440,
+    N00441 =   441, N00442 =   442, N00443 =   443, N00444 =   444, N00445 =   445,
+    N00446 =   446, N00447 =   447, N00448 =   448, N00449 =   449, N00450 =   450,
+    N00451 =   451, N00452 =   452, N00453 =   453, N00454 =   454, N00455 =   455,
+    N00456 =   456, N00457 =   457, N00458 =   458, N00459 =   459, N00460 =   460,
+    N00461 =   461, N00462 =   462, N00463 =   463, N00464 =   464, N00465 =   465,
+    N00466 =   466, N00467 =   467, N00468 =   468, N00469 =   469, N00470 =   470,
+    N00471 =   471, N00472 =   472, N00473 =   473, N00474 =   474, N00475 =   475,
+    N00476 =   476, N00477 =   477, N00478 =   478, N00479 =   479, N00480 =   480,
+    N00481 =   481, N00482 =   482, N00483 =   483, N00484 =   484, N00485 =   485,
+    N00486 =   486, N00487 =   487, N00488 =   488, N00489 =   489, N00490 =   490,
+    N00491 =   491, N00492 =   492, N00493 =   493, N00494 =   494, N00495 =   495,
+    N00496 =   496, N00497 =   497, N00498 =   498, N00499 =   499, N00500 =   500,
+    N00501 =   501, N00502 =   502, N00503 =   503, N00504 =   504, N00505 =   505,
+    N00506 =   506, N00507 =   507, N00508 =   508, N00509 =   509, N00510 =   510,
+    N00511 =   511, N00512 =   512, N00513 =   513, N00514 =   514, N00515 =   515,
+    N00516 =   516, N00517 =   517, N00518 =   518, N00519 =   519, N00520 =   520,
+    N00521 =   521, N00522 =   522, N00523 =   523, N00524 =   524, N00525 =   525,
+    N00526 =   526, N00527 =   527, N00528 =   528, N00529 =   529, N00530 =   530,
+    N00531 =   531, N00532 =   532, N00533 =   533, N00534 =   534, N00535 =   535,
+    N00536 =   536, N00537 =   537, N00538 =   538, N00539 =   539, N00540 =   540,
+    N00541 =   541, N00542 =   542, N00543 =   543, N00544 =   544, N00545 =   545,
+    N00546 =   546, N00547 =   547, N00548 =   548, N00549 =   549, N00550 =   550,
+    N00551 =   551, N00552 =   552, N00553 =   553, N00554 =   554, N00555 =   555,
+    N00556 =   556, N00557 =   557, N00558 =   558, N00559 =   559, N00560 =   560,
+    N00561 =   561, N00562 =   562, N00563 =   563, N00564 =   564, N00565 =   565,
+    N00566 =   566, N00567 =   567, N00568 =   568, N00569 =   569, N00570 =   570,
+    N00571 =   571, N00572 =   572, N00573 =   573, N00574 =   574, N00575 =   575,
+    N00576 =   576, N00577 =   577, N00578 =   578, N00579 =   579, N00580 =   580,
+    N00581 =   581, N00582 =   582, N00583 =   583, N00584 =   584, N00585 =   585,
+    N00586 =   586, N00587 =   587, N00588 =   588, N00589 =   589, N00590 =   590,
+    N00591 =   591, N00592 =   592, N00593 =   593, N00594 =   594, N00595 =   595,
+    N00596 =   596, N00597 =   597, N00598 =   598, N00599 =   599, N00600 =   600,
+    N00601 =   601, N00602 =   602, N00603 =   603, N00604 =   604, N00605 =   605,
+    N00606 =   606, N00607 =   607, N00608 =   608, N00609 =   609, N00610 =   610,
+    N00611 =   611, N00612 =   612, N00613 =   613, N00614 =   614, N00615 =   615,
+    N00616 =   616, N00617 =   617, N00618 =   618, N00619 =   619, N00620 =   620,
+    N00621 =   621, N00622 =   622, N00623 =   623, N00624 =   624, N00625 =   625,
+    N00626 =   626, N00627 =   627, N00628 =   628, N00629 =   629, N00630 =   630,
+    N00631 =   631, N00632 =   632, N00633 =   633, N00634 =   634, N00635 =   635,
+    N00636 =   636, N00637 =   637, N00638 =   638, N00639 =   639, N00640 =   640,
+    N00641 =   641, N00642 =   642, N00643 =   643, N00644 =   644, N00645 =   645,
+    N00646 =   646, N00647 =   647, N00648 =   648, N00649 =   649, N00650 =   650,
+    N00651 =   651, N00652 =   652, N00653 =   653, N00654 =   654, N00655 =   655,
+    N00656 =   656, N00657 =   657, N00658 =   658, N00659 =   659, N00660 =   660,
+    N00661 =   661, N00662 =   662, N00663 =   663, N00664 =   664, N00665 =   665,
+    N00666 =   666, N00667 =   667, N00668 =   668, N00669 =   669, N00670 =   670,
+    N00671 =   671, N00672 =   672, N00673 =   673, N00674 =   674, N00675 =   675,
+    N00676 =   676, N00677 =   677, N00678 =   678, N00679 =   679, N00680 =   680,
+    N00681 =   681, N00682 =   682, N00683 =   683, N00684 =   684, N00685 =   685,
+    N00686 =   686, N00687 =   687, N00688 =   688, N00689 =   689, N00690 =   690,
+    N00691 =   691, N00692 =   692, N00693 =   693, N00694 =   694, N00695 =   695,
+    N00696 =   696, N00697 =   697, N00698 =   698, N00699 =   699, N00700 =   700,
+    N00701 =   701, N00702 =   702, N00703 =   703, N00704 =   704, N00705 =   705,
+    N00706 =   706, N00707 =   707, N00708 =   708, N00709 =   709, N00710 =   710,
+    N00711 =   711, N00712 =   712, N00713 =   713, N00714 =   714, N00715 =   715,
+    N00716 =   716, N00717 =   717, N00718 =   718, N00719 =   719, N00720 =   720,
+    N00721 =   721, N00722 =   722, N00723 =   723, N00724 =   724, N00725 =   725,
+    N00726 =   726, N00727 =   727, N00728 =   728, N00729 =   729, N00730 =   730,
+    N00731 =   731, N00732 =   732, N00733 =   733, N00734 =   734, N00735 =   735,
+    N00736 =   736, N00737 =   737, N00738 =   738, N00739 =   739, N00740 =   740,
+    N00741 =   741, N00742 =   742, N00743 =   743, N00744 =   744, N00745 =   745,
+    N00746 =   746, N00747 =   747, N00748 =   748, N00749 =   749, N00750 =   750,
+    N00751 =   751, N00752 =   752, N00753 =   753, N00754 =   754, N00755 =   755,
+    N00756 =   756, N00757 =   757, N00758 =   758, N00759 =   759, N00760 =   760,
+    N00761 =   761, N00762 =   762, N00763 =   763, N00764 =   764, N00765 =   765,
+    N00766 =   766, N00767 =   767, N00768 =   768, N00769 =   769, N00770 =   770,
+    N00771 =   771, N00772 =   772, N00773 =   773, N00774 =   774, N00775 =   775,
+    N00776 =   776, N00777 =   777, N00778 =   778, N00779 =   779, N00780 =   780,
+    N00781 =   781, N00782 =   782, N00783 =   783, N00784 =   784, N00785 =   785,
+    N00786 =   786, N00787 =   787, N00788 =   788, N00789 =   789, N00790 =   790,
+    N00791 =   791, N00792 =   792, N00793 =   793, N00794 =   794, N00795 =   795,
+    N00796 =   796, N00797 =   797, N00798 =   798, N00799 =   799, N00800 =   800,
+    N00801 =   801, N00802 =   802, N00803 =   803, N00804 =   804, N00805 =   805,
+    N00806 =   806, N00807 =   807, N00808 =   808, N00809 =   809, N00810 =   810,
+    N00811 =   811, N00812 =   812, N00813 =   813, N00814 =   814, N00815 =   815,
+    N00816 =   816, N00817 =   817, N00818 =   818, N00819 =   819, N00820 =   820,
+    N00821 =   821, N00822 =   822, N00823 =   823, N00824 =   824, N00825 =   825,
+    N00826 =   826, N00827 =   827, N00828 =   828, N00829 =   829, N00830 =   830,
+    N00831 =   831, N00832 =   832, N00833 =   833, N00834 =   834, N00835 =   835,
+    N00836 =   836, N00837 =   837, N00838 =   838, N00839 =   839, N00840 =   840,
+    N00841 =   841, N00842 =   842, N00843 =   843, N00844 =   844, N00845 =   845,
+    N00846 =   846, N00847 =   847, N00848 =   848, N00849 =   849, N00850 =   850,
+    N00851 =   851, N00852 =   852, N00853 =   853, N00854 =   854, N00855 =   855,
+    N00856 =   856, N00857 =   857, N00858 =   858, N00859 =   859, N00860 =   860,
+    N00861 =   861, N00862 =   862, N00863 =   863, N00864 =   864, N00865 =   865,
+    N00866 =   866, N00867 =   867, N00868 =   868, N00869 =   869, N00870 =   870,
+    N00871 =   871, N00872 =   872, N00873 =   873, N00874 =   874, N00875 =   875,
+    N00876 =   876, N00877 =   877, N00878 =   878, N00879 =   879, N00880 =   880,
+    N00881 =   881, N00882 =   882, N00883 =   883, N00884 =   884, N00885 =   885,
+    N00886 =   886, N00887 =   887, N00888 =   888, N00889 =   889, N00890 =   890,
+    N00891 =   891, N00892 =   892, N00893 =   893, N00894 =   894, N00895 =   895,
+    N00896 =   896, N00897 =   897, N00898 =   898, N00899 =   899, N00900 =   900,
+    N00901 =   901, N00902 =   902, N00903 =   903, N00904 =   904, N00905 =   905,
+    N00906 =   906, N00907 =   907, N00908 =   908, N00909 =   909, N00910 =   910,
+    N00911 =   911, N00912 =   912, N00913 =   913, N00914 =   914, N00915 =   915,
+    N00916 =   916, N00917 =   917, N00918 =   918, N00919 =   919, N00920 =   920,
+    N00921 =   921, N00922 =   922, N00923 =   923, N00924 =   924, N00925 =   925,
+    N00926 =   926, N00927 =   927, N00928 =   928, N00929 =   929, N00930 =   930,
+    N00931 =   931, N00932 =   932, N00933 =   933, N00934 =   934, N00935 =   935,
+    N00936 =   936, N00937 =   937, N00938 =   938, N00939 =   939, N00940 =   940,
+    N00941 =   941, N00942 =   942, N00943 =   943, N00944 =   944, N00945 =   945,
+    N00946 =   946, N00947 =   947, N00948 =   948, N00949 =   949, N00950 =   950,
+    N00951 =   951, N00952 =   952, N00953 =   953, N00954 =   954, N00955 =   955,
+    N00956 =   956, N00957 =   957, N00958 =   958, N00959 =   959, N00960 =   960,
+    N00961 =   961, N00962 =   962, N00963 =   963, N00964 =   964, N00965 =   965,
+    N00966 =   966, N00967 =   967, N00968 =   968, N00969 =   969, N00970 =   970,
+    N00971 =   971, N00972 =   972, N00973 =   973, N00974 =   974, N00975 =   975,
+    N00976 =   976, N00977 =   977, N00978 =   978, N00979 =   979, N00980 =   980,
+    N00981 =   981, N00982 =   982, N00983 =   983, N00984 =   984, N00985 =   985,
+    N00986 =   986, N00987 =   987, N00988 =   988, N00989 =   989, N00990 =   990,
+    N00991 =   991, N00992 =   992, N00993 =   993, N00994 =   994, N00995 =   995,
+    N00996 =   996, N00997 =   997, N00998 =   998, N00999 =   999, N01000 =  1000,
+    N01001 =  1001, N01002 =  1002, N01003 =  1003, N01004 =  1004, N01005 =  1005,
+    N01006 =  1006, N01007 =  1007, N01008 =  1008, N01009 =  1009, N01010 =  1010,
+    N01011 =  1011, N01012 =  1012, N01013 =  1013, N01014 =  1014, N01015 =  1015,
+    N01016 =  1016, N01017 =  1017, N01018 =  1018, N01019 =  1019, N01020 =  1020,
+    N01021 =  1021, N01022 =  1022, N01023 =  1023,
 }
-
-impl From<ErrorCode> for usize {
-    fn from(error_code: ErrorCode) -> usize {
-        error_code.0
-    }
-}
-
-pub const FAIL: ErrorCode = ErrorCode(1);
-pub const BUSY: ErrorCode = ErrorCode(2);
-pub const ALREADY: ErrorCode = ErrorCode(3);
-pub const OFF: ErrorCode = ErrorCode(4);
-pub const RESERVE: ErrorCode = ErrorCode(5);
-pub const INVALID: ErrorCode = ErrorCode(6);
-pub const SIZE: ErrorCode = ErrorCode(7);
-pub const CANCEL: ErrorCode = ErrorCode(8);
-pub const NOMEM: ErrorCode = ErrorCode(9);
-pub const NOSUPPORT: ErrorCode = ErrorCode(10);
-pub const NODEVICE: ErrorCode = ErrorCode(11);
-pub const UNINSTALLED: ErrorCode = ErrorCode(12);
-pub const NOACK: ErrorCode = ErrorCode(13);
-pub const BADRVAL: ErrorCode = ErrorCode(1024);
diff --git a/core/platform/src/lib.rs b/core/platform/src/lib.rs
index 45021ca..d57644b 100644
--- a/core/platform/src/lib.rs
+++ b/core/platform/src/lib.rs
@@ -2,7 +2,7 @@
 
 mod async_traits;
 mod command_return;
-pub mod error_code;
+mod error_code;
 mod raw_syscalls;
 pub mod return_variant;
 mod syscalls;
diff --git a/core/runtime/asm/asm_riscv32.S b/core/runtime/asm/asm_riscv32.S
new file mode 100644
index 0000000..db1036e
--- /dev/null
+++ b/core/runtime/asm/asm_riscv32.S
@@ -0,0 +1,86 @@
+/* rt_header is defined by the general linker script (libtock_layout.ld). It has
+ * the following layout:
+ *
+ *     Field                       | Offset
+ *     ------------------------------------
+ *     Address of the start symbol |      0
+ *     Initial process break       |      4
+ *     Top of the stack            |      8
+ *     Size of .data               |     12
+ *     Start of .data in flash     |     16
+ *     Start of .data in ram       |     20
+ *     Size of .bss                |     24
+ *     Start of .bss in ram        |     28
+ */
+
+/* start is the entry point -- the first code executed by the kernel. The kernel
+ * passes arguments through 4 registers:
+ *
+ *     a0  Pointer to beginning of the process binary's code. The linker script
+ *         locates rt_header at this address.
+ *
+ *     a1  Address of the beginning of the process's usable memory region.
+ *     a2  Size of the process' allocated memory region (including grant region)
+ *     a3  Process break provided by the kernel.
+ *
+ * We currently only use the value in a0. It is copied into a5 early on because
+ * a0-a4 are needed to invoke system calls.
+ */
+.section .start, "ax"
+.globl start
+start:
+	/* First, verify the process binary was loaded at the correct address. The
+	 * check is performed by comparing the program counter at the start to the
+	 * address of `start`, which is stored in rt_header. */
+	auipc s0, 0            /* s0 = pc */
+	mv a5, a0              /* Save rt_header so syscalls don't overwrite it */
+	lw s1, 0(a5)           /* s1 = rt_header.start */
+	beq s0, s1, .Lset_brk  /* Skip error handling code if pc is correct */
+	/* If the beq on the previous line did not jump, then the binary is not at
+	 * the correct location. Report the error via LowLevelDebug then exit. */
+	li a0, 8  /* LowLevelDebug driver number */
+	li a1, 1  /* Command: Print alert code */
+	li a2, 2  /* Alert code 2 (incorrect location) */
+	li a4, 2  /* `command` class */
+	ecall
+	li a0, 0  /* exit-terminate */
+	/* TODO: Set a completion code, once completion codes are decided */
+	li a4, 6  /* `exit` class */
+	ecall
+
+.Lset_brk:
+	/* memop(): set brk to rt_header's initial break value */
+	li a0, 0      /* operation: set break */
+	lw a1, 4(a5)  /* rt_header's initial process break */
+	li a4, 5      /* `memop` class */
+	ecall
+
+	/* Set the stack pointer */
+	lw sp, 8(a5)  /* sp = rt_header._stack_top */
+
+	/* Copy .data into place. */
+	lw a0, 12(a5)              /* remaining = rt_header.data_size */
+	beqz a0, .Lzero_bss        /* Jump to zero_bss if remaining is zero */
+	lw a1, 16(a5)              /* src = rt_header.data_flash_start */
+	lw a2, 20(a5)              /* dest = rt_header.data_ram_start */
+.Ldata_loop_body:
+	lw a3, 0(a1)               /* a3 = *src */
+	sw a3, 0(a2)               /* *dest = a3 */
+	addi a0, a0, -4            /* remaining -= 4 */
+	addi a1, a1, 4             /* src += 4 */
+	addi a2, a2, 4             /* dest += 4 */
+	bnez a0, .Ldata_loop_body  /* Iterate again if remaining != 0 */
+
+.Lzero_bss:
+	lw a0, 24(a5)               /* remaining = rt_header.bss_size */
+	beqz a0, .Lcall_rust_start  /* Jump to call_Main if remaining is zero */
+	lw a1, 28(a5)               /* dest = rt_header.bss_start */
+.Lbss_loop_body:
+	sb zero, 0(a1)              /* *dest = zero */
+	addi a0, a0, -1             /* remaining -= 1 */
+	addi a1, a1, 1              /* dest += 1 */
+	bnez a0, .Lbss_loop_body    /* Iterate again if remaining != 0 */
+
+.Lcall_rust_start:
+	/* Note: rust_start must be a diverging function (i.e. return `!`) */
+	jal rust_start
diff --git a/core/runtime/asm/start_prototype.rs b/core/runtime/asm/start_prototype.rs
new file mode 100644
index 0000000..26c036d
--- /dev/null
+++ b/core/runtime/asm/start_prototype.rs
@@ -0,0 +1,94 @@
+// This file is not compiled or tested! It is kept in this repository in case
+// future libtock_runtime developers want to use it. To use this file, copy it
+// into libtock_runtime's src/ directory and add mod start_prototype; to
+// libtock_runtime's lib.rs.
+
+// 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`.
+
+#[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(); }
+}
diff --git a/core/runtime/build.rs b/core/runtime/build.rs
index d0281b5..347d82c 100644
--- a/core/runtime/build.rs
+++ b/core/runtime/build.rs
@@ -1,6 +1,8 @@
 use std::fs::copy;
 use std::path::PathBuf;
 
+mod extern_asm;
+
 // auto_layout() identifies the correct linker scripts to use based on the
 // LIBTOCK_PLATFORM environment variable, and copies the linker scripts into
 // OUT_DIR. The cargo invocation must pass -C link-arg=-Tlayout.ld to rustc
@@ -8,7 +10,7 @@
 #[cfg(not(feature = "no_auto_layout"))]
 fn auto_layout(out_dir: &str) {
     const PLATFORM_CFG_VAR: &str = "LIBTOCK_PLATFORM";
-    const LAYOUT_GENERIC_FILENAME: &str = "layout_generic.ld";
+    const LAYOUT_GENERIC_FILENAME: &str = "libtock_layout.ld";
 
     // Note: we need to print these rerun-if commands before using the variable
     // or file, so that if the build script fails cargo knows when to re-run it.
@@ -49,7 +51,9 @@
     #[cfg(not(feature = "no_auto_layout"))]
     auto_layout(out_dir);
 
-    // This link search path is used by both auto_layout() and extern_asm().
-    // TODO: Add external assembly and extern_asm().
+    extern_asm::build_and_link(out_dir);
+
+    // This link search path is used by both auto_layout() and
+    // extern_asm::build_and_link().
     println!("cargo:rustc-link-search={}", out_dir);
 }
diff --git a/core/runtime/extern_asm.rs b/core/runtime/extern_asm.rs
new file mode 100644
index 0000000..d0488f8
--- /dev/null
+++ b/core/runtime/extern_asm.rs
@@ -0,0 +1,140 @@
+//! Build script module for compiling the external assembly (used for the entry
+//! point) and linking it into the process binary. Requires out_dir to be added
+//! to rustc's link search path.
+
+pub(crate) fn build_and_link(out_dir: &str) {
+    use std::env::var;
+    let arch = var("CARGO_CFG_TARGET_ARCH").expect("Unable to read CARGO_CFG_TARGET_ARCH");
+
+    // Identify the toolchain configurations to try for the target architecture.
+    // We support trying multiple toolchains because not all toolchains are
+    // available on every OS that we want to support development on.
+    let build_configs = match arch.as_str() {
+        "riscv32" => &[
+            // First try riscv64-unknown-elf, as it is the toolchain used by
+            // libtock-c and the toolchain used in the CI environment.
+            AsmBuildConfig {
+                triple: "riscv64-unknown-elf",
+                as_extra_args: &["-march=rv32imc"],
+                strip: true,
+            },
+            // Second try riscv32-unknown-elf. This is the best match for Tock's
+            // risc-v targets, but is not as widely available (and has not been
+            // tested with libtock-rs yet).
+            AsmBuildConfig {
+                triple: "riscv32-unknown-elf",
+                as_extra_args: &[],
+                strip: false, // Untested, may need to change.
+            },
+            // Last try riscv64-linux-gnu, as it is the only option on Debian 10
+            AsmBuildConfig {
+                triple: "riscv64-linux-gnu",
+                as_extra_args: &["-march=rv32imc"],
+                strip: true,
+            },
+        ],
+        unknown_arch => {
+            panic!("Unsupported architecture {}", unknown_arch);
+        }
+    };
+
+    // Loop through toolchain configs until one works.
+    for &build_config in build_configs {
+        if try_build(&arch, build_config, out_dir).is_ok() {
+            return;
+        }
+    }
+}
+
+#[derive(Clone, Copy)]
+struct AsmBuildConfig {
+    // Triple name, which is prepended to the command names.
+    triple: &'static str,
+
+    // Extra arguments to pass to the assembler.
+    as_extra_args: &'static [&'static str],
+
+    // Do we need to strip the object file before packing it into the library
+    // archive? This should be set to true on platforms where the assembler adds
+    // local symbols to the object file.
+    strip: bool,
+}
+
+// Indicates the toolchain in the build config is unavailable.
+struct ToolchainUnavailable;
+
+fn try_build(
+    arch: &str,
+    build_config: AsmBuildConfig,
+    out_dir: &str,
+) -> Result<(), ToolchainUnavailable> {
+    use std::path::PathBuf;
+    use std::process::Command;
+
+    // Invoke the assembler to produce an object file.
+    let asm_source = &format!("asm/asm_{}.S", arch);
+    let obj_file_path = [out_dir, "libtock_rt_asm.o"].iter().collect::<PathBuf>();
+    let obj_file = obj_file_path.to_str().expect("Non-Unicode obj_file_path");
+    let as_result = Command::new(format!("{}-as", build_config.triple))
+        .args(build_config.as_extra_args)
+        .args(&[asm_source, "-o", obj_file])
+        .status();
+
+    match as_result {
+        Err(error) => {
+            if error.kind() == std::io::ErrorKind::NotFound {
+                // This `as` command does not exist. Return an error so
+                // build_an_link can try another config (if one is available).
+                return Err(ToolchainUnavailable);
+            } else {
+                panic!("Error invoking assembler: {}", error);
+            }
+        }
+        Ok(status) => {
+            assert!(status.success(), "Assembler returned an error");
+        }
+    }
+
+    // At this point, we know this toolchain is installed. We will fail if later
+    // commands are uninstalled rather than trying a different build config.
+
+    println!("cargo:rerun-if-changed={}", asm_source);
+
+    // Run `strip` if necessary.
+    if build_config.strip {
+        let strip_cmd = format!("{}-strip", build_config.triple);
+        let status = Command::new(&strip_cmd)
+            .args(&["-K", "start", "-K", "rust_start", obj_file])
+            .status()
+            .unwrap_or_else(|_| panic!("Failed to invoke {}", strip_cmd));
+        assert!(status.success(), "{} returned an error", strip_cmd);
+    }
+
+    // Remove the archive file in case there is something unexpected in it. This
+    // prevents issues from persisting across invocations of this script.
+    const ARCHIVE_NAME: &str = "tock_rt_asm";
+    let archive_path: PathBuf = [out_dir, &format!("lib{}.a", ARCHIVE_NAME)]
+        .iter()
+        .collect();
+    if let Err(error) = std::fs::remove_file(&archive_path) {
+        if error.kind() != std::io::ErrorKind::NotFound {
+            panic!("Unable to remove archive file {}", archive_path.display());
+        }
+    }
+
+    // Create the library archive.
+    let ar_cmd = format!("{}-ar", build_config.triple);
+    let archive = archive_path.to_str().expect("Non-Unicode archive_path");
+    let status = std::process::Command::new(&ar_cmd)
+        // c == Do not complain if archive needs to be created.
+        // r == Insert or replace file in archive.
+        .args(&["cr", archive, obj_file])
+        .status()
+        .unwrap_or_else(|_| panic!("Failed to invoke {}", ar_cmd));
+    assert!(status.success(), "{} returned an error", ar_cmd);
+
+    // Tell rustc to link the binary against the library archive.
+    println!("cargo:rustc-link-lib=static={}", ARCHIVE_NAME);
+
+    Ok(())
+}
diff --git a/core/runtime/layout_generic.ld b/core/runtime/layout_generic.ld
deleted file mode 100644
index 0bbef87..0000000
--- a/core/runtime/layout_generic.ld
+++ /dev/null
@@ -1,171 +0,0 @@
-/* Userland Generic Layout
- *
- * Currently, due to incomplete ROPI-RWPI support in rustc (see
- * https://github.com/tock/libtock-rs/issues/28), this layout implements static
- * linking. An application init script must define the FLASH and SRAM address
- * ranges as well as MPU_MIN_ALIGN before including this layout file.
- *
- * Here is a an example application linker script to get started:
- *     MEMORY {
- *         /* FLASH memory region must start immediately *after* the Tock
- *          * Binary Format headers, which means you need to offset the
- *          * beginning of FLASH memory region relative to where the
- *          * application is loaded.
- *         FLASH (rx) : ORIGIN = 0x10030, LENGTH = 0x0FFD0
- *         SRAM (RWX) : ORIGIN = 0x20000, LENGTH = 0x10000
- *     }
- *     MPU_MIN_ALIGN = 8K;
- *     INCLUDE ../libtock-rs/layout.ld
- */
-
-ENTRY(_start)
-
-SECTIONS {
-    /* Section for just the app crt0 header.
-     * This must be first so that the app can find it.
-     */
-    .crt0_header :
-    {
-        _beginning = .; /* Start of the app in flash. */
-        /**
-         * Populate the header expected by `crt0`:
-         *
-         *  struct hdr {
-         *    uint32_t got_sym_start;
-         *    uint32_t got_start;
-         *    uint32_t got_size;
-         *    uint32_t data_sym_start;
-         *    uint32_t data_start;
-         *    uint32_t data_size;
-         *    uint32_t bss_start;
-         *    uint32_t bss_size;
-         *    uint32_t reldata_start;
-         *    uint32_t stack_size;
-         *  };
-         */
-        /* Offset of GOT symbols in flash */
-        LONG(LOADADDR(.got) - _beginning);
-        /* Offset of GOT section in memory */
-        LONG(_got);
-        /* Size of GOT section */
-        LONG(SIZEOF(.got));
-        /* Offset of data symbols in flash */
-        LONG(LOADADDR(.data) - _beginning);
-        /* Offset of data section in memory */
-        LONG(_data);
-        /* Size of data section */
-        LONG(SIZEOF(.data));
-        /* Offset of BSS section in memory */
-        LONG(_bss);
-        /* Size of BSS section */
-        LONG(SIZEOF(.bss));
-        /* First address offset after program flash, where elf2tab places
-         * .rel.data section */
-        LONG(LOADADDR(.endflash) - _beginning);
-        /* The size of the stack requested by this application */
-        LONG(_stack_top_aligned - _sstack);
-        /* Pad the header out to a multiple of 32 bytes so there is not a gap
-         * between the header and subsequent .data section. It's unclear why,
-         * but LLD is aligning sections to a multiple of 32 bytes. */
-        . = ALIGN(32);
-    } > FLASH =0xFF
-
-    /* Text section, Code! */
-    .text :
-    {
-        . = ALIGN(4);
-        _text = .;
-        KEEP (*(.start))
-        *(.text*)
-        *(.rodata*)
-        KEEP (*(.syscalls))
-        *(.ARM.extab*)
-        . = ALIGN(4); /* Make sure we're word-aligned here */
-        _etext = .;
-    } > FLASH =0xFF
-
-    /* Application stack */
-    .stack (NOLOAD) :
-    {
-        /* elf2tab requires that the `_sram_origin` symbol be present to
-         * mark the first address in the SRAM memory. Since ELF files do
-         * not really need to specify this address as they only care about
-         * loading into flash, we need to manually mark this address for
-         * elf2tab. elf2tab will use it to add a fixed address header in the
-         * TBF header if needed.
-         */
-        _sram_origin = .;
-        _sstack = .;
-        KEEP(*(.stack_buffer))
-        _stack_top_unaligned = .;
-        . = ALIGN(8);
-        _stack_top_aligned = .;
-    } > SRAM
-
-    /* Data section, static initialized variables
-     *  Note: This is placed in Flash after the text section, but needs to be
-     *  moved to SRAM at runtime
-     */
-    .data : AT (_etext)
-    {
-        . = ALIGN(4); /* Make sure we're word-aligned here */
-        _data = .;
-        KEEP(*(.data*))
-        *(.sdata*) /* RISC-V small-pointer data section */
-        . = ALIGN(4); /* Make sure we're word-aligned at the end of flash */
-    } > SRAM
-
-    /* Global Offset Table */
-    .got :
-    {
-        . = ALIGN(4); /* Make sure we're word-aligned here */
-        _got = .;
-        *(.got*)
-        *(.got.plt*)
-        . = ALIGN(4);
-    } > SRAM
-
-    /* BSS section, static uninitialized variables */
-    .bss :
-    {
-        . = ALIGN(4); /* Make sure we're word-aligned here */
-        _bss = .;
-        KEEP(*(.bss* .sbss*))
-        *(COMMON)
-        . = ALIGN(4);
-    } > SRAM
-
-    /* End of flash. */
-    .endflash :
-    {
-    } > FLASH
-
-    /* ARM Exception support
-     *
-     * This contains compiler-generated support for unwinding the stack,
-     * consisting of key-value pairs of function addresses and information on
-     * how to unwind stack frames.
-     * https://wiki.linaro.org/KenWerner/Sandbox/libunwind?action=AttachFile&do=get&target=libunwind-LDS.pdf
-     *
-     * .ARM.exidx is sorted, so has to go in its own output section.
-     *
-     * __NOTE__: It's at the end because we currently don't actually serialize
-     * it to the binary in elf2tbf. If it was before the RAM sections, it would
-     * through off our calculations of the header.
-     */
-    PROVIDE_HIDDEN (__exidx_start = .);
-    .ARM.exidx :
-    {
-      /* (C++) Index entries for section unwinding */
-      *(.ARM.exidx* .gnu.linkonce.armexidx.*)
-    } > FLASH
-    PROVIDE_HIDDEN (__exidx_end = .);
-
-    /DISCARD/ :
-    {
-      *(.eh_frame)
-    }
-}
-
-ASSERT((_stack_top_aligned - _stack_top_unaligned) == 0, "
-STACK_SIZE must be 8 byte multiple")
diff --git a/core/runtime/layouts/apollo3.ld b/core/runtime/layouts/apollo3.ld
index 8713c27..464cf98 100644
--- a/core/runtime/layouts/apollo3.ld
+++ b/core/runtime/layouts/apollo3.ld
@@ -1,11 +1,9 @@
 /* Layout for the Apollo3 MCU, used by the examples in this repository. */
 
 MEMORY {
-  /* The application region is 64 bytes (0x40) */
-  FLASH (rx) : ORIGIN = 0x00040040, LENGTH = 0x0005FFC0
-  SRAM (rwx) : ORIGIN = 0x10002000, LENGTH = 0x2000
+  FLASH (X) : ORIGIN = 0x00040000, LENGTH = 0x00060000
+  RAM   (W) : ORIGIN = 0x10002000, LENGTH = 0x2000
 }
 
-MPU_MIN_ALIGN = 8K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/layouts/hail.ld b/core/runtime/layouts/hail.ld
index 179a883..db48142 100644
--- a/core/runtime/layouts/hail.ld
+++ b/core/runtime/layouts/hail.ld
@@ -1,11 +1,9 @@
 /* Layout for the Hail board, used by the examples in this repository. */
 
 MEMORY {
-  /* The application region is 64 bytes (0x40) */
-  FLASH (rx) : ORIGIN = 0x00030040, LENGTH = 0x0005FFC0
-  SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 62K
+  FLASH (X) : ORIGIN = 0x00030000, LENGTH = 0x00060000
+  RAM   (W) : ORIGIN = 0x20004000, LENGTH = 62K
 }
 
-MPU_MIN_ALIGN = 8K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/layouts/hifive1.ld b/core/runtime/layouts/hifive1.ld
index 20294ce..78dc51b 100644
--- a/core/runtime/layouts/hifive1.ld
+++ b/core/runtime/layouts/hifive1.ld
@@ -1,18 +1,12 @@
 /* Layout for the RISC-V 32 boards, used by the examples in this repository. */
 
 MEMORY {
-  /*
-   * The TBF header can change in size so use 0x40 combined with
-   * --protected-region-size with elf2tab to cover a header upto that
-   * size.
-   *
-   * Note that the SRAM address may need to be changed depending on
+  /* Note that the SRAM address may need to be changed depending on
    * the kernel binary, check for the actual address of APP_MEMORY!
    */
-  FLASH (rx) : ORIGIN = 0x20040040, LENGTH = 32M
-  SRAM (rwx) : ORIGIN = 0x80002400, LENGTH = 0x1C00
+  FLASH (X) : ORIGIN = 0x20040000, LENGTH = 32M
+  RAM   (W) : ORIGIN = 0x80002400, LENGTH = 0x1C00
 }
 
-MPU_MIN_ALIGN = 1K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/layouts/imxrt1050.ld b/core/runtime/layouts/imxrt1050.ld
index 3e7f904..1458870 100644
--- a/core/runtime/layouts/imxrt1050.ld
+++ b/core/runtime/layouts/imxrt1050.ld
@@ -1,11 +1,9 @@
 /* Layout for the iMX.RT1050 board, used by the examples in this repository. */
 
 MEMORY {
-  /* The application region is 64 bytes (0x40) */
-  FLASH (rx) : ORIGIN = 0x63002040, LENGTH = 0xFFFFC0
-  SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 112K
+  FLASH (X) : ORIGIN = 0x63002000, LENGTH = 0x1000000
+  RAM   (W) : ORIGIN = 0x20004000, LENGTH = 112K
 }
 
-MPU_MIN_ALIGN = 8K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/layouts/msp432.ld b/core/runtime/layouts/msp432.ld
index 2c9d2a4..170d4b1 100644
--- a/core/runtime/layouts/msp432.ld
+++ b/core/runtime/layouts/msp432.ld
@@ -1,9 +1,7 @@
 MEMORY {
-  /* The application region is 64 bytes (0x40) */
-  FLASH (rx) : ORIGIN = 0x00020040, LENGTH = 0x0001FFC0
-  SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 0x2000
+  FLASH (X) : ORIGIN = 0x00020000, LENGTH = 0x00020000
+  RAM   (W) : ORIGIN = 0x20004000, LENGTH = 0x2000
 }
 
-MPU_MIN_ALIGN = 8K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/layouts/nrf52.ld b/core/runtime/layouts/nrf52.ld
index 942e86b..fbbcdc3 100644
--- a/core/runtime/layouts/nrf52.ld
+++ b/core/runtime/layouts/nrf52.ld
@@ -1,11 +1,9 @@
 /* Layout for the nRF52-DK, used by the examples in this repository. */
 
 MEMORY {
-  /* The application region is 64 bytes (0x40) */
-  FLASH (rx) : ORIGIN = 0x00030040, LENGTH = 0x0005FFC0
-  SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 62K
+  FLASH (X) : ORIGIN = 0x00030000, LENGTH = 0x00060000
+  RAM   (W) : ORIGIN = 0x20004000, LENGTH = 62K
 }
 
-MPU_MIN_ALIGN = 8K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/layouts/nrf52840.ld b/core/runtime/layouts/nrf52840.ld
index 31bf346..06b6f6b 100644
--- a/core/runtime/layouts/nrf52840.ld
+++ b/core/runtime/layouts/nrf52840.ld
@@ -1,11 +1,9 @@
 /* Layout for the nRF52840-DK, usable by the examples in this repository. */
 
 MEMORY {
-  /* The application region is 64 bytes (0x40) */
-  FLASH (rx) : ORIGIN = 0x00030040, LENGTH = 0x000CFFC0
-  SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 62K
+  FLASH (X) : ORIGIN = 0x00030000, LENGTH = 0x000D0000
+  RAM   (W) : ORIGIN = 0x20004000, LENGTH = 62K
 }
 
-MPU_MIN_ALIGN = 8K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/layouts/nucleo_f429zi.ld b/core/runtime/layouts/nucleo_f429zi.ld
index 3e407b4..a9c634a 100644
--- a/core/runtime/layouts/nucleo_f429zi.ld
+++ b/core/runtime/layouts/nucleo_f429zi.ld
@@ -1,11 +1,9 @@
 /* Layout for the Nucleo F429zi, used by the examples in this repository. */
 
 MEMORY {
-  /* The application region is 64 bytes (0x40) */
-  FLASH (rx) : ORIGIN = 0x08040040, LENGTH = 255K
-  SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 112K
+  FLASH (X) : ORIGIN = 0x08040000, LENGTH = 255K
+  RAM   (W) : ORIGIN = 0x20004000, LENGTH = 112K
 }
 
-MPU_MIN_ALIGN = 8K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/layouts/nucleo_f446re.ld b/core/runtime/layouts/nucleo_f446re.ld
index 83e698b..d3953f5 100644
--- a/core/runtime/layouts/nucleo_f446re.ld
+++ b/core/runtime/layouts/nucleo_f446re.ld
@@ -1,11 +1,9 @@
 /* Layout for the Nucleo F446re, used by the examples in this repository. */
 
 MEMORY {
-  /* The application region is 64 bytes (0x40) */
-  FLASH (rx) : ORIGIN = 0x08040040, LENGTH = 255K
-  SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 176K
+  FLASH (X) : ORIGIN = 0x08040000, LENGTH = 255K
+  RAM   (W) : ORIGIN = 0x20004000, LENGTH = 176K
 }
 
-MPU_MIN_ALIGN = 8K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/layouts/opentitan.ld b/core/runtime/layouts/opentitan.ld
index ada781c..50ea340 100644
--- a/core/runtime/layouts/opentitan.ld
+++ b/core/runtime/layouts/opentitan.ld
@@ -1,18 +1,12 @@
 /* Layout for the RISC-V 32 boards, used by the examples in this repository. */
 
 MEMORY {
-  /*
-   * The TBF header can change in size so use 0x40 combined with
-   * --protected-region-size with elf2tab to cover a header upto that
-   * size.
-   *
-   * Note that the SRAM address may need to be changed depending on
+  /* Note that the SRAM address may need to be changed depending on
    * the kernel binary, check for the actual address of APP_MEMORY!
    */
-  FLASH (rx) : ORIGIN = 0x20030040, LENGTH = 32M
-  SRAM (rwx) : ORIGIN = 0x10004000, LENGTH = 512K
+  FLASH (X) : ORIGIN = 0x20030000, LENGTH = 32M
+  RAM   (W) : ORIGIN = 0x10004000, LENGTH = 512K
 }
 
-MPU_MIN_ALIGN = 1K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/layouts/stm32f3discovery.ld b/core/runtime/layouts/stm32f3discovery.ld
index 9368003..05b0b54 100644
--- a/core/runtime/layouts/stm32f3discovery.ld
+++ b/core/runtime/layouts/stm32f3discovery.ld
@@ -1,11 +1,9 @@
 /* Layout for the stm32f3discovery board, usable by the examples in this repository. */
 
 MEMORY {
-  /* The application region is 64 bytes (0x40) */
-  FLASH (rx) : ORIGIN = 0x08020040, LENGTH = 0x00020000
-  SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 48K
+  FLASH (X) : ORIGIN = 0x08020000, LENGTH = 0x00020000
+  RAM   (W) : ORIGIN = 0x20004000, LENGTH = 48K
 }
 
-MPU_MIN_ALIGN = 8K;
-
-INCLUDE layout_generic.ld
+TBF_HEADER_SIZE = 0x40;
+INCLUDE libtock_layout.ld
diff --git a/core/runtime/libtock_layout.ld b/core/runtime/libtock_layout.ld
new file mode 100644
index 0000000..fcff09a
--- /dev/null
+++ b/core/runtime/libtock_layout.ld
@@ -0,0 +1,129 @@
+/* Layout file for Tock process binaries that use libtock-rs. This currently
+ * implements static linking, because we do not have a working
+ * position-independent relocation solution. This layout works for all
+ * platforms libtock-rs supports (ARM and RISC-V).
+ *
+ * This layout should be included by a script that defines the FLASH and RAM
+ * regions for the board as well as TBF_HEADER_SIZE. Here is a an example
+ * process binary linker script to get started:
+ *     MEMORY {
+ *         FLASH (X) : ORIGIN = 0x10000, LENGTH = 0x10000
+ *         RAM   (W) : ORIGIN = 0x20000, LENGTH = 0x10000
+ *     }
+ *     TBF_HEADER_SIZE = 0x40;
+ *     INCLUDE ../libtock-rs/layout.ld
+ *
+ * FLASH refers to the area the process binary occupies in flash, including TBF
+ * headers. RAM refers to the area the process will have access to in memory.
+ * STACK_SIZE is the size of the process' stack (this layout file may round the
+ * stack size up for alignment purposes). TBF_HEADER_SIZE must correspond to the
+ * --protected-region-size flag passed to elf2tab.
+ *
+ * This places the flash sections in the following order:
+ *     1. .rt_header -- Constants used by runtime initialization.
+ *     2. .text      -- Executable code.
+ *     3. .rodata    -- Read-only global data (e.g. most string constants).
+ *     4. .data      -- Read-write data, copied to RAM at runtime.
+ *
+ * This places the RAM sections in the following order:
+ *     1. .stack -- The stack grows downward. Putting it first gives us
+ *                  MPU-based overflow detection.
+ *     2. .data  -- Read-write data, initialized by copying from flash.
+ *     3. .bss   -- Zero-initialized read-write global data.
+ *     4. Heap   -- The heap (optional) comes after .bss and grows upwards to
+ *                  the process break.
+ */
+
+/* TODO: Should TBF_HEADER_SIZE be configured via a similar mechanism to the
+ * stack size? We should see if that is possible.
+ */
+
+/* GNU LD looks for `start` as an entry point by default, while LLVM's LLD looks
+ * for `_start`. To be compatible with both, we manually specify an entry point.
+ */
+ENTRY(start)
+
+SECTIONS {
+    /* Sections located in FLASH at runtime.
+     */
+
+    /* Add a section where elf2tab will place the TBF headers, so that the rest
+     * of the FLASH sections are in the right locations. */
+    .tbf_header (NOLOAD) : {
+        . = . + TBF_HEADER_SIZE;
+    } > FLASH
+
+    /* Runtime header. Contains values the linker knows that the runtime needs
+     * to look up.
+     */
+    .rt_header : {
+        rt_header = .;
+        LONG(start);
+        LONG(ADDR(.bss) + SIZEOF(.bss)); /* Initial process break */
+        LONG(_stack_top);
+        LONG(SIZEOF(.data));
+        LONG(LOADADDR(.data));
+        LONG(ADDR(.data));
+        LONG(SIZEOF(.bss));
+        LONG(ADDR(.bss));
+    } > FLASH
+
+    /* Text section -- the application's code. */
+    .text ALIGN(4) : {
+        *(.start)
+        *(.text)
+    } > FLASH
+
+    /* Read-only data section. Contains strings and other global constants. */
+    .rodata ALIGN(4) : {
+        *(.rodata)
+        /* .data is placed after .rodata in flash. data_flash_start is used by
+         * AT() to place .data in flash as well as in rt_header.
+         */
+        _data_flash_start = .;
+    } > FLASH
+
+    /* Sections located in RAM at runtime.
+     */
+
+    /* Reserve space for the stack. Aligned to a multiple of 16 bytes for the
+     * RISC-V calling convention:
+     * https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
+     */
+    .stack (NOLOAD) : {
+        KEEP(*(.stack_buffer))
+        . = ALIGN(16);
+        _stack_top = .;  /* Used in rt_header */
+    } > RAM
+
+    /* Read-write data section. This is deployed as part of FLASH but is copied
+     * into RAM at runtime.
+     */
+    .data ALIGN(4) : AT(_data_flash_start) {
+        data_ram_start = .;
+        /* .sdata is the RISC-V small data section */
+        *(.sdata .data)
+        /* Pad to word alignment so the relocation loop can use word-sized
+         * copies.
+         */
+        . = ALIGN(4);
+    } > RAM
+
+    /* BSS section. These are zero-initialized static variables. This section is
+     * not copied from FLASH into RAM but rather directly initialized, and is
+     * mainly put in this linker script so that we get an error if it overflows
+     * the RAM region.
+     */
+    .bss ALIGN(4) (NOLOAD) : {
+        /* .sbss is the RISC-V small data section */
+        *(.sbss .bss)
+    } > RAM
+
+    _heap_start = ADDR(.bss) + SIZEOF(.bss);  /* Used by rt_header */
+
+    /* Sections we do not need. */
+    /DISCARD/ :
+    {
+      *(.ARM.exidx .eh_frame)
+    }
+}
diff --git a/tock2 b/tock2
new file mode 160000
index 0000000..17e698e
--- /dev/null
+++ b/tock2
@@ -0,0 +1 @@
+Subproject commit 17e698e8fb2c9628624398435f75db0f5a50dbcd