sim: Add simulator code
'sim' is a small simulator for the bootloader's update code. It tests
untimely powerdowns to ensure that the bootloader will recover from a
power loss or reset at any time during the boot.
Note that, as of this commit, there are some failures in the test that
need to be investigated.
Also note that this build script does not output proper dependencies for
source files outside of the simulator directory, and won't rebuild the C
files if they or headers are modified.
diff --git a/sim/.gitignore b/sim/.gitignore
new file mode 100644
index 0000000..9409674
--- /dev/null
+++ b/sim/.gitignore
@@ -0,0 +1,2 @@
+target
+.*.swp
diff --git a/sim/Cargo.lock b/sim/Cargo.lock
new file mode 100644
index 0000000..75ff4ae
--- /dev/null
+++ b/sim/Cargo.lock
@@ -0,0 +1,204 @@
+[root]
+name = "bootsim"
+version = "0.1.0"
+dependencies = [
+ "docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)",
+ "error-chain 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-demangle 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "backtrace-sys"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "dbghelp-sys"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "docopt"
+version = "0.6.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "error-chain"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gcc"
+version = "0.3.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lazy_static"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memchr"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex"
+version = "0.1.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rustc-serialize"
+version = "0.3.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "strsim"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "thread-id"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thread_local"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "utf8-ranges"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
+"checksum backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f551bc2ddd53aea015d453ef0b635af89444afa5ed2405dd0b2062ad5d600d80"
+"checksum backtrace-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3602e8d8c43336088a8505fa55cae2b3884a9be29440863a11528a42f46f6bb7"
+"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
+"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
+"checksum docopt 0.6.86 (registry+https://github.com/rust-lang/crates.io-index)" = "4a7ef30445607f6fc8720f0a0a2c7442284b629cf0d049286860fae23e71c4d9"
+"checksum error-chain 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cd681735364a04cd5d69f01a4f6768e70473941f8d86d8c224faf6955a75799"
+"checksum gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "553f11439bdefe755bf366b264820f1da70f3aaf3924e594b886beb9c831bcf5"
+"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+"checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b"
+"checksum libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "044d1360593a78f5c8e5e710beccdc24ab71d1f01bc19a29bcacdba22e8475d8"
+"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
+"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
+"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
+"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
+"checksum rustc-demangle 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1430d286cadb237c17c885e25447c982c97113926bb579f4379c0eca8d9586dc"
+"checksum rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "237546c689f20bb44980270c73c3b9edd0891c1be49cc1274406134a66d3957b"
+"checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c"
+"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
+"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
+"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
+"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
diff --git a/sim/Cargo.toml b/sim/Cargo.toml
new file mode 100644
index 0000000..6f685d5
--- /dev/null
+++ b/sim/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "bootsim"
+version = "0.1.0"
+authors = ["David Brown <davidb@davidb.org>"]
+build = "build.rs"
+
+[build-dependencies]
+gcc = "0.3.38"
+
+[dependencies]
+libc = "0.2.0"
+rand = "0.3.0"
+error-chain = "0.7.1"
+docopt = "0.6"
+rustc-serialize = "0.3"
+
+[profile.test]
+opt-level = 1
diff --git a/sim/build.rs b/sim/build.rs
new file mode 100644
index 0000000..c69c09a
--- /dev/null
+++ b/sim/build.rs
@@ -0,0 +1,15 @@
+// Build the library.
+
+extern crate gcc;
+
+fn main() {
+ let mut conf = gcc::Config::new();
+
+ conf.file("../boot/bootutil/src/loader.c");
+ conf.file("../boot/bootutil/src/bootutil_misc.c");
+ conf.file("csupport/run.c");
+ conf.include("../boot/bootutil/include");
+ conf.include("../zephyr/include");
+ conf.debug(true);
+ conf.compile("libbootutil.a");
+}
diff --git a/sim/csupport/run.c b/sim/csupport/run.c
new file mode 100644
index 0000000..677f40c
--- /dev/null
+++ b/sim/csupport/run.c
@@ -0,0 +1,195 @@
+/* Run the boot image. */
+
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <bootutil/bootutil.h>
+#include <bootutil/image.h>
+#include "flash_map/flash_map.h"
+
+#include "../../boot/bootutil/src/bootutil_priv.h"
+
+extern int sim_flash_erase(void *flash, uint32_t offset, uint32_t size);
+extern int sim_flash_read(void *flash, uint32_t offset, uint8_t *dest, uint32_t size);
+extern int sim_flash_write(void *flash, uint32_t offset, const uint8_t *src, uint32_t size);
+
+static void *flash_device;
+static jmp_buf boot_jmpbuf;
+int flash_counter;
+
+int jumped = 0;
+
+struct area {
+ struct flash_area whole;
+ struct flash_area *areas;
+ uint32_t num_areas;
+ uint8_t id;
+};
+
+struct area_desc {
+ struct area slots[16];
+ uint32_t num_slots;
+};
+
+static struct area_desc *flash_areas;
+
+int invoke_boot_go(void *flash, struct area_desc *adesc)
+{
+ int res;
+ struct boot_rsp rsp;
+
+ flash_device = flash;
+ flash_areas = adesc;
+ if (setjmp(boot_jmpbuf) == 0) {
+ res = boot_go(&rsp);
+ printf("boot_go result: %d (0x%08x)\n", res, rsp.br_image_addr);
+ return res;
+ } else {
+ return -0x13579;
+ }
+}
+
+int hal_flash_read(uint8_t flash_id, uint32_t address, void *dst,
+ uint32_t num_bytes)
+{
+ // printf("hal_flash_read: %d, 0x%08x (0x%x)\n",
+ // flash_id, address, num_bytes);
+ return sim_flash_read(flash_device, address, dst, num_bytes);
+}
+
+int hal_flash_write(uint8_t flash_id, uint32_t address,
+ const void *src, int32_t num_bytes)
+{
+ // printf("hal_flash_write: 0x%08x (0x%x)\n", address, num_bytes);
+ // fflush(stdout);
+ if (--flash_counter == 0) {
+ jumped++;
+ longjmp(boot_jmpbuf, 1);
+ }
+ return sim_flash_write(flash_device, address, src, num_bytes);
+}
+
+int hal_flash_erase(uint8_t flash_id, uint32_t address,
+ uint32_t num_bytes)
+{
+ // printf("hal_flash_erase: 0x%08x, (0x%x)\n", address, num_bytes);
+ // fflush(stdout);
+ if (--flash_counter == 0) {
+ jumped++;
+ longjmp(boot_jmpbuf, 1);
+ }
+ return sim_flash_erase(flash_device, address, num_bytes);
+}
+
+uint8_t hal_flash_align(uint8_t flash_id)
+{
+ return 1;
+}
+
+void *os_malloc(size_t size)
+{
+ // printf("os_malloc 0x%x bytes\n", size);
+ return malloc(size);
+}
+
+int flash_area_id_from_image_slot(int slot)
+{
+ return slot + 1;
+}
+
+int flash_area_open(uint8_t id, const struct flash_area **area)
+{
+ int i;
+ struct area *slot;
+
+ for (i = 0; i < flash_areas->num_slots; i++) {
+ if (flash_areas->slots[i].id == id)
+ break;
+ }
+ if (i == flash_areas->num_slots) {
+ printf("Unsupported area\n");
+ abort();
+ }
+
+ /* Unsure if this is right, just returning the first area. */
+ *area = &flash_areas->slots[i].whole;
+ return 0;
+}
+
+void flash_area_close(const struct flash_area *area)
+{
+}
+
+/*
+ * Read/write/erase. Offset is relative from beginning of flash area.
+ */
+int flash_area_read(const struct flash_area *area, uint32_t off, void *dst,
+ uint32_t len)
+{
+ return hal_flash_read(area->fa_id,
+ area->fa_off + off,
+ dst, len);
+}
+
+int flash_area_write(const struct flash_area *area, uint32_t off, const void *src,
+ uint32_t len)
+{
+ return hal_flash_write(area->fa_id,
+ area->fa_off + off,
+ src, len);
+}
+
+int flash_area_erase(const struct flash_area *area, uint32_t off, uint32_t len)
+{
+ return hal_flash_erase(area->fa_id,
+ area->fa_off + off,
+ len);
+}
+
+int flash_area_to_sectors(int idx, int *cnt, struct flash_area *ret)
+{
+ int i;
+ struct area *slot;
+
+ for (i = 0; i < flash_areas->num_slots; i++) {
+ if (flash_areas->slots[i].id == idx)
+ break;
+ }
+ if (i == flash_areas->num_slots) {
+ printf("Unsupported area\n");
+ abort();
+ }
+
+ slot = &flash_areas->slots[i];
+
+ if (slot->num_areas > *cnt) {
+ printf("Too many areas in slot\n");
+ abort();
+ }
+
+ *cnt = slot->num_areas;
+ memcpy(ret, slot->areas, slot->num_areas * sizeof(struct flash_area));
+
+ return 0;
+}
+
+uint8_t sim_flash_align = 1;
+uint8_t flash_area_align(const struct flash_area *area)
+{
+ return sim_flash_align;
+}
+
+
+int bootutil_img_validate(struct image_header *hdr,
+ const struct flash_area *fap,
+ uint8_t *tmp_buf, uint32_t tmp_buf_sz,
+ uint8_t *seed, int seed_len, uint8_t *out_hash)
+{
+ if (hal_flash_read(fap->fa_id, fap->fa_off, tmp_buf, 4)) {
+ printf("Flash read error\n");
+ abort();
+ }
+
+ return (*((uint32_t *) tmp_buf) != 0x96f3b83c);
+}
diff --git a/sim/src/api.rs b/sim/src/api.rs
new file mode 100644
index 0000000..71d4643
--- /dev/null
+++ b/sim/src/api.rs
@@ -0,0 +1,34 @@
+//! HAL api for MyNewt applications
+
+use flash::{Result, Flash};
+use libc;
+use std::slice;
+
+// This isn't meant to call directly, but by a wrapper.
+
+#[no_mangle]
+pub extern fn sim_flash_erase(dev: *mut Flash, offset: u32, size: u32) -> libc::c_int {
+ let mut dev: &mut Flash = unsafe { &mut *dev };
+ map_err(dev.erase(offset as usize, size as usize))
+}
+
+#[no_mangle]
+pub extern fn sim_flash_read(dev: *const Flash, offset: u32, dest: *mut u8, size: u32) -> libc::c_int {
+ let dev: &Flash = unsafe { &*dev };
+ let mut buf: &mut[u8] = unsafe { slice::from_raw_parts_mut(dest, size as usize) };
+ map_err(dev.read(offset as usize, &mut buf))
+}
+
+#[no_mangle]
+pub extern fn sim_flash_write(dev: *mut Flash, offset: u32, src: *const u8, size: u32) -> libc::c_int {
+ let mut dev: &mut Flash = unsafe { &mut *dev };
+ let buf: &[u8] = unsafe { slice::from_raw_parts(src, size as usize) };
+ map_err(dev.write(offset as usize, &buf))
+}
+
+fn map_err(err: Result<()>) -> libc::c_int {
+ match err {
+ Ok(()) => 0,
+ Err(_) => -1,
+ }
+}
diff --git a/sim/src/area.rs b/sim/src/area.rs
new file mode 100644
index 0000000..9bb7d57
--- /dev/null
+++ b/sim/src/area.rs
@@ -0,0 +1,161 @@
+//! Describe flash areas.
+
+use flash::{Flash, Sector};
+use std::marker::PhantomData;
+use std::ptr;
+
+/// Structure to build up the boot area table.
+#[derive(Debug)]
+pub struct AreaDesc {
+ areas: Vec<Vec<FlashArea>>,
+ whole: Vec<FlashArea>,
+ sectors: Vec<Sector>,
+}
+
+impl AreaDesc {
+ pub fn new(flash: &Flash) -> AreaDesc {
+ AreaDesc {
+ areas: vec![],
+ whole: vec![],
+ sectors: flash.sector_iter().collect(),
+ }
+ }
+
+ /// Add a slot to the image. The slot must align with erasable units in the flash device.
+ /// Panics if the description is not valid. There are also bootloader assumptions that the
+ /// slots are SLOT0, SLOT1, and SCRATCH in that order.
+ pub fn add_image(&mut self, base: usize, len: usize, id: FlashId) {
+ let nid = id as usize;
+ let orig_base = base;
+ let orig_len = len;
+ let mut base = base;
+ let mut len = len;
+
+ while nid > self.areas.len() {
+ self.areas.push(vec![]);
+ self.whole.push(Default::default());
+ }
+
+ if nid != self.areas.len() {
+ panic!("Flash areas not added in order");
+ }
+
+ let mut area = vec![];
+
+ for sector in &self.sectors {
+ if len == 0 {
+ break;
+ };
+ if base > sector.base + sector.size - 1 {
+ continue;
+ }
+ if sector.base != base {
+ panic!("Image does not start on a sector boundary");
+ }
+
+ area.push(FlashArea {
+ flash_id: id,
+ device_id: 42,
+ pad16: 0,
+ off: sector.base as u32,
+ size: sector.size as u32,
+ });
+
+ base += sector.size;
+ len -= sector.size;
+ }
+
+ if len != 0 {
+ panic!("Image goes past end of device");
+ }
+
+ self.areas.push(area);
+ self.whole.push(FlashArea {
+ flash_id: id,
+ device_id: 42,
+ pad16: 0,
+ off: orig_base as u32,
+ size: orig_len as u32,
+ });
+ }
+
+ pub fn get_c(&self) -> CAreaDesc {
+ let mut areas: CAreaDesc = Default::default();
+
+ assert_eq!(self.areas.len(), self.whole.len());
+
+ for (i, area) in self.areas.iter().enumerate() {
+ if area.len() > 0 {
+ areas.slots[i].areas = &area[0];
+ areas.slots[i].whole = self.whole[i].clone();
+ areas.slots[i].num_areas = area.len() as u32;
+ areas.slots[i].id = area[0].flash_id;
+ }
+ }
+
+ areas.num_slots = self.areas.len() as u32;
+
+ areas
+ }
+}
+
+/// The area descriptor, C format.
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct CAreaDesc<'a> {
+ slots: [CArea<'a>; 16],
+ num_slots: u32,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct CArea<'a> {
+ whole: FlashArea,
+ areas: *const FlashArea,
+ num_areas: u32,
+ id: FlashId,
+ phantom: PhantomData<&'a AreaDesc>,
+}
+
+impl<'a> Default for CArea<'a> {
+ fn default() -> CArea<'a> {
+ CArea {
+ areas: ptr::null(),
+ whole: Default::default(),
+ id: FlashId::BootLoader,
+ num_areas: 0,
+ phantom: PhantomData,
+ }
+ }
+}
+
+/// Flash area map.
+#[repr(u8)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+#[allow(dead_code)]
+pub enum FlashId {
+ BootLoader = 0,
+ Image0 = 1,
+ Image1 = 2,
+ ImageScratch = 3,
+ Nffs = 4,
+ Core = 5,
+ RebootLog = 6
+}
+
+impl Default for FlashId {
+ fn default() -> FlashId {
+ FlashId::BootLoader
+ }
+}
+
+#[repr(C)]
+#[derive(Debug, Clone, Default)]
+pub struct FlashArea {
+ flash_id: FlashId,
+ device_id: u8,
+ pad16: u16,
+ off: u32,
+ size: u32,
+}
+
diff --git a/sim/src/c.rs b/sim/src/c.rs
new file mode 100644
index 0000000..7ddb21b
--- /dev/null
+++ b/sim/src/c.rs
@@ -0,0 +1,50 @@
+/// Interface wrappers to C API entering to the bootloader
+
+use area::AreaDesc;
+use flash::Flash;
+use libc;
+
+/// Invoke the bootloader on this flash device.
+pub fn boot_go(flash: &mut Flash, areadesc: &AreaDesc) -> i32 {
+ unsafe { raw::invoke_boot_go(flash as *mut _ as *mut libc::c_void,
+ &areadesc.get_c() as *const _) as i32 }
+}
+
+/// Setter/getter for the flash counter. This isn't thread safe.
+pub fn get_flash_counter() -> i32 {
+ unsafe { raw::flash_counter as i32 }
+}
+
+/// Set the flash counter. Zero indicates the flash should not be interrupted. The counter will
+/// then go negative for each flash operation.
+pub fn set_flash_counter(counter: i32) {
+ unsafe { raw::flash_counter = counter as libc::c_int };
+}
+
+pub fn boot_trailer_sz() -> u32 {
+ unsafe { raw::boot_trailer_sz(raw::sim_flash_align) }
+}
+
+pub fn get_sim_flash_align() -> u8 {
+ unsafe { raw::sim_flash_align }
+}
+
+pub fn set_sim_flash_align(align: u8) {
+ unsafe { raw::sim_flash_align = align };
+}
+
+mod raw {
+ use area::CAreaDesc;
+ use libc;
+
+ extern "C" {
+ // This generates a warning about `CAreaDesc` not being foreign safe. There doesn't appear to
+ // be any way to get rid of this warning. See https://github.com/rust-lang/rust/issues/34798
+ // for information and tracking.
+ pub fn invoke_boot_go(flash: *mut libc::c_void, areadesc: *const CAreaDesc) -> libc::c_int;
+ pub static mut flash_counter: libc::c_int;
+
+ pub static mut sim_flash_align: u8;
+ pub fn boot_trailer_sz(min_write_sz: u8) -> u32;
+ }
+}
diff --git a/sim/src/flash.rs b/sim/src/flash.rs
new file mode 100644
index 0000000..8cf267c
--- /dev/null
+++ b/sim/src/flash.rs
@@ -0,0 +1,230 @@
+//! A flash simulator
+//!
+//! This module is capable of simulating the type of NOR flash commonly used in microcontrollers.
+//! These generally can be written as individual bytes, but must be erased in larger units.
+
+use std::iter::Enumerate;
+use std::slice;
+use pdump::HexDump;
+
+error_chain! {
+ errors {
+ OutOfBounds(t: String) {
+ description("Offset is out of bounds")
+ display("Offset out of bounds: {}", t)
+ }
+ Write(t: String) {
+ description("Invalid write")
+ display("Invalid write: {}", t)
+ }
+ }
+}
+
+fn ebounds<T: AsRef<str>>(message: T) -> ErrorKind {
+ ErrorKind::OutOfBounds(message.as_ref().to_owned())
+}
+
+fn ewrite<T: AsRef<str>>(message: T) -> ErrorKind {
+ ErrorKind::Write(message.as_ref().to_owned())
+}
+
+/// An emulated flash device. It is represented as a block of bytes, and a list of the sector
+/// mapings.
+#[derive(Clone)]
+pub struct Flash {
+ data: Vec<u8>,
+ sectors: Vec<usize>,
+}
+
+impl Flash {
+ /// Given a sector size map, construct a flash device for that.
+ pub fn new(sectors: Vec<usize>) -> Flash {
+ let total = sectors.iter().sum();
+ Flash {
+ data: vec![0xffu8; total],
+ sectors: sectors,
+ }
+ }
+
+ /// The flash drivers tend to erase beyond the bounds of the given range. Instead, we'll be
+ /// strict, and make sure that the passed arguments are exactly at a sector boundary, otherwise
+ /// return an error.
+ pub fn erase(&mut self, offset: usize, len: usize) -> Result<()> {
+ let (_start, slen) = self.get_sector(offset).ok_or_else(|| ebounds("start"))?;
+ let (end, elen) = self.get_sector(offset + len - 1).ok_or_else(|| ebounds("end"))?;
+
+ if slen != 0 {
+ bail!(ebounds("offset not at start of sector"));
+ }
+ if elen != self.sectors[end] - 1 {
+ bail!(ebounds("end not at start of sector"));
+ }
+
+ for x in &mut self.data[offset .. offset + len] {
+ *x = 0xff;
+ }
+
+ Ok(())
+ }
+
+ /// Writes are fairly unconstrained, but we restrict to only allowing writes of values that
+ /// are entirely written as 0xFF.
+ pub fn write(&mut self, offset: usize, payload: &[u8]) -> Result<()> {
+ if offset + payload.len() > self.data.len() {
+ bail!(ebounds("Write outside of device"));
+ }
+
+ let mut sub = &mut self.data[offset .. offset + payload.len()];
+ if sub.iter().any(|x| *x != 0xFF) {
+ bail!(ewrite("Write to non-FF location"));
+ }
+
+ sub.copy_from_slice(payload);
+ Ok(())
+ }
+
+ /// Read is simple.
+ pub fn read(&self, offset: usize, data: &mut [u8]) -> Result<()> {
+ if offset + data.len() > self.data.len() {
+ bail!(ebounds("Read outside of device"));
+ }
+
+ let sub = &self.data[offset .. offset + data.len()];
+ data.copy_from_slice(sub);
+ Ok(())
+ }
+
+ // Scan the sector map, and return the base and offset within a sector for this given byte.
+ // Returns None if the value is outside of the device.
+ fn get_sector(&self, offset: usize) -> Option<(usize, usize)> {
+ let mut offset = offset;
+ for (sector, &size) in self.sectors.iter().enumerate() {
+ if offset < size {
+ return Some((sector, offset));
+ }
+ offset -= size;
+ }
+ return None;
+ }
+
+ /// An iterator over each sector in the device.
+ pub fn sector_iter(&self) -> SectorIter {
+ SectorIter {
+ iter: self.sectors.iter().enumerate(),
+ base: 0,
+ }
+ }
+
+ pub fn device_size(&self) -> usize {
+ self.data.len()
+ }
+
+ pub fn dump(&self) {
+ self.data.dump();
+ }
+}
+
+/// It is possible to iterate over the sectors in the device, each element returning this.
+#[derive(Debug)]
+pub struct Sector {
+ /// Which sector is this, starting from 0.
+ pub num: usize,
+ /// The offset, in bytes, of the start of this sector.
+ pub base: usize,
+ /// The length, in bytes, of this sector.
+ pub size: usize,
+}
+
+pub struct SectorIter<'a> {
+ iter: Enumerate<slice::Iter<'a, usize>>,
+ base: usize,
+}
+
+impl<'a> Iterator for SectorIter<'a> {
+ type Item = Sector;
+
+ fn next(&mut self) -> Option<Sector> {
+ match self.iter.next() {
+ None => None,
+ Some((num, &size)) => {
+ let base = self.base;
+ self.base += size;
+ Some(Sector {
+ num: num,
+ base: base,
+ size: size,
+ })
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{Flash, Error, ErrorKind, Result, Sector};
+
+ #[test]
+ fn test_flash() {
+ // NXP-style, uniform sectors.
+ let mut f1 = Flash::new(vec![4096usize; 256]);
+ test_device(&mut f1);
+
+ // STM style, non-uniform sectors
+ let mut f2 = Flash::new(vec![16 * 1024, 16 * 1024, 16 * 1024, 64 * 1024,
+ 128 * 1024, 128 * 1024, 128 * 1024]);
+ test_device(&mut f2);
+ }
+
+ fn test_device(flash: &mut Flash) {
+ let sectors: Vec<Sector> = flash.sector_iter().collect();
+
+ flash.erase(0, sectors[0].size).unwrap();
+ let flash_size = flash.device_size();
+ flash.erase(0, flash_size).unwrap();
+ assert!(flash.erase(0, sectors[0].size - 1).is_bounds());
+
+ // Verify that write and erase do something.
+ flash.write(0, &[0]).unwrap();
+ let mut buf = [0; 4];
+ flash.read(0, &mut buf).unwrap();
+ assert_eq!(buf, [0, 0xff, 0xff, 0xff]);
+
+ flash.erase(0, sectors[0].size).unwrap();
+ flash.read(0, &mut buf).unwrap();
+ assert_eq!(buf, [0xff; 4]);
+
+ // Program the first and last byte of each sector, verify that has been done, and then
+ // erase to verify the erase boundaries.
+ for sector in §ors {
+ let byte = [(sector.num & 127) as u8];
+ flash.write(sector.base, &byte).unwrap();
+ flash.write(sector.base + sector.size - 1, &byte).unwrap();
+ }
+
+ // Verify the above
+ let mut buf = Vec::new();
+ for sector in §ors {
+ let byte = (sector.num & 127) as u8;
+ buf.resize(sector.size, 0);
+ flash.read(sector.base, &mut buf).unwrap();
+ assert_eq!(buf.first(), Some(&byte));
+ assert_eq!(buf.last(), Some(&byte));
+ assert!(buf[1..buf.len()-1].iter().all(|&x| x == 0xff));
+ }
+ }
+
+ // Helper checks for the result type.
+ trait EChecker {
+ fn is_bounds(&self) -> bool;
+ }
+
+ impl<T> EChecker for Result<T> {
+
+ fn is_bounds(&self) -> bool {
+ match *self {
+ Err(Error(ErrorKind::OutOfBounds(_), _)) => true,
+ _ => false,
+ }
+ }
+ }
+}
diff --git a/sim/src/main.rs b/sim/src/main.rs
new file mode 100644
index 0000000..6822605
--- /dev/null
+++ b/sim/src/main.rs
@@ -0,0 +1,347 @@
+extern crate docopt;
+extern crate libc;
+extern crate rand;
+extern crate rustc_serialize;
+
+#[macro_use]
+extern crate error_chain;
+
+use docopt::Docopt;
+use rand::{Rng, SeedableRng, XorShiftRng};
+use rustc_serialize::{Decodable, Decoder};
+use std::mem;
+use std::slice;
+
+mod area;
+mod c;
+mod flash;
+pub mod api;
+mod pdump;
+
+use flash::Flash;
+use area::{AreaDesc, FlashId};
+
+const USAGE: &'static str = "
+Mcuboot simulator
+
+Usage:
+ bootsim sizes
+ bootsim run --device TYPE [--align SIZE]
+ bootsim (--help | --version)
+
+Options:
+ -h, --help Show this message
+ --version Version
+ --device TYPE MCU to simulate
+ Valid values: stm32f4, k64f
+ --align SIZE Flash write alignment
+";
+
+#[derive(Debug, RustcDecodable)]
+struct Args {
+ flag_help: bool,
+ flag_version: bool,
+ flag_device: Option<DeviceName>,
+ flag_align: Option<AlignArg>,
+ cmd_sizes: bool,
+ cmd_run: bool,
+}
+
+#[derive(Debug, RustcDecodable)]
+enum DeviceName { Stm32f4, K64f }
+
+#[derive(Debug)]
+struct AlignArg(u8);
+
+impl Decodable for AlignArg {
+ // Decode the alignment ourselves, to restrict it to the valid possible alignments.
+ fn decode<D: Decoder>(d: &mut D) -> Result<AlignArg, D::Error> {
+ let m = d.read_u8()?;
+ match m {
+ 1 | 2 | 4 | 8 => Ok(AlignArg(m)),
+ _ => Err(d.error("Invalid alignment")),
+ }
+ }
+}
+
+fn main() {
+ let args: Args = Docopt::new(USAGE)
+ .and_then(|d| d.decode())
+ .unwrap_or_else(|e| e.exit());
+ // println!("args: {:#?}", args);
+
+ if args.cmd_sizes {
+ show_sizes();
+ return;
+ }
+
+ let (mut flash, areadesc) = match args.flag_device {
+ None => panic!("Missing mandatory argument"),
+ Some(DeviceName::Stm32f4) => {
+ // STM style flash. Large sectors, with a large scratch area.
+ let flash = Flash::new(vec![16 * 1024, 16 * 1024, 16 * 1024, 16 * 1024,
+ 64 * 1024,
+ 128 * 1024, 128 * 1024, 128 * 1024]);
+ let mut areadesc = AreaDesc::new(&flash);
+ areadesc.add_image(0x020000, 0x020000, FlashId::Image0);
+ areadesc.add_image(0x040000, 0x020000, FlashId::Image1);
+ areadesc.add_image(0x060000, 0x020000, FlashId::ImageScratch);
+ (flash, areadesc)
+ }
+ Some(DeviceName::K64f) => {
+ // NXP style flash. Small sectors, one small sector for scratch.
+ let flash = Flash::new(vec![4096; 128]);
+
+ let mut areadesc = AreaDesc::new(&flash);
+ areadesc.add_image(0x020000, 0x020000, FlashId::Image0);
+ areadesc.add_image(0x040000, 0x020000, FlashId::Image1);
+ areadesc.add_image(0x060000, 0x001000, FlashId::ImageScratch);
+ (flash, areadesc)
+ }
+ };
+
+ // println!("Areas: {:#?}", areadesc.get_c());
+
+ // Install the boot trailer signature, so that the code will start an upgrade.
+ let primary = install_image(&mut flash, 0x020000, 32779);
+
+ // Install an upgrade image.
+ let upgrade = install_image(&mut flash, 0x040000, 41922);
+
+ // Set an alignment, and position the magic value.
+ c::set_sim_flash_align(args.flag_align.map(|x| x.0).unwrap_or(1));
+ let trailer_size = c::boot_trailer_sz();
+
+ // Mark the upgrade as ready to install. (This looks like it might be a bug in the code,
+ // however.)
+ mark_upgrade(&mut flash, 0x060000 - trailer_size as usize);
+
+ let (fl2, total_count) = try_upgrade(&flash, &areadesc, None);
+ println!("First boot, count={}", total_count);
+ assert!(verify_image(&fl2, 0x020000, &upgrade));
+
+ let mut bad = 0;
+ // Let's try an image halfway through.
+ for i in 1 .. total_count {
+ println!("Try interruption at {}", i);
+ let (fl3, total_count) = try_upgrade(&flash, &areadesc, Some(i));
+ println!("Second boot, count={}", total_count);
+ if !verify_image(&fl3, 0x020000, &upgrade) {
+ println!("FAIL");
+ bad += 1;
+ }
+ if !verify_image(&fl3, 0x040000, &primary) {
+ println!("Slot 1 FAIL");
+ bad += 1;
+ }
+ }
+ println!("{} out of {} failed {:.2}%",
+ bad, total_count,
+ bad as f32 * 100.0 / total_count as f32);
+
+ println!("Try revert");
+ let fl2 = try_revert(&flash, &areadesc);
+ assert!(verify_image(&fl2, 0x020000, &primary));
+
+ println!("Try norevert");
+ let fl2 = try_norevert(&flash, &areadesc);
+ assert!(verify_image(&fl2, 0x020000, &upgrade));
+
+ /*
+ // show_flash(&flash);
+
+ println!("First boot for upgrade");
+ // c::set_flash_counter(570);
+ c::boot_go(&mut flash, &areadesc);
+ // println!("{} flash ops", c::get_flash_counter());
+
+ verify_image(&flash, 0x020000, &upgrade);
+
+ println!("\n------------------\nSecond boot");
+ c::boot_go(&mut flash, &areadesc);
+ */
+}
+
+/// Test a boot, optionally stopping after 'n' flash options. Returns a count of the number of
+/// flash operations done total.
+fn try_upgrade(flash: &Flash, areadesc: &AreaDesc, stop: Option<i32>) -> (Flash, i32) {
+ // Clone the flash to have a new copy.
+ let mut fl = flash.clone();
+
+ c::set_flash_counter(stop.unwrap_or(0));
+ let (first_interrupted, cnt1) = match c::boot_go(&mut fl, &areadesc) {
+ -0x13579 => (true, stop.unwrap()),
+ 0 => (false, -c::get_flash_counter()),
+ x => panic!("Unknown return: {}", x),
+ };
+ c::set_flash_counter(0);
+
+ if first_interrupted {
+ // fl.dump();
+ match c::boot_go(&mut fl, &areadesc) {
+ -0x13579 => panic!("Shouldn't stop again"),
+ 0 => (),
+ x => panic!("Unknown return: {}", x),
+ }
+ }
+
+ let cnt2 = cnt1 - c::get_flash_counter();
+
+ (fl, cnt2)
+}
+
+fn try_revert(flash: &Flash, areadesc: &AreaDesc) -> Flash {
+ let mut fl = flash.clone();
+ c::set_flash_counter(0);
+
+ assert_eq!(c::boot_go(&mut fl, &areadesc), 0);
+ assert_eq!(c::boot_go(&mut fl, &areadesc), 0);
+ fl
+}
+
+fn try_norevert(flash: &Flash, areadesc: &AreaDesc) -> Flash {
+ let mut fl = flash.clone();
+ c::set_flash_counter(0);
+ let align = c::get_sim_flash_align() as usize;
+
+ assert_eq!(c::boot_go(&mut fl, &areadesc), 0);
+ // Write boot_ok
+ fl.write(0x040000 - align, &[1]).unwrap();
+ assert_eq!(c::boot_go(&mut fl, &areadesc), 0);
+ fl
+}
+
+/// Show the flash layout.
+#[allow(dead_code)]
+fn show_flash(flash: &Flash) {
+ println!("---- Flash configuration ----");
+ for sector in flash.sector_iter() {
+ println!(" {:2}: 0x{:08x}, 0x{:08x}",
+ sector.num, sector.base, sector.size);
+ }
+ println!("");
+}
+
+/// Install a "program" into the given image. This fakes the image header, or at least all of the
+/// fields used by the given code. Returns a copy of the image that was written.
+fn install_image(flash: &mut Flash, offset: usize, len: usize) -> Vec<u8> {
+ let offset0 = offset;
+
+ // Generate a boot header. Note that the size doesn't include the header.
+ let header = ImageHeader {
+ magic: 0x96f3b83c,
+ tlv_size: 0,
+ _pad1: 0,
+ hdr_size: 32,
+ key_id: 0,
+ _pad2: 0,
+ img_size: len as u32,
+ flags: 0,
+ ver: ImageVersion {
+ major: 1,
+ minor: 0,
+ revision: 1,
+ build_num: 1,
+ },
+ _pad3: 0,
+ };
+
+ let b_header = header.as_raw();
+ /*
+ let b_header = unsafe { slice::from_raw_parts(&header as *const _ as *const u8,
+ mem::size_of::<ImageHeader>()) };
+ */
+ assert_eq!(b_header.len(), 32);
+ flash.write(offset, &b_header).unwrap();
+ let offset = offset + b_header.len();
+
+ // The core of the image itself is just pseudorandom data.
+ let mut buf = vec![0; len];
+ splat(&mut buf, offset);
+ flash.write(offset, &buf).unwrap();
+ let offset = offset + buf.len();
+
+ // Copy out the image so that we can verify that the image was installed correctly later.
+ let mut copy = vec![0u8; offset - offset0];
+ flash.read(offset0, &mut copy).unwrap();
+
+ copy
+}
+
+/// Verify that given image is present in the flash at the given offset.
+fn verify_image(flash: &Flash, offset: usize, buf: &[u8]) -> bool {
+ let mut copy = vec![0u8; buf.len()];
+ flash.read(offset, &mut copy).unwrap();
+
+ if buf != ©[..] {
+ for i in 0 .. buf.len() {
+ if buf[i] != copy[i] {
+ println!("First failure at {:#x}", offset + i);
+ break;
+ }
+ }
+ false
+ } else {
+ true
+ }
+}
+
+/// The image header
+#[repr(C)]
+pub struct ImageHeader {
+ magic: u32,
+ tlv_size: u16,
+ key_id: u8,
+ _pad1: u8,
+ hdr_size: u16,
+ _pad2: u16,
+ img_size: u32,
+ flags: u32,
+ ver: ImageVersion,
+ _pad3: u32,
+}
+
+impl AsRaw for ImageHeader {}
+
+#[repr(C)]
+pub struct ImageVersion {
+ major: u8,
+ minor: u8,
+ revision: u16,
+ build_num: u32,
+}
+
+/// Write out the magic so that the loader tries doing an upgrade.
+fn mark_upgrade(flash: &mut Flash, offset: usize) {
+ let magic = vec![0x77, 0xc2, 0x95, 0xf3,
+ 0x60, 0xd2, 0xef, 0x7f,
+ 0x35, 0x52, 0x50, 0x0f,
+ 0x2c, 0xb6, 0x79, 0x80];
+ flash.write(offset, &magic).unwrap();
+}
+
+// Drop some pseudo-random gibberish onto the data.
+fn splat(data: &mut [u8], seed: usize) {
+ let seed_block = [0x135782ea, 0x92184728, data.len() as u32, seed as u32];
+ let mut rng: XorShiftRng = SeedableRng::from_seed(seed_block);
+ rng.fill_bytes(data);
+}
+
+/// Return a read-only view into the raw bytes of this object
+trait AsRaw : Sized {
+ fn as_raw<'a>(&'a self) -> &'a [u8] {
+ unsafe { slice::from_raw_parts(self as *const _ as *const u8,
+ mem::size_of::<Self>()) }
+ }
+}
+
+fn show_sizes() {
+ // This isn't panic safe.
+ let old_align = c::get_sim_flash_align();
+ for min in &[1, 2, 4, 8] {
+ c::set_sim_flash_align(*min);
+ let msize = c::boot_trailer_sz();
+ println!("{:2}: {} (0x{:x})", min, msize, msize);
+ }
+ c::set_sim_flash_align(old_align);
+}
diff --git a/sim/src/pdump.rs b/sim/src/pdump.rs
new file mode 100644
index 0000000..dbb42d5
--- /dev/null
+++ b/sim/src/pdump.rs
@@ -0,0 +1,76 @@
+// Printable hexdump.
+
+pub trait HexDump {
+ // Output the data value in hex.
+ fn dump(&self);
+}
+
+struct Dumper {
+ hex: String,
+ ascii: String,
+ count: usize,
+ total_count: usize,
+}
+
+impl Dumper {
+ fn new() -> Dumper {
+ Dumper {
+ hex: String::with_capacity(49),
+ ascii: String::with_capacity(16),
+ count: 0,
+ total_count: 0,
+ }
+ }
+
+ fn add_byte(&mut self, ch: u8) {
+ if self.count == 16 {
+ self.ship();
+ }
+ if self.count == 8 {
+ self.hex.push(' ');
+ }
+ self.hex.push_str(&format!(" {:02x}", ch)[..]);
+ self.ascii.push(if ch >= ' ' as u8 && ch <= '~' as u8 {
+ ch as char
+ } else {
+ '.'
+ });
+ self.count += 1;
+ }
+
+ fn ship(&mut self) {
+ if self.count == 0 {
+ return;
+ }
+
+ println!("{:06x} {:-49} |{}|", self.total_count, self.hex, self.ascii);
+
+ self.hex.clear();
+ self.ascii.clear();
+ self.total_count += 16;
+ self.count = 0;
+ }
+}
+
+impl<'a> HexDump for &'a [u8] {
+ fn dump(&self) {
+ let mut dump = Dumper::new();
+ for ch in self.iter() {
+ dump.add_byte(*ch);
+ }
+ dump.ship();
+ }
+}
+
+impl HexDump for Vec<u8> {
+ fn dump(&self) {
+ (&self[..]).dump()
+ }
+}
+
+#[test]
+fn samples() {
+ "Hello".as_bytes().dump();
+ "This is a much longer string".as_bytes().dump();
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f".as_bytes().dump();
+}