u32_t tfile_get_size(tfile_size s) { switch (s) { case EMPTY: return 0; case SMALL: // half a data page return SPIFFS_DATA_PAGE_SIZE(FS)/2; case MEDIUM: // one block return SPIFFS_DATA_PAGE_SIZE(FS) * (SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS)); case LARGE: // third of fs return SPIFFS_DATA_PAGE_SIZE(FS) * (SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS)) * (FS)->block_count/3; } return 0; }
void dump_page(spiffs *fs, spiffs_page_ix p) { printf("page %04x ", p); uint32_t addr = SPIFFS_PAGE_TO_PADDR(fs, p); if (p % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { // obj lu page printf("OBJ_LU"); } else { uint32_t obj_id_addr = SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs , p)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, p) * sizeof(spiffs_obj_id); spiffs_obj_id obj_id = *((spiffs_obj_id *)&AREA(obj_id_addr)); // data page spiffs_page_header *ph = (spiffs_page_header *)&AREA(addr); printf("DATA %04x:%04x ", obj_id, ph->span_ix); printf("%s", ((ph->flags & SPIFFS_PH_FLAG_FINAL) == 0) ? "FIN " : "fin "); printf("%s", ((ph->flags & SPIFFS_PH_FLAG_DELET) == 0) ? "DEL " : "del "); printf("%s", ((ph->flags & SPIFFS_PH_FLAG_INDEX) == 0) ? "IDX " : "idx "); printf("%s", ((ph->flags & SPIFFS_PH_FLAG_USED) == 0) ? "USD " : "usd "); printf("%s ", ((ph->flags & SPIFFS_PH_FLAG_IXDELE) == 0) ? "IDL " : "idl "); if (obj_id & SPIFFS_OBJ_ID_IX_FLAG) { // object index printf("OBJ_IX"); if (ph->span_ix == 0) { printf("_HDR "); spiffs_page_object_ix_header *oix_hdr = (spiffs_page_object_ix_header *)&AREA(addr); printf("'%s' %i bytes type:%02x", oix_hdr->name, oix_hdr->size, oix_hdr->type); } } else { // data page printf("CONTENT"); } } printf("\n"); uint32_t len = fs->cfg.log_page_size; hexdump(addr, len); }
// Updates page statistics for a block that is about to be erased s32_t spiffs_gc_erase_page_stats( spiffs *fs, spiffs_block_ix bix) { s32_t res = SPIFFS_OK; int obj_lookup_page = 0; int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; int cur_entry = 0; u32_t dele = 0; u32_t allo = 0; // check each object lookup page while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { int entry_offset = obj_lookup_page * entries_per_page; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); // check each entry while (res == SPIFFS_OK && cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; if (obj_id == SPIFFS_OBJ_ID_FREE) { } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { dele++; } else { allo++; } cur_entry++; } // per entry obj_lookup_page++; } // per object lookup page SPIFFS_GC_DBG("gc_check: wipe pallo:%i pdele:%i\n", allo, dele); fs->stats_p_allocated -= allo; fs->stats_p_deleted -= dele; return res; }
s32_t ICACHE_FLASH_ATTR SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, u8_t *fd_space, u32_t fd_space_size, void *cache, u32_t cache_size, spiffs_check_callback check_cb_f) { SPIFFS_LOCK(fs); memset(fs, 0, sizeof(spiffs)); memcpy(&fs->cfg, config, sizeof(spiffs_config)); fs->block_count = SPIFFS_CFG_PHYS_SZ(fs) / SPIFFS_CFG_LOG_BLOCK_SZ(fs); fs->work = &work[0]; fs->lu_work = &work[SPIFFS_CFG_LOG_PAGE_SZ(fs)]; memset(fd_space, 0, fd_space_size); // align fd_space pointer to pointer size byte boundary, below is safe u8_t ptr_size = sizeof(void*); // #pragma GCC diagnostic push // #pragma GCC diagnostic ignored "-Wpointer-to-int-cast" u8_t addr_lsb = (u8_t)(((u32_t)fd_space) & (ptr_size-1)); // #pragma GCC diagnostic pop if (addr_lsb) { fd_space += (ptr_size-addr_lsb); fd_space_size -= (ptr_size-addr_lsb); } fs->fd_space = fd_space; fs->fd_count = (fd_space_size/sizeof(spiffs_fd)); // align cache pointer to 4 byte boundary, below is safe // #pragma GCC diagnostic push // #pragma GCC diagnostic ignored "-Wpointer-to-int-cast" addr_lsb = (u8_t)(((u32_t)cache) & (ptr_size-1)); // #pragma GCC diagnostic pop if (addr_lsb) { cache = (u8_t *)cache + (ptr_size-addr_lsb); cache_size -= (ptr_size-addr_lsb); } if (cache_size & (ptr_size-1)) { cache_size -= (cache_size & (ptr_size-1)); } #if SPIFFS_CACHE fs->cache = cache; fs->cache_size = cache_size; spiffs_cache_init(fs); #endif s32_t res = spiffs_obj_lu_scan(fs); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); SPIFFS_DBG("page index byte len: %i\n", SPIFFS_CFG_LOG_PAGE_SZ(fs)); SPIFFS_DBG("object lookup pages: %i\n", SPIFFS_OBJ_LOOKUP_PAGES(fs)); SPIFFS_DBG("page pages per block: %i\n", SPIFFS_PAGES_PER_BLOCK(fs)); SPIFFS_DBG("page header length: %i\n", sizeof(spiffs_page_header)); SPIFFS_DBG("object header index entries: %i\n", SPIFFS_OBJ_HDR_IX_LEN(fs)); SPIFFS_DBG("object index entries: %i\n", SPIFFS_OBJ_IX_LEN(fs)); SPIFFS_DBG("available file descriptors: %i\n", fs->fd_count); SPIFFS_DBG("free blocks: %i\n", fs->free_blocks); fs->check_cb_f = check_cb_f; SPIFFS_UNLOCK(fs); return 0; }
uint32_t tfile_get_size(tfile_size s) { switch (s) { case EMPTY: return 0; case SMALL: return SPIFFS_DATA_PAGE_SIZE(FS)/2; case MEDIUM: return SPIFFS_DATA_PAGE_SIZE(FS) * (SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS)); case LARGE: return (FS)->cfg.phys_size/3; } return 0; }
s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used) { s32_t res = SPIFFS_OK; SPIFFS_API_CHECK_CFG(fs); SPIFFS_API_CHECK_MOUNT(fs); SPIFFS_LOCK(fs); u32_t pages_per_block = SPIFFS_PAGES_PER_BLOCK(fs); u32_t blocks = fs->block_count; u32_t obj_lu_pages = SPIFFS_OBJ_LOOKUP_PAGES(fs); u32_t data_page_size = SPIFFS_DATA_PAGE_SIZE(fs); u32_t total_data_pages = (blocks - 2) * (pages_per_block - obj_lu_pages) + 1; // -2 for spare blocks, +1 for emergency page if (total) { *total = total_data_pages * data_page_size; } if (used) { *used = fs->stats_p_allocated * data_page_size; } SPIFFS_UNLOCK(fs); return res; }
// Empties given block by moving all data into free pages of another block // Strategy: // loop: // scan object lookup for object data pages // for first found id, check spix and load corresponding object index page to memory // push object scan lookup entry index // rescan object lookup, find data pages with same id and referenced by same object index // move data page, update object index in memory // when reached end of lookup, store updated object index // pop object scan lookup entry index // repeat loop until end of object lookup // scan object lookup again for remaining object index pages, move to new page in other block // s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { s32_t res = SPIFFS_OK; int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); int cur_entry = 0; spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; spiffs_gc gc; spiffs_page_ix cur_pix = 0; spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; SPIFFS_GC_DBG("gc_clean: cleaning block %i\n", bix); c_memset(&gc, 0, sizeof(spiffs_gc)); gc.state = FIND_OBJ_DATA; if (fs->free_cursor_block_ix == bix) { // move free cursor to next block, cannot use free pages from the block we want to clean fs->free_cursor_block_ix = (bix+1)%fs->block_count; fs->free_cursor_obj_lu_entry = 0; SPIFFS_GC_DBG("gc_clean: move free cursor to block %i\n", fs->free_cursor_block_ix); } while (res == SPIFFS_OK && gc.state != FINISHED) { SPIFFS_GC_DBG("gc_clean: state = %i entry:%i\n", gc.state, cur_entry); gc.obj_id_found = 0; // scan through lookup pages int obj_lookup_page = cur_entry / entries_per_page; u8_t scan = 1; // check each object lookup page while (scan && res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { int entry_offset = obj_lookup_page * entries_per_page; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); // check each entry while (scan && res == SPIFFS_OK && cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, cur_entry); // act upon object id depending on gc state switch (gc.state) { case FIND_OBJ_DATA: if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0)) { SPIFFS_GC_DBG("gc_clean: FIND_DATA state:%i - found obj id %04x\n", gc.state, obj_id); gc.obj_id_found = 1; gc.cur_obj_id = obj_id; scan = 0; } break; case MOVE_OBJ_DATA: if (obj_id == gc.cur_obj_id) { spiffs_page_header p_hdr; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); SPIFFS_CHECK_RES(res); SPIFFS_GC_DBG("gc_clean: MOVE_DATA found data page %04x:%04x @ %04x\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix); if (SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix) != gc.cur_objix_spix) { SPIFFS_GC_DBG("gc_clean: MOVE_DATA no objix spix match, take in another run\n"); } else { spiffs_page_ix new_data_pix; if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { // move page res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_data_pix); SPIFFS_GC_DBG("gc_clean: MOVE_DATA move objix %04x:%04x page %04x to %04x\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix); SPIFFS_CHECK_RES(res); // move wipes obj_lu, reload it res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); SPIFFS_CHECK_RES(res); } else { // page is deleted but not deleted in lookup, scrap it SPIFFS_GC_DBG("gc_clean: MOVE_DATA wipe objix %04x:%04x page %04x\n", obj_id, p_hdr.span_ix, cur_pix); res = spiffs_page_delete(fs, cur_pix); SPIFFS_CHECK_RES(res); new_data_pix = SPIFFS_OBJ_ID_FREE; } // update memory representation of object index page with new data page if (gc.cur_objix_spix == 0) { // update object index header page ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[p_hdr.span_ix] = new_data_pix; SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page %04x to objix_hdr entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); } else { // update object index page ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)] = new_data_pix; SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page %04x to objix entry %02x in mem\n", new_data_pix, SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); } } } break; case MOVE_OBJ_IX: if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { // found an index object id spiffs_page_header p_hdr; spiffs_page_ix new_pix; // load header res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); SPIFFS_CHECK_RES(res); if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { // move page res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_pix); SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX move objix %04x:%04x page %04x to %04x\n", obj_id, p_hdr.span_ix, cur_pix, new_pix); SPIFFS_CHECK_RES(res); spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_UPD, obj_id, p_hdr.span_ix, new_pix, 0); // move wipes obj_lu, reload it res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); SPIFFS_CHECK_RES(res); } else { // page is deleted but not deleted in lookup, scrap it SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX wipe objix %04x:%04x page %04x\n", obj_id, p_hdr.span_ix, cur_pix); res = spiffs_page_delete(fs, cur_pix); if (res == SPIFFS_OK) { spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0); } } SPIFFS_CHECK_RES(res); } break; default: scan = 0; break; } cur_entry++; } // per entry obj_lookup_page++; } // per object lookup page if (res != SPIFFS_OK) break; // state finalization and switch switch (gc.state) { case FIND_OBJ_DATA: if (gc.obj_id_found) { // find out corresponding obj ix page and load it to memory spiffs_page_header p_hdr; spiffs_page_ix objix_pix; gc.stored_scan_entry_index = cur_entry; cur_entry = 0; gc.state = MOVE_OBJ_DATA; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); SPIFFS_CHECK_RES(res); gc.cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix); SPIFFS_GC_DBG("gc_clean: FIND_DATA find objix span_ix:%04x\n", gc.cur_objix_spix); res = spiffs_obj_lu_find_id_and_span(fs, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix, 0, &objix_pix); SPIFFS_CHECK_RES(res); SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page %04x\n", objix_pix); res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res); SPIFFS_VALIDATE_OBJIX(objix->p_hdr, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix); gc.cur_objix_pix = objix_pix; } else { gc.state = MOVE_OBJ_IX; cur_entry = 0; // restart entry scan index } break; case MOVE_OBJ_DATA: { // store modified objix (hdr) page spiffs_page_ix new_objix_pix; gc.state = FIND_OBJ_DATA; cur_entry = gc.stored_scan_entry_index; if (gc.cur_objix_spix == 0) { // store object index header page res = spiffs_object_update_index_hdr(fs, 0, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_pix, fs->work, 0, 0, &new_objix_pix); SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, %04x:%04x\n", new_objix_pix, 0); SPIFFS_CHECK_RES(res); } else { // store object index page res = spiffs_page_move(fs, 0, fs->work, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, gc.cur_objix_pix, &new_objix_pix); SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix page, %04x:%04x\n", new_objix_pix, objix->p_hdr.span_ix); SPIFFS_CHECK_RES(res); spiffs_cb_object_event(fs, 0, SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); } } break; case MOVE_OBJ_IX: gc.state = FINISHED; break; default: cur_entry = 0; break; } SPIFFS_GC_DBG("gc_clean: state-> %i\n", gc.state); } // while state != FINISHED return res; }
// Searches for blocks where all entries are deleted - if one is found, // the block is erased. Compared to the non-quick gc, the quick one ensures // that no updates are needed on existing objects on pages that are erased. s32_t spiffs_gc_quick( spiffs *fs, u16_t max_free_pages) { s32_t res = SPIFFS_OK; u32_t blocks = fs->block_count; spiffs_block_ix cur_block = 0; u32_t cur_block_addr = 0; int cur_entry = 0; spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; SPIFFS_GC_DBG("gc_quick: running\n", cur_block); #if SPIFFS_GC_STATS fs->stats_gc_runs++; #endif int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); // find fully deleted blocks // check each block while (res == SPIFFS_OK && blocks--) { u16_t deleted_pages_in_block = 0; u16_t free_pages_in_block = 0; int obj_lookup_page = 0; // check each object lookup page while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { int entry_offset = obj_lookup_page * entries_per_page; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); // check each entry while (res == SPIFFS_OK && cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; if (obj_id == SPIFFS_OBJ_ID_DELETED) { deleted_pages_in_block++; } else if (obj_id == SPIFFS_OBJ_ID_FREE) { // kill scan, go for next block free_pages_in_block++; if (free_pages_in_block > max_free_pages) { obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs); res = 1; // kill object lu loop break; } } else { // kill scan, go for next block obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs); res = 1; // kill object lu loop break; } cur_entry++; } // per entry obj_lookup_page++; } // per object lookup page if (res == 1) res = SPIFFS_OK; if (res == SPIFFS_OK && deleted_pages_in_block + free_pages_in_block == SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs) && free_pages_in_block <= max_free_pages) { // found a fully deleted block fs->stats_p_deleted -= deleted_pages_in_block; res = spiffs_gc_erase_block(fs, cur_block); return res; } cur_entry = 0; cur_block++; cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); } // per block if (res == SPIFFS_OK) { res = SPIFFS_ERR_NO_DELETED_BLOCKS; } return res; }
// Finds block candidates to erase s32_t spiffs_gc_find_candidate( spiffs *fs, spiffs_block_ix **block_candidates, int *candidate_count, char fs_crammed) { s32_t res = SPIFFS_OK; u32_t blocks = fs->block_count; spiffs_block_ix cur_block = 0; u32_t cur_block_addr = 0; spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; int cur_entry = 0; // using fs->work area as sorted candidate memory, (spiffs_block_ix)cand_bix/(s32_t)score int max_candidates = MIN(fs->block_count, (SPIFFS_CFG_LOG_PAGE_SZ(fs)-8)/(sizeof(spiffs_block_ix) + sizeof(s32_t))); *candidate_count = 0; c_memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); // divide up work area into block indices and scores // todo alignment? spiffs_block_ix *cand_blocks = (spiffs_block_ix *)fs->work; s32_t *cand_scores = (s32_t *)(fs->work + max_candidates * sizeof(spiffs_block_ix)); *block_candidates = cand_blocks; int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); // check each block while (res == SPIFFS_OK && blocks--) { u16_t deleted_pages_in_block = 0; u16_t used_pages_in_block = 0; int obj_lookup_page = 0; // check each object lookup page while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { int entry_offset = obj_lookup_page * entries_per_page; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); // check each entry while (res == SPIFFS_OK && cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; if (obj_id == SPIFFS_OBJ_ID_FREE) { // when a free entry is encountered, scan logic ensures that all following entries are free also res = 1; // kill object lu loop break; } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { deleted_pages_in_block++; } else { used_pages_in_block++; } cur_entry++; } // per entry obj_lookup_page++; } // per object lookup page if (res == 1) res = SPIFFS_OK; // calculate score and insert into candidate table // stoneage sort, but probably not so many blocks if (res == SPIFFS_OK && deleted_pages_in_block > 0) { // read erase count spiffs_obj_id erase_count; res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0, SPIFFS_ERASE_COUNT_PADDR(fs, cur_block), sizeof(spiffs_obj_id), (u8_t *)&erase_count); SPIFFS_CHECK_RES(res); spiffs_obj_id erase_age; if (fs->max_erase_count > erase_count) { erase_age = fs->max_erase_count - erase_count; } else { erase_age = SPIFFS_OBJ_ID_FREE - (erase_count - fs->max_erase_count); } s32_t score = deleted_pages_in_block * SPIFFS_GC_HEUR_W_DELET + used_pages_in_block * SPIFFS_GC_HEUR_W_USED + erase_age * (fs_crammed ? 0 : SPIFFS_GC_HEUR_W_ERASE_AGE); int cand_ix = 0; SPIFFS_GC_DBG("gc_check: bix:%i del:%i use:%i score:%i\n", cur_block, deleted_pages_in_block, used_pages_in_block, score); while (cand_ix < max_candidates) { if (cand_blocks[cand_ix] == (spiffs_block_ix)-1) { cand_blocks[cand_ix] = cur_block; cand_scores[cand_ix] = score; break; } else if (cand_scores[cand_ix] < score) { int reorder_cand_ix = max_candidates - 2; while (reorder_cand_ix >= cand_ix) { cand_blocks[reorder_cand_ix + 1] = cand_blocks[reorder_cand_ix]; cand_scores[reorder_cand_ix + 1] = cand_scores[reorder_cand_ix]; reorder_cand_ix--; } cand_blocks[cand_ix] = cur_block; cand_scores[cand_ix] = score; break; } cand_ix++; } (*candidate_count)++; } cur_entry = 0; cur_block++; cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); } // per block return res; }
// Checks if garbage collecting is necessary. If so a candidate block is found, // cleansed and erased s32_t spiffs_gc_check( spiffs *fs, u32_t len) { s32_t res; s32_t free_pages = (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count-2) - fs->stats_p_allocated - fs->stats_p_deleted; int tries = 0; if (fs->free_blocks > 3 && (s32_t)len < free_pages * (s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { return SPIFFS_OK; } u32_t needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / SPIFFS_DATA_PAGE_SIZE(fs); // if (fs->free_blocks <= 2 && (s32_t)needed_pages > free_pages) { // SPIFFS_GC_DBG("gc: full freeblk:%i needed:%i free:%i dele:%i\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); // return SPIFFS_ERR_FULL; // } if ((s32_t)needed_pages > (s32_t)(free_pages + fs->stats_p_deleted)) { SPIFFS_GC_DBG("gc_check: full freeblk:%i needed:%i free:%i dele:%i\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); return SPIFFS_ERR_FULL; } do { SPIFFS_GC_DBG("\ngc_check #%i: run gc free_blocks:%i pfree:%i pallo:%i pdele:%i [%i] len:%i of %i\n", tries, fs->free_blocks, free_pages, fs->stats_p_allocated, fs->stats_p_deleted, (free_pages+fs->stats_p_allocated+fs->stats_p_deleted), len, free_pages*SPIFFS_DATA_PAGE_SIZE(fs)); spiffs_block_ix *cands; int count; spiffs_block_ix cand; s32_t prev_free_pages = free_pages; // if the fs is crammed, ignore block age when selecting candidate - kind of a bad state res = spiffs_gc_find_candidate(fs, &cands, &count, free_pages <= 0); SPIFFS_CHECK_RES(res); if (count == 0) { SPIFFS_GC_DBG("gc_check: no candidates, return\n"); return (s32_t)needed_pages < free_pages ? SPIFFS_OK : SPIFFS_ERR_FULL; } #if SPIFFS_GC_STATS fs->stats_gc_runs++; #endif cand = cands[0]; fs->cleaning = 1; //printf("gcing: cleaning block %i\n", cand); res = spiffs_gc_clean(fs, cand); fs->cleaning = 0; if (res < 0) { SPIFFS_GC_DBG("gc_check: cleaning block %i, result %i\n", cand, res); } else { SPIFFS_GC_DBG("gc_check: cleaning block %i, result %i\n", cand, res); } SPIFFS_CHECK_RES(res); res = spiffs_gc_erase_page_stats(fs, cand); SPIFFS_CHECK_RES(res); res = spiffs_gc_erase_block(fs, cand); SPIFFS_CHECK_RES(res); free_pages = (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) - fs->stats_p_allocated - fs->stats_p_deleted; if (prev_free_pages <= 0 && prev_free_pages == free_pages) { // abort early to reduce wear, at least tried once SPIFFS_GC_DBG("gc_check: early abort, no result on gc when fs crammed\n"); break; } } while (++tries < SPIFFS_GC_MAX_RUNS && (fs->free_blocks <= 2 || (s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs))); free_pages = (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) - fs->stats_p_allocated - fs->stats_p_deleted; if ((s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { res = SPIFFS_ERR_FULL; } SPIFFS_GC_DBG("gc_check: finished, %i dirty, blocks %i free, %i pages free, %i tries, res %i\n", fs->stats_p_allocated + fs->stats_p_deleted, fs->free_blocks, free_pages, tries, res); return res; }
s32_t SPIFFS_vis(spiffs *fs) { s32_t res = SPIFFS_OK; SPIFFS_API_CHECK_MOUNT(fs); SPIFFS_LOCK(fs); int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; spiffs_block_ix bix = 0; while (bix < fs->block_count) { // check each object lookup page int obj_lookup_page = 0; int cur_entry = 0; while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { int entry_offset = obj_lookup_page * entries_per_page; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); // check each entry while (res == SPIFFS_OK && cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; if (cur_entry == 0) { spiffs_printf("%4i ", bix); } else if ((cur_entry & 0x3f) == 0) { spiffs_printf(" "); } if (obj_id == SPIFFS_OBJ_ID_FREE) { spiffs_printf(SPIFFS_TEST_VIS_FREE_STR); } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { spiffs_printf(SPIFFS_TEST_VIS_DELE_STR); } else if (obj_id & SPIFFS_OBJ_ID_IX_FLAG){ spiffs_printf(SPIFFS_TEST_VIS_INDX_STR(obj_id)); } else { spiffs_printf(SPIFFS_TEST_VIS_DATA_STR(obj_id)); } cur_entry++; if ((cur_entry & 0x3f) == 0) { spiffs_printf("\n"); } } // per entry obj_lookup_page++; } // per object lookup page spiffs_obj_id erase_count; res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0, SPIFFS_ERASE_COUNT_PADDR(fs, bix), sizeof(spiffs_obj_id), (u8_t *)&erase_count); SPIFFS_CHECK_RES(res); if (erase_count != (spiffs_obj_id)-1) { spiffs_printf("\tera_cnt: %d\n", erase_count); } else { spiffs_printf("\tera_cnt: N/A\n"); } bix++; } // per block spiffs_printf("era_cnt_max: %d\n", fs->max_erase_count); spiffs_printf("last_errno: %d\n", fs->errno); spiffs_printf("blocks: %d\n", fs->block_count); spiffs_printf("free_blocks: %d\n", fs->free_blocks); spiffs_printf("page_alloc: %d\n", fs->stats_p_allocated); spiffs_printf("page_delet: %d\n", fs->stats_p_deleted); SPIFFS_UNLOCK(fs); return res; }
// Empties given block by moving all data into free pages of another block // Strategy: // loop: // scan object lookup for object data pages // for first found id, check spix and load corresponding object index page to memory // push object scan lookup entry index // rescan object lookup, find data pages with same id and referenced by same object index // move data page, update object index in memory // when reached end of lookup, store updated object index // pop object scan lookup entry index // repeat loop until end of object lookup // scan object lookup again for remaining object index pages, move to new page in other block // s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { s32_t res = SPIFFS_OK; const int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); // this is the global localizer being pushed and popped int cur_entry = 0; spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; spiffs_gc gc; // our stack frame/state spiffs_page_ix cur_pix = 0; spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; SPIFFS_GC_DBG("gc_clean: cleaning block "_SPIPRIbl"\n", bix); memset(&gc, 0, sizeof(spiffs_gc)); gc.state = FIND_OBJ_DATA; if (fs->free_cursor_block_ix == bix) { // move free cursor to next block, cannot use free pages from the block we want to clean fs->free_cursor_block_ix = (bix+1)%fs->block_count; fs->free_cursor_obj_lu_entry = 0; SPIFFS_GC_DBG("gc_clean: move free cursor to block "_SPIPRIbl"\n", fs->free_cursor_block_ix); } while (res == SPIFFS_OK && gc.state != FINISHED) { SPIFFS_GC_DBG("gc_clean: state = "_SPIPRIi" entry:"_SPIPRIi"\n", gc.state, cur_entry); gc.obj_id_found = 0; // reset (to no found data page) // scan through lookup pages int obj_lookup_page = cur_entry / entries_per_page; u8_t scan = 1; // check each object lookup page while (scan && res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { int entry_offset = obj_lookup_page * entries_per_page; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); // check each object lookup entry while (scan && res == SPIFFS_OK && cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, cur_entry); // act upon object id depending on gc state switch (gc.state) { case FIND_OBJ_DATA: // find a data page if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0)) { // found a data page, stop scanning and handle in switch case below SPIFFS_GC_DBG("gc_clean: FIND_DATA state:"_SPIPRIi" - found obj id "_SPIPRIid"\n", gc.state, obj_id); gc.obj_id_found = 1; gc.cur_obj_id = obj_id; gc.cur_data_pix = cur_pix; scan = 0; } break; case MOVE_OBJ_DATA: // evacuate found data pages for corresponding object index we have in memory, // update memory representation if (obj_id == gc.cur_obj_id) { spiffs_page_header p_hdr; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); SPIFFS_CHECK_RES(res); SPIFFS_GC_DBG("gc_clean: MOVE_DATA found data page "_SPIPRIid":"_SPIPRIsp" @ "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix); if (SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix) != gc.cur_objix_spix) { SPIFFS_GC_DBG("gc_clean: MOVE_DATA no objix spix match, take in another run\n"); } else { spiffs_page_ix new_data_pix; if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { // move page res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_data_pix); SPIFFS_GC_DBG("gc_clean: MOVE_DATA move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix); SPIFFS_CHECK_RES(res); // move wipes obj_lu, reload it res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); SPIFFS_CHECK_RES(res); } else { // page is deleted but not deleted in lookup, scrap it - // might seem unnecessary as we will erase this block, but // we might get aborted SPIFFS_GC_DBG("gc_clean: MOVE_DATA wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix); res = spiffs_page_delete(fs, cur_pix); SPIFFS_CHECK_RES(res); new_data_pix = SPIFFS_OBJ_ID_FREE; } // update memory representation of object index page with new data page if (gc.cur_objix_spix == 0) { // update object index header page ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[p_hdr.span_ix] = new_data_pix; SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); } else { // update object index page ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)] = new_data_pix; SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); } } } break; case MOVE_OBJ_IX: // find and evacuate object index pages if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { // found an index object id spiffs_page_header p_hdr; spiffs_page_ix new_pix; // load header res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); SPIFFS_CHECK_RES(res); if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { // move page res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_pix); SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix, new_pix); SPIFFS_CHECK_RES(res); spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&p_hdr, SPIFFS_EV_IX_MOV, obj_id, p_hdr.span_ix, new_pix, 0); // move wipes obj_lu, reload it res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); SPIFFS_CHECK_RES(res); } else { // page is deleted but not deleted in lookup, scrap it - // might seem unnecessary as we will erase this block, but // we might get aborted SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix); res = spiffs_page_delete(fs, cur_pix); if (res == SPIFFS_OK) { spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0); } } SPIFFS_CHECK_RES(res); } break; default: scan = 0; break; } // switch gc state cur_entry++; } // per entry obj_lookup_page++; // no need to check scan variable here, obj_lookup_page is set in start of loop } // per object lookup page if (res != SPIFFS_OK) break; // state finalization and switch switch (gc.state) { case FIND_OBJ_DATA: if (gc.obj_id_found) { // handle found data page - // find out corresponding obj ix page and load it to memory spiffs_page_header p_hdr; spiffs_page_ix objix_pix; gc.stored_scan_entry_index = cur_entry; // push cursor cur_entry = 0; // restart scan from start gc.state = MOVE_OBJ_DATA; res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); SPIFFS_CHECK_RES(res); gc.cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix); SPIFFS_GC_DBG("gc_clean: FIND_DATA find objix span_ix:"_SPIPRIsp"\n", gc.cur_objix_spix); res = spiffs_obj_lu_find_id_and_span(fs, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix, 0, &objix_pix); if (res == SPIFFS_ERR_NOT_FOUND) { // on borked systems we might get an ERR_NOT_FOUND here - // this is handled by simply deleting the page as it is not referenced // from anywhere SPIFFS_GC_DBG("gc_clean: FIND_OBJ_DATA objix not found! Wipe page "_SPIPRIpg"\n", gc.cur_data_pix); res = spiffs_page_delete(fs, gc.cur_data_pix); SPIFFS_CHECK_RES(res); // then we restore states and continue scanning for data pages cur_entry = gc.stored_scan_entry_index; // pop cursor gc.state = FIND_OBJ_DATA; break; // done } SPIFFS_CHECK_RES(res); SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page "_SPIPRIpg"\n", objix_pix); res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); SPIFFS_CHECK_RES(res); // cannot allow a gc if the presumed index in fact is no index, a // check must run or lot of data may be lost SPIFFS_VALIDATE_OBJIX(objix->p_hdr, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix); gc.cur_objix_pix = objix_pix; } else { // no more data pages found, passed thru all block, start evacuating object indices gc.state = MOVE_OBJ_IX; cur_entry = 0; // restart entry scan index } break; case MOVE_OBJ_DATA: { // store modified objix (hdr) page residing in memory now that all // data pages belonging to this object index and residing in the block // we want to evacuate spiffs_page_ix new_objix_pix; gc.state = FIND_OBJ_DATA; cur_entry = gc.stored_scan_entry_index; // pop cursor if (gc.cur_objix_spix == 0) { // store object index header page res = spiffs_object_update_index_hdr(fs, 0, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_pix, fs->work, 0, 0, 0, &new_objix_pix); SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, 0); SPIFFS_CHECK_RES(res); } else { // store object index page res = spiffs_page_move(fs, 0, fs->work, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, gc.cur_objix_pix, &new_objix_pix); SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, objix->p_hdr.span_ix); SPIFFS_CHECK_RES(res); spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); } } break; case MOVE_OBJ_IX: // scanned thru all block, no more object indices found - our work here is done gc.state = FINISHED; break; default: cur_entry = 0; break; } // switch gc.state SPIFFS_GC_DBG("gc_clean: state-> "_SPIPRIi"\n", gc.state); } // while state != FINISHED return res; }
s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, u8_t *fd_space, u32_t fd_space_size, void *cache, u32_t cache_size, spiffs_check_callback check_cb_f) { void *user_data; SPIFFS_LOCK(fs); user_data = fs->user_data; memset(fs, 0, sizeof(spiffs)); memcpy(&fs->cfg, config, sizeof(spiffs_config)); fs->user_data = user_data; fs->block_count = SPIFFS_CFG_PHYS_SZ(fs) / SPIFFS_CFG_LOG_BLOCK_SZ(fs); fs->work = &work[0]; fs->lu_work = &work[SPIFFS_CFG_LOG_PAGE_SZ(fs)]; memset(fd_space, 0, fd_space_size); // align fd_space pointer to pointer size byte boundary u8_t ptr_size = sizeof(void*); u8_t addr_lsb = ((u8_t)(intptr_t)fd_space) & (ptr_size-1); if (addr_lsb) { fd_space += (ptr_size-addr_lsb); fd_space_size -= (ptr_size-addr_lsb); } fs->fd_space = fd_space; fs->fd_count = (fd_space_size/sizeof(spiffs_fd)); // align cache pointer to 4 byte boundary addr_lsb = ((u8_t)(intptr_t)cache) & (ptr_size-1); if (addr_lsb) { u8_t *cache_8 = (u8_t *)cache; cache_8 += (ptr_size-addr_lsb); cache = cache_8; cache_size -= (ptr_size-addr_lsb); } if (cache_size & (ptr_size-1)) { cache_size -= (cache_size & (ptr_size-1)); } #if SPIFFS_CACHE fs->cache = cache; fs->cache_size = (cache_size > (SPIFFS_CFG_LOG_PAGE_SZ(fs)*32)) ? SPIFFS_CFG_LOG_PAGE_SZ(fs)*32 : cache_size; spiffs_cache_init(fs); #endif s32_t res; #if SPIFFS_USE_MAGIC res = SPIFFS_CHECK_MAGIC_POSSIBLE(fs) ? SPIFFS_OK : SPIFFS_ERR_MAGIC_NOT_POSSIBLE; SPIFFS_API_CHECK_RES_UNLOCK(fs, res); #endif fs->config_magic = SPIFFS_CONFIG_MAGIC; res = spiffs_obj_lu_scan(fs); SPIFFS_API_CHECK_RES_UNLOCK(fs, res); SPIFFS_DBG("page index byte len: %i\n", SPIFFS_CFG_LOG_PAGE_SZ(fs)); SPIFFS_DBG("object lookup pages: %i\n", SPIFFS_OBJ_LOOKUP_PAGES(fs)); SPIFFS_DBG("page pages per block: %i\n", SPIFFS_PAGES_PER_BLOCK(fs)); SPIFFS_DBG("page header length: %i\n", sizeof(spiffs_page_header)); SPIFFS_DBG("object header index entries: %i\n", SPIFFS_OBJ_HDR_IX_LEN(fs)); SPIFFS_DBG("object index entries: %i\n", SPIFFS_OBJ_IX_LEN(fs)); SPIFFS_DBG("available file descriptors: %i\n", fs->fd_count); SPIFFS_DBG("free blocks: %i\n", fs->free_blocks); fs->check_cb_f = check_cb_f; fs->mounted = 1; SPIFFS_UNLOCK(fs); return 0; }
// Checks if garbaga collecting is necessary. If so a candidate block is found, // cleansed and erased s32_t spiffs_gc_check( spiffs *fs, u32_t len) { s32_t res; s32_t free_pages = (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count-2) - fs->stats_p_allocated - fs->stats_p_deleted; int tries = 0; if (fs->free_blocks > 3 && (s32_t)len < free_pages * (s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { return SPIFFS_OK; } u32_t needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / SPIFFS_DATA_PAGE_SIZE(fs); if (fs->free_blocks <= 2 && (s32_t)needed_pages > free_pages) { return SPIFFS_ERR_FULL; } //printf("gcing started %i dirty, blocks %i free, want %i bytes\n", fs->stats_p_allocated + fs->stats_p_deleted, fs->free_blocks, len); do { SPIFFS_GC_DBG("\ngc_check #%i: run gc free_blocks:%i pfree:%i pallo:%i pdele:%i [%i] len:%i of %i\n", tries, fs->free_blocks, free_pages, fs->stats_p_allocated, fs->stats_p_deleted, (free_pages+fs->stats_p_allocated+fs->stats_p_deleted), len, free_pages*SPIFFS_DATA_PAGE_SIZE(fs)); spiffs_block_ix *cands; int count; spiffs_block_ix cand; res = spiffs_gc_find_candidate(fs, &cands, &count); SPIFFS_CHECK_RES(res); if (count == 0) { SPIFFS_GC_DBG("gc_check: no candidates, return\n"); return res; } #if SPIFFS_GC_STATS fs->stats_gc_runs++; #endif cand = cands[0]; fs->cleaning = 1; //printf("gcing: cleaning block %i\n", cand); res = spiffs_gc_clean(fs, cand); fs->cleaning = 0; if (res < 0) { SPIFFS_GC_DBG("gc_check: cleaning block %i, result %i\n", cand, res); } else { SPIFFS_GC_DBG("gc_check: cleaning block %i, result %i\n", cand, res); } SPIFFS_CHECK_RES(res); res = spiffs_gc_erase_page_stats(fs, cand); SPIFFS_CHECK_RES(res); res = spiffs_gc_erase_block(fs, cand); SPIFFS_CHECK_RES(res); free_pages = (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) - fs->stats_p_allocated - fs->stats_p_deleted; } while (++tries < SPIFFS_GC_MAX_RUNS && (fs->free_blocks <= 2 || (s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs))); free_pages = (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) - fs->stats_p_allocated - fs->stats_p_deleted; if ((s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { res = SPIFFS_ERR_FULL; } SPIFFS_GC_DBG("gc_check: finished, %i dirty, blocks %i free, %i pages free, %i tries, res %i\n", fs->stats_p_allocated + fs->stats_p_deleted, fs->free_blocks, free_pages, tries, res); return res; }