931 lines
29 KiB
C
931 lines
29 KiB
C
/*
|
|
* Copyright (c) 2013 Heather Lee Wilson
|
|
*
|
|
* Use of this source code is governed by a MIT-style
|
|
* license that can be found in the LICENSE file or at
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
#include <dev/flash_nor.h>
|
|
#include <lib/norfs.h>
|
|
#include <lib/norfs_inode.h>
|
|
#include <lib/norfs_config.h>
|
|
#include <iovec.h>
|
|
#include <stdlib.h>
|
|
#include <lk/err.h>
|
|
#include <string.h>
|
|
#include <platform.h>
|
|
#include <lib/cksum.h>
|
|
#include <platform/flash_nor_config.h>
|
|
#include <lk/list.h>
|
|
#include <lk/debug.h>
|
|
|
|
/* FRIEND_TEST non-static if unit testing, in order to
|
|
* allow functions to be exposed by a test header file.
|
|
*/
|
|
#ifdef WITH_LIB_UNITTEST
|
|
#define FRIEND_TEST
|
|
#else
|
|
#define FRIEND_TEST static
|
|
#endif
|
|
|
|
#define member_size(type, member) sizeof(((type *)0)->member)
|
|
|
|
|
|
/* Handle rolling over of version field beyond max(uint16_t) by assuming that
|
|
* the difference between the smallest and largest versions are no more than
|
|
* half the range of uint16_t. By subtracting one unsigned uint16_t from
|
|
* another, then casting the delta to int16_t, if the delta is greater than
|
|
* 1/2 * max(uint16_t), then the most significant bit will be a 1, causing the
|
|
* signed integer to be negative and VERSION_GREATER_THAN to return false.
|
|
*/
|
|
#define VERSION_GREATER_THAN(a, b) ((int16_t)((a) - (b)) > 0)
|
|
|
|
struct norfs_header {
|
|
uint32_t key;
|
|
uint16_t version;
|
|
uint16_t len;
|
|
uint8_t flags;
|
|
uint16_t crc;
|
|
};
|
|
|
|
/* Block header written after successful erase. */
|
|
FRIEND_TEST const unsigned char NORFS_BLOCK_HEADER[4] = {'T', 'O', 'F', 'U'};
|
|
/* Block header to indicate garbage collection has started. */
|
|
FRIEND_TEST const unsigned char NORFS_BLOCK_GC_STARTED_HEADER[2] = {'S', 'O'};
|
|
/* Block header to indicate block was not interrupted during garbage
|
|
* collection.
|
|
*/
|
|
FRIEND_TEST const unsigned char NORFS_BLOCK_GC_FINISHED_HEADER[2] = {'U', 'P'};
|
|
|
|
FRIEND_TEST uint32_t write_pointer = 0;
|
|
FRIEND_TEST uint32_t total_remaining_space = NORFS_AVAILABLE_SPACE;
|
|
FRIEND_TEST uint8_t num_free_blocks = 0;
|
|
static bool fs_mounted = false;
|
|
FRIEND_TEST uint32_t norfs_nvram_offset;
|
|
static struct list_node inode_list;
|
|
|
|
static bool block_free[NORFS_NUM_BLOCKS];
|
|
|
|
static status_t collect_garbage(void);
|
|
static status_t load_and_verify_obj(uint32_t *ptr, struct norfs_header *header);
|
|
|
|
FRIEND_TEST uint8_t block_num(uint32_t flash_pointer) {
|
|
return flash_pointer/FLASH_PAGE_SIZE;
|
|
}
|
|
|
|
/* Update pointer to a free block. If no free blocks, return error. */
|
|
FRIEND_TEST status_t find_free_block(uint32_t *ptr) {
|
|
uint8_t i = block_num(*ptr) + 1;
|
|
uint8_t imod;
|
|
for (uint8_t j = 0; j < NORFS_NUM_BLOCKS; i++, j++) {
|
|
imod = i % NORFS_NUM_BLOCKS;
|
|
/* If is a free block, return. */
|
|
if (block_free[imod]) {
|
|
*ptr = imod * FLASH_PAGE_SIZE + sizeof(NORFS_BLOCK_HEADER);
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
/* A free block could not be found. */
|
|
return ERR_NO_MEMORY;
|
|
}
|
|
|
|
static uint32_t curr_block_free_space(uint32_t pointer) {
|
|
return (block_num(pointer) + 1) * FLASH_PAGE_SIZE - pointer;
|
|
}
|
|
|
|
static bool block_full(uint8_t block, uint32_t ptr) {
|
|
if (block != block_num(ptr)) {
|
|
return true;
|
|
}
|
|
return curr_block_free_space(ptr) < NORFS_OBJ_OFFSET;
|
|
}
|
|
|
|
static uint8_t select_garbage_block(uint32_t ptr) {
|
|
return (block_num(ptr) + 1) % 8;
|
|
}
|
|
|
|
static ssize_t nvram_read(size_t offset, size_t length, void *ptr) {
|
|
return flash_nor_read(NORFS_BANK, offset + norfs_nvram_offset, length, ptr);
|
|
}
|
|
|
|
static ssize_t nvram_write(size_t offset, size_t length, const void *ptr) {
|
|
return flash_nor_write(NORFS_BANK, offset + norfs_nvram_offset, length,
|
|
ptr);
|
|
}
|
|
|
|
static ssize_t nvram_erase_pages(size_t offset, size_t length) {
|
|
return flash_nor_erase_pages(NORFS_BANK, offset + norfs_nvram_offset,
|
|
length);
|
|
}
|
|
|
|
unsigned char *nvram_flash_pointer(uint32_t loc) {
|
|
return FLASH_PTR(flash_nor_get_bank(NORFS_BANK), loc + norfs_nvram_offset);
|
|
}
|
|
|
|
FRIEND_TEST bool get_inode(uint32_t key, struct norfs_inode **inode) {
|
|
struct list_node *curr_lnode;
|
|
struct norfs_inode *curr_inode;
|
|
uint32_t curr_key;
|
|
|
|
if (!inode)
|
|
return false;
|
|
|
|
*inode = NULL;
|
|
list_for_every(&inode_list, curr_lnode) {
|
|
curr_inode = containerof(curr_lnode, struct norfs_inode, lnode);
|
|
nvram_read(curr_inode->location + NORFS_KEY_OFFSET,
|
|
sizeof(curr_key), &curr_key);
|
|
if (curr_key == key) {
|
|
*inode = curr_inode;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint16_t calculate_header_crc(uint32_t key, uint16_t version,
|
|
uint16_t len, uint8_t flags) {
|
|
uint16_t crc = crc16((unsigned char *) &key, sizeof(key));
|
|
crc = update_crc16(crc, (unsigned char *) &version, sizeof(version));
|
|
crc = update_crc16(crc, (unsigned char *) &len, sizeof(len));
|
|
crc = update_crc16(crc, (unsigned char *) &flags, sizeof(flags));
|
|
return crc;
|
|
}
|
|
|
|
/* Read header into parameter buffers. Return bytes written. */
|
|
static ssize_t read_header(uint32_t ptr, struct norfs_header *header) {
|
|
ssize_t total_bytes_read = 0;
|
|
ssize_t bytes_read = 0;
|
|
bytes_read = nvram_read(ptr + NORFS_KEY_OFFSET,
|
|
sizeof(header->key), &header->key);
|
|
if (bytes_read < 0) {
|
|
return bytes_read;
|
|
}
|
|
total_bytes_read += bytes_read;
|
|
bytes_read = nvram_read(ptr + NORFS_VERSION_OFFSET,
|
|
sizeof(header->version), &header->version);
|
|
if (bytes_read < 0) {
|
|
return bytes_read;
|
|
}
|
|
total_bytes_read += bytes_read;
|
|
bytes_read = nvram_read(ptr + NORFS_LENGTH_OFFSET,
|
|
sizeof(header->len), &header->len);
|
|
if (bytes_read < 0) {
|
|
return bytes_read;
|
|
}
|
|
total_bytes_read += bytes_read;
|
|
bytes_read = nvram_read(ptr + NORFS_FLAGS_OFFSET,
|
|
sizeof(header->flags), &header->flags);
|
|
if (bytes_read < 0) {
|
|
return bytes_read;
|
|
}
|
|
total_bytes_read += bytes_read;
|
|
|
|
/* Increment pointer by one byte to account for unused padding. */
|
|
total_bytes_read += 1;
|
|
bytes_read = nvram_read(ptr + NORFS_CHECKSUM_OFFSET,
|
|
sizeof(header->crc), &header->crc);
|
|
if (bytes_read < 0) {
|
|
return bytes_read;
|
|
}
|
|
total_bytes_read += bytes_read;
|
|
|
|
return total_bytes_read;
|
|
}
|
|
|
|
status_t norfs_read_obj_iovec(uint32_t key, iovec_t *obj_iov,
|
|
uint32_t iov_count, size_t *bytes_read, uint8_t flags) {
|
|
if (!fs_mounted)
|
|
return ERR_NOT_MOUNTED;
|
|
|
|
uint32_t read_ptr;
|
|
uint16_t to_read, total_to_read;
|
|
struct norfs_inode *inode;
|
|
struct norfs_header stored_header;
|
|
int16_t bytes = 0;
|
|
uint8_t i = 0;
|
|
ssize_t iov_size = iovec_size(obj_iov, iov_count);
|
|
|
|
if (bytes_read) {
|
|
*bytes_read = 0;
|
|
}
|
|
|
|
if (!get_inode(key, &inode)) {
|
|
return ERR_NOT_FOUND;
|
|
}
|
|
read_ptr = inode->location;
|
|
bytes = read_header(read_ptr, &stored_header);
|
|
|
|
total_to_read = MIN(stored_header.len, iov_size);
|
|
if (bytes < 0) {
|
|
TRACEF("Error reading header. Status: %d\n", bytes);
|
|
return bytes;
|
|
}
|
|
read_ptr += bytes;
|
|
|
|
if (stored_header.flags & NORFS_DELETED_MASK) {
|
|
TRACEF("Object is already deleted.\n");
|
|
return ERR_NOT_FOUND;
|
|
}
|
|
|
|
while (*bytes_read < total_to_read) {
|
|
to_read = MIN(total_to_read - *bytes_read, obj_iov[i].iov_len);
|
|
bytes = nvram_read(read_ptr + *bytes_read,
|
|
to_read, obj_iov[i].iov_base);
|
|
if (bytes < 0) {
|
|
TRACEF("Read failed with error: %d\n", bytes);
|
|
return bytes;
|
|
}
|
|
if (bytes_read) {
|
|
*bytes_read += bytes;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t write_obj_header(uint32_t *ptr, uint32_t key, uint16_t version,
|
|
uint16_t len, uint8_t flags, uint16_t crc) {
|
|
unsigned char buff[WORD_SIZE];
|
|
int bytes_written;
|
|
|
|
bytes_written = nvram_write(*ptr, sizeof(key), &key);
|
|
if (bytes_written < 0) {
|
|
return bytes_written;
|
|
}
|
|
*ptr += bytes_written;
|
|
|
|
memcpy(buff, &version, sizeof(version));
|
|
memcpy(buff + sizeof(version), &len, sizeof(len));
|
|
bytes_written = nvram_write(*ptr, sizeof(buff), &buff);
|
|
if (bytes_written < 0) {
|
|
return bytes_written;
|
|
}
|
|
*ptr += bytes_written;
|
|
|
|
memcpy(buff, &flags, sizeof(flags));
|
|
memset(buff + 1, 1, sizeof(flags));
|
|
memcpy(buff + 2, &crc, sizeof(crc));
|
|
bytes_written = nvram_write(*ptr, sizeof(buff), &buff);
|
|
if (bytes_written < 0) {
|
|
return bytes_written;
|
|
}
|
|
*ptr += bytes_written;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t copy_iovec_to_disk(const struct iovec *iov, uint16_t iov_count,
|
|
uint32_t *location, uint16_t *crc) {
|
|
unsigned char word[4] = {0};
|
|
uint16_t iov_ptr = 0;
|
|
uint16_t word_ptr = 0;
|
|
uint8_t word_size = sizeof(word);
|
|
int bytes_written;
|
|
for (uint16_t i = 0; i < iov_count; i++) {
|
|
while ((uint16_t)iov[i].iov_len - iov_ptr > word_size - word_ptr) {
|
|
memcpy(word + word_ptr,
|
|
(unsigned char *)iov[i].iov_base + iov_ptr, word_size -
|
|
word_ptr);
|
|
iov_ptr += word_size - word_ptr;
|
|
|
|
bytes_written = nvram_write(*location, sizeof(word),
|
|
&word);
|
|
if (bytes_written < 0) {
|
|
TRACEF("Error while writing: %d\n", bytes_written);
|
|
return bytes_written;
|
|
}
|
|
|
|
*location += bytes_written;
|
|
*crc = update_crc16(*crc, word, sizeof(word));
|
|
memset(word, 0, sizeof(word));
|
|
word_ptr = 0;
|
|
}
|
|
|
|
memcpy((unsigned char *)word + word_ptr,
|
|
(unsigned char *)iov[i].iov_base + iov_ptr,
|
|
iov[i].iov_len - iov_ptr);
|
|
word_ptr += iov[i].iov_len - iov_ptr;
|
|
|
|
iov_ptr = 0;
|
|
}
|
|
|
|
if (word_ptr > 0) {
|
|
bytes_written = nvram_write(*location, sizeof(word),
|
|
&word);
|
|
|
|
if (bytes_written < 0) {
|
|
TRACEF("Error writing: %d\n", bytes_written);
|
|
return bytes_written;
|
|
}
|
|
|
|
*location += bytes_written;
|
|
*crc = update_crc16(*crc, word, word_ptr);
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t initialize_next_block(uint32_t *ptr) {
|
|
uint32_t header_pointer;
|
|
ssize_t bytes_written;
|
|
status_t status;
|
|
|
|
/* Update write pointer. */
|
|
status = find_free_block(ptr);
|
|
if (status) {
|
|
TRACEF("Error finding free block. Status: %d\n", status);
|
|
return status;
|
|
}
|
|
|
|
num_free_blocks--;
|
|
block_free[block_num(*ptr)] = false;
|
|
bytes_written = nvram_write(*ptr,
|
|
sizeof(NORFS_BLOCK_GC_STARTED_HEADER), &NORFS_BLOCK_GC_STARTED_HEADER);
|
|
|
|
if (bytes_written < 0) {
|
|
TRACEF("Error writing header.\n");
|
|
return bytes_written;
|
|
}
|
|
|
|
header_pointer = *ptr + bytes_written;
|
|
*ptr += sizeof(NORFS_BLOCK_GC_STARTED_HEADER) +
|
|
sizeof(NORFS_BLOCK_GC_FINISHED_HEADER);
|
|
|
|
if (num_free_blocks < NORFS_MIN_FREE_BLOCKS) {
|
|
status = collect_garbage();
|
|
if (status) {
|
|
TRACEF("Failed to collection garbage. Error: %d\n.",
|
|
status);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
status = nvram_write(header_pointer,
|
|
sizeof(NORFS_BLOCK_GC_FINISHED_HEADER),
|
|
&NORFS_BLOCK_GC_FINISHED_HEADER);
|
|
if (status < 0) {
|
|
TRACEF("Failed to write header.\n");
|
|
return status;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t write_obj_iovec(const iovec_t *iov, uint iov_count, uint *location,
|
|
uint32_t key, uint16_t version, uint8_t flags) {
|
|
uint16_t crc = 0;
|
|
uint16_t len = iovec_size(iov, iov_count);
|
|
status_t status;
|
|
|
|
uint32_t header_loc = *location;
|
|
*location += NORFS_OBJ_OFFSET;
|
|
crc = calculate_header_crc(key, version, len, flags);
|
|
|
|
if (!(flags & NORFS_DELETED_MASK)) {
|
|
/* If object is not a deletion header, write object bytes to disk. */
|
|
status = copy_iovec_to_disk(iov, iov_count, location, &crc);
|
|
if (status) {
|
|
TRACEF("Error writing. Status: %d\n", status);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
status = write_obj_header(&header_loc, key, version, len, flags, crc);
|
|
if (status) {
|
|
TRACEF("Error writing. Status: %d\n", status);
|
|
return status;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t norfs_read_obj(uint32_t key, unsigned char *buffer,
|
|
uint16_t buffer_len, size_t *bytes_read,
|
|
uint8_t flags) {
|
|
if (!fs_mounted)
|
|
return ERR_NOT_MOUNTED;
|
|
|
|
struct iovec vec[1];
|
|
vec->iov_base = buffer;
|
|
vec->iov_len = buffer_len;
|
|
status_t status = norfs_read_obj_iovec(key, vec, 1, bytes_read, flags);
|
|
|
|
return status;
|
|
}
|
|
|
|
status_t norfs_put_obj(uint32_t key, unsigned char *obj, uint16_t len,
|
|
uint8_t flags) {
|
|
if (!fs_mounted)
|
|
return ERR_NOT_MOUNTED;
|
|
|
|
if (key == 0xFFFF) {
|
|
return ERR_INVALID_ARGS;
|
|
}
|
|
status_t status;
|
|
struct iovec const vec[1] = {{obj, len}};
|
|
status = norfs_put_obj_iovec(key, vec, 1, flags);
|
|
return status;
|
|
}
|
|
|
|
bool is_deleted(uint32_t loc) {
|
|
uint8_t flags;
|
|
nvram_read(loc + NORFS_FLAGS_OFFSET, sizeof(flags), &flags);
|
|
return NORFS_DELETED_MASK & flags;
|
|
}
|
|
|
|
status_t norfs_remove_obj(uint32_t key) {
|
|
if (!fs_mounted)
|
|
return ERR_NOT_MOUNTED;
|
|
|
|
struct norfs_inode *inode;
|
|
uint16_t prior_len;
|
|
struct iovec iov[1];
|
|
status_t status;
|
|
bool success = get_inode(key, &inode);
|
|
if (!success || is_deleted(inode->location))
|
|
return ERR_NOT_FOUND;
|
|
|
|
status = nvram_read(inode->location + NORFS_LENGTH_OFFSET,
|
|
sizeof(uint16_t), &prior_len);
|
|
if (status < 0) {
|
|
TRACEF("Failed to read during norfs_remove_obj. Status: %d\n", status);
|
|
return status;
|
|
}
|
|
|
|
iov->iov_base = NULL;
|
|
iov->iov_len = 0;
|
|
|
|
/*
|
|
* Write a deleted object by passing a null iovec pointer. Only header
|
|
* will be written.
|
|
*/
|
|
status = norfs_put_obj_iovec(key, iov, 0, NORFS_DELETED_MASK);
|
|
if (status)
|
|
TRACEF("Error putting object. %d\n", status);
|
|
|
|
return status;
|
|
}
|
|
|
|
static status_t find_space_for_object(uint16_t obj_len, uint32_t *ptr) {
|
|
status_t status;
|
|
uint8_t initial_block_num = block_num(*ptr);
|
|
while (curr_block_free_space(*ptr) < (uint16_t) NORFS_FLASH_SIZE(obj_len)) {
|
|
status = initialize_next_block(ptr);
|
|
if (status)
|
|
return status;
|
|
/* If we have already tried and failed to write to this block, exit. */
|
|
if (block_num(*ptr) == initial_block_num)
|
|
return ERR_NO_MEMORY;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Store an object in flash. Moves write_pointer to new block and garbage
|
|
* collects if needed. If write fails, will reattempt.
|
|
* How to handle write failures is not fully defined - at the moment I stop once
|
|
* find_free_block is attempting to rewrite to a block it has already failed to
|
|
* write to - after a full loop in a round-robin style garbage selection of
|
|
* blocks. Which is a lot of write attempts.
|
|
*/
|
|
status_t norfs_put_obj_iovec(uint32_t key, const iovec_t *iov,
|
|
uint32_t iov_count, uint8_t flags) {
|
|
if (!fs_mounted)
|
|
return ERR_NOT_MOUNTED;
|
|
|
|
if (key == 0xFFFF) {
|
|
return ERR_INVALID_ARGS;
|
|
}
|
|
|
|
uint8_t block_num_to_write;
|
|
struct norfs_inode *inode;
|
|
uint16_t len = iovec_size(iov, iov_count);
|
|
status_t status;
|
|
uint8_t stored_flags;
|
|
uint16_t version = 0;
|
|
uint32_t header_loc;
|
|
bool deletion = flags & NORFS_DELETED_MASK;
|
|
bool obj_preexists = get_inode(key, &inode);
|
|
if (obj_preexists) {
|
|
nvram_read(inode->location + NORFS_FLAGS_OFFSET,
|
|
sizeof(stored_flags), &stored_flags);
|
|
if (stored_flags & flags & NORFS_DELETED_MASK) {
|
|
/* Attempting to delete an object no longer in filesystem. */
|
|
TRACEF("The object attempting to be removed has already been \
|
|
deleted. stored_flags: 0x%x\n", stored_flags);
|
|
return ERR_NOT_FOUND;
|
|
}
|
|
|
|
nvram_read(inode->location + NORFS_VERSION_OFFSET,
|
|
sizeof(version), &version);
|
|
version++;
|
|
} else if (deletion) {
|
|
/* Attempting to delete a non-existent object. */
|
|
TRACEF("Attempting to remove an object not in filesystem.\n");
|
|
return ERR_NOT_FOUND;
|
|
} else {
|
|
inode = malloc(sizeof(struct norfs_inode));
|
|
inode->reference_count = 1;
|
|
}
|
|
|
|
flash_nor_begin(NORFS_BANK);
|
|
|
|
if (NORFS_FLASH_SIZE(len) > NORFS_MAX_OBJ_LEN) {
|
|
TRACEF("Object too big. Not adding.\n");
|
|
flash_nor_end(NORFS_BANK);
|
|
return ERR_TOO_BIG;
|
|
}
|
|
|
|
if (!deletion && (NORFS_FLASH_SIZE(len) > total_remaining_space)) {
|
|
TRACEF("Not enough remaining space. Remaining space: %d\tObject len: \
|
|
%d\n", total_remaining_space, len);
|
|
flash_nor_end(NORFS_BANK);
|
|
return ERR_NO_MEMORY;
|
|
}
|
|
|
|
status = find_space_for_object(len, &write_pointer);
|
|
if (status) {
|
|
TRACEF("Error finding space for object. Status: %d\n", status);
|
|
flash_nor_end(NORFS_BANK);
|
|
return ERR_IO;
|
|
}
|
|
|
|
block_num_to_write = block_num(write_pointer);
|
|
header_loc = write_pointer;
|
|
status = write_obj_iovec(iov, iov_count, &write_pointer, key,
|
|
version, flags);
|
|
if (!status) {
|
|
if (!obj_preexists) {
|
|
list_add_tail(&inode_list, &inode->lnode);
|
|
} else {
|
|
/* If object preexists, remove outdated version from remaining space. */
|
|
uint16_t prior_len;
|
|
nvram_read(inode->location + NORFS_LENGTH_OFFSET,
|
|
sizeof(uint16_t), &prior_len);
|
|
total_remaining_space += NORFS_FLASH_SIZE(prior_len);
|
|
inode->reference_count++;
|
|
}
|
|
inode->location = header_loc;
|
|
total_remaining_space -= NORFS_FLASH_SIZE(len);
|
|
} else {
|
|
TRACEF("Error writing object. Status: %d\n", status);
|
|
}
|
|
|
|
/* If write error, or if fell off block, find new block. */
|
|
if (status < 0 || block_num(write_pointer) != block_num_to_write) {
|
|
initialize_next_block(&write_pointer);
|
|
}
|
|
|
|
flash_nor_end(NORFS_BANK);
|
|
return status;
|
|
}
|
|
|
|
static void remove_inode(struct norfs_inode *inode) {
|
|
if (!inode)
|
|
return;
|
|
list_delete(&inode->lnode);
|
|
free(inode);
|
|
inode = NULL;
|
|
}
|
|
|
|
/* Verifies objects, and copies to new block if it is the latest version. */
|
|
static status_t collect_garbage_object(uint32_t *garbage_read_pointer,
|
|
uint32_t *garbage_write_pointer) {
|
|
struct norfs_inode *inode;
|
|
struct norfs_header header;
|
|
bool inode_found;
|
|
status_t status;
|
|
struct iovec iov[1];
|
|
uint32_t new_obj_loc;
|
|
uint32_t garb_obj_loc = *garbage_read_pointer;
|
|
status = load_and_verify_obj(garbage_read_pointer, &header);
|
|
if (status) {
|
|
TRACEF("Failed to load garbage_obj at %d\n", *garbage_read_pointer);
|
|
return status;
|
|
}
|
|
inode_found = get_inode(header.key, &inode);
|
|
if (inode_found) {
|
|
if (garb_obj_loc == inode->location) {
|
|
/* Object in garbage block is latest version. */
|
|
if (header.flags & NORFS_DELETED_MASK && (inode->reference_count == 1)) {
|
|
/* If last version of object, remove. */
|
|
remove_inode(inode);
|
|
total_remaining_space += NORFS_OBJ_OFFSET;
|
|
return NO_ERROR;
|
|
}
|
|
iov->iov_base = nvram_flash_pointer(garb_obj_loc + NORFS_OBJ_OFFSET);
|
|
iov->iov_len = header.len;
|
|
new_obj_loc = *garbage_write_pointer;
|
|
status = write_obj_iovec(iov, 1, garbage_write_pointer, header.key,
|
|
header.version + 1, header.flags);
|
|
if (status) {
|
|
TRACEF("Failed to copy garbage object. Status: %d\n", status);
|
|
return status;
|
|
}
|
|
inode->location = new_obj_loc;
|
|
return NO_ERROR;
|
|
} else {
|
|
inode->reference_count--;
|
|
return NO_ERROR;
|
|
}
|
|
}
|
|
|
|
if (is_deleted(*garbage_read_pointer)) {
|
|
return NO_ERROR;
|
|
}
|
|
return ERR_NOT_FOUND;
|
|
}
|
|
|
|
static status_t erase_block(uint8_t block) {
|
|
ssize_t bytes_erased;
|
|
ssize_t bytes_written;
|
|
status_t status;
|
|
uint32_t loc = block * FLASH_PAGE_SIZE;
|
|
|
|
/* Block must fall within range of actual number of NVRAM blocks. */
|
|
if (block > NORFS_NUM_BLOCKS) {
|
|
TRACEF("Invalid block number: %d.\n", block);
|
|
return ERR_INVALID_ARGS;
|
|
}
|
|
status = flash_nor_begin(NORFS_BANK);
|
|
if (status) {
|
|
flash_nor_end(NORFS_BANK);
|
|
return status;
|
|
}
|
|
|
|
bytes_erased = nvram_erase_pages(loc, FLASH_PAGE_SIZE);
|
|
if (bytes_erased != FLASH_PAGE_SIZE) {
|
|
flash_nor_end(NORFS_BANK);
|
|
TRACEF("Did not erase exactly one flash page. Something went \
|
|
wrong.\n");
|
|
return ERR_IO;
|
|
}
|
|
|
|
bytes_written = nvram_write(loc, sizeof(NORFS_BLOCK_HEADER),
|
|
&NORFS_BLOCK_HEADER);
|
|
|
|
flash_nor_end(NORFS_BANK);
|
|
if (bytes_written < 0) {
|
|
TRACEF("Error during nvram_write. Status: %d\n", bytes_written);
|
|
return bytes_written;
|
|
}
|
|
block_free[block] = true;
|
|
num_free_blocks++;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
FRIEND_TEST status_t collect_block(uint32_t garbage_block,
|
|
uint32_t *garbage_write_ptr) {
|
|
status_t status;
|
|
uint32_t garbage_read_ptr = garbage_block * FLASH_PAGE_SIZE +
|
|
NORFS_BLOCK_HEADER_SIZE;
|
|
|
|
while (!(block_full(garbage_block, garbage_read_ptr))) {
|
|
status = collect_garbage_object(&garbage_read_ptr, garbage_write_ptr);
|
|
if (status) {
|
|
break;
|
|
}
|
|
}
|
|
return erase_block(garbage_block);
|
|
}
|
|
|
|
static status_t collect_garbage(void) {
|
|
status_t status;
|
|
uint8_t garbage_read_block = select_garbage_block(write_pointer);
|
|
status = collect_block(garbage_read_block, &write_pointer);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Load object into buffer and verify object's integrity via crc. ptr parameter
|
|
* is updated upon successful verification.
|
|
*/
|
|
static status_t load_and_verify_obj(uint32_t *ptr, struct norfs_header *header) {
|
|
uint16_t calculated_crc;
|
|
ssize_t bytes;
|
|
ssize_t total_bytes_read = 0;
|
|
bytes = read_header(*ptr, header);
|
|
unsigned char *obj_ptr;
|
|
|
|
if (bytes < 0) {
|
|
TRACEF("Reading header failed at location: %d. Err: %d.\n", *ptr, bytes);
|
|
return bytes;
|
|
}
|
|
|
|
total_bytes_read += bytes;
|
|
|
|
/* If object is longer than the remaining space in current block, fail.
|
|
* When attempting to verify unwritten file space, will fail here. */
|
|
if (block_num(*ptr + header->len - 1) != block_num(*ptr)) {
|
|
return ERR_IO;
|
|
}
|
|
|
|
calculated_crc = calculate_header_crc(header->key, header->version,
|
|
header->len, header->flags);
|
|
|
|
|
|
obj_ptr = nvram_flash_pointer(*ptr + total_bytes_read);
|
|
calculated_crc = update_crc16(calculated_crc, obj_ptr, header->len);
|
|
|
|
total_bytes_read += header->len;
|
|
if (calculated_crc != header->crc) {
|
|
TRACEF("CRC check failed. Calculated: 0x%x\tActual: 0x%x\n",
|
|
calculated_crc, header->crc);
|
|
return ERR_CRC_FAIL;
|
|
}
|
|
*ptr += total_bytes_read;
|
|
*ptr = ROUNDUP(*ptr, WORD_SIZE);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t read_block_verification(uint32_t *ptr) {
|
|
unsigned char block_header[sizeof(NORFS_BLOCK_HEADER) +
|
|
sizeof(NORFS_BLOCK_GC_STARTED_HEADER) +
|
|
sizeof(NORFS_BLOCK_GC_FINISHED_HEADER)];
|
|
int bytes_read = nvram_read(*ptr, sizeof(block_header),
|
|
block_header);
|
|
|
|
if (bytes_read < 0) {
|
|
TRACEF("Error reading while verifying block. Location: %d\n", *ptr);
|
|
return ERR_BAD_STATE;
|
|
}
|
|
|
|
for (uint8_t i = 0; i < sizeof(NORFS_BLOCK_HEADER); i++) {
|
|
if (block_header[i] != *(NORFS_BLOCK_HEADER + i)) {
|
|
return ERR_BAD_STATE;
|
|
}
|
|
}
|
|
|
|
bool valid_free_block = true;
|
|
for (uint8_t i = sizeof(NORFS_BLOCK_HEADER);
|
|
i < sizeof(NORFS_BLOCK_HEADER) +
|
|
sizeof(NORFS_BLOCK_GC_STARTED_HEADER) +
|
|
sizeof(NORFS_BLOCK_GC_FINISHED_HEADER);
|
|
i++) {
|
|
valid_free_block = valid_free_block && (block_header[i] == 0xFF);
|
|
}
|
|
|
|
/* This block has been successfully erased but has not been written to. */
|
|
if (valid_free_block) {
|
|
return ERR_NOT_CONFIGURED;
|
|
}
|
|
|
|
if (block_header[4] != NORFS_BLOCK_GC_STARTED_HEADER[0] ||
|
|
block_header[5] != NORFS_BLOCK_GC_STARTED_HEADER[1] ||
|
|
block_header[6] != NORFS_BLOCK_GC_FINISHED_HEADER[0] ||
|
|
block_header[7] != NORFS_BLOCK_GC_FINISHED_HEADER[1]) {
|
|
TRACEF("Garbage collection header invalid.\n");
|
|
return ERR_BAD_STATE;
|
|
}
|
|
|
|
*ptr += bytes_read;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t mount_next_obj(void) {
|
|
uint16_t curr_obj_loc;
|
|
uint16_t inode_version, inode_len;
|
|
curr_obj_loc = write_pointer;
|
|
struct norfs_inode *inode;
|
|
struct norfs_header header;
|
|
status_t status;
|
|
|
|
status = load_and_verify_obj(&write_pointer, &header);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
if (get_inode(header.key, &inode)) {
|
|
nvram_read(inode->location + NORFS_VERSION_OFFSET,
|
|
sizeof(inode_version), &inode_version);
|
|
if (VERSION_GREATER_THAN(header.version, inode_version)) {
|
|
/* This is a newer version of object than the version
|
|
currently being linked to in the inode. */
|
|
nvram_read(inode->location + NORFS_LENGTH_OFFSET,
|
|
sizeof(inode_len), &inode_len);
|
|
total_remaining_space += inode_len;
|
|
total_remaining_space -= header.len;
|
|
inode->location = curr_obj_loc;
|
|
}
|
|
inode->reference_count += 1;
|
|
} else {
|
|
/* Object not yet held in memory. Create new inode. */
|
|
inode = malloc(sizeof(struct norfs_inode));
|
|
inode->location = curr_obj_loc;
|
|
|
|
inode->reference_count = 1;
|
|
|
|
list_add_tail(&inode_list, &inode->lnode);
|
|
total_remaining_space -= NORFS_FLASH_SIZE(header.len);
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Inodes for deleted objects need to be maintained during mounting, in case
|
|
* references show up in later blocks. However, these references need to be
|
|
* pruned prior to usage.
|
|
*/
|
|
static void purge_unreferenced_inodes(void) {
|
|
struct list_node *curr_lnode, *temp_node;
|
|
struct norfs_inode *curr_inode;
|
|
list_for_every_safe(&inode_list, curr_lnode, temp_node) {
|
|
curr_inode = containerof(curr_lnode, struct norfs_inode, lnode);
|
|
if (curr_inode->reference_count == 0) {
|
|
remove_inode(curr_inode);
|
|
}
|
|
}
|
|
}
|
|
|
|
status_t norfs_mount_fs(uint32_t offset) {
|
|
if (fs_mounted) {
|
|
TRACEF("Filesystem already mounted.\n");
|
|
return ERR_ALREADY_MOUNTED;
|
|
}
|
|
status_t status = 0;
|
|
norfs_nvram_offset = offset;
|
|
|
|
list_initialize(&inode_list);
|
|
flash_nor_begin(NORFS_BANK);
|
|
srand(current_time());
|
|
|
|
total_remaining_space = NORFS_AVAILABLE_SPACE;
|
|
num_free_blocks = 0;
|
|
TRACEF("Mounting NOR file system.\n");
|
|
for (uint8_t i = 0; i < NORFS_NUM_BLOCKS; i++) {
|
|
write_pointer = i * FLASH_PAGE_SIZE;
|
|
status = read_block_verification(&write_pointer);
|
|
if (status == ERR_BAD_STATE) {
|
|
erase_block(i);
|
|
continue;
|
|
} else if (status == ERR_NOT_CONFIGURED) {
|
|
/* Valid empty block. */
|
|
block_free[i] = true;
|
|
num_free_blocks++;
|
|
continue;
|
|
} else if (status != NO_ERROR) {
|
|
TRACEF("Unexpected status: %d. Exiting.\n", status);
|
|
flash_nor_end(NORFS_BANK);
|
|
return status;
|
|
}
|
|
block_free[i] = false;
|
|
while (!block_full(i, write_pointer)) {
|
|
status = mount_next_obj();
|
|
if (status)
|
|
break;
|
|
}
|
|
}
|
|
|
|
purge_unreferenced_inodes();
|
|
|
|
write_pointer = rand() % NORFS_NVRAM_SIZE;
|
|
status = initialize_next_block(&write_pointer);
|
|
if (status) {
|
|
TRACEF("Failed to find free block after mount.\n");
|
|
flash_nor_end(NORFS_BANK);
|
|
return status;
|
|
}
|
|
|
|
TRACEF("NOR filesystem successfully mounted.\n");
|
|
flash_nor_end(NORFS_BANK);
|
|
fs_mounted = true;
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void norfs_unmount_fs(void) {
|
|
TRACEF("Unmounting NOR file system\n");
|
|
struct list_node *curr_lnode;
|
|
struct norfs_inode *curr_inode;
|
|
struct list_node *temp_node;
|
|
|
|
if (!fs_mounted) {
|
|
TRACEF("Filesystem not mounted.\n");
|
|
return;
|
|
}
|
|
if (!list_is_empty(&inode_list)) {
|
|
list_for_every_safe(&inode_list, curr_lnode, temp_node) {
|
|
if (curr_lnode) {
|
|
curr_inode = containerof(curr_lnode, struct norfs_inode, lnode);
|
|
remove_inode(curr_inode);
|
|
}
|
|
}
|
|
}
|
|
write_pointer = rand() % NORFS_NVRAM_SIZE;
|
|
total_remaining_space = NORFS_AVAILABLE_SPACE;
|
|
num_free_blocks = 0;
|
|
for (uint8_t i = 0; i < NORFS_NUM_BLOCKS; i++) {
|
|
block_free[i] = false;
|
|
}
|
|
fs_mounted = false;
|
|
}
|
|
|
|
void norfs_wipe_fs(void) {
|
|
norfs_unmount_fs();
|
|
flash_nor_begin(0);
|
|
nvram_erase_pages(0, 8 * FLASH_PAGE_SIZE);
|
|
flash_nor_end(0);
|
|
norfs_mount_fs(norfs_nvram_offset);
|
|
}
|