[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