/**************************************************************************** * fs/spiffs/src/spiffs_cache.c * * Copyright (C) 2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * 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 #include #include #include #include "spiffs.h" #include "spiffs_mtd.h" #include "spiffs_core.h" #include "spiffs_cache.h" /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: spiffs_cache_page_get * * Description: * Returns cached page for give page index, or null if no such cached page * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * pgndx - Page index to get * * Returned Value: * Reference to struct spiffs_cache_page_s instance. * ****************************************************************************/ FAR static FAR struct spiffs_cache_page_s * spiffs_cache_page_get(FAR struct spiffs_s *fs, int16_t pgndx) { FAR struct spiffs_cache_s *cache; FAR struct spiffs_cache_page_s *cp; int i; cache = spiffs_get_cache(fs); if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) { return 0; } for (i = 0; i < cache->cpage_count; i++) { cp = spiffs_get_cache_page_hdr(fs, cache, i); if ((cache->cpage_use_map & (1 << i)) != 0 && (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && cp->pgndx == pgndx) { cp->last_access = cache->last_access; return cp; } } return NULL; } /**************************************************************************** * Name: spiffs_cache_page_free * * Description: * Frees cached page * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * cpndx - Cache page index * write_back - True: Write dirty pages back to hardware * * Returned Value: * Zero (OK) is returned on success; A negated errno value is returned on * any failure. * ****************************************************************************/ static int spiffs_cache_page_free(FAR struct spiffs_s *fs, int cpndx, bool write_back) { FAR struct spiffs_cache_s *cache; FAR struct spiffs_cache_page_s *cp; int ret = OK; cache = spiffs_get_cache(fs); cp = spiffs_get_cache_page_hdr(fs, cache, cpndx); if (cache->cpage_use_map & (1 << cpndx)) { if (write_back && (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && (cp->flags & SPIFFS_CACHE_FLAG_DIRTY) != 0) { FAR uint8_t *mem; mem = spiffs_get_cache_page(fs, cache, cpndx); spiffs_cacheinfo("Write cache page=%d pgndx %04x\n", cpndx, cp->pgndx); ret = spiffs_mtd_write(fs, SPIFFS_PAGE_TO_PADDR(fs, cp->pgndx), SPIFFS_GEO_PAGE_SIZE(fs), mem); } if (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) { spiffs_cacheinfo("Free cache page=%d objid=%04x\n", cpndx, cp->objid); } else { spiffs_cacheinfo("Free cache page %d pgndx %04x\n", cpndx, cp->pgndx); } cache->cpage_use_map &= ~(1 << cpndx); cp->flags = 0; } return ret; } /**************************************************************************** * Name: spiffs_cache_page_remove_oldest * * Description: * Removes the oldest accessed cached page * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * cpndx - Cache page index * write_back - True: Write dirty pages back to hardware * * Returned Value: * Zero (OK) is returned on success; A negated errno value is returned on * any failure. * ****************************************************************************/ static int spiffs_cache_page_remove_oldest(FAR struct spiffs_s *fs, uint8_t flag_mask, uint8_t flags) { FAR struct spiffs_cache_s *cache = spiffs_get_cache(fs); uint32_t oldest_val = 0; int cpndx = -1; int ret = OK; int i; /* Don't remove any cache pages unless there are no free cache pages */ if ((cache->cpage_use_map & cache->cpage_use_mask) != cache->cpage_use_mask) { /* At least one free cpage */ return OK; } /* All busy, scan through all to find the cache page which has oldest * access time. */ for (i = 0; i < cache->cpage_count; i++) { FAR struct spiffs_cache_page_s *cp; cp = spiffs_get_cache_page_hdr(fs, cache, i); if ((cache->last_access - cp->last_access) > oldest_val && (cp->flags & flag_mask) == flags) { oldest_val = cache->last_access - cp->last_access; cpndx = i; } } /* Then free that cache page */ if (cpndx >= 0) { ret = spiffs_cache_page_free(fs, cpndx, true); } return ret; } /**************************************************************************** * Name: spiffs_cache_page_allocate * * Description: * Allocates a new cached page and returns it, or null if all cache pages * are busy. * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * cpndx - Cache page index * write_back - True: Write dirty pages back to hardware * * Returned Value: * A reference to the allocated cache page. NULL is returned if we were * unable to allocated a cache page. * ****************************************************************************/ static FAR struct spiffs_cache_page_s * spiffs_cache_page_allocate(FAR struct spiffs_s *fs) { FAR struct spiffs_cache_s *cache; int i; /* Check if any cache pages are available */ cache = spiffs_get_cache(fs); if (cache->cpage_use_map == 0xffffffff) { /* No.. Out of cache memory */ return NULL; } /* Search for a free cache page */ for (i = 0; i < cache->cpage_count; i++) { if ((cache->cpage_use_map & (1 << i)) == 0) { FAR struct spiffs_cache_page_s *cp; /* We found one */ cp = spiffs_get_cache_page_hdr(fs, cache, i); cache->cpage_use_map |= (1 << i); cp->last_access = cache->last_access; return cp; } } /* Out of cache entries */ return NULL; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: spiffs_cache_initialize * * Description: * Initializes the cache * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * cp - The cache page to be released * * Returned Value: * None * ****************************************************************************/ void spiffs_cache_initialize(FAR struct spiffs_s *fs) { FAR struct spiffs_cache_s *cp; struct spiffs_cache_s cache; uint32_t cache_mask = 0; uint32_t sz; int i; int cache_entries; sz = fs->cache_size; cache_entries = (sz - sizeof(struct spiffs_cache_s)) / (SPIFFS_CACHE_PAGE_SIZE(fs)); if (fs->cache == 0) { return; } if (cache_entries <= 0) { return; } for (i = 0; i < cache_entries; i++) { cache_mask <<= 1; cache_mask |= 1; } memset(&cache, 0, sizeof(struct spiffs_cache_s)); cache.cpage_count = cache_entries; cache.cpages = (FAR uint8_t *) ((FAR uint8_t *)fs->cache + sizeof(struct spiffs_cache_s)); cache.cpage_use_map = 0xffffffff; cache.cpage_use_mask = cache_mask; memcpy(fs->cache, &cache, sizeof(struct spiffs_cache_s)); cp = spiffs_get_cache(fs); memset(cp->cpages, 0, cp->cpage_count * SPIFFS_CACHE_PAGE_SIZE(fs)); cp->cpage_use_map &= ~(cp->cpage_use_mask); for (i = 0; i < cache.cpage_count; i++) { spiffs_get_cache_page_hdr(fs, cp, i)->cpndx = i; } } /**************************************************************************** * Name: spiffs_cache_drop_page * * Description: * Drops the cache page for give page index * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * pgndx - Page index to be dropped * * Returned Value: * None * ****************************************************************************/ void spiffs_cache_drop_page(FAR struct spiffs_s *fs, int16_t pgndx) { FAR struct spiffs_cache_page_s *cp = spiffs_cache_page_get(fs, pgndx); if (cp) { spiffs_cache_page_free(fs, cp->cpndx, false); } } /**************************************************************************** * Name: spiffs_cache_read * * Description: * Reads from SPI FLASH or the cache * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * op - Read options * objid - File object ID to read * addr - Address to read from * len - The number of bytes to be read * dest - The location in which the read data is to be returned. * * Returned Value: * The number of bytes read (len) is returned on success; A negated * errno value is returned on any failure. * ****************************************************************************/ ssize_t spiffs_cache_read(FAR struct spiffs_s *fs, uint8_t op, int16_t objid, off_t addr, size_t len, FAR uint8_t *dest) { FAR struct spiffs_cache_s *cache; FAR struct spiffs_cache_page_s *cp; int ret = OK; spiffs_cacheinfo("op=%02x, objid=%04x addr=%ld len=%lu\n", op, objid, (long)addr, (unsigned long)len); cache = spiffs_get_cache(fs); cp = spiffs_cache_page_get(fs, SPIFFS_PADDR_TO_PAGE(fs, addr)); cache->last_access++; if (cp != NULL) { FAR uint8_t *mem; /* We've already got a cache page */ #ifdef CONFIG_SPIFFS_CACHEDBG fs->cache_hits++; #endif cp->last_access = cache->last_access; mem = spiffs_get_cache_page(fs, cache, cp->cpndx); memcpy(dest, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); } else { /* Check for second layer lookup */ if ((op & SPIFFS_OP_TYPE_MASK) == SPIFFS_OP_T_OBJ_LU2) { /* For second layer lookup functions, we do not cache in order to * prevent shredding */ ret = spiffs_mtd_read(fs, addr, len, dest); } else { #ifdef CONFIG_SPIFFS_CACHEDBG fs->cache_misses++; #endif /* This operation will always free one cache page (unless all * already free), the result code stems from the write operation * of the possibly freed cache page */ ret = spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); /* Allocate a new cache page */ cp = spiffs_cache_page_allocate(fs); if (cp != NULL) { FAR uint8_t *mem; cp->flags = SPIFFS_CACHE_FLAG_WRTHRU; cp->pgndx = SPIFFS_PADDR_TO_PAGE(fs, addr); spiffs_cacheinfo("Allocated cache page %d for pgndx %04x\n", cp->cpndx, cp->pgndx); ret = spiffs_mtd_read(fs, addr - SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr), SPIFFS_GEO_PAGE_SIZE(fs), spiffs_get_cache_page(fs, cache, cp->cpndx)); mem = spiffs_get_cache_page(fs, cache, cp->cpndx); memcpy(dest, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); } else { /* This will never happen, last resort for sake of symmetry */ ret = spiffs_mtd_read(fs, addr, len, dest); } } } if (ret < 0) { ferr("ERROR: spiffs_mtd_read: failed: %d\n", ret); return (ssize_t)ret; } return (ssize_t)len; } /**************************************************************************** * Name: spiffs_cache_write * * Description: * Writes to SPI FLASH and/or the cache * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * op - Write options * objid - File object ID to write * addr - Address to write to * len - The number of bytes to be write * dest - The location that provide the data to be written. * * Returned Value: * The number of bytes written (len) is returned on success; A negated * errno value is returned on any failure. * ****************************************************************************/ size_t spiffs_cache_write(FAR struct spiffs_s *fs, uint8_t op, int16_t objid, off_t addr, size_t len, FAR uint8_t *src) { FAR struct spiffs_cache_s *cache; FAR struct spiffs_cache_page_s *cp; int16_t pgndx; int ret = OK; spiffs_cacheinfo("op=%02x, objid=%04x addr=%ld len=%lu\n", op, objid, (long)addr, (unsigned long)len); pgndx = SPIFFS_PADDR_TO_PAGE(fs, addr); cache = spiffs_get_cache(fs); cp = spiffs_cache_page_get(fs, pgndx); /* Check for write-through */ if (cp != NULL && (op & SPIFFS_OP_COM_MASK) != SPIFFS_OP_C_WRTHRU) { /* Have a cache page. Copy in data to cache page */ if ((op & SPIFFS_OP_COM_MASK) == SPIFFS_OP_C_DELE && (op & SPIFFS_OP_TYPE_MASK) != SPIFFS_OP_T_OBJ_LU) { /* Page is being deleted, wipe from cache - unless it is a lookup * page */ spiffs_cache_page_free(fs, cp->cpndx, false); ret = spiffs_mtd_write(fs, addr, len, src); } else { uint8_t *mem = spiffs_get_cache_page(fs, cache, cp->cpndx); memcpy(&mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], src, len); cache->last_access++; cp->last_access = cache->last_access; if (cp->flags & SPIFFS_CACHE_FLAG_WRTHRU) { /* Page is being updated, no write-cache, just pass through */ ret = spiffs_mtd_write(fs, addr, len, src); } else { ret = OK; } } } else { /* No cache page, no write cache - just write through */ ret = spiffs_mtd_write(fs, addr, len, src); } if (ret < 0) { ferr("ERROR: spiffs_mtd_write: failed: %d\n", ret); return (ssize_t)ret; } return (ssize_t)len; } /**************************************************************************** * Name: spiffs_cache_write * * Description: * Returns the cache page that this file object refers to (or NULL if * there is no cache page * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * fobj - The file object instance * * Returned Value: * Returns the cache page that this file object refers to (or NULL if * there is no cache page * ****************************************************************************/ FAR struct spiffs_cache_page_s * spiffs_cache_page_get_byobjid(FAR struct spiffs_s *fs, FAR struct spiffs_file_s *fobj) { FAR struct spiffs_cache_s *cache = spiffs_get_cache(fs); int i; if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) { /* All cache pages free, no cache page can be assigned to the ID */ return NULL; } /* Look at each cache index */ for (i = 0; i < cache->cpage_count; i++) { FAR struct spiffs_cache_page_s *cp; /* Is this page available? Is is writable? Do the object IDs match? */ cp = spiffs_get_cache_page_hdr(fs, cache, i); if ((cache->cpage_use_map & (1 << i)) && (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) && cp->objid == fobj->objid) { /* Yes... return the cache page reference */ return cp; } } /* Not found */ return NULL; } /**************************************************************************** * Name: spiffs_cache_page_allocate_byobjid * * Description: * Allocates a new cache page and refers this to given object ID. It * flushes an old cache page if all cache pages are busy * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * fobj - The file object instance * * Returned Value: * Returns the allocated cache page(or NULL if the cache page could not * be allocated). * ****************************************************************************/ FAR struct spiffs_cache_page_s * spiffs_cache_page_allocate_byobjid(FAR struct spiffs_s *fs, FAR struct spiffs_file_s *fobj) { FAR struct spiffs_cache_page_s *cp; /* before this function is called, it is ensured that there is no already * existing cache page with same object ID */ spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); cp = spiffs_cache_page_allocate(fs); if (cp == NULL) { /* could not get cache page */ return NULL; } cp->flags = SPIFFS_CACHE_FLAG_TYPE_WR; cp->objid = fobj->objid; fobj->cache_page = cp; spiffs_cacheinfo("Allocated cache page %d for objid=%d\n", cp->cpndx, fobj->objid); return cp; } /**************************************************************************** * Name: spiffs_cache_page_release * * Description: * "Unrefers" all file objects that this cache page refers to and releases * the cache page * * Input Parameters: * fs - A reference to the SPIFFS volume object instance * cp - The cache page to be released * * Returned Value: * None * ****************************************************************************/ void spiffs_cache_page_release(FAR struct spiffs_s *fs, FAR struct spiffs_cache_page_s *cp) { FAR struct spiffs_file_s *fobj; /* Verify that a non-NULL cache page was provided */ if (cp != NULL) { /* Visit each file object */ for (fobj = (FAR struct spiffs_file_s *)dq_peek(&fs->objq); fobj != NULL; fobj = (FAR struct spiffs_file_s *)dq_next((FAR dq_entry_t *)fobj)) { /* "Unrefer" the cache page */ if (fobj->cache_page == cp) { fobj->cache_page = NULL; } } /* Free the cache page */ spiffs_cache_page_free(fs, cp->cpndx, false); cp->objid = 0; } }