/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include #include #include "nffs/nffs.h" #include "nffs_priv.h" TAILQ_HEAD(nffs_cache_inode_list, nffs_cache_inode); static struct nffs_cache_inode_list nffs_cache_inode_list = TAILQ_HEAD_INITIALIZER(nffs_cache_inode_list); static void nffs_cache_reclaim_blocks(void); static struct nffs_cache_block * nffs_cache_block_alloc(void) { struct nffs_cache_block *entry; entry = os_memblock_get(&nffs_cache_block_pool); if (entry != NULL) { memset(entry, 0, sizeof *entry); } return entry; } static void nffs_cache_block_free(struct nffs_cache_block *entry) { if (entry != NULL) { os_memblock_put(&nffs_cache_block_pool, entry); } } static struct nffs_cache_block * nffs_cache_block_acquire(void) { struct nffs_cache_block *cache_block; cache_block = nffs_cache_block_alloc(); if (cache_block == NULL) { nffs_cache_reclaim_blocks(); cache_block = nffs_cache_block_alloc(); } assert(cache_block != NULL); return cache_block; } static int nffs_cache_block_populate(struct nffs_cache_block *cache_block, struct nffs_hash_entry *block_entry, uint32_t end_offset) { int rc; rc = nffs_block_from_hash_entry(&cache_block->ncb_block, block_entry); if (rc != 0) { return rc; } cache_block->ncb_file_offset = end_offset - cache_block->ncb_block.nb_data_len; return 0; } static struct nffs_cache_inode * nffs_cache_inode_alloc(void) { struct nffs_cache_inode *entry; entry = os_memblock_get(&nffs_cache_inode_pool); if (entry != NULL) { memset(entry, 0, sizeof *entry); TAILQ_INIT(&entry->nci_block_list); } return entry; } static void nffs_cache_inode_free_blocks(struct nffs_cache_inode *cache_inode) { struct nffs_cache_block *cache_block; while ((cache_block = TAILQ_FIRST(&cache_inode->nci_block_list)) != NULL) { TAILQ_REMOVE(&cache_inode->nci_block_list, cache_block, ncb_link); nffs_cache_block_free(cache_block); } } static void nffs_cache_inode_free(struct nffs_cache_inode *entry) { if (entry != NULL) { nffs_cache_inode_free_blocks(entry); os_memblock_put(&nffs_cache_inode_pool, entry); } } static struct nffs_cache_inode * nffs_cache_inode_acquire(void) { struct nffs_cache_inode *entry; entry = nffs_cache_inode_alloc(); if (entry == NULL) { entry = TAILQ_LAST(&nffs_cache_inode_list, nffs_cache_inode_list); assert(entry != NULL); TAILQ_REMOVE(&nffs_cache_inode_list, entry, nci_link); nffs_cache_inode_free(entry); entry = nffs_cache_inode_alloc(); } assert(entry != NULL); return entry; } static int nffs_cache_inode_populate(struct nffs_cache_inode *cache_inode, struct nffs_inode_entry *inode_entry) { int rc; memset(cache_inode, 0, sizeof *cache_inode); rc = nffs_inode_from_entry(&cache_inode->nci_inode, inode_entry); if (rc != 0) { return rc; } rc = nffs_inode_calc_data_length(cache_inode->nci_inode.ni_inode_entry, &cache_inode->nci_file_size); if (rc != 0) { return rc; } return 0; } /** * Retrieves the block entry corresponding to the last cached block in the * specified inode's list. If the inode has no cached blocks, this function * returns null. */ static struct nffs_hash_entry * nffs_cache_inode_last_entry(struct nffs_cache_inode *cache_inode) { struct nffs_cache_block *cache_block; if (TAILQ_EMPTY(&cache_inode->nci_block_list)) { return NULL; } cache_block = TAILQ_LAST(&cache_inode->nci_block_list, nffs_cache_block_list); return cache_block->ncb_block.nb_hash_entry; } static struct nffs_cache_inode * nffs_cache_inode_find(const struct nffs_inode_entry *inode_entry) { struct nffs_cache_inode *cur; TAILQ_FOREACH(cur, &nffs_cache_inode_list, nci_link) { if (cur->nci_inode.ni_inode_entry == inode_entry) { return cur; } } return NULL; } void nffs_cache_inode_range(const struct nffs_cache_inode *cache_inode, uint32_t *out_start, uint32_t *out_end) { struct nffs_cache_block *cache_block; cache_block = TAILQ_FIRST(&cache_inode->nci_block_list); if (cache_block == NULL) { *out_start = 0; *out_end = 0; return; } *out_start = cache_block->ncb_file_offset; cache_block = TAILQ_LAST(&cache_inode->nci_block_list, nffs_cache_block_list); *out_end = cache_block->ncb_file_offset + cache_block->ncb_block.nb_data_len; } static void nffs_cache_reclaim_blocks(void) { struct nffs_cache_inode *cache_inode; TAILQ_FOREACH_REVERSE(cache_inode, &nffs_cache_inode_list, nffs_cache_inode_list, nci_link) { if (!TAILQ_EMPTY(&cache_inode->nci_block_list)) { nffs_cache_inode_free_blocks(cache_inode); return; } } assert(0); } void nffs_cache_inode_delete(const struct nffs_inode_entry *inode_entry) { struct nffs_cache_inode *entry; entry = nffs_cache_inode_find(inode_entry); if (entry == NULL) { return; } TAILQ_REMOVE(&nffs_cache_inode_list, entry, nci_link); nffs_cache_inode_free(entry); } int nffs_cache_inode_ensure(struct nffs_cache_inode **out_cache_inode, struct nffs_inode_entry *inode_entry) { struct nffs_cache_inode *cache_inode; int rc; cache_inode = nffs_cache_inode_find(inode_entry); if (cache_inode != NULL) { rc = 0; goto done; } cache_inode = nffs_cache_inode_acquire(); rc = nffs_cache_inode_populate(cache_inode, inode_entry); if (rc != 0) { goto done; } TAILQ_INSERT_HEAD(&nffs_cache_inode_list, cache_inode, nci_link); rc = 0; done: if (rc == 0) { *out_cache_inode = cache_inode; } else { nffs_cache_inode_free(cache_inode); *out_cache_inode = NULL; } return rc; } /** * Recaches all cached inodes. All cached blocks are deleted from the cache * during this operation. This function should be called after garbage * collection occurs to ensure the cache is consistent. * * @return 0 on success; nonzero on failure. */ int nffs_cache_inode_refresh(void) { struct nffs_cache_inode *cache_inode; struct nffs_inode_entry *inode_entry; int rc; TAILQ_FOREACH(cache_inode, &nffs_cache_inode_list, nci_link) { /* Clear entire block list. */ nffs_cache_inode_free_blocks(cache_inode); inode_entry = cache_inode->nci_inode.ni_inode_entry; rc = nffs_inode_from_entry(&cache_inode->nci_inode, inode_entry); if (rc != 0) { return rc; } /* File size remains valid. */ } return 0; } static void nffs_cache_log_block(struct nffs_cache_inode *cache_inode, struct nffs_cache_block *cache_block) { NFFS_LOG(DEBUG, "id=%u inode=%u flash_off=0x%08x " "file_off=%u len=%d (entry=%p)\n", cache_block->ncb_block.nb_hash_entry->nhe_id, cache_inode->nci_inode.ni_inode_entry->nie_hash_entry.nhe_id, cache_block->ncb_block.nb_hash_entry->nhe_flash_loc, cache_block->ncb_file_offset, cache_block->ncb_block.nb_data_len, cache_block->ncb_block.nb_hash_entry); } static void nffs_cache_log_insert_block(struct nffs_cache_inode *cache_inode, struct nffs_cache_block *cache_block, int tail) { NFFS_LOG(DEBUG, "caching block (%s): ", tail ? "tail" : "head"); nffs_cache_log_block(cache_inode, cache_block); } void nffs_cache_insert_block(struct nffs_cache_inode *cache_inode, struct nffs_cache_block *cache_block, int tail) { if (tail) { TAILQ_INSERT_TAIL(&cache_inode->nci_block_list, cache_block, ncb_link); } else { TAILQ_INSERT_HEAD(&cache_inode->nci_block_list, cache_block, ncb_link); } nffs_cache_log_insert_block(cache_inode, cache_block, tail); } /** * Finds the data block containing the specified offset within a file inode. * If the block is not yet cached, it gets cached as a result of this * operation. This function modifies the inode's cached block list according * to the following procedure: * * 1. If none of the owning inode's blocks are currently cached, allocate a * cached block entry and insert it into the inode's list. * 2. Else if the requested file offset is less than that of the first cached * block, bridge the gap between the inode's sequence of cached blocks and * the block that now needs to be cached. This is accomplished by caching * each block in the gap, finishing with the requested block. * 3. Else (the requested offset is beyond the end of the cache), * a. If the requested offset belongs to the block that immediately * follows the end of the cache, cache the block and append it to the * list. * b. Else, clear the cache, and populate it with the single entry * corresponding to the requested block. * * @param cache_inode The cached file inode to seek within. * @param seek_offset The file offset to seek to. * @param out_cache_block On success, the requested cached block gets * written here; pass null if you don't need * this. * * @return 0 on success; nonzero on failure. */ int nffs_cache_seek(struct nffs_cache_inode *cache_inode, uint32_t seek_offset, struct nffs_cache_block **out_cache_block) { struct nffs_cache_block *cache_block; struct nffs_hash_entry *last_cached_entry; struct nffs_hash_entry *block_entry; struct nffs_hash_entry *pred_entry; struct nffs_block block; uint32_t cache_start; uint32_t cache_end; uint32_t block_start; uint32_t block_end; int rc; /* Empty files have no blocks that can be cached. */ if (cache_inode->nci_file_size == 0) { return FS_ENOENT; } nffs_cache_inode_range(cache_inode, &cache_start, &cache_end); if (cache_end != 0 && seek_offset < cache_start) { /* Seeking prior to cache. Iterate backwards from cache start. */ cache_block = TAILQ_FIRST(&cache_inode->nci_block_list); block_entry = cache_block->ncb_block.nb_prev; block_end = cache_block->ncb_file_offset; cache_block = NULL; } else if (seek_offset < cache_end) { /* Seeking within cache. Iterate backwards from cache end. */ cache_block = TAILQ_LAST(&cache_inode->nci_block_list, nffs_cache_block_list); block_entry = cache_block->ncb_block.nb_hash_entry; block_end = cache_end; } else { /* Seeking beyond end of cache. Iterate backwards from file end. If * sought-after block is adjacent to cache end, its cache entry will * get appended to the current cache. Otherwise, the current cache * will be freed and replaced with the single requested block. */ cache_block = NULL; block_entry = cache_inode->nci_inode.ni_inode_entry->nie_last_block_entry; block_end = cache_inode->nci_file_size; } /* Scan backwards until we find the block containing the seek offest. */ while (1) { if (block_end <= cache_start) { /* We are looking before the start of the cache. Allocate a new * cache block and prepend it to the cache. */ assert(cache_block == NULL); cache_block = nffs_cache_block_acquire(); rc = nffs_cache_block_populate(cache_block, block_entry, block_end); if (rc != 0) { return rc; } nffs_cache_insert_block(cache_inode, cache_block, 0); } /* Calculate the file offset of the start of this block. This is used * to determine if this block contains the sought-after offset. */ if (cache_block != NULL) { /* Current block is cached. */ block_start = cache_block->ncb_file_offset; pred_entry = cache_block->ncb_block.nb_prev; } else { /* We are looking beyond the end of the cache. Read the data block * from flash. */ rc = nffs_block_from_hash_entry(&block, block_entry); if (rc != 0) { return rc; } block_start = block_end - block.nb_data_len; pred_entry = block.nb_prev; } if (block_start <= seek_offset) { /* This block contains the requested address; iteration is * complete. */ if (cache_block == NULL) { /* The block isn't cached, so it must come after the cache end. * Append it to the cache if it directly follows. Otherwise, * erase the current cache and populate it with this single * block. */ cache_block = nffs_cache_block_acquire(); cache_block->ncb_block = block; cache_block->ncb_file_offset = block_start; last_cached_entry = nffs_cache_inode_last_entry(cache_inode); if (last_cached_entry != NULL && last_cached_entry == pred_entry) { nffs_cache_insert_block(cache_inode, cache_block, 1); } else { nffs_cache_inode_free_blocks(cache_inode); nffs_cache_insert_block(cache_inode, cache_block, 0); } } if (out_cache_block != NULL) { *out_cache_block = cache_block; } break; } /* Prepare for next iteration. */ if (cache_block != NULL) { cache_block = TAILQ_PREV(cache_block, nffs_cache_block_list, ncb_link); } block_entry = pred_entry; block_end = block_start; } return 0; } /** * Frees all cached inodes and blocks. */ void nffs_cache_clear(void) { struct nffs_cache_inode *entry; while ((entry = TAILQ_FIRST(&nffs_cache_inode_list)) != NULL) { TAILQ_REMOVE(&nffs_cache_inode_list, entry, nci_link); nffs_cache_inode_free(entry); } }