6a396eb224
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com> Change-Id: Id46daa2ee690a73d3187b479f0e7ab0e2e361764
1277 lines
43 KiB
C
1277 lines
43 KiB
C
/****************************************************************************
|
|
* 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;
|
|
}
|