/** * Formats a single scratch area. */ int nffs_format_area(uint8_t area_idx, int is_scratch) { struct nffs_disk_area disk_area; struct nffs_area *area; uint32_t write_len; int rc; area = nffs_areas + area_idx; rc = hal_flash_erase(area->na_flash_id, area->na_offset, area->na_length); if (rc != 0) { return rc; } area->na_cur = 0; nffs_area_to_disk(area, &disk_area); if (is_scratch) { nffs_areas[area_idx].na_id = NFFS_AREA_ID_NONE; write_len = sizeof disk_area - sizeof disk_area.nda_id; } else { write_len = sizeof disk_area; } rc = nffs_flash_write(area_idx, 0, &disk_area.nda_magic, write_len); if (rc != 0) { return rc; } return 0; }
/** * Turns a scratch area into a non-scratch area. If the specified area is not * actually a scratch area, this function falls back to a slower full format * operation. */ int nffs_format_from_scratch_area(uint8_t area_idx, uint8_t area_id) { struct nffs_disk_area disk_area; int rc; assert(area_idx < nffs_num_areas); rc = nffs_flash_read(area_idx, 0, &disk_area, sizeof disk_area); if (rc != 0) { return rc; } nffs_areas[area_idx].na_id = area_id; if (!nffs_area_is_scratch(&disk_area)) { rc = nffs_format_area(area_idx, 0); if (rc != 0) { return rc; } } else { disk_area.nda_id = area_id; rc = nffs_flash_write(area_idx, NFFS_AREA_OFFSET_ID, &disk_area.nda_id, sizeof disk_area.nda_id); if (rc != 0) { return rc; } } return 0; }
/** * Copies a chunk of data from one region of flash to another. * * @param area_idx_from The index of the area to copy from. * @param area_offset_from The offset within the area to copy from. * @param area_idx_to The index of the area to copy to. * @param area_offset_to The offset within the area to copy to. * @param len The number of bytes to copy. * * @return 0 on success; nonzero on failure. */ int nffs_flash_copy(uint8_t area_idx_from, uint32_t area_offset_from, uint8_t area_idx_to, uint32_t area_offset_to, uint32_t len) { uint32_t chunk_len; int rc; /* Copy data in chunks small enough to fit in the flash buffer. */ while (len > 0) { if (len > sizeof nffs_flash_buf) { chunk_len = sizeof nffs_flash_buf; } else { chunk_len = len; } rc = nffs_flash_read(area_idx_from, area_offset_from, nffs_flash_buf, chunk_len); if (rc != 0) { return rc; } rc = nffs_flash_write(area_idx_to, area_offset_to, nffs_flash_buf, chunk_len); if (rc != 0) { return rc; } area_offset_from += chunk_len; area_offset_to += chunk_len; len -= chunk_len; } return 0; }
/** * Writes the specified data block to a suitable location in flash. * * @param disk_block Points to the disk block to write. * @param data The contents of the data block. * @param out_area_idx On success, contains the index of the area * written to. * @param out_area_offset On success, contains the offset within the area * written to. * * @return 0 on success; nonzero on failure. */ int nffs_block_write_disk(const struct nffs_disk_block *disk_block, const void *data, uint8_t *out_area_idx, uint32_t *out_area_offset) { uint32_t area_offset; uint8_t area_idx; int rc; rc = nffs_misc_reserve_space(sizeof *disk_block + disk_block->ndb_data_len, &area_idx, &area_offset); if (rc != 0) { return rc; } rc = nffs_flash_write(area_idx, area_offset, disk_block, sizeof *disk_block); if (rc != 0) { return rc; } if (disk_block->ndb_data_len > 0) { rc = nffs_flash_write(area_idx, area_offset + sizeof *disk_block, data, disk_block->ndb_data_len); if (rc != 0) { return rc; } } *out_area_idx = area_idx; *out_area_offset = area_offset; ASSERT_IF_TEST(nffs_crc_disk_block_validate(disk_block, area_idx, area_offset) == 0); return 0; }
/** * Moves a chain of blocks from one area to another. This function attempts to * collate the blocks into a single new block in the destination area. * * @param last_entry The last block entry in the chain. * @param data_len The total length of data to collate. * @param to_area_idx The index of the area to copy to. * @param inout_next This parameter is only necessary if you are * calling this function during an iteration * of the entire hash table; pass null * otherwise. * On input, this points to the next hash entry * you plan on processing. * On output, this points to the next hash entry * that should be processed. * * @return 0 on success; * FS_ENOMEM if there is insufficient heap; * other nonzero on failure. */ static int nffs_gc_block_chain_collate(struct nffs_hash_entry *last_entry, uint32_t data_len, uint8_t to_area_idx, struct nffs_hash_entry **inout_next) { struct nffs_disk_block disk_block; struct nffs_hash_entry *entry; struct nffs_area *to_area; struct nffs_block last_block; struct nffs_block block; uint32_t to_area_offset; uint32_t from_area_offset; uint32_t data_offset; uint8_t *data; uint8_t from_area_idx; int rc; memset(&last_block, 0, sizeof last_block); data = malloc(data_len); if (data == NULL) { rc = FS_ENOMEM; goto done; } memset(&last_block, 0, sizeof(last_block)); to_area = nffs_areas + to_area_idx; entry = last_entry; data_offset = data_len; while (data_offset > 0) { rc = nffs_block_from_hash_entry(&block, entry); if (rc != 0) { goto done; } data_offset -= block.nb_data_len; nffs_flash_loc_expand(block.nb_hash_entry->nhe_flash_loc, &from_area_idx, &from_area_offset); from_area_offset += sizeof disk_block; STATS_INC(nffs_stats, nffs_readcnt_gccollate); rc = nffs_flash_read(from_area_idx, from_area_offset, data + data_offset, block.nb_data_len); if (rc != 0) { goto done; } if (entry != last_entry) { if (inout_next != NULL && *inout_next == entry) { *inout_next = SLIST_NEXT(entry, nhe_next); } nffs_block_delete_from_ram(entry); } else { last_block = block; } entry = block.nb_prev; } /* we had better have found the last block */ assert(last_block.nb_hash_entry); /* The resulting block should inherit its ID from its last constituent * block (this is the ID referenced by the parent inode and subsequent data * block). The previous ID gets inherited from the first constituent * block. */ memset(&disk_block, 0, sizeof disk_block); disk_block.ndb_id = last_block.nb_hash_entry->nhe_id; disk_block.ndb_seq = last_block.nb_seq + 1; disk_block.ndb_inode_id = last_block.nb_inode_entry->nie_hash_entry.nhe_id; if (entry == NULL) { disk_block.ndb_prev_id = NFFS_ID_NONE; } else { disk_block.ndb_prev_id = entry->nhe_id; } disk_block.ndb_data_len = data_len; nffs_crc_disk_block_fill(&disk_block, data); to_area_offset = to_area->na_cur; rc = nffs_flash_write(to_area_idx, to_area_offset, &disk_block, sizeof disk_block); if (rc != 0) { goto done; } rc = nffs_flash_write(to_area_idx, to_area_offset + sizeof disk_block, data, data_len); if (rc != 0) { goto done; } last_entry->nhe_flash_loc = nffs_flash_loc(to_area_idx, to_area_offset); rc = 0; ASSERT_IF_TEST(nffs_crc_disk_block_validate(&disk_block, to_area_idx, to_area_offset) == 0); done: free(data); return rc; }
/** * Moves a chain of blocks from one area to another. This function attempts to * collate the blocks into a single new block in the destination area. * * @param last_entry The last block entry in the chain. * @param data_len The total length of data to collate. * @param to_area_idx The index of the area to copy to. * @param inout_next This parameter is only necessary if you are * calling this function during an iteration * of the entire hash table; pass null * otherwise. * On input, this points to the next hash entry * you plan on processing. * On output, this points to the next hash entry * that should be processed. * * @return 0 on success; * FS_ENOMEM if there is insufficient heap; * other nonzero on failure. */ static int nffs_gc_block_chain_collate(struct nffs_hash_entry *last_entry, uint32_t data_len, uint8_t to_area_idx, struct nffs_hash_entry **inout_next) { struct nffs_disk_block disk_block; struct nffs_hash_entry *entry; struct nffs_area *to_area; struct nffs_block block; uint32_t to_area_offset; uint32_t from_area_offset; uint32_t data_offset; uint8_t *data; uint8_t from_area_idx; int rc; data = malloc(data_len); if (data == NULL) { rc = FS_ENOMEM; goto done; } to_area = nffs_areas + to_area_idx; entry = last_entry; data_offset = data_len; while (data_offset > 0) { rc = nffs_block_from_hash_entry(&block, entry); if (rc != 0) { goto done; } data_offset -= block.nb_data_len; nffs_flash_loc_expand(block.nb_hash_entry->nhe_flash_loc, &from_area_idx, &from_area_offset); from_area_offset += sizeof disk_block; rc = nffs_flash_read(from_area_idx, from_area_offset, data + data_offset, block.nb_data_len); if (rc != 0) { goto done; } if (entry != last_entry) { if (inout_next != NULL && *inout_next == entry) { *inout_next = SLIST_NEXT(entry, nhe_next); } nffs_block_delete_from_ram(entry); } entry = block.nb_prev; } memset(&disk_block, 0, sizeof disk_block); disk_block.ndb_magic = NFFS_BLOCK_MAGIC; disk_block.ndb_id = block.nb_hash_entry->nhe_id; disk_block.ndb_seq = block.nb_seq + 1; disk_block.ndb_inode_id = block.nb_inode_entry->nie_hash_entry.nhe_id; if (entry == NULL) { disk_block.ndb_prev_id = NFFS_ID_NONE; } else { disk_block.ndb_prev_id = entry->nhe_id; } disk_block.ndb_data_len = data_len; nffs_crc_disk_block_fill(&disk_block, data); to_area_offset = to_area->na_cur; rc = nffs_flash_write(to_area_idx, to_area_offset, &disk_block, sizeof disk_block); if (rc != 0) { goto done; } rc = nffs_flash_write(to_area_idx, to_area_offset + sizeof disk_block, data, data_len); if (rc != 0) { goto done; } last_entry->nhe_flash_loc = nffs_flash_loc(to_area_idx, to_area_offset); rc = 0; ASSERT_IF_TEST(nffs_crc_disk_block_validate(&disk_block, to_area_idx, to_area_offset) == 0); done: free(data); return rc; }
/** * Overwrites an existing data block. The resulting block has the same ID as * the old one, but it supersedes it with a greater sequence number. * * @param entry The data block to overwrite. * @param left_copy_len The number of bytes of existing data to retain * before the new data begins. * @param new_data The new data to write to the block. * @param new_data_len The number of new bytes to write to the block. * If this value plus left_copy_len is less * than the existing block's data length, * previous data at the end of the block is * retained. * * @return 0 on success; nonzero on failure. */ static int nffs_write_over_block(struct nffs_hash_entry *entry, uint16_t left_copy_len, const void *new_data, uint16_t new_data_len) { struct nffs_disk_block disk_block; struct nffs_block block; uint32_t src_area_offset; uint32_t dst_area_offset; uint16_t right_copy_len; uint16_t block_off; uint8_t src_area_idx; uint8_t dst_area_idx; int rc; rc = nffs_block_from_hash_entry(&block, entry); if (rc != 0) { return rc; } assert(left_copy_len <= block.nb_data_len); /* Determine how much old data at the end of the block needs to be * retained. If the new data doesn't extend to the end of the block, the * the rest of the block retains its old contents. */ if (left_copy_len + new_data_len > block.nb_data_len) { right_copy_len = 0; } else { right_copy_len = block.nb_data_len - left_copy_len - new_data_len; } block.nb_seq++; block.nb_data_len = left_copy_len + new_data_len + right_copy_len; nffs_block_to_disk(&block, &disk_block); nffs_flash_loc_expand(entry->nhe_flash_loc, &src_area_idx, &src_area_offset); rc = nffs_write_fill_crc16_overwrite(&disk_block, src_area_idx, src_area_offset, left_copy_len, right_copy_len, new_data, new_data_len); if (rc != 0) { return rc; } rc = nffs_misc_reserve_space(sizeof disk_block + disk_block.ndb_data_len, &dst_area_idx, &dst_area_offset); if (rc != 0) { return rc; } block_off = 0; /* Write the block header. */ rc = nffs_flash_write(dst_area_idx, dst_area_offset + block_off, &disk_block, sizeof disk_block); if (rc != 0) { return rc; } block_off += sizeof disk_block; /* Copy data from the start of the old block, in case the new data starts * at a non-zero offset. */ if (left_copy_len > 0) { rc = nffs_flash_copy(src_area_idx, src_area_offset + block_off, dst_area_idx, dst_area_offset + block_off, left_copy_len); if (rc != 0) { return rc; } block_off += left_copy_len; } /* Write the new data into the data block. This may extend the block's * length beyond its old value. */ rc = nffs_flash_write(dst_area_idx, dst_area_offset + block_off, new_data, new_data_len); if (rc != 0) { return rc; } block_off += new_data_len; /* Copy data from the end of the old block, in case the new data doesn't * extend to the end of the block. */ if (right_copy_len > 0) { rc = nffs_flash_copy(src_area_idx, src_area_offset + block_off, dst_area_idx, dst_area_offset + block_off, right_copy_len); if (rc != 0) { return rc; } block_off += right_copy_len; } assert(block_off == sizeof disk_block + block.nb_data_len); entry->nhe_flash_loc = nffs_flash_loc(dst_area_idx, dst_area_offset); ASSERT_IF_TEST(nffs_crc_disk_block_validate(&disk_block, dst_area_idx, dst_area_offset) == 0); return 0; }