#include "spiffs.h" #include "spiffs_nucleus.h" #if !SPIFFS_READ_ONLY // Erases a logical block and updates the erase counter. // If cache is enabled, all pages that might be cached in this block // is dropped. static s32_t spiffs_gc_erase_block( spiffs *fs, spiffs_block_ix bix) { s32_t res; SPIFFS_GC_DBG("gc: erase block "_SPIPRIbl"\n", bix); res = spiffs_erase_block(fs, bix); SPIFFS_CHECK_RES(res); #if SPIFFS_CACHE { u32_t i; for (i = 0; i < SPIFFS_PAGES_PER_BLOCK(fs); i++) { spiffs_cache_drop_page(fs, SPIFFS_PAGE_FOR_BLOCK(fs, bix) + i); } } #endif 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"); #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; } // 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:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\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:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); return SPIFFS_ERR_FULL; } do { SPIFFS_GC_DBG("\ngc_check #"_SPIPRIi": run gc free_blocks:"_SPIPRIi" pfree:"_SPIPRIi" pallo:"_SPIPRIi" pdele:"_SPIPRIi" ["_SPIPRIi"] len:"_SPIPRIi" of "_SPIPRIi"\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, (u32_t)(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; //SPIFFS_GC_DBG("gcing: cleaning block "_SPIPRIi"\n", cand); res = spiffs_gc_clean(fs, cand); fs->cleaning = 0; if (res < 0) { SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res); } else { SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\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, "_SPIPRIi" dirty, blocks "_SPIPRIi" free, "_SPIPRIi" pages free, "_SPIPRIi" tries, res "_SPIPRIi"\n", fs->stats_p_allocated + fs->stats_p_deleted, fs->free_blocks, free_pages, tries, res); return res; } // 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:"_SPIPRIi" pdele:"_SPIPRIi"\n", allo, dele); fs->stats_p_allocated -= allo; fs->stats_p_deleted -= dele; 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; memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); // divide up work area into block indices and scores spiffs_block_ix *cand_blocks = (spiffs_block_ix *)fs->work; s32_t *cand_scores = (s32_t *)(fs->work + max_candidates * sizeof(spiffs_block_ix)); // align cand_scores on s32_t boundary cand_scores = (s32_t*)(((intptr_t)cand_scores + sizeof(intptr_t) - 1) & ~(sizeof(intptr_t) - 1)); *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:"_SPIPRIbl" del:"_SPIPRIi" use:"_SPIPRIi" score:"_SPIPRIi"\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; } typedef enum { FIND_OBJ_DATA, MOVE_OBJ_DATA, MOVE_OBJ_IX, FINISHED } spiffs_gc_clean_state; typedef struct { spiffs_gc_clean_state state; spiffs_obj_id cur_obj_id; spiffs_span_ix cur_objix_spix; spiffs_page_ix cur_objix_pix; spiffs_page_ix cur_data_pix; int stored_scan_entry_index; u8_t obj_id_found; } spiffs_gc; // 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; } #endif // !SPIFFS_READ_ONLY