/**************************************************************************** * fs/mnemofs/mnemofs_lru.c * LRU cache of mnemofs. * * 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. * * Alternatively, the contents of this file may be used under the terms of * the BSD-3-Clause license: * * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2024 Saurav Pal * * 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 of the author nor the names of its contributors may * be used to endorse or promote products derived from this software * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * ****************************************************************************/ /**************************************************************************** * LRU (Least Recently Used) cache takes in all the changes the user wants * to do to the on-flash storage, and stores them in memory. When a * significant amount of changes are accumulated, the LRU writes the new * information to the flash. * * LRU is a kernel list of nodes. Each node represents a CTZ list. Each node * contains a kernel list of changes requested for the CTZ list, called as * deltas. * * When LRU is full the last node is flushed (it can be explicitly flushed as * well) and all the changes are written at once on the flash, and the new * location is noted down in the journal, and an entry for the location * update is added to the LRU for the parent. ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include "mnemofs.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /**************************************************************************** * Private Types ****************************************************************************/ enum { MFS_LRU_UPD, MFS_LRU_DEL, }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static void lru_nodesearch(FAR struct mfs_sb_s * const sb, FAR const struct mfs_path_s * const path, const mfs_t depth, FAR struct mfs_node_s **node); static bool lru_islrufull(FAR struct mfs_sb_s * const sb); static bool lru_isnodefull(FAR struct mfs_node_s *node); static int lru_nodeflush(FAR struct mfs_sb_s * const sb, FAR struct mfs_path_s * const path, const mfs_t depth, const mfs_t ctz_sz, FAR struct mfs_node_s *node, bool clean_node); /**************************************************************************** * Private Data ****************************************************************************/ /**************************************************************************** * Public Data ****************************************************************************/ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: lru_nodesearch * * Description: * Searches a node by the `path` and `depth` in the LRU. * * Input Parameters: * sb - Superblock instance of the device. * path - CTZ representation of the relpath. * depth - Depth of `path`. * node - To populate with the node corresponding to the CTZ. * ****************************************************************************/ static void lru_nodesearch(FAR struct mfs_sb_s * const sb, FAR const struct mfs_path_s * const path, const mfs_t depth, FAR struct mfs_node_s **node) { bool found; mfs_t i; FAR struct mfs_node_s *n; *node = NULL; list_for_every_entry(&MFS_LRU(sb), n, struct mfs_node_s, list) { if (n == NULL) { break; } found = true; if (n->depth != depth) { continue; } if (depth != 0) { for (i = depth - 1; i + 1 > 0; i--) /* i + 1 prevents underflow. */ { if (n->path[i].off == path[i].off && n->path[i].ctz.idx_e == path[i].ctz.idx_e && n->path[i].ctz.pg_e == path[i].ctz.pg_e) { /* OK */ } else { found = false; break; } } } if (found) { finfo("Node search ended with match of node %p at depth %u" " for CTZ of %u original size with range [%u, %u).", n, n->depth, n->sz, n->range_min, n->range_max); *node = n; return; } } *node = NULL; finfo("Node search ended without match."); } /**************************************************************************** * Name: lru_islrufull * * Description: * Check whether the number of nodes in the LRU has reaches its limit. * * Input Parameters: * sb - Superblock instance of the device. * * Returned Value: * true - LRU is full * false - LRU is not full. * ****************************************************************************/ static bool lru_islrufull(FAR struct mfs_sb_s * const sb) { return sb->n_lru == CONFIG_MNEMOFS_NLRU; } /**************************************************************************** * Name: lru_isnodefull * * Description: * Check whether the number of deltas in an LRU node has reaches its limit. * * Input Parameters: * node - LRU node. * * Returned Value: * true - LRU node is full * false - LRU node is not full. * ****************************************************************************/ static bool lru_isnodefull(FAR struct mfs_node_s *node) { return node->n_list == CONFIG_MNEMOFS_NLRUDELTA; } static void lru_free_delta(FAR struct mfs_delta_s *delta) { kmm_free(delta->upd); kmm_free(delta); } /**************************************************************************** * Name: lru_nodeflush * * Description: * Clear out the deltas in a node by writing them to the flash, and adding * a log about it to the journal. * * Input Parameters: * sb - Superblock instance of the device. * path - CTZ representation of the relpath. * depth - Depth of `path`. * node - LRU node to flush. * clean_node - To remove node out of LRU (true), or just clear the * deltas (false). * * Returned Value: * 0 - OK * < 0 - Error * ****************************************************************************/ static int lru_nodeflush(FAR struct mfs_sb_s * const sb, FAR struct mfs_path_s * const path, const mfs_t depth, const mfs_t ctz_sz, FAR struct mfs_node_s *node, bool clean_node) { int ret = OK; FAR struct mfs_delta_s *delta = NULL; FAR struct mfs_delta_s *tmp = NULL; if (predict_false(node == NULL)) { return -ENOMEM; } ret = mfs_ctz_nwrtooff(sb, node, path, depth, ctz_sz, &path[depth - 1].ctz); if (predict_false(ret < 0)) { goto errout; } /* Reset node stats. */ node->range_max = 0; node->range_min = UINT32_MAX; /* Free deltas after flush. */ list_for_every_entry_safe(&node->list, delta, tmp, struct mfs_delta_s, list) { list_delete_init(&delta->list); lru_free_delta(delta); } errout: return ret; } static int lru_wrtooff(FAR struct mfs_sb_s * const sb, const mfs_t data_off, mfs_t bytes, mfs_t ctz_sz, int op, FAR struct mfs_path_s * const path, const mfs_t depth, FAR const char *buf) { int ret = OK; bool found = true; FAR struct mfs_node_s *node = NULL; FAR struct mfs_node_s *last_node = NULL; FAR struct mfs_delta_s *delta = NULL; lru_nodesearch(sb, path, depth, &node); if (node == NULL) { node = kmm_zalloc(sizeof(*node) + (depth * sizeof(struct mfs_path_s))); if (predict_false(node == NULL)) { ret = -ENOMEM; goto errout; } node->sz = ctz_sz; node->depth = depth; node->n_list = 0; node->range_max = 0; node->range_min = UINT32_MAX; list_initialize(&node->delta); memcpy(node->path, path, depth * sizeof(struct mfs_path_s)); found = false; finfo("Node not found, allocated, ready to be inserted into LRU."); } if (!found) { if (lru_islrufull(sb)) { finfo("LRU is full, need to flush a node."); last_node = list_container_of(list_peek_tail(&MFS_LRU(sb)), struct mfs_node_s, list); list_delete_init(&last_node->list); list_add_tail(&MFS_LRU(sb), &node->list); finfo("LRU flushing node complete, now only %u nodes", sb->n_lru); } else { list_add_tail(&MFS_LRU(sb), &node->list); sb->n_lru++; finfo("Node inserted into LRU, and it now %u node(s).", sb->n_lru); } } else if (found && lru_isnodefull(node)) { /* Node flush writes to the flash and journal. */ ret = lru_nodeflush(sb, path, depth, ctz_sz, node, false); if (predict_false(ret < 0)) { goto errout_with_node; } } /* Add delta to node. */ finfo("Adding delta to the node."); delta = kmm_zalloc(sizeof(*delta)); if (predict_false(delta == NULL)) { ret = -ENOMEM; goto errout_with_node; } finfo("Delta allocated."); if (op == MFS_LRU_UPD) { delta->upd = kmm_zalloc(bytes); if (predict_false(delta->upd == NULL)) { ret = -ENOMEM; goto errout_with_delta; } finfo("Delta is of the update type, has %u bytes of updates." , bytes); } delta->n_b = bytes; delta->off = data_off; list_add_tail(&node->delta, &delta->list); if (op == MFS_LRU_UPD) { memcpy(delta->upd, buf, bytes); } node->n_list++; node->range_min = MIN(node->range_min, data_off); node->range_max = MAX(node->range_max, data_off + bytes); finfo("Delta attached to node. Now there are %lu nodes and the node has" " %lu deltas. Node with range [%u, %u).", list_length(&MFS_LRU(sb)), list_length(&node->delta), node->range_min, node->range_max); return ret; errout_with_delta: kmm_free(delta); errout_with_node: if (!found && node != NULL) { kmm_free(node); } errout: return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ int mfs_lru_ctzflush(FAR struct mfs_sb_s * const sb, FAR struct mfs_path_s * const path, const mfs_t depth, const mfs_t ctz_sz) { struct mfs_node_s *node = NULL; lru_nodesearch(sb, path, depth, &node); if (node == NULL) { return OK; } return lru_nodeflush(sb, path, depth, ctz_sz, node, true); } int mfs_lru_del(FAR struct mfs_sb_s * const sb, const mfs_t data_off, mfs_t bytes, mfs_t ctz_sz, FAR struct mfs_path_s * const path, const mfs_t depth) { return lru_wrtooff(sb, data_off, bytes, ctz_sz, MFS_LRU_DEL, path, depth, NULL); } int mfs_lru_wr(FAR struct mfs_sb_s * const sb, const mfs_t data_off, mfs_t bytes, mfs_t ctz_sz, FAR struct mfs_path_s * const path, const mfs_t depth, FAR const char *buf) { return lru_wrtooff(sb, data_off, bytes, ctz_sz, MFS_LRU_UPD, path, depth, buf); } int mfs_lru_rdfromoff(FAR struct mfs_sb_s * const sb, const mfs_t data_off, FAR struct mfs_path_s * const path, const mfs_t depth, FAR char *buf, const mfs_t buflen) { int ret = OK; mfs_t s; mfs_t e; mfs_t end; mfs_t start; mfs_t del_b; FAR struct mfs_node_s *node = NULL; FAR struct mfs_delta_s *delta = NULL; finfo("Reading from offset %u, %u bytes", data_off, buflen); ret = mfs_ctz_rdfromoff(sb, data_off, path, depth, buf, buflen); if (predict_false(ret < 0)) { goto errout; } lru_nodesearch(sb, path, depth, &node); if (node == NULL) { goto errout; } start = data_off; end = data_off + buflen; del_b = 0; do { list_for_every_entry(&node->delta, delta, struct mfs_delta_s, list) { if (delta->upd) { s = MAX(delta->off, start); e = MIN(delta->off + delta->n_b, end); if (s >= e) { continue; } memcpy(buf + (s - data_off), delta->upd + (s - delta->off), e - s); } else { s = MAX(delta->off, start); e = MIN(delta->off + delta->n_b, end); if (s >= end) { continue; } if (e <= start) { del_b += delta->n_b; s = data_off + delta->n_b; e = end; memmove(buf, buf + delta->n_b, buflen - delta->n_b); e = data_off + buflen - delta->n_b; } else { del_b += e - s; memmove(buf + s, buf + e, buflen - (e - s)); e = data_off + buflen - (e - s); } } start = s; end = e; } start = end; end = data_off + buflen; } while (start != end); errout: return ret; } void mfs_lru_init(FAR struct mfs_sb_s * const sb) { list_initialize(&MFS_LRU(sb)); sb->n_lru = 0; finfo("LRU Initialized\n"); } void mfs_lru_updatedsz(FAR struct mfs_sb_s * const sb, FAR const struct mfs_path_s * const path, const mfs_t depth, mfs_t *n_sz) { mfs_t o_sz; FAR struct mfs_node_s *node = NULL; FAR struct mfs_delta_s *delta = NULL; if (depth == 0) { /* Master node */ o_sz = 0; } if (depth == 1) { /* Root node. */ o_sz = MFS_MN(sb).root_sz; } else { o_sz = path[depth - 1].sz; } *n_sz = o_sz; lru_nodesearch(sb, path, depth, &node); if (node == NULL) { finfo("No updates for file size, with depth %u.", depth); return; } list_for_every_entry(&node->delta, delta, struct mfs_delta_s, list) { finfo("depth %u %u %u %p", depth, delta->n_b, delta->off, delta->upd); if (delta->upd == NULL) { *n_sz -= MIN((*n_sz) - delta->off, delta->n_b); } else { *n_sz = MAX(*n_sz, delta->off + delta->n_b); } } finfo("Updated file size is %u with depth %u.", *n_sz, depth); }