/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include /* 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); }