diff --git a/fs/mnemofs/CMakeLists.txt b/fs/mnemofs/CMakeLists.txt index 79490363532..4d15a764c88 100644 --- a/fs/mnemofs/CMakeLists.txt +++ b/fs/mnemofs/CMakeLists.txt @@ -50,6 +50,7 @@ if(CONFIG_FS_MNEMOFS) target_sources( fs PRIVATE mnemofs_blkalloc.c + mnemofs_ctz.c mnemofs_fsobj.c mnemofs_journal.c mnemofs_lru.c diff --git a/fs/mnemofs/Make.defs b/fs/mnemofs/Make.defs index 97fe304c439..06661d0ee63 100644 --- a/fs/mnemofs/Make.defs +++ b/fs/mnemofs/Make.defs @@ -53,6 +53,7 @@ ifeq ($(CONFIG_FS_MNEMOFS),y) # Add the mnemofs C files to the build CSRCS += mnemofs_blkalloc.c +CSRCS += mnemofs_ctz.c CSRCS += mnemofs_fsobj.c CSRCS += mnemofs_journal.c CSRCS += mnemofs_lru.c diff --git a/fs/mnemofs/mnemofs.h b/fs/mnemofs/mnemofs.h index 2dd0cbbdf66..389b82ba5dd 100644 --- a/fs/mnemofs/mnemofs.h +++ b/fs/mnemofs/mnemofs.h @@ -677,6 +677,28 @@ int mfs_isbadblk(FAR const struct mfs_sb_s * const sb, mfs_t blk); int mfs_markbadblk(FAR const struct mfs_sb_s * const sb, mfs_t blk); +/**************************************************************************** + * Name: mfs_write_page + * + * Description: + * Write a page. + * + * Input Parameters: + * sb - Superblock instance of the device. + * data - Buffer + * datalen - Length of buffer. + * pg - Page number. + * pgoff - Offset into the page. + * + * Assumptions/Limitations: + * This assumes a locked environment when called. + * + ****************************************************************************/ + +ssize_t mfs_write_page(FAR const struct mfs_sb_s * const sb, + FAR const char *data, const mfs_t datalen, + const off_t page, const mfs_t pgoff); + /**************************************************************************** * Name: mfs_read_page * @@ -998,6 +1020,103 @@ mfs_t mfs_v2n(mfs_t n); mfs_t mfs_set_msb(mfs_t n); +/* mnemofs_ctz.c */ + +/**************************************************************************** + * Name: mfs_ctz_rdfromoff + * + * Description: + * Read data from data offset in a CTZ list. This includes updates from the + * journal. The ctz list is taken as the last element in the path, got + * using `path[depth - 1]`. + * + * Input Parameters: + * sb - Superblock instance of the device. + * data_off - Data offset into the CTZ list. + * path - CTZ representation of the relpath. + * depth - Depth of the path. + * buf - Buffer will be populated with the contents. + * buflen - Length of `buf`. + * + * Returned Value: + * 0 - OK + * < 0 - Error + * + * Assumptions/Limitations: + * This updates the value of path to reflect the latest location. + * + ****************************************************************************/ + +int mfs_ctz_rdfromoff(FAR struct mfs_sb_s * const sb, mfs_t data_off, + FAR struct mfs_path_s * const path, const mfs_t depth, + FAR char *buf, mfs_t buflen); + +/**************************************************************************** + * Name: mfs_ctz_wrtooff + * + * Description: + * Replace `o_bytes` of data from CTZ list with `n_bytes` of data from + * `buf` at CTZ data offset `data_off`. + * + * In mnemofs, the CTZ lists are all stored in a Copy On Write manner. + * Hence to update a CTZ list, the common CTZ blocks will be kept as it is, + * then in the CTZ block containing `data_off`, the bytes appearing before + * `data_off` (which remain unchanged) will be copied to the new CTZ block + * then `n_bytes` of content from `buf` will follow, and then the data from + * `data_off + o_bytes` will follow (both these will be copied to new + * CTZ blocks as well due to Copy On Write). + * + * The new location will be written to the journal upon success as well. + * + * Input Parameters: + * sb - Superblock instance of the device. + * data_off - Data offset into the CTZ list. + * o_bytes - Number of bytes in old CTZ list from `data_off` that will be + * replaced. + * n_bytes - Number of bytes in new CTZ list from `data_off` that will be + * replacing `o_bytes`. + * o_ctz_sz - The size in bytes of the old CTZ list. + * path - CTZ representation of the relpath. + * depth - Depth of the path. + * buf - Buffer that contains the data to be replaced in CTZ list. + * ctz - CTZ list to be updated with the new position. + * + * Returned Value: + * 0 - OK + * < 0 - Error + * + * Assumptions/Limitations: + * This updates the value of path to reflect the latest location.s + * + ****************************************************************************/ + +int mfs_ctz_wrtooff(FAR struct mfs_sb_s * const sb, const mfs_t data_off, + mfs_t o_bytes, const mfs_t n_bytes, + mfs_t o_ctz_sz, FAR struct mfs_path_s * const path, + const mfs_t depth, FAR const char *buf, + FAR struct mfs_ctz_s *ctz); + +/**************************************************************************** + * Name: mfs_ctz_nwrtooff + * + * Description: + * Write deltas of an LRU node to flash. + * + * Input Parameters: + * sb - Superblock instance of the device. + * node - LRU Node. + * path - CTZ representation of the relpath. + * depth - Depth of path. + * ctz_sz - Number of bytes in the data of the CTZ list. + * new_ctz - New CTZ location + * + ****************************************************************************/ + +int mfs_ctz_nwrtooff(FAR struct mfs_sb_s * const sb, + FAR struct mfs_node_s *node, + FAR struct mfs_path_s * const path, const mfs_t depth, + const mfs_t ctz_sz, FAR struct mfs_ctz_s *new_ctz); + /* mnemofs_lru.c */ /**************************************************************************** diff --git a/fs/mnemofs/mnemofs_ctz.c b/fs/mnemofs/mnemofs_ctz.c new file mode 100644 index 00000000000..403821d6b20 --- /dev/null +++ b/fs/mnemofs/mnemofs_ctz.c @@ -0,0 +1,766 @@ +/**************************************************************************** + * fs/mnemofs/mnemofs_ctz.c + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * In mnemofs, the files and directories use the CTZ skip list data structure + * defined by littlefs. These are reverse skip lists with a specific number + * of pointers for each block. The number of pointers for a block at index + * `x` is `ctz(x) + 1`. There are no pointers if the index is 0. + * + * The pointers all point to some CTZ block other than the CTZ block they are + * part of. The `k`th pointer of a CTZ block at index `x` points to the + * CTZ block at index `x - 2^k`. + * + * For example, CTZ block at index 2 has 2 pointers, and they point to the + * block at index 1, and index 0 respectively. + * + * File/Dir Ptr + * | + * V + * +------+ +------+ +------+ +------+ +------+ +------+ + * | |<--| |---| |---| |---| | | | + * | Node |<--| Node |---| Node |<--| Node |---| Node | | Node | + * | 0 |<--| 1 |<--| 2 |<--| 3 |<--| 4 |<--| 5 | + * +------+ +------+ +------+ +------+ +------+ +------+ + * + * In mnemofs, each CTZ block is stored in a page on the flash. All code in + * this entire file will call CTZ blocks as blocks to honour the original + * naming, and will specify wherever it deviates from this assumption. + * + * Littlefs's design documentation lists all the benefits that this data + * structure brings to the table when it comes to storing large pieces of + * data that will be modified considerably frequently, while being in a + * Copy On Write (CoW) environment. + * + * In mnemofs, the CTZ methods only interface with the underlying R/W methods + * , journal on the lower side and on the upper side, the LRU, and ensures + * that whatever data it provides considers both the on-flash data, as well + * the journal logs. + * + * The pointers are stored such that the first pointer, which points to + * (x - 2^0), is stored at the very end of the CTZ block. The second pointer + * is stored second last, and so on. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "mnemofs.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define MFS_CTZ_PTRSZ (sizeof(mfs_t)) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static mfs_t ctz_idx_nptrs(const mfs_t idx); +static void ctz_off2loc(FAR const struct mfs_sb_s * const sb, mfs_t off, + FAR mfs_t *idx, FAR mfs_t *pgoff); +static mfs_t ctz_blkdatasz(FAR const struct mfs_sb_s * const sb, + const mfs_t idx); +static mfs_t ctz_travel(FAR const struct mfs_sb_s * const sb, mfs_t idx_src, + mfs_t pg_src, mfs_t idx_dest); +static void ctz_copyidxptrs(FAR const struct mfs_sb_s * const sb, + FAR struct mfs_ctz_s ctz, const mfs_t idx, + FAR char *buf); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: ctz_idx_nptrs + * + * Description: + * Gives the numbers of pointers that a CTZ block of given index should + * have. + * + * Input Parameters: + * idx - Index of the ctz block. + * + * Returned Value: + * The number of pointers in the CTZ block. + * + ****************************************************************************/ + +static mfs_t ctz_idx_nptrs(const mfs_t idx) +{ + return idx == 0 ? 0 : mfs_ctz(idx) + 1; +} + +/**************************************************************************** + * Name: ctz_off2loc + * + * Description: + * Converts ctz offset (which is the offset of the data stored in the ctz + * list, which is unaware of the presence of pointers) into the CTZ + * block index and the offset in that CTZ block. + * + * Input Parameters: + * sb - Superblock instance of the device. + * off - Offset of the data stored in the CTZ list. + * idx - Indes of the CTZ block, to be populated. + * pgoff - Offset inside the CTZ block, to be populated. + * + ****************************************************************************/ + +static void ctz_off2loc(FAR const struct mfs_sb_s * const sb, mfs_t off, + FAR mfs_t *idx, FAR mfs_t *pgoff) +{ + const mfs_t den = MFS_PGSZ(sb) - 8; + if (off < MFS_PGSZ(sb)) + { + *idx = 0; + *pgoff = off; + return; + } + + *idx = floor((off - 4 * (__builtin_popcount((off / den) - 1) + 2)) + / den); + *pgoff = off - den * (*idx) - 4 * __builtin_popcount(*idx); +} + +/**************************************************************************** + * Name: ctz_blkdatasz + * + * Description: + * The size of data in B that can be fit inside a CTZ block at index `idx`. + * + * Input Parameters: + * sb - Superblock instance of the device. + * idx - Index of the ctz block. + * + * Returned Value: + * The size of data in the CTZ block. + * + ****************************************************************************/ + +static mfs_t ctz_blkdatasz(FAR const struct mfs_sb_s * const sb, + const mfs_t idx) +{ + return MFS_PGSZ(sb) - (ctz_idx_nptrs(idx) * MFS_LOGPGSZ(sb)); +} + +/**************************************************************************** + * Name: ctz_travel + * + * Description: + * From CTZ block at page `pg_src` and index `idx_src`, give the page + * number of index `idx_dest`. + * + * The source is preferably the last CTZ block in the CTZ list, but it can + * realistically be any CTZ block in the CTZ list whos position is known. + * However, `idx_dest <= idx_src` has to be followed. Takes O(log(n)) + * complexity to travel. + * + * Input Parameters: + * sb - Superblock instance of the device. + * idx_src - Index of the source ctz block. + * pg_src - Page number of the source ctz block. + * idx_dest - Index of the destination ctz block. + * + * Returned Value: + * The page number corresponding to `idx_dest`. + * + * Assumptions/Limitations: + * `idx_dest <= idx_src`. + * + ****************************************************************************/ + +static mfs_t ctz_travel(FAR const struct mfs_sb_s * const sb, mfs_t idx_src, + mfs_t pg_src, mfs_t idx_dest) +{ + char buf[4]; + mfs_t pg; + mfs_t idx; + mfs_t pow; + mfs_t diff; + mfs_t max_pow; + + /* Rising phase. */ + + max_pow = (sizeof(mfs_t) * 8) - mfs_clz(idx_src ^ idx_dest); + idx = idx_src; + pow = 1; + pg = pg_src; + + for (pow = mfs_ctz(idx); pow < max_pow - 1; pow = mfs_ctz(idx)) + { + mfs_read_page(sb, buf, 4, pg, MFS_PGSZ(sb) - (4 * pow)); + mfs_deser_mfs(buf, &pg); + idx -= (1 << pow); + } + + if (idx == idx_dest) + { + return pg; + } + + /* Falling phase. */ + + diff = idx - idx_dest; + + for (pow = mfs_set_msb(diff); diff != 0; pow = mfs_set_msb(diff)) + { + mfs_read_page(sb, buf, 4, pg, MFS_PGSZ(sb) - (4 * pow)); + mfs_deser_mfs(buf, &pg); + idx -= (1 << pow); + diff -= (1 << pow); + } + + return pg; +} + +/**************************************************************************** + * Name: ctz_copyidxptrs + * + * Description: + * This is used for cases when you want to expand a CTZ list from any point + * in the list. If we want to expand the CTZ list from a particular index, + * say `start_idx`, while keeping all indexes before it untouched, we + * would need to first allocate new blocks on the flash, and then copy + * the pointers to the location. + * + * Usage of this function is, the caller needs to first allocate a CTZ + * block (a page on flash), allocate buffer which is the size of a CTZ + * block (a page on flash), and use this method to copy the pointers to the + * buffer, then write the data to the flash. + * + * Input Parameters: + * sb - Superblock instance of the device. + * ctz - CTZ list to use as a reference. + * idx - Index of the block who's supposed pointers are to be copied. + * buf - Buffer representing the entire CTZ block where pointers are + * copied to. + * + * Assumptions/Limitations: + * This assumes `idx` is not more than `ctz->idx_e + 1`. + * + ****************************************************************************/ + +static void ctz_copyidxptrs(FAR const struct mfs_sb_s * const sb, + FAR struct mfs_ctz_s ctz, const mfs_t idx, + FAR char *buf) +{ + mfs_t i; + mfs_t n_ptrs; + mfs_t prev_pg; + mfs_t prev_idx; + + if (idx == 0) + { + /* No pointers for first block. */ + + return; + } + + n_ptrs = ctz_idx_nptrs(idx); + + if (idx != ctz.idx_e + 1) + { + ctz.pg_e = ctz_travel(sb, ctz.idx_e, ctz.pg_e, idx - 1); + ctz.idx_e = idx - 1; + } + + buf += MFS_PGSZ(sb); /* Go to buf + pg_sz */ + + for (i = 0; i < n_ptrs; i++) + { + if (predict_false(i == 0)) + { + prev_idx = ctz.idx_e; + prev_pg = ctz.pg_e; + } + else + { + prev_pg = ctz_travel(sb, prev_idx, prev_pg, prev_idx - 1); + prev_idx--; + } + + ctz.idx_e = prev_idx; + + /* Do buf + pg_sz - (idx * sizeof(mfs_t)) iteratively. */ + + buf -= MFS_CTZ_PTRSZ; + mfs_ser_mfs(prev_pg, buf); + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int mfs_ctz_rdfromoff(FAR struct mfs_sb_s * const sb, mfs_t data_off, + FAR struct mfs_path_s * const path, const mfs_t depth, + FAR char *buf, mfs_t buflen) +{ + int ret = OK; + mfs_t sz; + mfs_t pg; + mfs_t idx; + mfs_t off; + struct mfs_ctz_s ctz; + + /* Get updated location from the journal */ + + DEBUGASSERT(depth > 0); + ctz = path[depth - 1].ctz; + + ctz_off2loc(sb, data_off, &idx, &off); + + /* TODO: Make the traversal in reverse direction. It would cause + * a lot less traversals. + */ + + while (idx <= ctz.idx_e && off - data_off < buflen) + { + sz = ctz_blkdatasz(sb, idx); + pg = ctz_travel(sb, ctz.idx_e, ctz.pg_e, idx); + + ret = mfs_read_page(sb, buf, sz, pg, off); + if (predict_false(ret < 0)) + { + return ret; + } + + off = 0; + idx++; + buf += sz; + } + + return ret; +} + +int mfs_ctz_nwrtooff(FAR struct mfs_sb_s * const sb, + FAR struct mfs_node_s *node, + FAR struct mfs_path_s * const path, const mfs_t depth, + const mfs_t ctz_sz, FAR struct mfs_ctz_s *new_ctz) +{ + int ret = OK; + bool del; + mfs_t pg; + mfs_t sz; + mfs_t inc; + mfs_t idx; + mfs_t bytes; + mfs_t pgoff; + mfs_t itr_min; + mfs_t itr_max; + mfs_t neg_off; /* Negative offset due to delete. */ + FAR char *buf = NULL; + const mfs_t range_min = node->range_min; + const mfs_t range_max = node->range_max; + struct mfs_ctz_s ctz; + FAR struct mfs_delta_s *delta = NULL; + + buf = kmm_zalloc(MFS_PGSZ(sb)); + if (predict_false(buf == NULL)) + { + ret = -ENOMEM; + goto errout; + } + + /* CoW of items in block before range_min */ + + ctz_off2loc(sb, range_min, &idx, &pgoff); + DEBUGASSERT(depth > 0); + pg = ctz_travel(sb, path[depth - 1].ctz.idx_e, path[depth - 1].ctz.pg_e, + idx); + if (predict_false(pg == 0)) + { + ret = -EINVAL; + goto errout_with_buf; + } + + ctz.idx_e = idx - 1; + ctz.pg_e = pg; + mfs_read_page(sb, buf, pgoff, pg, pgoff); + + /* Updates */ + + bytes = range_min; + itr_max = range_min; + neg_off = 0; + del = false; + + while (bytes < range_max) + { + if (!del) + { + memset(buf, 0, MFS_PGSZ(sb)); + sz = ctz_blkdatasz(sb, ctz.idx_e + 1); + itr_min = itr_max; + itr_max += sz; + ctz_copyidxptrs(sb, ctz, ctz.idx_e + 1, buf); + } + else + { + inc = itr_max - itr_min; + itr_min = itr_max; + itr_min = sz - inc; + } + + del = false; + mfs_ctz_rdfromoff(sb, itr_min + neg_off, path, depth, buf + pgoff, sz); + + list_for_every_entry(&node->delta, delta, struct mfs_delta_s, list) + { + if (delta->off + delta->n_b <= itr_min || itr_max <= delta->off) + { + /* Out of range */ + + continue; + } + + inc = MIN(itr_max, delta->off + delta->n_b) - delta->off; + + if (delta->upd != NULL) + { + /* Update */ + + memcpy(buf + pgoff + (delta->off - itr_min), delta->upd, inc); + } + else + { + /* Delete */ + + memmove(buf + pgoff + (delta->off - itr_min), + buf + pgoff + (delta->off - itr_min) + inc, + itr_max - (delta->off + inc)); + itr_max -= inc; + neg_off += inc; + del = true; + } + } + + bytes += itr_max - itr_min; + + if (!del) + { + pgoff = 0; + pg = mfs_ba_getpg(sb); + if (pg == 0) + { + ret = -ENOSPC; + goto errout_with_buf; + } + + mfs_write_page(sb, buf, MFS_PGSZ(sb), pg, 0); + ctz.pg_e = pg; + ctz.idx_e++; + } + else + { + pgoff += itr_max - itr_min; + } + } + + /* Copy rest of the file. */ + + if (del) + { + mfs_ctz_rdfromoff(sb, itr_max + neg_off, path, depth, buf + pgoff, + sz - pgoff); + pg = mfs_ba_getpg(sb); + if (pg == 0) + { + ret = -ENOSPC; + goto errout_with_buf; + } + + mfs_write_page(sb, buf, MFS_PGSZ(sb), pg, 0); + ctz.pg_e = pg; + ctz.idx_e++; + itr_max += sz - pgoff; + pgoff = 0; + } + + while (bytes < ctz_sz) + { + sz = ctz_blkdatasz(sb, ctz.idx_e + 1); + mfs_ctz_rdfromoff(sb, itr_max + neg_off, path, depth, buf, + MIN(MFS_PGSZ(sb), ctz_sz - bytes)); + pg = mfs_ba_getpg(sb); + if (pg == 0) + { + ret = -ENOSPC; + goto errout_with_buf; + } + + mfs_write_page(sb, buf, MFS_PGSZ(sb), pg, 0); + bytes += MIN(MFS_PGSZ(sb), ctz_sz - bytes); + } + + /* TODO: Check for cases where delete, but no further data at the end. + * , which might cause an infinite loop. + */ + +errout_with_buf: + kmm_free(buf); + +errout: + return ret; +} + +int mfs_ctz_wrtooff(FAR struct mfs_sb_s * const sb, const mfs_t data_off, + mfs_t o_bytes, const mfs_t n_bytes, + mfs_t o_ctz_sz, FAR struct mfs_path_s * const path, + const mfs_t depth, FAR const char *buf, + FAR struct mfs_ctz_s *ctz) +{ + int ret = OK; + bool partial; + mfs_t partial_bytes; /* Bytes partially filled. */ + mfs_t pg; + mfs_t off; + mfs_t idx; + mfs_t bytes; + mfs_t o_pg; + mfs_t o_off; + mfs_t o_idx; + mfs_t o_rembytes; + mfs_t o_data_off; + mfs_t n_pg; + mfs_t n_data_off; + mfs_t ctz_blk_datasz; + FAR char *data; + struct mfs_ctz_s o_ctz; + struct mfs_ctz_s n_ctz; + + data = kmm_zalloc(MFS_PGSZ(sb)); + if (predict_false(data == NULL)) + { + ret = -ENOMEM; + goto errout; + } + + /* Get updated location from the journal. */ + + DEBUGASSERT(depth > 0); + o_ctz = path[depth - 1].ctz; + + /* TODO: Make the traversal in reverse direction. It would cause + * a lot less traversals. + */ + + ctz_off2loc(sb, data_off, &idx, &off); + + /* Reach the common part. */ + + if (idx != 0 && off != 0) + { + pg = ctz_travel(sb, o_ctz.idx_e, o_ctz.pg_e, idx - 1); + n_ctz.idx_e = idx - 1; + n_ctz.pg_e = pg; + } + else + { + pg = o_ctz.pg_e; + n_ctz.idx_e = 0; + n_ctz.pg_e = pg; + finfo("CTZ: %u %u %u", pg, n_ctz.idx_e, n_ctz.pg_e); + } + + /* Add new data from buf. */ + + partial = false; + o_rembytes = o_ctz_sz - (data_off + o_bytes); + bytes = data_off; + if (n_bytes != 0) + { + while (bytes < data_off + n_bytes) + { + n_pg = mfs_ba_getpg(sb); + if (n_pg) + { + ret = -ENOSPC; + goto errout_with_data; + } + + ctz_copyidxptrs(sb, n_ctz, idx, data); /* Handles idx == 0. */ + ctz_blk_datasz = ctz_blkdatasz(sb, idx); + + if (predict_false(off != 0)) + { + /* This happens at max for the first iteration of the loop. */ + + mfs_read_page(sb, data, off, + ctz_travel(sb, o_ctz.idx_e, o_ctz.pg_e, idx), 0); + bytes += off; + } + + memcpy(data + off, buf + bytes, + MIN(ctz_blk_datasz - off, n_bytes - bytes)); + if (n_bytes - bytes < ctz_blk_datasz - off && o_rembytes != 0) + { + partial = true; + partial_bytes = n_bytes - bytes; + } + else + { + bytes += ctz_blk_datasz - off; + } + + if (predict_false(off != 0)) + { + /* This happens at max for the first iteration of the loop. */ + + off = 0; + } + + if (predict_true(!partial)) + { + mfs_write_page(sb, data, MFS_PGSZ(sb), n_pg, 0); + n_ctz.idx_e = idx; + n_ctz.pg_e = pg; + } + else + { + /* In this case, we have a situation where there is still + * some empty area left in the page, and there is some data + * that is waiting to be fit in there, so we won't write it + * to the flash JUST yet. We'll fill in the rest of the data + * and THEN write it. + * + * It's okay to leave the loop as this case will happen, if at + * all, on the last iteration of the loop. + */ + + n_ctz.idx_e = idx; + n_ctz.pg_e = pg; + break; + } + + idx++; + + memset(data, 0, MFS_PGSZ(sb)); + } + } + + o_data_off = data_off + o_bytes; + n_data_off = data_off + n_bytes; + + /* Completing partially filled data, if present. */ + + if (partial) + { + ctz_off2loc(sb, o_data_off, &o_idx, &o_off); + o_pg = ctz_travel(sb, o_ctz.idx_e, o_ctz.pg_e, o_idx); + mfs_read_page(sb, data + partial_bytes, + ctz_blkdatasz(sb, n_ctz.idx_e) - partial_bytes, o_pg, + o_off); + mfs_write_page(sb, data, MFS_PGSZ(sb), n_ctz.pg_e, 0); + + o_data_off += partial_bytes; + n_data_off += partial_bytes; + partial_bytes = 0; + partial = false; + } + + /* Adding old bytes in. */ + + while (o_data_off < o_ctz_sz) + { + memset(data, 0, MFS_PGSZ(sb)); + + pg = mfs_ba_getpg(sb); + if (predict_false(pg == 0)) + { + ret = -ENOSPC; + goto errout_with_data; + } + + ctz_blk_datasz = ctz_blkdatasz(sb, n_ctz.idx_e + 1); + ctz_copyidxptrs(sb, n_ctz, n_ctz.idx_e + 1, data); + + mfs_ctz_rdfromoff(sb, o_data_off, path, depth, data, ctz_blk_datasz); + mfs_write_page(sb, data, MFS_PGSZ(sb), pg, 0); + + n_ctz.idx_e++; + n_ctz.pg_e = pg; + + o_data_off += ctz_blk_datasz; + } + + /* path is not updated to point to the new ctz. This is upto the caller. */ + + *ctz = n_ctz; + +errout_with_data: + kmm_free(data); + +errout: + return ret; +} diff --git a/fs/mnemofs/mnemofs_lru.c b/fs/mnemofs/mnemofs_lru.c index 0776ff58ee5..a1b032e44dd 100644 --- a/fs/mnemofs/mnemofs_lru.c +++ b/fs/mnemofs/mnemofs_lru.c @@ -83,10 +83,26 @@ * 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 ****************************************************************************/ @@ -99,54 +115,476 @@ * 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) + FAR struct mfs_path_s * const path, const mfs_t depth, + const mfs_t ctz_sz) { - /* TODO */ + struct mfs_node_s *node = NULL; - return OK; + 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) { - /* TODO */ - - return OK; + 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) + mfs_t bytes, mfs_t ctz_sz, FAR struct mfs_path_s * const path, + const mfs_t depth, FAR const char *buf) { - /* TODO */ - - return OK; + 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) { - /* TODO */ + 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; - return OK; + 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) { - /* TODO */ + 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) + FAR const struct mfs_path_s * const path, + const mfs_t depth, mfs_t *n_sz) { - /* TODO */ + 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); } diff --git a/fs/mnemofs/mnemofs_rw.c b/fs/mnemofs/mnemofs_rw.c index ffadd2e7717..306051d5b28 100644 --- a/fs/mnemofs/mnemofs_rw.c +++ b/fs/mnemofs/mnemofs_rw.c @@ -93,10 +93,21 @@ int mfs_markbadblk(FAR const struct mfs_sb_s * const sb, mfs_t blk) return MTD_ISBAD(MFS_MTD(sb), blk); } +ssize_t mfs_write_page(FAR const struct mfs_sb_s * const sb, + FAR const char *data, const mfs_t datalen, + const off_t page, const mfs_t pgoff) +{ + /* TODO */ + + return OK; +} + ssize_t mfs_read_page(FAR const struct mfs_sb_s * const sb, FAR char *data, const mfs_t datalen, const off_t page, const mfs_t pgoff) { + /* TODO */ + return OK; } diff --git a/fs/mnemofs/mnemofs_util.c b/fs/mnemofs/mnemofs_util.c index 9227ea09be0..0815bd352e7 100644 --- a/fs/mnemofs/mnemofs_util.c +++ b/fs/mnemofs/mnemofs_util.c @@ -221,7 +221,7 @@ mfs_t mfs_v2n(mfs_t n) return (n & (~(n - 1))); } -mfs_t set_msb(mfs_t n) +mfs_t mfs_set_msb(mfs_t n) { return 31 - mfs_clz(n); }