[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bsp/lk/lib/fs/spifs/rules.mk b/src/bsp/lk/lib/fs/spifs/rules.mk
new file mode 100644
index 0000000..b313d2b
--- /dev/null
+++ b/src/bsp/lk/lib/fs/spifs/rules.mk
@@ -0,0 +1,13 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/spifs.c \
+
+MODULE_DEPS += \
+	lib/fs \
+	lib/cksum \
+	lib/bio
+
+include make/module.mk
diff --git a/src/bsp/lk/lib/fs/spifs/spifs.c b/src/bsp/lk/lib/fs/spifs/spifs.c
new file mode 100644
index 0000000..6700651
--- /dev/null
+++ b/src/bsp/lk/lib/fs/spifs/spifs.c
@@ -0,0 +1,1209 @@
+/*
+ * Copyright (c) 2015 Gurjant Kalsi <me@gurjantkalsi.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <debug.h>
+#include <err.h>
+#include <pow2.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <trace.h>
+
+#include <kernel/mutex.h>
+#include <lib/bio.h>
+#include <lib/cksum.h>
+#include <lib/console.h>
+#include <lib/fs.h>
+#include <lib/fs/spifs.h>
+#include <list.h>
+#include <lk/init.h>
+
+#define LOCAL_TRACE 0
+
+#define FS_VERSION 1
+#define FS_MAGIC 0x53504653  // SPFS
+
+#define SPIFS_ENTRY_LENGTH 32
+
+#define TOC_HEADER_RESERVED_BYTES 16
+#define TOC_FOOTER_RESERVED_BYTES 28
+#define MAX_FILENAME_LENGTH 20
+
+#define CORRUPT_TOC 0
+#define NO_OPEN_RUNS 0
+
+#define FRONT_TOC (1)
+#define BACK_TOC  (-1)
+
+#define FRONT_TOC_LABEL "front-toc"
+#define BACK_TOC_LABEL "back-toc"
+
+typedef int32_t toc_position_t;
+
+typedef struct {
+    uint8_t *page;
+    uint32_t page_size;
+    uint32_t page_count;
+    uint32_t blocks_per_page;
+
+    uint32_t generation;
+    uint32_t num_entries;
+    toc_position_t toc_position;
+
+    struct list_node files;
+    struct list_node dcookies;
+
+    bdev_t *dev;
+
+    mutex_t lock;
+} spifs_t;
+
+typedef struct {
+    uint32_t magic;
+    uint32_t version;
+    uint32_t num_entries;
+    uint32_t generation;
+
+    uint8_t _reserved[TOC_HEADER_RESERVED_BYTES];
+} toc_header_t;
+
+typedef struct {
+    uint32_t page_idx;
+    uint32_t length;
+    uint32_t capacity;
+    char filename[MAX_FILENAME_LENGTH];
+} toc_file_t;
+
+typedef struct {
+    uint8_t _reserved[TOC_FOOTER_RESERVED_BYTES];
+    uint32_t checksum;
+} toc_footer_t;
+
+typedef struct {
+    struct list_node node;
+    spifs_t *fs_handle;
+    toc_file_t metadata;
+} spifs_file_t;
+
+struct dircookie {
+    struct list_node node;
+    spifs_t *fs;
+
+    spifs_file_t *next_file;
+};
+
+typedef struct {
+    uint32_t page_id;
+    int32_t direction;
+    uint32_t entry_length;
+    uint8_t *data;
+    uint8_t *page;
+    spifs_t *spifs;
+} cursor_t;
+
+static status_t spifs_read_page(spifs_t *spifs, uint32_t page_addr);
+static status_t spifs_write_page(spifs_t *spifs, uint32_t page_addr);
+
+static status_t get_device_page_info(bdev_t *dev, uint32_t *page_size,
+                                     uint32_t *page_count);
+
+
+static status_t cursor_init(
+    cursor_t *cursor, spifs_t *spifs, int32_t direction, uint32_t page_id,
+    uint32_t entry_length
+)
+{
+    // Make sure the cursor can only be advanced an integer number of times
+    // per page.
+    DEBUG_ASSERT(ispow2(entry_length));
+    DEBUG_ASSERT(spifs->page_size % entry_length == 0);
+    DEBUG_ASSERT(spifs->page);
+
+    cursor->page_id = page_id;
+    cursor->direction = direction;
+    cursor->entry_length = entry_length;
+    cursor->data = spifs->page;
+    cursor->spifs = spifs;
+
+
+    return spifs_read_page(spifs, page_id);
+}
+
+static uint8_t *cursor_get(cursor_t *cursor)
+{
+    spifs_t *spifs = cursor->spifs;
+
+    uint8_t *page_end = spifs->page + spifs->page_size;
+    DEBUG_ASSERT(cursor->data < page_end);
+
+    return cursor->data;
+}
+
+static status_t cursor_advance(cursor_t *cursor)
+{
+    spifs_t *spifs = cursor->spifs;
+
+    uint8_t *page_end = spifs->page + spifs->page_size;
+
+    cursor->data += cursor->entry_length;
+
+    // We have walked past the end of our buffer.
+    DEBUG_ASSERT(page_end >= cursor->data);
+
+    // If we're at the end of this page, read the next page and move the cursor
+    // to the beginning of it.
+    if (cursor->data == page_end) {
+        cursor->page_id += cursor->direction;
+        cursor->data = spifs->page;
+
+        return spifs_read_page(spifs, cursor->page_id);
+    }
+
+    return NO_ERROR;
+}
+
+static spifs_file_t *find_file(spifs_t *spifs, const char *name)
+{
+    spifs_file_t *file;
+
+    list_for_every_entry(&spifs->files, file, spifs_file_t, node) {
+        // Skip the ToC Entries
+        if (file == list_peek_head_type(&spifs->files, spifs_file_t, node) ||
+                file == list_peek_tail_type(&spifs->files, spifs_file_t, node)) {
+            continue;
+        }
+
+        if (!strncmp(name, file->metadata.filename, MAX_FILENAME_LENGTH)) {
+            return file;
+        }
+    }
+
+    return NULL;
+}
+
+static uint32_t find_open_run(spifs_t *spifs, uint32_t requested_length)
+{
+    spifs_file_t *file;
+    list_for_every_entry(&spifs->files, file, spifs_file_t, node) {
+        // Number of pages that this file occupies
+        uint32_t page_size_shift = log2_uint(file->fs_handle->page_size);
+
+        uint32_t file_page_length =
+            divpow2(file->metadata.capacity, page_size_shift);
+
+        // Index of the page immediately following the last page of this file.
+        uint32_t file_end_page = file->metadata.page_idx + file_page_length;
+
+        // Determine the page that the next file starts at.
+        spifs_file_t *next =
+            list_next_type(&spifs->files, &file->node, spifs_file_t, node);
+
+        // End of list?
+        if (next == NULL) {
+            return NO_OPEN_RUNS;
+        }
+
+        uint32_t available_pages = next->metadata.page_idx - file_end_page;
+        uint32_t available_bytes = available_pages * file->fs_handle->page_size;
+        if (available_bytes >= requested_length) {
+            return file_end_page;
+        }
+    }
+    return NO_OPEN_RUNS;
+}
+
+static uint64_t used_space(spifs_t *spifs)
+{
+    uint64_t result = 0;
+
+    spifs_file_t *file;
+    list_for_every_entry(&spifs->files, file, spifs_file_t, node) {
+        result += file->metadata.capacity;
+    }
+
+    return result;
+}
+
+static bool consistency_check(spifs_t *spifs)
+{
+    /* Return true iff the ToC is in a consistent state. */
+    spifs_file_t *file;
+    list_for_every_entry(&spifs->files, file, spifs_file_t, node) {
+        // Number of pages that this file occupies
+        uint32_t file_page_length =
+            file->metadata.capacity / file->fs_handle->page_size;
+
+        // Index of the last page of this file.
+        uint32_t file_end_page = file->metadata.page_idx + file_page_length - 1;
+
+        // Determine the page that the next file starts at.
+        spifs_file_t *next =
+            list_next_type(&spifs->files, &file->node, spifs_file_t, node);
+
+        // End of list?
+        if (next == NULL) {
+            continue;
+        }
+
+        if (next->metadata.page_idx <= file_end_page) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static status_t spifs_commit_toc(spifs_t *spifs)
+{
+    status_t err;
+
+    // Get the next logical ToC.
+    toc_position_t target_toc =
+        spifs->toc_position == FRONT_TOC ? BACK_TOC : FRONT_TOC;
+
+    // Bump the generation counter.
+    uint32_t target_generation = spifs->generation + 1;
+
+    uint32_t crc = 0;
+    uint8_t *cursor = spifs->page;
+    uint32_t toc_page_addr = target_toc == FRONT_TOC ?
+                             0 : spifs->page_count - 1;
+
+    // Setup the ToC Header.
+    toc_header_t header = {
+        .magic       = FS_MAGIC,
+        .version     = FS_VERSION,
+        .num_entries = spifs->num_entries,
+        .generation  = target_generation,
+    };
+    memset(header._reserved, 0, TOC_HEADER_RESERVED_BYTES);
+
+    crc = crc32(crc, (uint8_t *)&header, SPIFS_ENTRY_LENGTH);
+
+    memcpy(cursor, (uint8_t *)&header, SPIFS_ENTRY_LENGTH);
+    cursor += SPIFS_ENTRY_LENGTH;
+
+    // Create an empty file to copy into the empty spots in the ToC
+    toc_file_t empty;
+    memset(&empty, 0, SPIFS_ENTRY_LENGTH);
+
+    spifs_file_t *file = list_peek_head_type(&spifs->files, spifs_file_t, node);
+    for (uint32_t i = 0; i < spifs->num_entries; i++) {
+        uint8_t *page_end = spifs->page + spifs->page_size;
+        DEBUG_ASSERT(cursor <= page_end);
+
+        if (cursor == page_end) {
+            err = spifs_write_page(spifs, toc_page_addr);
+            if (err != NO_ERROR) {
+                return err;
+            }
+
+            toc_page_addr += target_toc;
+            cursor = spifs->page;
+        }
+
+        if (file) {
+            crc = crc32(crc, (uint8_t *)&file->metadata, SPIFS_ENTRY_LENGTH);
+            memcpy(cursor, (uint8_t *)&file->metadata, SPIFS_ENTRY_LENGTH);
+            file = list_next_type(&spifs->files, &file->node, spifs_file_t, node);
+        } else {
+            crc = crc32(crc, (uint8_t *)&empty, SPIFS_ENTRY_LENGTH);
+            memcpy(cursor, (uint8_t *)&empty, SPIFS_ENTRY_LENGTH);
+        }
+
+        cursor += SPIFS_ENTRY_LENGTH;
+    }
+
+    // Sanity check. The cursor should be at the last position in this page
+    // at this point.
+    uint8_t *expected_cursor_location =
+        (spifs->page + spifs->page_size) - SPIFS_ENTRY_LENGTH;
+    DEBUG_ASSERT(cursor == expected_cursor_location);
+
+    toc_footer_t footer;
+    memset(&footer, 0, SPIFS_ENTRY_LENGTH);
+    footer.checksum = crc;
+    memcpy(cursor, (uint8_t *)&footer, SPIFS_ENTRY_LENGTH);
+
+    err = spifs_write_page(spifs, toc_page_addr);
+    if (err != NO_ERROR)
+        return err;
+
+    // Only update this once we're sure that the write went through.
+    // This way, if the write failed, we'll try writing over the bad ToC again
+    // rather than potentially corrupting both ToCs.
+    spifs->generation = target_generation;
+    spifs->toc_position = target_toc;
+
+    return NO_ERROR;
+}
+
+static void spifs_add_ascending(spifs_t *spifs, spifs_file_t *target)
+{
+    spifs_file_t *file;
+    list_for_every_entry(&spifs->files, file, spifs_file_t, node) {
+        if (file->metadata.page_idx > target->metadata.page_idx) {
+            list_add_before(&file->node, &target->node);
+            return;
+        }
+    }
+
+    list_add_tail(&spifs->files, &target->node);
+}
+
+
+static status_t spifs_read_page(spifs_t *spifs, uint32_t page_addr)
+{
+    off_t block_addr = page_addr * spifs->blocks_per_page;
+
+    ssize_t bytes = bio_read_block(spifs->dev, spifs->page, block_addr,
+                                   spifs->blocks_per_page);
+
+    if ((uint32_t)bytes != spifs->page_size) {
+        return ERR_IO;
+    }
+
+    return NO_ERROR;
+}
+
+static status_t spifs_write_page(spifs_t *spifs, uint32_t page_addr)
+{
+    off_t block_addr = page_addr * spifs->blocks_per_page;
+    off_t device_addr = block_addr * spifs->dev->block_size;
+
+    // Device requires erase before write?
+    if (spifs->dev->geometry_count != 0) {
+        ssize_t bytes = bio_erase(spifs->dev, device_addr, spifs->page_size);
+        if ((uint32_t)bytes != spifs->page_size) {
+            return ERR_IO;
+        }
+    }
+
+    ssize_t bytes = bio_write_block(spifs->dev, spifs->page, block_addr,
+                                    spifs->blocks_per_page);
+
+    if ((uint32_t)bytes != spifs->page_size) {
+        return ERR_IO;
+    }
+
+    return NO_ERROR;
+}
+
+static uint32_t get_toc_generation(spifs_t *spifs, toc_position_t toc_pos)
+{
+    LTRACEF("spifs %p\n", spifs);
+
+    uint32_t candidate_generation;
+
+    DEBUG_ASSERT(spifs);
+
+    DEBUG_ASSERT(toc_pos == FRONT_TOC || toc_pos == BACK_TOC);
+    uint32_t toc_page = toc_pos == FRONT_TOC ?
+                        0 : (spifs->page_count - 1);
+
+
+    cursor_t cursor;
+    if (cursor_init(&cursor, spifs, toc_pos, toc_page, SPIFS_ENTRY_LENGTH) !=
+            NO_ERROR) {
+        return CORRUPT_TOC;
+    }
+
+    toc_header_t *header = (toc_header_t *)cursor_get(&cursor);
+
+    if (header->magic != FS_MAGIC) {
+        return CORRUPT_TOC;
+    }
+
+    if (header->version != FS_VERSION) {
+        return CORRUPT_TOC;
+    }
+
+    candidate_generation = header->generation;
+    uint32_t num_toc_entries = header->num_entries;
+
+    uint32_t crc = 0;
+    crc = crc32(crc, (uint8_t *)header, SPIFS_ENTRY_LENGTH);
+
+    header = NULL;
+
+    for (size_t i = 0; i < num_toc_entries; i++) {
+        if (cursor_advance(&cursor) != NO_ERROR)
+            return CORRUPT_TOC;
+
+        crc = crc32(crc, cursor_get(&cursor), SPIFS_ENTRY_LENGTH);
+    }
+
+    if (cursor_advance(&cursor) != NO_ERROR)
+        return CORRUPT_TOC;
+
+    toc_footer_t *footer = (toc_footer_t *)cursor_get(&cursor);
+    if (footer->checksum != crc) {
+        return CORRUPT_TOC;
+    }
+
+    return candidate_generation;
+}
+
+// page_size will be populated with the device's page size if this function
+// returns NO_ERROR, otherwise the contents of page_size are undefined.
+static status_t get_device_page_info(bdev_t *dev, uint32_t *page_size, uint32_t *page_count)
+{
+    LTRACEF("dev %p, page_size %p\n", dev, page_size);
+
+    switch (dev->geometry_count) {
+        case 0: {
+            // Device has no erase geometry; overwriting is supported.
+            *page_size = dev->block_size;
+            *page_count = dev->total_size / (*page_size);
+            return NO_ERROR;
+        }
+        case 1: {
+            // Device has erase geometry.
+            size_t erase_size = valpow2(dev->geometry->erase_size);
+            size_t block_size = dev->block_size;
+
+            if (erase_size % block_size != 0) {
+                // erase_size must be a multiple of the block size.
+                return ERR_NOT_SUPPORTED;
+            }
+
+            *page_size = erase_size;
+            *page_count = dev->total_size / (*page_size);
+            return NO_ERROR;
+        }
+        default: {
+            // We don't support non-uniform erase geometry.
+            return ERR_NOT_SUPPORTED;
+        }
+    }
+}
+
+static status_t spifs_format(bdev_t *dev, const void *args)
+{
+    status_t err = NO_ERROR;
+
+    LTRACEF("dev %p, args %p\n", dev, args);
+
+    if (!dev) {
+        return ERR_INVALID_ARGS;
+    }
+
+    spifs_format_args_t *spifs_args;
+    spifs_format_args_t default_args = {
+        .toc_pages = 1,
+    };
+
+    if (!args) {
+        spifs_args = &default_args;
+    } else {
+        spifs_args = (spifs_format_args_t *)args;
+    }
+
+    // Make sure that each of the three data structures are the same size.
+    STATIC_ASSERT(sizeof(toc_header_t) == SPIFS_ENTRY_LENGTH);
+    STATIC_ASSERT(sizeof(toc_file_t) == SPIFS_ENTRY_LENGTH);
+    STATIC_ASSERT(sizeof(toc_footer_t) == SPIFS_ENTRY_LENGTH);
+
+    uint32_t page_size;
+    uint32_t page_count;
+    err = get_device_page_info(dev, &page_size, &page_count);
+    if (err != NO_ERROR)
+        return err;
+
+    // Make sure entries can be exactly packed into pages.
+    if (page_size % SPIFS_ENTRY_LENGTH != 0) {
+        return ERR_NOT_SUPPORTED;
+    }
+
+    // Make sure the device size is some multiple of the page size;
+    // we don't want a partial page at the end of the device.
+    if (dev->total_size % page_size != 0) {
+        return ERR_NOT_SUPPORTED;
+    }
+
+    uint32_t entires_per_page = page_size / SPIFS_ENTRY_LENGTH;
+
+    // Number of ToC entrries is the total number of entries less 2 for the
+    // header/footer
+    uint32_t num_entries = spifs_args->toc_pages * entires_per_page;
+    uint32_t num_toc_entries = num_entries - 2;
+
+    // Four entries will be consumed by metadata: Header, Front ToC entry,
+    // Back ToC entry, footer. If there are only four entries, there will be
+    // no room for files.
+    if (num_entries <= 4) {
+        return ERR_TOO_BIG;
+    }
+
+    // Create a mock spifs_t for the purposes of formatting the fs.
+    spifs_t spifs = {
+        .page_size = page_size,
+        .page_count = page_count,
+        .blocks_per_page = divpow2(page_size, dev->block_shift),
+        .generation = 1,
+        .num_entries = num_toc_entries,
+        .toc_position = FRONT_TOC,
+        .dev = dev,
+    };
+    spifs.page = memalign(CACHE_LINE, page_size);
+    list_initialize(&spifs.files);
+    list_initialize(&spifs.dcookies);
+    mutex_init(&spifs.lock);
+
+    spifs_file_t f_toc;
+    f_toc.metadata.page_idx = 0;
+    f_toc.metadata.length = spifs_args->toc_pages * page_size;
+    f_toc.metadata.capacity = spifs_args->toc_pages * page_size;
+    f_toc.fs_handle = &spifs;
+    memset(f_toc.metadata.filename, 0, MAX_FILENAME_LENGTH);
+    strlcpy(f_toc.metadata.filename, FRONT_TOC_LABEL, MAX_FILENAME_LENGTH);
+
+    spifs_file_t b_toc;
+    b_toc.metadata.page_idx = page_count - spifs_args->toc_pages;
+    b_toc.metadata.length = spifs_args->toc_pages * page_size;
+    b_toc.metadata.capacity = spifs_args->toc_pages * page_size;
+    b_toc.fs_handle = &spifs;
+    memset(b_toc.metadata.filename, 0, MAX_FILENAME_LENGTH);
+    strlcpy(b_toc.metadata.filename, BACK_TOC_LABEL, MAX_FILENAME_LENGTH);
+
+    spifs_add_ascending(&spifs, &f_toc);
+    spifs_add_ascending(&spifs, &b_toc);
+
+    // Commit the first toc.
+    err = spifs_commit_toc(&spifs);
+    if (err != NO_ERROR)
+        goto err;
+
+    // Commit the other toc.
+    err = spifs_commit_toc(&spifs);
+    if (err != NO_ERROR)
+        goto err;
+
+err:
+    free(spifs.page);
+
+    return err;
+}
+
+static status_t spifs_mount(bdev_t *dev, fscookie **cookie)
+{
+    status_t status;
+
+    LTRACEF("dev %p, cookie %p\n", dev, cookie);
+
+    spifs_t *spifs = malloc(sizeof(*spifs));
+    if (!spifs) {
+        return ERR_NO_MEMORY;
+    }
+
+    status = get_device_page_info(dev, &spifs->page_size, &spifs->page_count);
+    if (status != NO_ERROR) {
+        free(spifs);
+        return status;
+    }
+
+    spifs->blocks_per_page = divpow2(spifs->page_size, dev->block_shift);
+
+    spifs->page = memalign(CACHE_LINE, spifs->page_size);
+    if (!spifs->page) {
+        free(spifs);
+        return ERR_NO_MEMORY;
+    }
+
+    spifs->dev = dev;
+
+    list_initialize(&spifs->files);
+    list_initialize(&spifs->dcookies);
+    mutex_init(&spifs->lock);
+
+    // Determine which of the two Table of Contents we should use.
+    uint32_t f_toc_generation = get_toc_generation(spifs, FRONT_TOC);
+    uint32_t b_toc_generation = get_toc_generation(spifs, BACK_TOC);
+
+    if (f_toc_generation == CORRUPT_TOC && b_toc_generation == CORRUPT_TOC) {
+        // Both ToCs are corrupt.
+        status = ERR_CRC_FAIL;
+        goto err;
+    }
+
+    spifs->toc_position =
+        f_toc_generation > b_toc_generation ? FRONT_TOC : BACK_TOC;
+    spifs->generation = MAX(f_toc_generation, b_toc_generation);
+
+    uint32_t toc_page_addr = spifs->toc_position == FRONT_TOC ?
+                             0 : spifs->page_count - 1;
+
+    cursor_t cursor;
+    status = cursor_init(&cursor, spifs, spifs->toc_position, toc_page_addr,
+                         SPIFS_ENTRY_LENGTH);
+    if (status != NO_ERROR)
+        goto err;
+
+    toc_header_t *header = (toc_header_t *)cursor_get(&cursor);
+    spifs->num_entries = header->num_entries;
+    header = NULL;
+
+    // Create in-memory versions of metadata for files.
+    spifs_file_t *file;
+    for (size_t i = 0; i < spifs->num_entries; i++) {
+        status = cursor_advance(&cursor);
+        if (status != NO_ERROR)
+            goto err;
+
+        toc_file_t *file_entry = (toc_file_t *)cursor_get(&cursor);
+        if (file_entry->capacity == 0) {
+            continue;
+        }
+
+        file = malloc(sizeof(*file));
+        if (!file) {
+            status = ERR_NO_MEMORY;
+            goto err;
+        }
+
+        memcpy(&file->metadata, file_entry, SPIFS_ENTRY_LENGTH);
+
+        file->fs_handle = spifs;
+
+        list_add_tail(&spifs->files, &file->node);
+    }
+
+    if (!consistency_check(spifs)) {
+        status = ERR_BAD_STATE;
+        goto err;
+    }
+
+    *cookie = (fscookie *)spifs;
+
+    return NO_ERROR;
+
+err:
+    while ((file = list_remove_head_type(&spifs->files, spifs_file_t, node))) {
+        free(file);
+    }
+
+    free(spifs->page);
+    free(spifs);
+    return status;
+}
+
+static status_t spifs_unmount(fscookie *cookie)
+{
+    LTRACEF("cookie %p\n", cookie);
+
+    spifs_t *spifs = (spifs_t *)cookie;
+
+    mutex_acquire(&spifs->lock);
+
+    spifs_file_t *file;
+    while ((file = list_remove_head_type(&spifs->files, spifs_file_t, node))) {
+        free(file);
+    }
+
+    free(spifs->page);
+
+    mutex_release(&spifs->lock);
+
+    free(spifs);
+
+    return NO_ERROR;
+}
+
+static status_t spifs_create(fscookie *cookie, const char *name, filecookie **fcookie, uint64_t len)
+{
+    status_t status = NO_ERROR;
+
+    LTRACEF("cookie %p name '%s' filecookie %p len %llu\n", cookie, name, fcookie, len);
+
+    spifs_t *spifs = (spifs_t *)cookie;
+
+    // Strip leading fwd-slashes
+    name = trim_name(name);
+
+    // File system is flat, directories not supported.
+    if (strchr(name, '/'))
+        return ERR_NOT_SUPPORTED;
+
+    // Check that filename is not too long.
+    if (strnlen(name, MAX_FILENAME_LENGTH) == MAX_FILENAME_LENGTH)
+        return ERR_BAD_PATH;
+
+    // Length is bigger than 4GB?
+    if (len > 0xFFFFFFFF)
+        return ERR_TOO_BIG;
+
+    mutex_acquire(&spifs->lock);
+
+    if (find_file(spifs, name)) {
+        status = ERR_ALREADY_EXISTS;
+        goto err;
+    }
+
+    // Is the ToC full? Have we reached the limit on the number of files?
+    size_t num_files_in_toc = list_length(&spifs->files);
+    DEBUG_ASSERT(num_files_in_toc <= spifs->num_entries);
+    if (num_files_in_toc >= spifs->num_entries) {
+        status = ERR_TOO_BIG;
+        goto err;
+    }
+
+    uint32_t capacity;
+    if (len == 0) {
+        capacity = spifs->page_size;
+    } else {
+        capacity = ROUNDUP(len, spifs->page_size);
+    }
+
+    uint32_t open_run = find_open_run(spifs, capacity);
+    if (open_run == NO_OPEN_RUNS) {
+        status = ERR_TOO_BIG;
+        goto err;
+    }
+
+    spifs_file_t *file = malloc(sizeof(*file));
+    if (!file) {
+        status = ERR_NO_MEMORY;
+        goto err;
+    }
+
+    file->fs_handle = spifs;
+    file->metadata.page_idx = open_run;
+    file->metadata.length = len;
+    file->metadata.capacity = capacity;
+    memset(file->metadata.filename, 0, MAX_FILENAME_LENGTH);
+    strlcpy(file->metadata.filename, name, MAX_FILENAME_LENGTH);
+
+    // Erase the memory allocated to the file.
+    if (bio_erase(spifs->dev, open_run * spifs->page_size, capacity) !=
+            (ssize_t)capacity) {
+
+        free(file);
+
+        status = ERR_IO;
+        goto err;
+    }
+
+    spifs_add_ascending(spifs, file);
+
+    if (spifs_commit_toc(spifs) != NO_ERROR) {
+        // If the commit fails, make sure we don't leave any residue of the file
+        // lying around.
+        list_delete(&file->node);
+        free(file);
+        *fcookie = NULL;
+
+        status = ERR_IO;
+        goto err;
+    }
+
+    *fcookie = (filecookie *) file;
+
+err:
+    mutex_release(&spifs->lock);
+
+    return status;
+}
+
+static status_t spifs_open(fscookie *cookie, const char *name, filecookie **fcookie)
+{
+    LTRACEF("cookie %p name '%s' filecookie %p\n", cookie, name, fcookie);
+
+    spifs_t *spifs = (spifs_t *)cookie;
+
+    name = trim_name(name);
+
+    mutex_acquire(&spifs->lock);
+
+    spifs_file_t *file = find_file(spifs, name);
+
+    mutex_release(&spifs->lock);
+
+    if (!file)
+        return ERR_NOT_FOUND;
+
+    *fcookie = (filecookie *)file;
+
+    return NO_ERROR;
+}
+
+static status_t spifs_close(filecookie *fcookie)
+{
+    spifs_file_t *file = (spifs_file_t *)fcookie;
+
+    LTRACEF("cookie %p name '%s'\n", fcookie, file->metadata.filename);
+
+    return NO_ERROR;
+}
+
+static status_t spifs_remove(fscookie *cookie, const char *name)
+{
+    status_t status;
+
+    LTRACEF("cookie %p name '%s'\n", cookie, name);
+
+    spifs_t *spifs = (spifs_t *)cookie;
+
+    // make sure we strip out any leading /
+    name = trim_name(name);
+
+    mutex_acquire(&spifs->lock);
+
+    spifs_file_t *file = find_file(spifs, name);
+
+    if (!file) {
+        status = ERR_NOT_FOUND;
+        goto err;
+    }
+
+    // Make sure there are no dirents open that point to the file that we're
+    // deleting.
+    dircookie *dcookie;
+    list_for_every_entry(&spifs->dcookies, dcookie, dircookie, node) {
+        if (dcookie->next_file == file) {
+            dcookie->next_file = list_next_type(&dcookie->fs->files,
+                                                &dcookie->next_file->node,
+                                                spifs_file_t, node);
+        }
+    }
+
+    list_delete(&file->node);
+    free(file);
+
+    status = spifs_commit_toc(spifs);
+
+err:
+    mutex_release(&spifs->lock);
+
+    return status;
+}
+
+static ssize_t spifs_read(filecookie *fcookie, void *buf, off_t off, size_t len)
+{
+    LTRACEF("filecookie %p buf %p offset %lld len %zu\n", fcookie, buf, off, len);
+
+    spifs_file_t *file = (spifs_file_t *)fcookie;
+    spifs_t *spifs = file->fs_handle;
+
+    if (off < 0)
+        return ERR_INVALID_ARGS;
+
+    mutex_acquire(&spifs->lock);
+
+    uint32_t file_start = file->fs_handle->page_size * file->metadata.page_idx;
+    uint32_t file_end = file_start + file->metadata.length;
+
+    uint32_t read_start = file_start + off;
+    uint32_t read_end = read_start + len;
+
+    if (read_start >= file_end) {
+        len = 0;
+    } else if (read_end > file_end) {
+        len = file_end - read_start;
+    }
+
+    DEBUG_ASSERT(file->fs_handle->dev);
+
+    ssize_t result = bio_read(file->fs_handle->dev, buf, read_start, len);
+
+    mutex_release(&spifs->lock);
+
+    return result;
+}
+
+static ssize_t spifs_write(filecookie *fcookie, const void *buf, off_t off, size_t size)
+{
+    status_t err = NO_ERROR;
+    size_t len = size;
+
+    LTRACEF("filecookie %p buf %p offset %lld len %zu\n", fcookie, buf, off, len);
+
+    spifs_file_t *file = (spifs_file_t *)fcookie;
+    spifs_t *spifs = (spifs_t *)(file->fs_handle);
+
+    if (off < 0)
+        return ERR_INVALID_ARGS;
+
+    mutex_acquire(&spifs->lock);
+
+    if (off + len > file->metadata.capacity) {
+        err = ERR_OUT_OF_RANGE;
+        goto err;
+    }
+
+    bool dirty_toc = false;
+
+    uint32_t start_addr =
+        off + (file->metadata.page_idx * spifs->page_size);
+
+    uint32_t page_shift = log2_uint(spifs->page_size);
+    uint32_t target_page_id = divpow2(start_addr, page_shift);
+
+    // Are we growing the file?
+    if (off + len > file->metadata.length) {
+        file->metadata.length = off + len;
+        dirty_toc = true;
+    }
+
+    // Leading Partial Page.
+    uint32_t page_offset = start_addr % spifs->page_size;
+    if (page_offset) {
+        uint32_t page_end = ROUNDUP(start_addr, spifs->page_size);
+
+        uint32_t n_bytes = MIN(len, page_end - start_addr);
+
+        // read..
+        err = spifs_read_page(spifs, target_page_id);
+        if (err != NO_ERROR) {
+            goto err;
+        }
+
+        // modify..
+        memcpy(spifs->page + page_offset, buf, n_bytes);
+
+        // write..
+        err = spifs_write_page(spifs, target_page_id);
+        if (err != NO_ERROR) {
+            goto err;
+        }
+
+        len -= n_bytes;
+        buf += n_bytes;
+        target_page_id++;
+    }
+
+    // Internal Full Pages.
+    while (len >= spifs->page_size) {
+        memcpy(spifs->page, buf, spifs->page_size);
+        err = spifs_write_page(spifs, target_page_id);
+        if (err != NO_ERROR) {
+            goto err;
+        }
+
+        len -= spifs->page_size;
+        buf += spifs->page_size;
+        target_page_id++;
+    }
+
+    // Trailing Partial Page.
+    if (len) { // Bytes remaining?
+        // read..
+        err = spifs_read_page(spifs, target_page_id);
+        if (err != NO_ERROR) {
+            goto err;
+        }
+
+        // modify..
+        memcpy(spifs->page, buf, len);
+
+        // write..
+        err = spifs_write_page(spifs, target_page_id);
+        if (err != NO_ERROR) {
+            goto err;
+        } else {
+            len = 0;
+        }
+    }
+
+    if (dirty_toc) {
+        err = spifs_commit_toc(spifs);
+    }
+
+err:
+    mutex_release(&spifs->lock);
+    return len == 0 ? (ssize_t)size : err;
+}
+
+static status_t spifs_stat(filecookie *fcookie, struct file_stat *stat)
+{
+    LTRACEF("filecookie %p stat %p\n", fcookie, stat);
+
+    spifs_file_t *file = (spifs_file_t *)fcookie;
+
+    mutex_acquire(&file->fs_handle->lock);
+
+    if (stat) {
+        stat->is_dir = false;
+        stat->size = file->metadata.length;
+        stat->capacity = file->metadata.capacity;
+    }
+
+    mutex_release(&file->fs_handle->lock);
+
+    return NO_ERROR;
+}
+
+static status_t spifs_opendir(fscookie *cookie, const char *name, dircookie **dcookie)
+{
+    LTRACEF("cookie %p name '%s' dircookie %p\n", cookie, name, dcookie);
+
+    spifs_t *spifs = (spifs_t *)cookie;
+
+    name = trim_name(name);
+
+    if (strcmp("", name))
+        return ERR_NOT_FOUND;
+
+    dircookie *dir = malloc(sizeof(*dir));
+    if (!dir)
+        return ERR_NO_MEMORY;
+
+    dir->fs = spifs;
+
+    mutex_acquire(&spifs->lock);
+
+    spifs_file_t *front_toc_file =
+        list_peek_head_type(&spifs->files, spifs_file_t, node);
+    dir->next_file = list_next_type(&spifs->files, &front_toc_file->node,
+                                    spifs_file_t, node);
+    list_add_head(&spifs->dcookies, &dir->node);
+
+    mutex_release(&spifs->lock);
+
+    *dcookie = dir;
+
+    return NO_ERROR;
+}
+
+static status_t spifs_readdir(dircookie *dcookie, struct dirent *ent)
+{
+    status_t err;
+
+    LTRACEF("dircookie %p ent %p\n", dcookie, ent);
+
+    mutex_acquire(&dcookie->fs->lock);
+
+    spifs_file_t *back_toc_file =
+        list_peek_tail_type(&dcookie->fs->files, spifs_file_t, node);
+
+    if (dcookie->next_file != back_toc_file) {
+        strlcpy(ent->name, dcookie->next_file->metadata.filename, sizeof(ent->name));
+        dcookie->next_file =
+            list_next_type(&dcookie->fs->files, &dcookie->next_file->node,
+                           spifs_file_t, node);
+        err = NO_ERROR;
+    } else {
+        err = ERR_NOT_FOUND;
+    }
+
+    mutex_release(&dcookie->fs->lock);
+
+    return err;
+}
+
+static status_t spifs_closedir(dircookie *dcookie)
+{
+    LTRACEF("dircookie %p\n", dcookie);
+
+    mutex_acquire(&dcookie->fs->lock);
+    list_delete(&dcookie->node);
+    mutex_release(&dcookie->fs->lock);
+
+    free(dcookie);
+
+    return NO_ERROR;
+}
+
+static status_t spifs_fs_stat(fscookie *cookie, struct fs_stat *stat)
+{
+    LTRACEF("cookie %p, stat %p\n", cookie, stat);
+
+    spifs_t *spifs = (spifs_t *)cookie;
+
+    stat->total_space = (uint64_t)spifs->dev->total_size;
+    stat->free_space  = stat->total_space - used_space(spifs);
+
+    stat->total_inodes = spifs->num_entries;
+    stat->free_inodes  = stat->total_inodes - list_length(&spifs->files);
+
+    return NO_ERROR;
+}
+
+static status_t spifs_ioctl_get_file_addr(filecookie *cookie, void **argp)
+{
+    LTRACEF("cookie %p, argp %p\n", cookie, argp);
+
+    if (unlikely(!argp)) {
+        return ERR_INVALID_ARGS;
+    }
+
+    status_t result;
+
+    spifs_file_t *file = (spifs_file_t *)cookie;
+    spifs_t *spifs = file->fs_handle;
+    bdev_t *dev = spifs->dev;
+
+    // Get the base address of the underlying BIO device.
+    void *result_addr;
+    result = bio_ioctl(dev, BIO_IOCTL_GET_MAP_ADDR, &result_addr);
+    if (result != NO_ERROR) {
+        return result;
+    }
+
+    // Get the offset of the file.
+    result_addr += file->metadata.page_idx * spifs->page_size;
+    *argp = result_addr;
+
+    return NO_ERROR;
+}
+
+static status_t spifs_file_ioctl(filecookie *cookie, int request, void *argp)
+{
+    LTRACEF("request %d, argp %p\n", request, argp);
+
+    switch (request) {
+        case FS_IOCTL_GET_FILE_ADDR: {
+            return spifs_ioctl_get_file_addr(cookie, (void **)argp);
+        }
+        default: {
+            return ERR_NOT_SUPPORTED;
+        }
+    }
+    return ERR_NOT_SUPPORTED;
+}
+
+static const struct fs_api spifs_api = {
+    .format = spifs_format,
+    .fs_stat = spifs_fs_stat,
+
+    .mount = spifs_mount,
+    .unmount = spifs_unmount,
+
+    .create = spifs_create,
+    .open = spifs_open,
+    .remove = spifs_remove,
+    .close = spifs_close,
+
+    .read = spifs_read,
+    .write = spifs_write,
+
+    .stat = spifs_stat,
+
+    .file_ioctl = spifs_file_ioctl,
+
+    .opendir = spifs_opendir,
+    .readdir = spifs_readdir,
+    .closedir = spifs_closedir,
+};
+
+STATIC_FS_IMPL(spifs, &spifs_api);
diff --git a/src/bsp/lk/lib/fs/spifs/test/rules.mk b/src/bsp/lk/lib/fs/spifs/test/rules.mk
new file mode 100644
index 0000000..344c7b5
--- /dev/null
+++ b/src/bsp/lk/lib/fs/spifs/test/rules.mk
@@ -0,0 +1,8 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/spifstest.c
+
+include make/module.mk
diff --git a/src/bsp/lk/lib/fs/spifs/test/spifstest.c b/src/bsp/lk/lib/fs/spifs/test/spifstest.c
new file mode 100644
index 0000000..d2de090
--- /dev/null
+++ b/src/bsp/lk/lib/fs/spifs/test/spifstest.c
@@ -0,0 +1,684 @@
+/*
+ * Copyright (c) 2015 Gurjant Kalsi <me@gurjantkalsi.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#if LK_DEBUGLEVEL > 1
+
+#include <string.h>
+#include <err.h>
+#include <stdlib.h>
+
+#include <lib/bio.h>
+#include <lib/console.h>
+#include <lib/fs/spifs.h>
+
+#define FS_NAME "spifs"
+#define MNT_PATH "/s"
+#define TEST_FILE_PATH "/s/test"
+#define TEST_PATH_MAX_SIZE 16
+
+typedef bool(*test_func)(const char *);
+
+typedef struct {
+    test_func func;
+    const char* name;
+    uint32_t toc_pages;
+} test;
+
+static bool test_empty_after_format(const char *);
+static bool test_double_create_file(const char *);
+static bool test_write_read_normal(const char *);
+static bool test_write_past_eof(const char *);
+static bool test_full_toc(const char *);
+static bool test_full_fs(const char *);
+static bool test_write_past_end_of_capacity(const char *);
+static bool test_rm_reclaim(const char *);
+static bool test_corrupt_toc(const char *);
+static bool test_write_with_offset(const char *);
+static bool test_read_write_big(const char *);
+static bool test_rm_active_dirent(const char *);
+
+static test tests[] = {
+    {&test_empty_after_format, "Test no files in ToC after format.", 1},
+    {&test_write_read_normal, "Test the normal read/write file paths.", 1},
+    {&test_double_create_file, "Test file cannot be created if it already exists.", 1},
+    {&test_write_past_eof, "Test that file can grow up to capacity.", 1},
+    {&test_full_toc, "Test that files cannot be created once the ToC is full.", 2},
+    {&test_full_fs, "Test that files cannot be created once the device is full.", 1},
+    {&test_rm_reclaim, "Test that files can be deleted and that used space is reclaimed.", 1},
+    {&test_write_past_end_of_capacity, "Test that we cannot write past the capacity of a file.", 1},
+    {&test_corrupt_toc, "Test that FS can be mounted with one corrupt ToC.", 1},
+    {&test_write_with_offset, "Test that files can be written to at an offset.", 1},
+    {&test_read_write_big, "Test that an unaligned ~10kb buffer can be written and read.", 1},
+    {&test_rm_active_dirent, "Test that we can remove a file with an open dirent.", 1},
+};
+
+bool test_setup(const char *dev_name, uint32_t toc_pages)
+{
+    spifs_format_args_t args = {
+        .toc_pages = toc_pages,
+    };
+
+    status_t res = fs_format_device(FS_NAME, dev_name, (void*)&args);
+    if (res != NO_ERROR) {
+        printf("spifs_format failed dev = %s, toc_pages = %u, retcode = %d\n",
+               dev_name, toc_pages, res);
+        return false;
+    }
+
+    res = fs_mount(MNT_PATH, FS_NAME, dev_name);
+    if (res != NO_ERROR) {
+        printf("fs_mount failed path = %s, fs name = %s, dev name = %s,"
+               " retcode = %d\n", MNT_PATH, FS_NAME, dev_name, res);
+        return false;
+    }
+
+    return true;
+}
+
+bool test_teardown(void)
+{
+    if (fs_unmount(MNT_PATH) != NO_ERROR) {
+        printf("Unmount failed\n");
+        return false;
+    }
+
+    return true;;
+}
+
+static bool test_empty_after_format(const char *dev_name)
+{
+    dirhandle *dhandle;
+    status_t err = fs_open_dir(MNT_PATH, &dhandle);
+    if (err != NO_ERROR) {
+        return false;
+    }
+
+    struct dirent ent;
+    if (fs_read_dir(dhandle, &ent) >= 0) {
+        fs_close_dir(dhandle);
+        return false;
+    }
+
+    fs_close_dir(dhandle);
+    return true;
+}
+
+static bool test_double_create_file(const char *dev_name)
+{
+    status_t status;
+
+    struct dirent *ent = malloc(sizeof(*ent));
+    size_t num_files = 0;
+
+    filehandle *handle;
+    status = fs_create_file(TEST_FILE_PATH, &handle, 10);
+    if (status != NO_ERROR) {
+        goto err;
+    }
+    fs_close_file(handle);
+
+    filehandle *duphandle;
+    status = fs_create_file(TEST_FILE_PATH, &duphandle, 20);
+    if (status != ERR_ALREADY_EXISTS) {
+        goto err;
+    }
+
+    dirhandle *dhandle;
+    status = fs_open_dir(MNT_PATH, &dhandle);
+    if (status != NO_ERROR) {
+        goto err;
+    }
+
+    while ((status = fs_read_dir(dhandle, ent)) >= 0) {
+        num_files++;
+    }
+
+    status = NO_ERROR;
+
+    fs_close_dir(dhandle);
+
+
+err:
+    free(ent);
+
+    return status == NO_ERROR ? num_files == 1 : false;
+}
+
+static bool test_write_read_normal(const char *dev_name)
+{
+    char test_message[] = "spifs test";
+    char test_buf[sizeof(test_message)];
+
+    bdev_t *dev = bio_open(dev_name);
+    if (!dev) {
+        return false;
+    }
+    uint8_t erase_byte = dev->erase_byte;
+    bio_close(dev);
+
+    filehandle *handle;
+    status_t status =
+        fs_create_file(TEST_FILE_PATH, &handle, sizeof(test_message));
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    ssize_t bytes;
+
+    // New files should be initialized to 'erase_byte'
+    bytes = fs_read_file(handle, test_buf, 0, sizeof(test_buf));
+    if (bytes != sizeof(test_buf)) {
+        return false;
+    }
+
+    for (size_t i = 0; i < sizeof(test_buf); i++) {
+        if (test_buf[i] != erase_byte) {
+            return false;
+        }
+    }
+
+    bytes = fs_write_file(handle, test_message, 0, sizeof(test_message));
+    if (bytes != sizeof(test_message)) {
+        return false;
+    }
+
+    bytes = fs_read_file(handle, test_buf, 0, sizeof(test_buf));
+    if (bytes != sizeof(test_buf)) {
+        return false;
+    }
+
+    status = fs_close_file(handle);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    return strncmp(test_message, test_buf, sizeof(test_message)) == 0;
+}
+
+static bool test_write_past_eof(const char *dev_name)
+{
+    char test_message[] = "spifs test";
+
+    // Create a 0 length file.
+    filehandle *handle;
+    status_t status =
+        fs_create_file(TEST_FILE_PATH, &handle, 0);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    ssize_t bytes = fs_write_file(handle, test_message, 0, sizeof(test_message));
+    if (bytes != sizeof(test_message)) {
+        return false;
+    }
+
+    // Make sure the file grows.
+    struct file_stat stat;
+    fs_stat_file(handle, &stat);
+    if (stat.is_dir != false && stat.size != sizeof(test_message)) {
+        return false;
+    }
+
+    fs_close_file(handle);
+
+    return true;
+}
+
+static bool test_full_toc(const char *dev_name)
+{
+    struct fs_stat stat;
+
+    fs_stat_fs(MNT_PATH, &stat);
+
+    char test_file_name[TEST_PATH_MAX_SIZE];
+
+    filehandle *handle;
+    for (size_t i = 0; i < stat.free_inodes; i++) {
+        memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
+
+        char filenum[] = "000";
+        filenum[0] += i / 100;
+        filenum[1] += (i / 10) % 10;
+        filenum[2] += i % 10;
+
+        strlcat(test_file_name, MNT_PATH, sizeof(test_file_name));
+        strlcat(test_file_name, "/", sizeof(test_file_name));
+        strlcat(test_file_name, filenum, sizeof(test_file_name));
+
+        status_t status =
+            fs_create_file(test_file_name, &handle, 1);
+
+        if (status != NO_ERROR) {
+            return false;
+        }
+
+        fs_close_file(handle);
+    }
+
+    // There shouldn't be enough space for this file since we've exhausted all
+    // the inodes.
+
+    status_t status = fs_create_file(TEST_FILE_PATH, &handle, 1);
+    if (status != ERR_TOO_BIG) {
+        return false;
+    }
+
+    return true;
+}
+
+static bool test_rm_reclaim(const char *dev_name)
+{
+    // Create some number of files that's a power of 2;
+    size_t n_files = 4;
+
+    struct fs_stat stat;
+
+    fs_stat_fs(MNT_PATH, &stat);
+
+    size_t file_size = stat.free_space / (n_files + 1);
+
+    char test_file_name[TEST_PATH_MAX_SIZE];
+
+    filehandle *handle;
+    for (size_t i = 0; i < n_files; i++) {
+        memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
+
+        char filenum[] = "000";
+        filenum[0] += i / 100;
+        filenum[1] += (i / 10) % 10;
+        filenum[2] += i % 10;
+
+        strcat(test_file_name, MNT_PATH);
+        strcat(test_file_name, filenum);
+
+        status_t status =
+            fs_create_file(test_file_name, &handle, file_size);
+
+        if (status != NO_ERROR) {
+            return false;
+        }
+
+        fs_close_file(handle);
+    }
+
+    // Try to create a new Big file.
+    char filename[] = "BIGFILE";
+    memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
+    strcat(test_file_name, MNT_PATH);
+    strcat(test_file_name, filename);
+
+    status_t status;
+
+    // This should fail because there's no more space.
+    fs_stat_fs(MNT_PATH, &stat);
+    status = fs_create_file(test_file_name, &handle, stat.free_space + 1);
+    if (status != ERR_TOO_BIG) {
+        return false;
+    }
+
+    // Delete an existing file to make space for the new file.
+    char existing_filename[] = "001";
+    memset(test_file_name, 0, TEST_PATH_MAX_SIZE);
+    strcat(test_file_name, MNT_PATH);
+    strcat(test_file_name, existing_filename);
+
+    status = fs_remove_file(test_file_name);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+
+    // Now this should go through because we've reclaimed the space.
+    status = fs_create_file(test_file_name, &handle, stat.free_space + 1);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    fs_close_file(handle);
+    return true;
+}
+
+static bool test_full_fs(const char *dev_name)
+{
+    struct fs_stat stat;
+
+    fs_stat_fs(MNT_PATH, &stat);
+
+    char second_file_path[TEST_PATH_MAX_SIZE];
+    memset(second_file_path, 0, TEST_PATH_MAX_SIZE);
+    strcpy(second_file_path, MNT_PATH);
+    strcat(second_file_path, "/fail");
+
+    filehandle *handle;
+    status_t status = fs_create_file(TEST_FILE_PATH, &handle, stat.free_space);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    fs_close_file(handle);
+
+    // There shouldn't be enough space for this file since we've used all the
+    // space.
+    status = fs_create_file(second_file_path, &handle, 1);
+    if (status != ERR_TOO_BIG) {
+        return false;
+    }
+
+    return true;
+}
+
+static bool test_write_past_end_of_capacity(const char *dev_name)
+{
+    filehandle *handle;
+    status_t status = fs_create_file(TEST_FILE_PATH, &handle, 0);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    struct file_stat stat;
+    status = fs_stat_file(handle, &stat);
+    if (status != NO_ERROR) {
+        goto finish;
+    }
+
+    // We shouldn't be able to write past the capacity of a file.
+    char buf[1];
+    status = fs_write_file(handle, buf, stat.capacity, 1);
+    if (status == ERR_OUT_OF_RANGE) {
+        status = NO_ERROR;
+    } else {
+        status = ERR_IO;
+    }
+
+finish:
+    fs_close_file(handle);
+    return status == NO_ERROR;
+}
+
+static bool test_corrupt_toc(const char *dev_name)
+{
+    // Create a zero byte file.
+    filehandle *handle;
+    status_t status = fs_create_file(TEST_FILE_PATH, &handle, 0);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    // Grow the file to one byte. This should trigger a ToC flush. Now the
+    // ToC record for this file should exist in both ToCs. Therefore corrupting
+    // either of the ToCs will still yield this file readable.
+    char buf[1] = { 'a' };
+    status = fs_write_file(handle, buf, 0, 1);
+    if (status != 1) {
+        return false;
+    }
+
+    status = fs_close_file(handle);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    status = fs_unmount(MNT_PATH);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    // Now we're going to manually corrupt one of the ToCs
+    bdev_t *dev = bio_open(dev_name);
+    if (!dev) {
+        return false;
+    }
+
+    // Directly write 0s to the block that contains the front-ToC.
+    size_t block_size = dev->block_size;
+    uint8_t *block_buf = memalign(CACHE_LINE, block_size);
+    if (!block_buf) {
+        return false;
+    }
+    memset(block_buf, 0, block_size);
+
+    ssize_t bytes = bio_write_block(dev, block_buf, 0, 1);
+
+    free(block_buf);
+
+    bio_close(dev);
+
+    if (bytes != (ssize_t)block_size) {
+        return false;
+    }
+
+    // Mount the FS again and make sure that the file we created is still there.
+    status = fs_mount(MNT_PATH, FS_NAME, dev_name);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    status = fs_open_file(TEST_FILE_PATH, &handle);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    struct file_stat stat;
+    status = fs_stat_file(handle, &stat);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    status = fs_close_file(handle);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    return true;
+}
+
+static bool test_write_with_offset(const char *dev_name)
+{
+    size_t repeats = 3;
+    char test_message[] = "test";
+    size_t msg_len = strnlen(test_message, sizeof(test_message));
+    char test_buf[msg_len * repeats];
+
+    filehandle *handle;
+    status_t status = fs_create_file(TEST_FILE_PATH, &handle, msg_len);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    ssize_t bytes;
+    for (size_t pos = 0; pos < repeats; pos++) {
+        bytes = fs_write_file(handle, test_message, pos * msg_len, msg_len);
+        if ((size_t)bytes != msg_len) {
+            return false;
+        }
+    }
+
+    bytes = fs_read_file(handle, test_buf, 0, msg_len * repeats);
+    if ((size_t)bytes != msg_len * repeats) {
+        return false;
+    }
+
+    status = fs_close_file(handle);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    bool success = true;
+    for (size_t i = 0; i < repeats; i++) {
+        success &= (memcmp(test_message,
+                           test_buf + i * msg_len,
+                           msg_len) == 0);
+    }
+    return success;
+}
+
+static bool test_read_write_big(const char *dev_name)
+{
+    bool success = true;
+
+    size_t buflen = 10013;
+
+    uint8_t *rbuf = malloc(buflen);
+    if (!rbuf) {
+        return false;
+    }
+
+    uint8_t *wbuf = malloc(buflen);
+    if (!wbuf) {
+        free(rbuf);
+        return false;
+    }
+
+    for (size_t i = 0; i < buflen; i++) {
+        wbuf[i] = rand() % sizeof(uint8_t);
+    }
+
+    filehandle *handle;
+    status_t status = fs_create_file(TEST_FILE_PATH, &handle, buflen);
+    if (status != NO_ERROR) {
+        success = false;
+        goto err;
+    }
+
+    ssize_t bytes = fs_write_file(handle, wbuf, 0, buflen);
+    if ((size_t)bytes != buflen) {
+        success = false;
+        goto err;
+    }
+
+    bytes = fs_read_file(handle, rbuf, 0, buflen);
+    if ((size_t)bytes != buflen) {
+        success = false;
+        goto err;
+    }
+
+    for (size_t i = 0; i < buflen; i++) {
+        if (wbuf[i] != rbuf[i]) {
+            success = false;
+            break;
+        }
+    }
+
+err:
+    success &= fs_close_file(handle) == NO_ERROR;
+
+    free(rbuf);
+    free(wbuf);
+    return success;
+}
+
+static bool test_rm_active_dirent(const char *dev_name)
+{
+    filehandle *handle;
+    status_t status = fs_create_file(TEST_FILE_PATH, &handle, 0);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    status = fs_close_file(handle);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    dirhandle *dhandle;
+    status = fs_open_dir(MNT_PATH, &dhandle);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    // Dir handle should now be pointing to the only file in our FS.
+    status = fs_remove_file(TEST_FILE_PATH);
+    if (status != NO_ERROR) {
+        fs_close_dir(dhandle);
+        return false;
+    }
+
+    bool success = true;
+    struct dirent *ent = malloc(sizeof(*ent));
+    if (fs_read_dir(dhandle, ent) >= 0) {
+        success = false;
+    }
+
+    success &= fs_close_dir(dhandle) == NO_ERROR;
+    free(ent);
+
+    return success;
+}
+
+static int cmd_spifs(int argc, const cmd_args *argv)
+{
+    if (argc < 3) {
+notenoughargs:
+        printf("not enough arguments:\n");
+usage:
+        printf("%s test <device>\n", argv[0].str);
+        return -1;
+    }
+
+    if (strcmp(argv[1].str, "test")) {
+        goto usage;
+    }
+
+    // Make sure this block device is legit.
+    bdev_t *dev = bio_open(argv[2].str);
+    if (!dev) {
+        printf("error: could not open block device %s\n", argv[2].str);
+        return -1;
+    }
+    bio_close(dev);
+
+    size_t passed = 0;
+    size_t attempted = 0;
+    for (size_t i = 0; i < countof(tests); i++) {
+        ++attempted;
+        if (!test_setup(argv[2].str, tests[i].toc_pages)) {
+            printf("Test Setup failed before %s. Exiting.\n", tests[i].name);
+            break;
+        }
+
+        if (tests[i].func(argv[2].str)) {
+            printf(" [Passed] %s\n", tests[i].name);
+            ++passed;
+        } else {
+            printf(" [Failed] %s\n", tests[i].name);
+        }
+
+        if (!test_teardown()) {
+            printf("Test teardown failed after %s. Exiting.\n", tests[i].name);
+            break;
+        }
+    }
+    printf("\nPassed %u of %u tests.\n", passed, attempted);
+
+    if (attempted != countof(tests)) {
+        printf("(Skipped %u)\n", countof(tests) - attempted);
+    }
+
+    return countof(tests) - passed;
+}
+
+STATIC_COMMAND_START
+STATIC_COMMAND("spifs", "commands related to the spifs implementation.", &cmd_spifs)
+STATIC_COMMAND_END(spifs);
+
+#endif  // LK_DEBUGLEVEL > 1
\ No newline at end of file