| /**************************************************************************** |
| * fs/spiffs/src/spiffs_gc.c |
| * |
| * Copyright (C) 2018 Gregory Nutt. All rights reserved. |
| * Author: Gregory Nutt <gnutt@nuttx.org> |
| * |
| * This is a port of version 0.3.7 of SPIFFS by Peter Andersion. That |
| * version was originally released under the MIT license but is here re- |
| * released under the NuttX BSD license. |
| * |
| * Copyright (c) 2013-2017 Peter Andersson (pelleplutt1976@gmail.com) |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * 3. Neither the name NuttX nor the names of its contributors may be |
| * used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <debug.h> |
| #include <inttypes.h> |
| |
| #include "spiffs.h" |
| #include "spiffs_core.h" |
| #include "spiffs_cache.h" |
| #include "spiffs_gc.h" |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| enum spiffs_gc_clean_state_e |
| { |
| FIND_OBJ_DATA, |
| MOVE_OBJ_DATA, |
| MOVE_OBJ_NDX, |
| FINISHED |
| }; |
| |
| struct spiffs_gc_s |
| { |
| enum spiffs_gc_clean_state_e state; |
| int16_t cur_objid; |
| int16_t cur_objndx_spndx; |
| int16_t cur_objndx_pgndx; |
| int16_t cur_data_pgndx; |
| int stored_scan_entry_index; |
| bool objid_found; |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: spiffs_gc_erase_block |
| * |
| * Description: |
| * Erases a logical block and updates the erase counter. |
| * If cache is enabled, all pages that might be cached in this block |
| * is dropped. |
| * |
| * Input Parameters: |
| * fs - A reference to the SPIFFS volume object instance |
| * blkndx - The block index to erase |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success; A negated errno value is returned on |
| * any failure. |
| * |
| ****************************************************************************/ |
| |
| static int spiffs_gc_erase_block(FAR struct spiffs_s *fs, int16_t blkndx) |
| { |
| int ret; |
| int i; |
| |
| spiffs_gcinfo("Erase block=%04x\n", blkndx); |
| |
| /* Erase the block */ |
| |
| ret = spiffs_erase_block(fs, blkndx); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_erase_block() blkndx=%d failed: %d\n", |
| blkndx, ret); |
| } |
| |
| /* Then remove the pages from the cache. */ |
| |
| for (i = 0; i < SPIFFS_GEO_PAGES_PER_BLOCK(fs); i++) |
| { |
| spiffs_cache_drop_page(fs, SPIFFS_PAGE_FOR_BLOCK(fs, blkndx) + i); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: spiffs_gc_epage_stats |
| * |
| * Description: |
| * Checks if garbage collecting is necessary. If so a candidate block is |
| * found, cleansed and erased |
| * |
| * This function will try to make room for given amount of bytes in the |
| * filesystem by moving pages and erasing blocks. |
| * |
| * If it is physically impossible, err_no will be set to -ENOSPC. If |
| * there already is this amount (or more) of free space, SPIFFS_gc will |
| * silently return. It is recommended to call statfs() before invoking |
| * this method in order to determine what amount of bytes to give. |
| * |
| * NB: the garbage collector is automatically called when spiffs needs free |
| * pages. The reason for this function is to give possibility to do |
| * background tidying when user knows the system is idle. |
| * |
| * Input Parameters: |
| * fs the file system struct |
| * len amount of data that should be freed |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success; A negated errno value is returned on |
| * any failure. -ENODATA is returned if no blocks were deleted. |
| * |
| ****************************************************************************/ |
| |
| /* Updates page statistics for a block that is about to be erased */ |
| |
| static int spiffs_gc_epage_stats(FAR struct spiffs_s *fs, int16_t blkndx) |
| { |
| FAR int16_t *objlu_buf = (int16_t *) fs->lu_work; |
| uint32_t dele = 0; |
| uint32_t allo = 0; |
| int entries_per_page = (SPIFFS_GEO_PAGE_SIZE(fs) / sizeof(int16_t)); |
| int cur_entry = 0; |
| int obj_lookup_page = 0; |
| int ret = OK; |
| |
| /* Check each object lookup page */ |
| |
| while (ret >= 0 && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) |
| { |
| int entry_offset = obj_lookup_page * entries_per_page; |
| ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, 0, |
| blkndx * SPIFFS_GEO_BLOCK_SIZE(fs) + |
| SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), |
| SPIFFS_GEO_PAGE_SIZE(fs), fs->lu_work); |
| |
| /* Check each entry */ |
| |
| while (ret >= 0 && |
| cur_entry - entry_offset < entries_per_page && |
| cur_entry < |
| (int)(SPIFFS_GEO_PAGES_PER_BLOCK(fs) - |
| SPIFFS_OBJ_LOOKUP_PAGES(fs))) |
| { |
| int16_t id = objlu_buf[cur_entry - entry_offset]; |
| |
| if (id == SPIFFS_OBJID_FREE) |
| { |
| } |
| else if (id == SPIFFS_OBJID_DELETED) |
| { |
| dele++; |
| } |
| else |
| { |
| allo++; |
| } |
| |
| cur_entry++; |
| } |
| |
| obj_lookup_page++; |
| } |
| |
| spiffs_gcinfo("Wipe pallo=%" PRIu32 " pdele=%" PRIu32 "\n", allo, dele); |
| |
| fs->alloc_pages -= allo; |
| fs->deleted_pages -= dele; |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: spiffs_gc_find_candidate |
| * |
| * Description: |
| * Finds block candidates to erase |
| * |
| * Input Parameters: |
| * fs - A reference to the SPIFFS volume object instance |
| * block_candidates - Memory to return an array of candidates |
| * candidate_count - Memory to return the number of candidates |
| * fs_crammed - True: Filesystem is full |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success; A negated errno value is returned on |
| * any failure. -ENODATA is returned if no blocks were deleted. |
| * |
| ****************************************************************************/ |
| |
| static int spiffs_gc_find_candidate(FAR struct spiffs_s *fs, |
| FAR int16_t **block_candidates, |
| FAR int *candidate_count, |
| bool fs_crammed) |
| { |
| FAR int32_t *cand_scores; |
| FAR int16_t *objlu_buf = (FAR int16_t *)fs->lu_work; |
| FAR int16_t *cand_blocks; |
| uint32_t blocks = SPIFFS_GEO_BLOCK_COUNT(fs); |
| int16_t cur_block = 0; |
| uint32_t cur_block_addr = 0; |
| int entries_per_page; |
| int max_candidates; |
| int cur_entry = 0; |
| int ret = OK; |
| |
| /* Using fs->work area as sorted candidate memory, |
| * (int16_t)cand_blkndx/(int32_t)score |
| */ |
| |
| max_candidates = |
| MIN(SPIFFS_GEO_BLOCK_COUNT(fs), |
| (SPIFFS_GEO_PAGE_SIZE(fs) - 8) / |
| (sizeof(int16_t) + sizeof(int32_t))); |
| *candidate_count = 0; |
| memset(fs->work, 0xff, SPIFFS_GEO_PAGE_SIZE(fs)); |
| |
| /* Divide up work area into block indices and scores */ |
| |
| cand_blocks = (FAR int16_t *)fs->work; |
| cand_scores = |
| (FAR int32_t *)(fs->work + max_candidates * sizeof(int16_t)); |
| |
| /* Align cand_scores on int32_t boundary */ |
| |
| cand_scores = |
| (FAR int32_t *)(((intptr_t)cand_scores + sizeof(intptr_t) - 1) & |
| ~(sizeof(intptr_t) - 1)); |
| |
| *block_candidates = cand_blocks; |
| |
| entries_per_page = (SPIFFS_GEO_PAGE_SIZE(fs) / sizeof(int16_t)); |
| |
| /* Check each block */ |
| |
| while (ret >= 0 && blocks--) |
| { |
| uint16_t deleted_pages_in_block = 0; |
| uint16_t used_pages_in_block = 0; |
| int obj_lookup_page = 0; |
| |
| /* check each object lookup page */ |
| |
| while (ret >= 0 && |
| obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) |
| { |
| int entry_offset = obj_lookup_page * entries_per_page; |
| ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, |
| 0, cur_block_addr + |
| SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), |
| SPIFFS_GEO_PAGE_SIZE(fs), fs->lu_work); |
| |
| /* Check each entry */ |
| |
| while (ret >= 0 && |
| cur_entry - entry_offset < entries_per_page && |
| cur_entry < |
| (int)(SPIFFS_GEO_PAGES_PER_BLOCK(fs) - |
| SPIFFS_OBJ_LOOKUP_PAGES(fs))) |
| { |
| int16_t id = objlu_buf[cur_entry - entry_offset]; |
| |
| if (id == SPIFFS_OBJID_FREE) |
| { |
| /* When a free entry is encountered, scan logic ensures |
| * that all following entries are free also |
| */ |
| |
| ret = 1; /* Kill object lu loop */ |
| break; |
| } |
| else if (id == SPIFFS_OBJID_DELETED) |
| { |
| deleted_pages_in_block++; |
| } |
| else |
| { |
| used_pages_in_block++; |
| } |
| |
| cur_entry++; |
| } |
| |
| obj_lookup_page++; |
| } |
| |
| if (ret == 1) |
| { |
| ret = OK; |
| } |
| |
| /* Calculate score and insert into candidate table stoneage sort, but |
| * probably not so many blocks |
| */ |
| |
| if (ret >= 0 /* && deleted_pages_in_block > 0 */) |
| { |
| int32_t score; |
| int16_t erase_count; |
| int16_t erase_age; |
| int candndx; |
| |
| /* Read erase count */ |
| |
| ret = spiffs_cache_read(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, |
| 0, SPIFFS_ERASE_COUNT_PADDR(fs, cur_block), |
| sizeof(int16_t), (uint8_t *)&erase_count); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* Calculate the erase age */ |
| |
| if (fs->max_erase_count > erase_count) |
| { |
| erase_age = fs->max_erase_count - erase_count; |
| } |
| else |
| { |
| erase_age = |
| SPIFFS_OBJID_FREE - (erase_count - fs->max_erase_count); |
| } |
| |
| score = |
| deleted_pages_in_block * CONFIG_SPIFFS_GC_DELWGT + |
| used_pages_in_block * CONFIG_SPIFFS_GC_USEDWGT + |
| erase_age * (fs_crammed ? 0 : CONFIG_SPIFFS_GC_ERASEAGEWGT); |
| |
| candndx = 0; |
| |
| spiffs_gcinfo("blkndx=%04x del=%d use=%d score=%" PRIu32 "\n", |
| cur_block, deleted_pages_in_block, |
| used_pages_in_block, score); |
| |
| while (candndx < max_candidates) |
| { |
| if (cand_blocks[candndx] == (int16_t) - 1) |
| { |
| cand_blocks[candndx] = cur_block; |
| cand_scores[candndx] = score; |
| break; |
| } |
| else if (cand_scores[candndx] < score) |
| { |
| int reorder_candndx = max_candidates - 2; |
| while (reorder_candndx >= candndx) |
| { |
| cand_blocks[reorder_candndx + 1] = |
| cand_blocks[reorder_candndx]; |
| cand_scores[reorder_candndx + 1] = |
| cand_scores[reorder_candndx]; |
| reorder_candndx--; |
| } |
| |
| cand_blocks[candndx] = cur_block; |
| cand_scores[candndx] = score; |
| break; |
| } |
| |
| candndx++; |
| } |
| |
| (*candidate_count)++; |
| } |
| |
| cur_entry = 0; |
| cur_block++; |
| cur_block_addr += SPIFFS_GEO_BLOCK_SIZE(fs); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: spiffs_gc_clean |
| * |
| * Description: |
| * 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 spndx 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. |
| * |
| * Input Parameters: |
| * fs the file system struct |
| * len amount of data that should be freed |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success; A negated errno value is returned on |
| * any failure. -ENODATA is returned if no blocks were deleted. |
| * |
| ****************************************************************************/ |
| |
| static int spiffs_gc_clean(FAR struct spiffs_s *fs, int16_t blkndx) |
| { |
| const int entries_per_page = (SPIFFS_GEO_PAGE_SIZE(fs) / sizeof(int16_t)); |
| int ret = OK; |
| |
| /* This is the global localizer being pushed and popped */ |
| |
| struct spiffs_gc_s gc; /* Our stack frame/state */ |
| FAR int16_t *objlu_buf; |
| FAR struct spiffs_pgobj_ndxheader_s *objhdr; |
| FAR struct spiffs_page_objndx_s *objndx; |
| int16_t cur_pgndx = 0; |
| int cur_entry = 0; |
| |
| spiffs_gcinfo("Cleaning block %04x\n", blkndx); |
| |
| memset(&gc, 0, sizeof(struct spiffs_gc_s)); |
| gc.state = FIND_OBJ_DATA; |
| |
| objlu_buf = (FAR int16_t *)fs->lu_work; |
| objhdr = (FAR struct spiffs_pgobj_ndxheader_s *)fs->work; |
| objndx = (struct spiffs_page_objndx_s *)fs->work; |
| |
| if (fs->free_blkndx == blkndx) |
| { |
| /* Move free cursor to next block, cannot use free pages from the block |
| * we want to clean |
| */ |
| |
| fs->free_blkndx = (blkndx + 1) % SPIFFS_GEO_BLOCK_COUNT(fs); |
| fs->free_entry = 0; |
| spiffs_gcinfo("Move free cursor to block=%04x\n", fs->free_blkndx); |
| } |
| |
| while (ret >= 0 && gc.state != FINISHED) |
| { |
| int obj_lookup_page; |
| uint8_t scan; |
| |
| spiffs_gcinfo("state=%d entry=%d\n", gc.state, cur_entry); |
| |
| gc.objid_found = false; /* Reset (to no found data page) */ |
| |
| /* Scan through lookup pages */ |
| |
| obj_lookup_page = cur_entry / entries_per_page; |
| scan = 1; |
| |
| /* Check each object lookup page */ |
| |
| while (scan && ret >= 0 && |
| obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) |
| { |
| int entry_offset = obj_lookup_page * entries_per_page; |
| |
| ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, |
| 0, blkndx * SPIFFS_GEO_BLOCK_SIZE(fs) + |
| SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), |
| SPIFFS_GEO_PAGE_SIZE(fs), fs->lu_work); |
| |
| /* Check each object lookup entry */ |
| |
| while (scan && ret >= 0 && |
| cur_entry - entry_offset < entries_per_page && |
| cur_entry < |
| (int)(SPIFFS_GEO_PAGES_PER_BLOCK(fs) - |
| SPIFFS_OBJ_LOOKUP_PAGES(fs))) |
| { |
| int16_t id = objlu_buf[cur_entry - entry_offset]; |
| |
| cur_pgndx = |
| SPIFFS_OBJ_LOOKUP_ENTRY_TO_PGNDX(fs, blkndx, cur_entry); |
| |
| /* Act upon object id depending on gc state */ |
| |
| switch (gc.state) |
| { |
| case FIND_OBJ_DATA: |
| |
| /* Find a data page. */ |
| |
| if (id != SPIFFS_OBJID_DELETED && |
| id != SPIFFS_OBJID_FREE && |
| ((id & SPIFFS_OBJID_NDXFLAG) == 0)) |
| { |
| /* Found a data page, stop scanning and handle in |
| * switch case below |
| */ |
| |
| spiffs_gcinfo( |
| "Found data page, state=%d, objid=%04x\n", |
| gc.state, id); |
| |
| gc.objid_found = true; |
| gc.cur_objid = id; |
| gc.cur_data_pgndx = cur_pgndx; |
| scan = 0; |
| } |
| break; |
| |
| case MOVE_OBJ_DATA: |
| |
| /* Evacuate found data pages for corresponding object index |
| * we have in memory, update memory representation |
| */ |
| |
| if (id == gc.cur_objid) |
| { |
| struct spiffs_page_header_s phdr; |
| |
| ret = spiffs_cache_read(fs, |
| SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, |
| 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pgndx), |
| sizeof(struct spiffs_page_header_s), |
| (FAR uint8_t *)&phdr); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_cache_read() failed: %d\n", |
| ret); |
| return ret; |
| } |
| |
| spiffs_gcinfo("Found data page %04x:%04x @%04x\n", |
| gc.cur_objid, phdr.spndx, cur_pgndx); |
| |
| if (SPIFFS_OBJNDX_ENTRY_SPNDX(fs, phdr.spndx) != |
| gc.cur_objndx_spndx) |
| { |
| spiffs_gcinfo( |
| "No objndx spndx match, take in another run\n"); |
| } |
| else |
| { |
| int16_t new_data_pgndx; |
| if (phdr.flags & SPIFFS_PH_FLAG_DELET) |
| { |
| /* Move page */ |
| |
| ret = spiffs_page_move(fs, 0, 0, id, &phdr, |
| cur_pgndx, &new_data_pgndx); |
| |
| spiffs_gcinfo( |
| "Move objndx=%04x:%04x page=%04x to %04x\n", |
| gc.cur_objid, phdr.spndx, |
| cur_pgndx, new_data_pgndx); |
| |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_page_move(): %d\n", |
| ret); |
| return ret; |
| } |
| |
| /* Move wipes obj_lu, reload it */ |
| |
| ret = |
| spiffs_cache_read(fs, |
| SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, |
| 0, |
| blkndx * SPIFFS_GEO_BLOCK_SIZE(fs) + |
| SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), |
| SPIFFS_GEO_PAGE_SIZE(fs), |
| fs->lu_work); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_cache_read(): %d\n", |
| ret); |
| return ret; |
| } |
| } |
| 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_gcinfo( |
| "Wipe objndx=%04x:%04x page=%04x\n", |
| id, phdr.spndx, cur_pgndx); |
| |
| ret = spiffs_page_delete(fs, cur_pgndx); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_page_delete(): %d\n", |
| ret); |
| return ret; |
| } |
| |
| new_data_pgndx = SPIFFS_OBJID_FREE; |
| } |
| |
| /* Update memory representation of object index |
| * page with new data page |
| */ |
| |
| if (gc.cur_objndx_spndx == 0) |
| { |
| /* Update object index header page */ |
| |
| ((FAR int16_t *)((FAR uint8_t *)objhdr + |
| sizeof(struct spiffs_pgobj_ndxheader_s))) |
| [phdr.spndx] = new_data_pgndx; |
| |
| spiffs_gcinfo( |
| "Wrote page=%04x to objhdr entry=%04x\n", |
| new_data_pgndx, |
| (int)SPIFFS_OBJNDX_ENTRY(fs, phdr.spndx)); |
| } |
| else |
| { |
| /* Update object index page */ |
| |
| ((FAR int16_t *)((FAR uint8_t *)objndx + |
| sizeof(struct spiffs_page_objndx_s))) |
| [SPIFFS_OBJNDX_ENTRY(fs, phdr.spndx)] = |
| new_data_pgndx; |
| |
| spiffs_gcinfo( |
| "Wrote page=%04x to objndx entry=%04x\n", |
| new_data_pgndx, |
| (int)SPIFFS_OBJNDX_ENTRY(fs, phdr.spndx)); |
| } |
| } |
| } |
| break; |
| |
| case MOVE_OBJ_NDX: |
| |
| /* Find and evacuate object index pages */ |
| |
| if (id != SPIFFS_OBJID_DELETED && |
| id != SPIFFS_OBJID_FREE && |
| (id & SPIFFS_OBJID_NDXFLAG) != 0) |
| { |
| /* Found an index object id */ |
| |
| struct spiffs_page_header_s phdr; |
| int16_t new_pgndx; |
| |
| /* Load header */ |
| |
| ret = spiffs_cache_read(fs, |
| SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, |
| 0, |
| SPIFFS_PAGE_TO_PADDR(fs, cur_pgndx), |
| sizeof(struct spiffs_page_header_s), |
| (FAR uint8_t *)&phdr); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_cache_read() failed: %d\n", |
| ret); |
| return ret; |
| } |
| |
| if (phdr.flags & SPIFFS_PH_FLAG_DELET) |
| { |
| /* Move page */ |
| |
| ret = spiffs_page_move(fs, 0, 0, id, &phdr, |
| cur_pgndx, &new_pgndx); |
| |
| spiffs_gcinfo( |
| "Move objndx=%04x:%04x page=%04x to %04x\n", |
| id, phdr.spndx, cur_pgndx, new_pgndx); |
| |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_page_move() failed: %d\n", |
| ret); |
| return ret; |
| } |
| |
| spiffs_fobj_event(fs, |
| (FAR struct spiffs_page_objndx_s *)&phdr, |
| SPIFFS_EV_NDXMOV, id, |
| phdr.spndx, new_pgndx, 0); |
| |
| /* Move wipes obj_lu, reload it */ |
| |
| ret = |
| spiffs_cache_read(fs, |
| SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, |
| 0, |
| blkndx * SPIFFS_GEO_BLOCK_SIZE(fs) + |
| SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), |
| SPIFFS_GEO_PAGE_SIZE(fs), fs->lu_work); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_cache_read() failed: %d\n", |
| ret); |
| return ret; |
| } |
| } |
| 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_gcinfo("Wipe objndx=%04x:%04x page=%04x\n", |
| id, phdr.spndx, cur_pgndx); |
| |
| ret = spiffs_page_delete(fs, cur_pgndx); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_fobj_event() failed: %d\n", |
| ret); |
| return ret; |
| } |
| |
| spiffs_fobj_event(fs, NULL, |
| SPIFFS_EV_NDXDEL, id, |
| phdr.spndx, cur_pgndx, 0); |
| } |
| } |
| break; |
| |
| default: |
| scan = 0; |
| break; |
| } |
| |
| cur_entry++; |
| } |
| |
| obj_lookup_page++; /* No need to check scan variable here, |
| * obj_lookup_page is set in start of loop |
| */ |
| } |
| |
| if (ret < 0) |
| { |
| break; |
| } |
| |
| /* State finalization and switch */ |
| |
| switch (gc.state) |
| { |
| case FIND_OBJ_DATA: |
| if (gc.objid_found) |
| { |
| struct spiffs_page_header_s phdr; |
| int16_t objndx_pgndx; |
| |
| /* Handle found data page. Find out corresponding objndx page |
| * and load it to memory |
| */ |
| |
| gc.stored_scan_entry_index = cur_entry; /* Push cursor */ |
| cur_entry = 0; /* Sestart scan from start */ |
| gc.state = MOVE_OBJ_DATA; |
| |
| ret = spiffs_cache_read(fs, |
| SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, |
| 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pgndx), |
| sizeof(struct spiffs_page_header_s), |
| (FAR uint8_t *)&phdr); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_fobj_event() failed: %d\n", ret); |
| return ret; |
| } |
| |
| gc.cur_objndx_spndx = |
| SPIFFS_OBJNDX_ENTRY_SPNDX(fs, phdr.spndx); |
| |
| spiffs_gcinfo("Find objndx spndx=%04x\n", gc.cur_objndx_spndx); |
| |
| ret = spiffs_objlu_find_id_and_span(fs, |
| gc.cur_objid | SPIFFS_OBJID_NDXFLAG, |
| gc.cur_objndx_spndx, 0, |
| &objndx_pgndx); |
| if (ret == -ENOENT) |
| { |
| /* 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_gcinfo("objndx not found! Wipe page %04x\n", |
| gc.cur_data_pgndx); |
| |
| ret = spiffs_page_delete(fs, gc.cur_data_pgndx); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_page_delete() failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* 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; |
| } |
| |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| spiffs_gcinfo("Found object index at page=%04x\n", |
| objndx_pgndx); |
| |
| ret = spiffs_cache_read(fs, |
| SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, |
| 0, |
| SPIFFS_PAGE_TO_PADDR(fs, objndx_pgndx), |
| SPIFFS_GEO_PAGE_SIZE(fs), fs->work); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_cache_read() failed: %d\n", ret); |
| return ret; |
| } |
| |
| /* Cannot allow a gc if the presumed index in fact is no |
| * index, a check must run or lot of data may be lost |
| */ |
| |
| ret = spiffs_validate_objndx(&objndx->phdr, |
| gc.cur_objid | SPIFFS_OBJID_NDXFLAG, |
| gc.cur_objndx_spndx); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_validate_objndx() failed: %d\n", ret); |
| return ret; |
| } |
| |
| gc.cur_objndx_pgndx = objndx_pgndx; |
| } |
| else |
| { |
| /* No more data pages found, passed through all block, start |
| * evacuating object indices |
| */ |
| |
| gc.state = MOVE_OBJ_NDX; |
| cur_entry = 0; /* Restart entry scan index */ |
| } |
| break; |
| |
| case MOVE_OBJ_DATA: |
| { |
| int16_t new_objndx_pgndx; |
| |
| /* Store modified objndx (hdr) page residing in memory now that |
| * all data pages belonging to this object index and residing in |
| * the block we want to evacuate |
| */ |
| |
| gc.state = FIND_OBJ_DATA; |
| cur_entry = gc.stored_scan_entry_index; /* pop cursor */ |
| |
| if (gc.cur_objndx_spndx == 0) |
| { |
| /* Store object index header page */ |
| |
| ret = spiffs_fobj_update_ndxhdr(fs, 0, |
| gc.cur_objid | SPIFFS_OBJID_NDXFLAG, |
| gc.cur_objndx_pgndx, fs->work, 0, |
| 0, &new_objndx_pgndx); |
| |
| spiffs_gcinfo("Store modified objhdr page=%04x:%04x\n", |
| new_objndx_pgndx, 0); |
| |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_fobj_update_ndxhdr() failed: %d\n", |
| ret); |
| return ret; |
| } |
| } |
| else |
| { |
| /* Store object index page */ |
| |
| ret = spiffs_page_move(fs, 0, fs->work, |
| gc.cur_objid | SPIFFS_OBJID_NDXFLAG, |
| 0, gc.cur_objndx_pgndx, |
| &new_objndx_pgndx); |
| |
| spiffs_gcinfo("Store modified objndx page=%04x:%04x\n", |
| new_objndx_pgndx, objndx->phdr.spndx); |
| |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_page_move() failed: %d\n", ret); |
| return ret; |
| } |
| |
| spiffs_fobj_event(fs, |
| (FAR struct spiffs_page_objndx_s *)fs->work, |
| SPIFFS_EV_NDXUPD, gc.cur_objid, |
| objndx->phdr.spndx, |
| new_objndx_pgndx, 0); |
| } |
| } |
| break; |
| |
| case MOVE_OBJ_NDX: |
| /* Scanned through all blocks, no more object indices found - our |
| * work here is done |
| */ |
| |
| gc.state = FINISHED; |
| break; |
| |
| default: |
| cur_entry = 0; |
| break; |
| } |
| |
| spiffs_gcinfo("state=%d\n", gc.state); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: spiffs_gc_quick |
| * |
| * Description: |
| * 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. |
| * |
| * Input Parameters: |
| * fs - A reference to the SPIFFS volume object instance |
| * max_free_pages - The maximum pages to free |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success; A negated errno value is returned on |
| * any failure. -ENODATA is returned if no blocks were deleted. |
| * |
| ****************************************************************************/ |
| |
| int spiffs_gc_quick(FAR struct spiffs_s *fs, uint16_t max_free_pages) |
| { |
| FAR int16_t *objlu_buf = (int16_t *) fs->lu_work; |
| uint32_t cur_block_addr = 0; |
| uint32_t blocks = SPIFFS_GEO_BLOCK_COUNT(fs); |
| int16_t cur_block = 0; |
| int entries_per_page = (SPIFFS_GEO_PAGE_SIZE(fs) / sizeof(int16_t)); |
| int cur_entry = 0; |
| int ret = OK; |
| |
| spiffs_gcinfo("max_free_pages=%u\n", max_free_pages); |
| #ifdef CONFIG_SPIFFS_GCDBG |
| fs->stats_gc_runs++; |
| #endif |
| |
| /* Find fully deleted blocks */ |
| |
| while (ret >= 0 && blocks--) |
| { |
| uint16_t deleted_pages_in_block = 0; |
| uint16_t free_pages_in_block = 0; |
| int obj_lookup_page = 0; |
| |
| /* Check each object lookup page */ |
| |
| while (ret >= 0 && |
| obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) |
| { |
| int entry_offset = obj_lookup_page * entries_per_page; |
| |
| ret = spiffs_cache_read(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, |
| 0, cur_block_addr + |
| SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), |
| SPIFFS_GEO_PAGE_SIZE(fs), fs->lu_work); |
| |
| /* Check each entry */ |
| |
| while (ret >= 0 && |
| cur_entry - entry_offset < entries_per_page && |
| cur_entry < |
| (int)(SPIFFS_GEO_PAGES_PER_BLOCK(fs) - |
| SPIFFS_OBJ_LOOKUP_PAGES(fs))) |
| { |
| int16_t id = objlu_buf[cur_entry - entry_offset]; |
| |
| if (id == SPIFFS_OBJID_DELETED) |
| { |
| deleted_pages_in_block++; |
| } |
| else if (id == SPIFFS_OBJID_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); |
| ret = 1; /* Kill object lu loop */ |
| break; |
| } |
| } |
| else |
| { |
| /* Kill scan, go for next block */ |
| |
| obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs); |
| ret = 1; /* Kill object lu loop */ |
| break; |
| } |
| |
| cur_entry++; |
| } |
| |
| obj_lookup_page++; |
| } |
| |
| if (ret == 1) |
| { |
| ret = OK; |
| } |
| |
| if (ret >= 0 && |
| deleted_pages_in_block + free_pages_in_block == |
| SPIFFS_GEO_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs) && |
| free_pages_in_block <= max_free_pages) |
| { |
| /* Found a fully deleted block */ |
| |
| fs->deleted_pages -= deleted_pages_in_block; |
| ret = spiffs_gc_erase_block(fs, cur_block); |
| return ret; |
| } |
| |
| cur_entry = 0; |
| cur_block++; |
| cur_block_addr += SPIFFS_GEO_BLOCK_SIZE(fs); |
| } |
| |
| if (ret >= 0) |
| { |
| /* No deleted blocks */ |
| |
| ret = -ENODATA; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: spiffs_gc_check |
| * |
| * Description: |
| * Checks if garbage collecting is necessary. If so a candidate block is |
| * found, cleansed and erased |
| * |
| * This function will try to make room for given amount of bytes in the |
| * filesystem by moving pages and erasing blocks. |
| * |
| * If it is physically impossible, err_no will be set to -ENOSPC. If |
| * there already is this amount (or more) of free space, SPIFFS_gc will |
| * silently return. It is recommended to call statfs() before invoking |
| * this method in order to determine what amount of bytes to give. |
| * |
| * NB: the garbage collector is automatically called when spiffs needs free |
| * pages. The reason for this function is to give possibility to do |
| * background tidying when user knows the system is idle. |
| * |
| * Input Parameters: |
| * fs the file system struct |
| * len amount of data that should be freed |
| * |
| * Returned Value: |
| * Zero (OK) is returned on success; A negated errno value is returned on |
| * any failure. -ENODATA is returned if no blocks were deleted. |
| * |
| ****************************************************************************/ |
| |
| int spiffs_gc_check(FAR struct spiffs_s *fs, off_t len) |
| { |
| int32_t free_pages; |
| uint32_t needed_pages; |
| int tries = 0; |
| int ret; |
| |
| /* Get the number of free pages */ |
| |
| free_pages = (SPIFFS_GEO_PAGES_PER_BLOCK(fs) - |
| SPIFFS_OBJ_LOOKUP_PAGES(fs)) * |
| (SPIFFS_GEO_BLOCK_COUNT(fs) - 2) - |
| fs->alloc_pages - fs->deleted_pages; |
| |
| spiffs_gcinfo("len=%ld free_blocks=%lu free_pages=%ld\n", |
| (long)len, (unsigned long)fs->free_blocks, |
| (long)free_pages); |
| |
| if (fs->free_blocks > 3 && |
| (int32_t)len < free_pages * (int32_t)SPIFFS_DATA_PAGE_SIZE(fs)) |
| { |
| spiffs_gcinfo("Sufficient free space is available Do nothing.\n"); |
| return OK; |
| } |
| |
| /* Get the number of pages needed */ |
| |
| needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / |
| SPIFFS_DATA_PAGE_SIZE(fs); |
| |
| #if 0 |
| if (fs->free_blocks <= 2 && (int32_t)needed_pages > free_pages) |
| { |
| spiffs_gcinfo("Full freeblk=%d needed=%d free=%d dele=%d\n", |
| fs->free_blocks, needed_pages, free_pages, |
| fs->deleted_pages); |
| return -ENOSPC; |
| } |
| #endif |
| |
| if ((int32_t)needed_pages > (int32_t)(free_pages + fs->deleted_pages)) |
| { |
| spiffs_gcinfo("Full freeblk=%" PRIu32 " needed=%" PRIu32 " " |
| "free=%" PRIu32 " dele=%" PRIu32 "\n", |
| fs->free_blocks, needed_pages, free_pages, |
| fs->deleted_pages); |
| return -ENOSPC; |
| } |
| |
| do |
| { |
| FAR int16_t *cands; |
| int count; |
| int16_t cand; |
| int32_t prev_free_pages = free_pages; |
| |
| spiffs_gcinfo("#%d: run gc free_blocks=%" PRIu32 " pfree=" |
| "%" PRIu32 " pallo=%" PRIu32 " pdele=%" PRIu32 "" |
| " [%" PRIu32 "] len=%" PRIuOFF " of %" PRIu32 "\n", |
| tries, fs->free_blocks, free_pages, |
| fs->alloc_pages, fs->deleted_pages, |
| (free_pages + fs->alloc_pages + fs->deleted_pages), |
| len, (uint32_t)(free_pages * SPIFFS_DATA_PAGE_SIZE(fs))); |
| |
| /* If the fs is crammed, ignore block age when selecting candidate - |
| * kind of a bad state |
| */ |
| |
| ret = spiffs_gc_find_candidate(fs, &cands, &count, free_pages <= 0); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_gc_find_candidate() failed: %d\n", ret); |
| return ret; |
| } |
| |
| if (count == 0) |
| { |
| spiffs_gcinfo("No candidates, return\n"); |
| return (int32_t) needed_pages < free_pages ? OK : -ENOSPC; |
| } |
| |
| #ifdef CONFIG_SPIFFS_GCDBG |
| fs->stats_gc_runs++; |
| #endif |
| cand = cands[0]; |
| |
| ret = spiffs_gc_clean(fs, cand); |
| |
| spiffs_gcinfo("Cleaning block %d, result=%d\n", cand, ret); |
| |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_gc_clean() failed: %d\n", ret); |
| return ret; |
| } |
| |
| ret = spiffs_gc_epage_stats(fs, cand); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_gc_epage_stats() failed: %d\n", ret); |
| return ret; |
| } |
| |
| ret = spiffs_gc_erase_block(fs, cand); |
| if (ret < 0) |
| { |
| ferr("ERROR: spiffs_gc_erase_block() failed: %d\n", ret); |
| return ret; |
| } |
| |
| free_pages = (SPIFFS_GEO_PAGES_PER_BLOCK(fs) - |
| SPIFFS_OBJ_LOOKUP_PAGES(fs)) * |
| (SPIFFS_GEO_BLOCK_COUNT(fs) - 2) - |
| fs->alloc_pages - fs->deleted_pages; |
| |
| if (prev_free_pages <= 0 && prev_free_pages == free_pages) |
| { |
| /* Abort early to reduce wear, at least tried once */ |
| |
| spiffs_gcinfo("Early abort, no result on gc when fs crammed\n"); |
| break; |
| } |
| } |
| while (++tries < CONFIG_SPIFFS_GC_MAXRUNS && |
| (fs->free_blocks <= 2 || |
| (int32_t) len > free_pages * (int32_t) SPIFFS_DATA_PAGE_SIZE(fs))); |
| |
| /* Re-calculate the number of free pages */ |
| |
| free_pages = (SPIFFS_GEO_PAGES_PER_BLOCK(fs) - |
| SPIFFS_OBJ_LOOKUP_PAGES(fs)) * |
| (SPIFFS_GEO_BLOCK_COUNT(fs) - 2) - |
| fs->alloc_pages - fs->deleted_pages; |
| |
| if ((int32_t) len > free_pages * (int32_t)SPIFFS_DATA_PAGE_SIZE(fs)) |
| { |
| ret = -ENOSPC; |
| } |
| |
| spiffs_gcinfo("Finished, %" PRIu32 " dirty, blocks, %" PRIu32 " " |
| "free, %" PRIu32 " pages free, %d tries, ret=%d\n", |
| fs->alloc_pages + fs->deleted_pages, |
| fs->free_blocks, free_pages, tries, ret); |
| |
| return ret; |
| } |