mirror of
https://github.com/apache/nuttx.git
synced 2026-08-01 20:28:58 +00:00
fs/xipfs: Add a contiguous execute-in-place file system.
ROMFS is the usual way to carry executables on a NOMMU target with memory mapped NOR flash: it can hand out a real flash pointer from mmap(), so the NXFLAT loader maps a module's text in place instead of copying it into RAM. But a ROMFS image is built on the host and is read only, so a module cannot be downloaded onto the board at run time. xipfs is a writable file system with the same in-place property. Each file is stored as one physically contiguous, erase-block aligned extent, so an mmap() of it resolves to flash_base + extent_offset and a loader can execute the file where it already lies. This needs the underlying MTD driver to answer BIOC_XIPBASE; on the RP2350 rp23xx_flash_mtd.c does. Files are write once. A file is created, its size is declared, it is written sequentially, closed, and is thereafter immutable until it is deleted. That is the whole life cycle of a downloaded module, and it is what licenses the design: the exact extent is reserved at create time, so no file ever grows, moves, or fragments internally. Random writes, appends and truncation of a written file are not supported and are refused. The only source of fragmentation is therefore free space holes left by deletes. Allocation fails with -ENOSPC when no single contiguous run is large enough, and never defragments on its own; the caller decides whether to compact and retry, through XIPFSIOC_DEFRAG. Defragmentation is manual, best effort and interruptible: it is a loop of atomic single-extent relocations, each one copy, commit, erase, so every stop point -- a time budget, a pinned extent, an erase error -- leaves a consistent layout that is simply less compact. It reports the largest contiguous run it achieved, which is what tells the caller whether the retry will fit. Metadata is committed power safely. Two metadata block sets are used in ping-pong, each generation carrying a sequence number and a CRC, and every state change is ordered as write the new data, flip the metadata reference, then erase what the old one referenced. Mount scans both sets and selects the last fully valid generation, so a torn write costs the interrupted operation and nothing else. A mapping takes a pin on the extent, and the pin lives on the extent rather than on the file descriptor, so three running instances of one module hold three pins and the extent becomes movable only when the last one goes. Defragmentation skips pinned extents, which is what stops it relocating code that is executing. The pin is released by munmap() or by the task teardown walk, so a task that dies without unmapping does not leak it. Directories are records in that same generation, carrying their own identity and the identity of the directory holding them; the root is implicit and owns identity zero. They are deliberately NOT objects in the data region, which is what keeps the commit story in one piece: mkdir and rmdir add or remove a record and commit one generation, exactly as create and unlink do, so there is never a multi-object update to journal or an orphan to collect at mount. An empty directory therefore exists, survives a remount, and costs one entry out of the volume's fixed supply and no flash blocks at all. A name is one path component; depth comes from the parent, so XIPFS_NAME_MAX bounds a component, which is what statfs reports it as. Mount rebuilds the tree and checks that it is one: identities unique, names unique within a directory, every parent a live directory, and following parents reaching the root -- a cycle on the medium would otherwise hang a path walk rather than merely answering wrongly. '.' and '..' are refused as components, since an entry stored under either could never be reached again. The commands that act on the volume rather than on one file -- XIPFSIOC_DEFRAG and XIPFSIOC_LISTPINNED -- are reached through the ioctldir method, on a descriptor for the mountpoint directory. They are accepted on a descriptor for a file inside the volume too, but that route holds the file open for the duration and an open extent cannot be relocated, so a pass asked for that way is obstructed by the act of asking. mmap() falls back to the generic RAM copy for ordinary readers when the media cannot be addressed directly. A module loader must not silently get a RAM copy, so MAP_XIP_STRICT is added: with it the mapping either resolves in place or fails with -ENXIO, which the caller can turn into defragment and retry. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
This commit is contained in:
parent
ccbe771ad0
commit
d018ccd89d
16 changed files with 4931 additions and 1 deletions
|
|
@ -181,4 +181,5 @@ source "fs/hostfs/Kconfig"
|
|||
source "fs/rpmsgfs/Kconfig"
|
||||
source "fs/zipfs/Kconfig"
|
||||
source "fs/mnemofs/Kconfig"
|
||||
source "fs/xipfs/Kconfig"
|
||||
source "fs/v9fs/Kconfig"
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ include littlefs/Make.defs
|
|||
include rpmsgfs/Make.defs
|
||||
include zipfs/Make.defs
|
||||
include mnemofs/Make.defs
|
||||
include xipfs/Make.defs
|
||||
include v9fs/Make.defs
|
||||
endif
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@
|
|||
/* These file systems require MTD drivers */
|
||||
|
||||
#if (defined(CONFIG_FS_SPIFFS) || defined(CONFIG_FS_LITTLEFS) || \
|
||||
defined(CONFIG_FS_MNEMOFS)) && defined(CONFIG_MTD)
|
||||
defined(CONFIG_FS_MNEMOFS) || defined(CONFIG_FS_XIPFS)) && \
|
||||
defined(CONFIG_MTD)
|
||||
# define MDFS_SUPPORT 1
|
||||
#endif
|
||||
|
||||
|
|
@ -136,6 +137,9 @@ extern const struct mountpt_operations g_littlefs_operations;
|
|||
#ifdef CONFIG_FS_MNEMOFS
|
||||
extern const struct mountpt_operations g_mnemofs_operations;
|
||||
#endif
|
||||
#ifdef CONFIG_FS_XIPFS
|
||||
extern const struct mountpt_operations g_xipfs_operations;
|
||||
#endif
|
||||
|
||||
static const struct fsmap_t g_mdfsmap[] =
|
||||
{
|
||||
|
|
@ -147,6 +151,9 @@ static const struct fsmap_t g_mdfsmap[] =
|
|||
#endif
|
||||
#ifdef CONFIG_FS_MNEMOFS
|
||||
{ "mnemofs", &g_mnemofs_operations },
|
||||
#endif
|
||||
#ifdef CONFIG_FS_XIPFS
|
||||
{ "xipfs", &g_xipfs_operations },
|
||||
#endif
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
|
|
|||
26
fs/xipfs/CMakeLists.txt
Normal file
26
fs/xipfs/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# ##############################################################################
|
||||
# fs/xipfs/CMakeLists.txt
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ##############################################################################
|
||||
|
||||
if(CONFIG_FS_XIPFS)
|
||||
target_sources(fs PRIVATE xipfs_alloc.c xipfs_defrag.c xipfs_flash.c
|
||||
xipfs_meta.c xipfs_mmap.c xipfs_vfs.c)
|
||||
endif()
|
||||
42
fs/xipfs/Kconfig
Normal file
42
fs/xipfs/Kconfig
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#
|
||||
# For a description of the syntax of this configuration file,
|
||||
# see the file kconfig-language.txt in the NuttX tools repository.
|
||||
#
|
||||
|
||||
config FS_XIPFS
|
||||
bool "XIPFS contiguous execute-in-place file system"
|
||||
default n
|
||||
depends on !DISABLE_MOUNTPOINT && MTD
|
||||
---help---
|
||||
Enable support for XIPFS, a file system that stores each file as a
|
||||
single physically contiguous, erase-block aligned extent on memory
|
||||
mapped flash.
|
||||
|
||||
Because files are contiguous and the media is directly addressable,
|
||||
XIPFS can return a real pointer into flash from mmap(), so a module
|
||||
can be executed in place without its read-only text and rodata ever
|
||||
being copied into RAM. This requires the underlying MTD driver to
|
||||
implement the BIOC_XIPBASE ioctl.
|
||||
|
||||
Files are write once: a file is created at a known size, written
|
||||
sequentially, closed, and is immutable thereafter until it is
|
||||
deleted. Random writes, appends and growth are not supported.
|
||||
|
||||
Directories are supported. They are records in the same metadata
|
||||
generation the files are, not objects in the data region, so an
|
||||
empty one costs one entry out of the volume's fixed supply and no
|
||||
flash blocks at all.
|
||||
|
||||
if FS_XIPFS
|
||||
|
||||
config FS_XIPFS_FAULT_INJECT
|
||||
bool "XIPFS flash fault injection"
|
||||
default n
|
||||
---help---
|
||||
Add a countdown to the XIPFS flash write and erase paths so that a
|
||||
test can fail an arbitrary flash operation and then remount, which
|
||||
models a power loss at that exact point.
|
||||
|
||||
This is a test-only facility. Do not enable it in production.
|
||||
|
||||
endif # FS_XIPFS
|
||||
38
fs/xipfs/Make.defs
Normal file
38
fs/xipfs/Make.defs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
############################################################################
|
||||
# fs/xipfs/Make.defs
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
ifeq ($(CONFIG_FS_XIPFS),y)
|
||||
|
||||
# Add the xipfs C files to the build
|
||||
|
||||
CSRCS += xipfs_alloc.c
|
||||
CSRCS += xipfs_defrag.c
|
||||
CSRCS += xipfs_flash.c
|
||||
CSRCS += xipfs_meta.c
|
||||
CSRCS += xipfs_mmap.c
|
||||
CSRCS += xipfs_vfs.c
|
||||
|
||||
# Add the xipfs directory to the build
|
||||
|
||||
DEPPATH += --dep-path xipfs
|
||||
VPATH += :xipfs
|
||||
endif
|
||||
392
fs/xipfs/xipfs.h
Normal file
392
fs/xipfs/xipfs.h
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
/****************************************************************************
|
||||
* fs/xipfs/xipfs.h
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __FS_XIPFS_XIPFS_H
|
||||
#define __FS_XIPFS_XIPFS_H
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <sys/statfs.h>
|
||||
#include <sys/types.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <nuttx/compiler.h>
|
||||
#include <nuttx/fs/fs.h>
|
||||
#include <nuttx/fs/xipfs.h>
|
||||
#include <nuttx/mtd/mtd.h>
|
||||
#include <nuttx/mutex.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
/* On-media identification */
|
||||
|
||||
#define XIPFS_SBLK_MAGIC XIPFS_MAGIC /* "XIPF", see sys/statfs.h */
|
||||
#define XIPFS_GEN_MAGIC 0x5847454e /* "XGEN" */
|
||||
#define XIPFS_VERSION 2
|
||||
|
||||
/* Volume geometry.
|
||||
*
|
||||
* Block 0 and block 1 hold the two superblock copies. The next
|
||||
* XIPFS_META_NBLOCKS blocks form the metadata ring. Everything after that
|
||||
* is the data region, allocated in whole erase blocks.
|
||||
*/
|
||||
|
||||
#define XIPFS_SBLK_A 0
|
||||
#define XIPFS_SBLK_B 1
|
||||
#define XIPFS_META_START 2
|
||||
#define XIPFS_META_NBLOCKS 4
|
||||
#define XIPFS_DATA_START (XIPFS_META_START + XIPFS_META_NBLOCKS)
|
||||
|
||||
/* The smallest volume that can hold the superblocks, the metadata ring and
|
||||
* at least one data block.
|
||||
*/
|
||||
|
||||
#define XIPFS_MIN_BLOCKS (XIPFS_DATA_START + 1)
|
||||
|
||||
/* Directory entry size */
|
||||
|
||||
#define XIPFS_DIRENT_SIZE 64
|
||||
|
||||
/* Dirent flags */
|
||||
|
||||
#define XIPFS_DIRENT_DIR (1 << 0) /* The entry is a directory */
|
||||
|
||||
/* The root directory is implicit: it owns id 0 and has no record of its
|
||||
* own, so it always exists and cannot be removed.
|
||||
*/
|
||||
|
||||
#define XIPFS_ROOT_ID 0
|
||||
|
||||
/****************************************************************************
|
||||
* Public Types
|
||||
****************************************************************************/
|
||||
|
||||
/* On-media superblock. Written once by mkfs into both block 0 and block 1.
|
||||
* The CRC covers every byte preceding the crc field.
|
||||
*/
|
||||
|
||||
begin_packed_struct struct xipfs_sblk_s
|
||||
{
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint32_t erasesize;
|
||||
uint32_t blocksize;
|
||||
uint32_t nblocks; /* Total erase blocks in the volume */
|
||||
uint32_t meta_start; /* First block of the metadata ring */
|
||||
uint32_t meta_nblocks; /* Length of the metadata ring */
|
||||
uint32_t data_start; /* First block of the data region */
|
||||
uint32_t data_nblocks; /* Length of the data region */
|
||||
uint32_t crc;
|
||||
} end_packed_struct;
|
||||
|
||||
/* On-media directory entry. One per file and one per directory, stored in
|
||||
* the body of a metadata generation.
|
||||
*
|
||||
* Directories are records here and nowhere else. They are not objects in
|
||||
* the data region, which is what keeps the power-safety story intact: mkdir
|
||||
* and rmdir add or remove a record and commit one generation, exactly as
|
||||
* create and unlink do, so there is never a multi-object update to journal
|
||||
* or an orphan to collect at mount.
|
||||
*
|
||||
* 'name' is one path component, not a path. Depth comes from 'parent', so
|
||||
* XIPFS_NAME_MAX bounds a component, which is what statfs reports it as.
|
||||
*
|
||||
* Padded to XIPFS_DIRENT_SIZE so that a whole number of dirents fits in a
|
||||
* read/write block, which keeps the body write loop aligned without any
|
||||
* straddling entries.
|
||||
*/
|
||||
|
||||
begin_packed_struct struct xipfs_dirent_s
|
||||
{
|
||||
char name[XIPFS_NAME_MAX + 1]; /* 32 bytes, one component */
|
||||
uint32_t size; /* File size in bytes; 0 for a dir */
|
||||
uint32_t start_block; /* Extent block; 0 for a directory */
|
||||
uint32_t nblocks; /* Extent length; 0 for a directory */
|
||||
uint32_t flags; /* XIPFS_DIRENT_DIR */
|
||||
uint16_t id; /* Identity, unique, never 0 */
|
||||
uint16_t parent; /* Containing directory's id */
|
||||
uint8_t reserved[12]; /* Pad to XIPFS_DIRENT_SIZE */
|
||||
} end_packed_struct;
|
||||
|
||||
/* The padding above is maintained by hand, and the body read loop indexes
|
||||
* the generation at a fixed XIPFS_DIRENT_SIZE stride. A field added without
|
||||
* taking it out of 'reserved' would therefore not fail loudly: it would
|
||||
* reparse every existing volume at the wrong offset.
|
||||
*/
|
||||
|
||||
static_assert(sizeof(struct xipfs_dirent_s) == XIPFS_DIRENT_SIZE,
|
||||
"xipfs dirent layout mismatch");
|
||||
|
||||
/* On-media metadata generation header.
|
||||
*
|
||||
* This lives in the first read/write block of a metadata ring block; the
|
||||
* dirent array follows in the blocks after it. The body is written first
|
||||
* and the header last, so the single header write IS the commit point: a
|
||||
* torn body leaves no valid header, and a torn header fails its own CRC.
|
||||
* Either way mount falls back to the previous generation.
|
||||
*/
|
||||
|
||||
begin_packed_struct struct xipfs_genhdr_s
|
||||
{
|
||||
uint32_t magic;
|
||||
uint32_t seq; /* Monotonic generation number */
|
||||
uint32_t nentries; /* Number of dirents in body */
|
||||
uint32_t crc; /* CRC over header + body */
|
||||
} end_packed_struct;
|
||||
|
||||
/* In-RAM directory entry. One per file and one per directory; a directory
|
||||
* carries no extent, so its start_block, nblocks and size are all zero and
|
||||
* everything that accounts for blocks has to skip it.
|
||||
*
|
||||
* For a file this is also the object the pin count lives on -- deliberately
|
||||
* NOT the fd and NOT the open file struct, so that N mappings of one module
|
||||
* produce a pin count of N and the extent only becomes movable when the
|
||||
* last one goes away.
|
||||
*/
|
||||
|
||||
struct xipfs_extent_s
|
||||
{
|
||||
FAR struct xipfs_extent_s *flink;
|
||||
|
||||
/* Back pointer to the owning mount. The task teardown unmap path can
|
||||
* only reach the extent, and must not consult the current task's group,
|
||||
* so the extent has to be able to name its own filesystem.
|
||||
*/
|
||||
|
||||
FAR struct xipfs_mount_s *fs;
|
||||
|
||||
/* One path component, not a path; depth comes from 'parent' */
|
||||
|
||||
char name[XIPFS_NAME_MAX + 1];
|
||||
|
||||
uint16_t id; /* Identity, unique, never 0 */
|
||||
uint16_t parent; /* Containing directory; XIPFS_ROOT_ID */
|
||||
uint32_t size;
|
||||
uint32_t start_block;
|
||||
uint32_t nblocks;
|
||||
uint32_t pincount; /* Mappings that alias flash (true XIP) */
|
||||
uint32_t openrefs; /* Open file descriptors */
|
||||
bool isdir; /* A directory record, with no extent */
|
||||
bool writing; /* Create-time write still in progress */
|
||||
bool unlinked; /* Detached from directory, awaiting free */
|
||||
};
|
||||
|
||||
/* In-RAM mount state */
|
||||
|
||||
struct xipfs_mount_s
|
||||
{
|
||||
FAR struct inode *driver;
|
||||
FAR struct mtd_dev_s *mtd;
|
||||
struct mtd_geometry_s geo;
|
||||
|
||||
/* Direct address of the media, from BIOC_XIPBASE. NULL when the
|
||||
* underlying driver cannot expose one, in which case XIP mappings are
|
||||
* impossible and strict requests fail with -ENXIO.
|
||||
*/
|
||||
|
||||
FAR uint8_t *xipbase;
|
||||
|
||||
uint32_t meta_start;
|
||||
uint32_t meta_nblocks;
|
||||
uint32_t meta_slot; /* Ring slot holding the live generation */
|
||||
uint32_t meta_seq; /* Sequence number of that generation */
|
||||
uint32_t data_start;
|
||||
uint32_t data_nblocks;
|
||||
|
||||
FAR unsigned long *bitmap; /* Free bitmap over the data region */
|
||||
size_t bitmapsize;
|
||||
|
||||
/* Staging buffer for defrag relocation. Reserved once at bind time so
|
||||
* that a relocation can never fail part way through because a transient
|
||||
* allocation did not succeed.
|
||||
*/
|
||||
|
||||
FAR uint8_t *stage;
|
||||
|
||||
/* Scratch for building and verifying a metadata generation. Separate
|
||||
* from 'stage' because defrag commits a generation while its relocation
|
||||
* copy is in flight.
|
||||
*/
|
||||
|
||||
FAR uint8_t *metabuf;
|
||||
|
||||
uint32_t entries_per_blk; /* Dirents in one read/write block */
|
||||
uint32_t max_entries; /* Dirents that fit in one ring block */
|
||||
|
||||
FAR struct xipfs_extent_s *extents;
|
||||
uint32_t nextents;
|
||||
|
||||
/* Single guard over extent metadata, the allocator and the pin counts.
|
||||
* The map path holds it across "range check -> resolve -> take pin" and
|
||||
* defrag holds it across "observe pincount == 0 -> relocate", so a new
|
||||
* mapping cannot slip in between the compactor's check and its move.
|
||||
*/
|
||||
|
||||
rmutex_t lock;
|
||||
|
||||
bool unmounted;
|
||||
|
||||
#ifdef CONFIG_FS_XIPFS_FAULT_INJECT
|
||||
bool media_dead;
|
||||
int32_t fault_countdown; /* Negative disables injection */
|
||||
uint8_t fault_mode; /* XIPFS_FAULT_CLEAN or XIPFS_FAULT_TORN */
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Per open file state, hung off filep->f_priv */
|
||||
|
||||
struct xipfs_file_s
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
|
||||
/* Create-time write state. NOR programs whole pages, so bytes are
|
||||
* accumulated here and flushed a page at a time.
|
||||
*/
|
||||
|
||||
FAR uint8_t *wrbuf;
|
||||
uint32_t wrpos; /* Bytes buffered but not yet programmed */
|
||||
uint32_t written; /* Bytes already programmed to flash */
|
||||
uint32_t erased; /* Blocks of the extent already erased */
|
||||
bool writing;
|
||||
bool greedy; /* Extent was over-reserved, trim at close */
|
||||
};
|
||||
|
||||
/* Directory enumeration state */
|
||||
|
||||
/* An open directory: which directory it is, and how far the walk has got */
|
||||
|
||||
struct xipfs_dir_s
|
||||
{
|
||||
struct fs_dirent_s base;
|
||||
uint16_t dirid;
|
||||
uint32_t index;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Public Data
|
||||
****************************************************************************/
|
||||
|
||||
#undef EXTERN
|
||||
#if defined(__cplusplus)
|
||||
#define EXTERN extern "C"
|
||||
extern "C"
|
||||
{
|
||||
#else
|
||||
#define EXTERN extern
|
||||
#endif
|
||||
|
||||
EXTERN const struct mountpt_operations g_xipfs_operations;
|
||||
|
||||
/****************************************************************************
|
||||
* Public Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
/* xipfs_flash.c ************************************************************/
|
||||
|
||||
int xipfs_flash_probe(FAR struct xipfs_mount_s *fs);
|
||||
int xipfs_flash_read(FAR struct xipfs_mount_s *fs, uint32_t block,
|
||||
uint32_t offset, FAR void *buffer, size_t nbytes);
|
||||
int xipfs_flash_write(FAR struct xipfs_mount_s *fs, uint32_t block,
|
||||
uint32_t offset, FAR const void *buffer,
|
||||
size_t nbytes);
|
||||
int xipfs_flash_erase(FAR struct xipfs_mount_s *fs, uint32_t block,
|
||||
uint32_t nblocks);
|
||||
|
||||
/* Direct address of a block, or NULL when the media is not memory mapped */
|
||||
|
||||
FAR uint8_t *xipfs_flash_addr(FAR struct xipfs_mount_s *fs, uint32_t block);
|
||||
|
||||
/* xipfs_meta.c *************************************************************/
|
||||
|
||||
int xipfs_meta_format(FAR struct xipfs_mount_s *fs);
|
||||
int xipfs_meta_mount(FAR struct xipfs_mount_s *fs);
|
||||
int xipfs_meta_commit(FAR struct xipfs_mount_s *fs);
|
||||
void xipfs_meta_freeextents(FAR struct xipfs_mount_s *fs);
|
||||
|
||||
FAR struct xipfs_extent_s *xipfs_meta_find(FAR struct xipfs_mount_s *fs,
|
||||
uint16_t parent,
|
||||
FAR const char *name,
|
||||
size_t namelen);
|
||||
FAR struct xipfs_extent_s *xipfs_meta_add(FAR struct xipfs_mount_s *fs,
|
||||
uint16_t parent,
|
||||
FAR const char *name,
|
||||
size_t namelen, bool isdir,
|
||||
uint32_t start_block,
|
||||
uint32_t nblocks, uint32_t size);
|
||||
bool xipfs_meta_haschildren(FAR struct xipfs_mount_s *fs, uint16_t dirid);
|
||||
void xipfs_meta_path(FAR struct xipfs_mount_s *fs,
|
||||
FAR struct xipfs_extent_s *ext,
|
||||
FAR char *buf, size_t buflen);
|
||||
void xipfs_meta_detach(FAR struct xipfs_mount_s *fs,
|
||||
FAR struct xipfs_extent_s *ext);
|
||||
|
||||
/* xipfs_alloc.c ************************************************************/
|
||||
|
||||
int xipfs_alloc_init(FAR struct xipfs_mount_s *fs);
|
||||
void xipfs_alloc_rebuild(FAR struct xipfs_mount_s *fs);
|
||||
int xipfs_alloc(FAR struct xipfs_mount_s *fs, uint32_t nblocks,
|
||||
FAR uint32_t *start_block);
|
||||
void xipfs_free(FAR struct xipfs_mount_s *fs, uint32_t start_block,
|
||||
uint32_t nblocks);
|
||||
uint32_t xipfs_alloc_largestrun(FAR struct xipfs_mount_s *fs);
|
||||
uint32_t xipfs_alloc_freeblocks(FAR struct xipfs_mount_s *fs);
|
||||
|
||||
/* xipfs_mmap.c *************************************************************/
|
||||
|
||||
int xipfs_mmap(FAR struct file *filep, FAR struct mm_map_entry_s *map);
|
||||
|
||||
/* xipfs_defrag.c ***********************************************************/
|
||||
|
||||
int xipfs_defrag(FAR struct xipfs_mount_s *fs, uint32_t max_ms,
|
||||
FAR struct xipfs_defrag_result_s *result);
|
||||
int xipfs_listpinned(FAR struct xipfs_mount_s *fs,
|
||||
FAR struct xipfs_pinned_entry_s *entries,
|
||||
size_t nentries, FAR size_t *count);
|
||||
|
||||
/****************************************************************************
|
||||
* Inline Functions
|
||||
****************************************************************************/
|
||||
|
||||
static inline int xipfs_lock(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
return nxrmutex_lock(&fs->lock);
|
||||
}
|
||||
|
||||
static inline void xipfs_unlock(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
nxrmutex_unlock(&fs->lock);
|
||||
}
|
||||
|
||||
#undef EXTERN
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __FS_XIPFS_XIPFS_H */
|
||||
272
fs/xipfs/xipfs_alloc.c
Normal file
272
fs/xipfs/xipfs_alloc.c
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/****************************************************************************
|
||||
* fs/xipfs/xipfs_alloc.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
*
|
||||
* Allocation is in whole erase blocks and always contiguous. Because a
|
||||
* file's full size is known when it is created, the exact extent is
|
||||
* reserved up front and never grows, so a file can never become internally
|
||||
* fragmented. The only source of fragmentation is the holes left behind by
|
||||
* deletes, which is what the defragmenter coalesces.
|
||||
*
|
||||
* The free map is a bitmap over the data region, derived entirely from the
|
||||
* committed directory: it is state that can always be rebuilt, never state
|
||||
* that has to be recovered.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <debug.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nuttx/bits.h>
|
||||
#include <nuttx/kmalloc.h>
|
||||
|
||||
#include "xipfs.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_alloc_init
|
||||
*
|
||||
* Description:
|
||||
* Allocate the free bitmap. Sized for the data region only.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_alloc_init(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
fs->bitmapsize = BITS_TO_LONGS(fs->data_nblocks) * sizeof(unsigned long);
|
||||
fs->bitmap = kmm_zalloc(fs->bitmapsize);
|
||||
|
||||
if (fs->bitmap == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_alloc_rebuild
|
||||
*
|
||||
* Description:
|
||||
* Recompute the free bitmap from the extent list. Called after mount and
|
||||
* after any change that moves extents.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void xipfs_alloc_rebuild(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
uint32_t index;
|
||||
|
||||
memset(fs->bitmap, 0, fs->bitmapsize);
|
||||
|
||||
for (ext = fs->extents; ext != NULL; ext = ext->flink)
|
||||
{
|
||||
/* A directory record owns no blocks, and its start_block lies below
|
||||
* the data region, so the index must not be formed for one.
|
||||
*/
|
||||
|
||||
if (ext->nblocks == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
index = ext->start_block - fs->data_start;
|
||||
|
||||
DEBUGASSERT(index + ext->nblocks <= fs->data_nblocks);
|
||||
bitmap_set(fs->bitmap, index, ext->nblocks);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_alloc
|
||||
*
|
||||
* Description:
|
||||
* Reserve a contiguous run of 'nblocks' erase blocks using best fit.
|
||||
*
|
||||
* This deliberately does NOT invoke the defragmenter on failure. A
|
||||
* caller that hits -ENOSPC gets to decide whether compacting is worth it
|
||||
* right now, and the defragmenter therefore always runs from a known
|
||||
* clean state rather than from inside a half-finished allocation.
|
||||
*
|
||||
* Returned Value:
|
||||
* OK with *start_block set, or -ENOSPC if no single contiguous run of
|
||||
* the requested size exists.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_alloc(FAR struct xipfs_mount_s *fs, uint32_t nblocks,
|
||||
FAR uint32_t *start_block)
|
||||
{
|
||||
uint32_t best_start = 0;
|
||||
uint32_t best_len = 0;
|
||||
uint32_t run_start = 0;
|
||||
uint32_t run_len = 0;
|
||||
uint32_t i;
|
||||
int ret;
|
||||
|
||||
if (nblocks == 0)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i <= fs->data_nblocks; i++)
|
||||
{
|
||||
bool used = (i == fs->data_nblocks) || test_bit(i, fs->bitmap) != 0;
|
||||
|
||||
if (!used)
|
||||
{
|
||||
if (run_len == 0)
|
||||
{
|
||||
run_start = i;
|
||||
}
|
||||
|
||||
run_len++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* End of a free run. Keep it if it is the tightest fit so far. */
|
||||
|
||||
if (run_len >= nblocks && (best_len == 0 || run_len < best_len))
|
||||
{
|
||||
best_len = run_len;
|
||||
best_start = run_start;
|
||||
}
|
||||
|
||||
run_len = 0;
|
||||
}
|
||||
|
||||
if (best_len == 0)
|
||||
{
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
/* The scan already proved this run free under the mount lock, so -EBUSY
|
||||
* is not a full volume: the bitmap and the extent list disagree.
|
||||
*/
|
||||
|
||||
ret = bitmap_allocate_region(fs->bitmap, best_start, nblocks);
|
||||
if (ret < 0)
|
||||
{
|
||||
ferr("ERROR: Free run %" PRIu32 "+%" PRIu32 " is already allocated\n",
|
||||
best_start, nblocks);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*start_block = fs->data_start + best_start;
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_free
|
||||
*
|
||||
* Description:
|
||||
* Release a previously allocated run. Coalescing is implicit: the run
|
||||
* simply becomes part of whatever free neighbours surround it.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void xipfs_free(FAR struct xipfs_mount_s *fs, uint32_t start_block,
|
||||
uint32_t nblocks)
|
||||
{
|
||||
/* Freeing nothing is a no-op. This is not a corner case to tolerate but
|
||||
* the correct answer for an extent whose reservation never landed: a
|
||||
* create cut short before xipfs_reserve succeeds leaves start_block at 0,
|
||||
* which is below the data region, and the cleanup path still hands that
|
||||
* extent here. A live extent always has nblocks > 0, so guarding on the
|
||||
* length is exactly equivalent to guarding on "was this ever reserved".
|
||||
*/
|
||||
|
||||
if (nblocks == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUGASSERT(start_block >= fs->data_start);
|
||||
DEBUGASSERT(start_block + nblocks <= fs->data_start + fs->data_nblocks);
|
||||
|
||||
bitmap_clear(fs->bitmap, start_block - fs->data_start, nblocks);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_alloc_largestrun
|
||||
*
|
||||
* Description:
|
||||
* Length in blocks of the largest contiguous free run. This is what
|
||||
* tells a caller whose allocation just failed whether a retry after
|
||||
* defragmenting can possibly succeed.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
uint32_t xipfs_alloc_largestrun(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
uint32_t best = 0;
|
||||
uint32_t run = 0;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < fs->data_nblocks; i++)
|
||||
{
|
||||
if (test_bit(i, fs->bitmap))
|
||||
{
|
||||
run = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
run++;
|
||||
if (run > best)
|
||||
{
|
||||
best = run;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_alloc_freeblocks
|
||||
****************************************************************************/
|
||||
|
||||
uint32_t xipfs_alloc_freeblocks(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
uint32_t count = 0;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < fs->data_nblocks; i++)
|
||||
{
|
||||
if (!test_bit(i, fs->bitmap))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
468
fs/xipfs/xipfs_defrag.c
Normal file
468
fs/xipfs/xipfs_defrag.c
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
/****************************************************************************
|
||||
* fs/xipfs/xipfs_defrag.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
*
|
||||
* Because files are written once at a known size and never grow, a file can
|
||||
* never fragment internally. The only thing that fragments is free space,
|
||||
* via the holes deletes leave behind. So this is not a continuous
|
||||
* compactor: it is an occasional coalescing pass, invoked explicitly.
|
||||
*
|
||||
* It is structured as a loop of atomic relocations. Each relocation copies
|
||||
* one closed, unpinned extent into free space, commits a new metadata
|
||||
* generation naming the new location, and only then erases the blocks it
|
||||
* vacated. Every iteration boundary is therefore a fully consistent
|
||||
* layout, merely a less compact one, and every way of stopping -- a time
|
||||
* budget, a pinned extent in the way, a media error, a power cut -- lands
|
||||
* on one of those boundaries. Nothing is ever left half moved.
|
||||
*
|
||||
* The pass is best effort by design. It reports what it achieved and why
|
||||
* it stopped rather than succeeding or failing, because the caller asked
|
||||
* for it after an allocation did not fit and what it actually needs to know
|
||||
* is whether retrying that allocation can now work.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <debug.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nuttx/clock.h>
|
||||
|
||||
#include "xipfs.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_defrag_findlowestfree
|
||||
*
|
||||
* Description:
|
||||
* Find the lowest addressed free run in the data region.
|
||||
*
|
||||
* Returned Value:
|
||||
* true if a free run exists, with its start and length reported.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static bool xipfs_defrag_findlowestfree(FAR struct xipfs_mount_s *fs,
|
||||
FAR uint32_t *start,
|
||||
FAR uint32_t *len)
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
uint32_t block;
|
||||
uint32_t run;
|
||||
bool used;
|
||||
|
||||
for (block = fs->data_start;
|
||||
block < fs->data_start + fs->data_nblocks;
|
||||
block++)
|
||||
{
|
||||
used = false;
|
||||
|
||||
for (ext = fs->extents; ext != NULL; ext = ext->flink)
|
||||
{
|
||||
if (block >= ext->start_block &&
|
||||
block < ext->start_block + ext->nblocks)
|
||||
{
|
||||
used = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (used)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Measure the run starting here */
|
||||
|
||||
run = 0;
|
||||
while (block + run < fs->data_start + fs->data_nblocks)
|
||||
{
|
||||
bool inuse = false;
|
||||
|
||||
for (ext = fs->extents; ext != NULL; ext = ext->flink)
|
||||
{
|
||||
if (block + run >= ext->start_block &&
|
||||
block + run < ext->start_block + ext->nblocks)
|
||||
{
|
||||
inuse = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (inuse)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
run++;
|
||||
}
|
||||
|
||||
*start = block;
|
||||
*len = run;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_defrag_pickvictim
|
||||
*
|
||||
* Description:
|
||||
* Choose the unpinned extent that starts immediately above the given free
|
||||
* run and fits inside it. Moving that one down is what merges the hole
|
||||
* with whatever free space follows the extent.
|
||||
*
|
||||
* Returned Value:
|
||||
* The extent to relocate, or NULL if none qualifies. 'blocked' is set
|
||||
* when the only candidates were rejected because they are pinned, which
|
||||
* is the difference between "nothing left to do" and "cannot proceed
|
||||
* until something is unloaded".
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static FAR struct xipfs_extent_s *
|
||||
xipfs_defrag_pickvictim(FAR struct xipfs_mount_s *fs, uint32_t free_start,
|
||||
uint32_t free_len, FAR bool *pin_blocked,
|
||||
FAR bool *open_blocked,
|
||||
FAR uint32_t *pinned_blocks)
|
||||
{
|
||||
FAR struct xipfs_extent_s *best = NULL;
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
|
||||
*pin_blocked = false;
|
||||
*open_blocked = false;
|
||||
|
||||
for (ext = fs->extents; ext != NULL; ext = ext->flink)
|
||||
{
|
||||
if (ext->isdir)
|
||||
{
|
||||
/* A directory is a metadata record with no blocks to move */
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ext->start_block <= free_start)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ext->nblocks > free_len)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ext->pincount > 0)
|
||||
{
|
||||
/* Pinned extents sitting mid-flash split the free space and bound
|
||||
* how much can ever be reclaimed. That is inherent to executing
|
||||
* in place, not a defect: the module is running from those very
|
||||
* blocks.
|
||||
*/
|
||||
|
||||
*pin_blocked = true;
|
||||
*pinned_blocks += ext->nblocks;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ext->writing || ext->openrefs > 0)
|
||||
{
|
||||
/* Merely open, not mapped. Still not movable -- a reader holds a
|
||||
* file position against it -- but the caller resolves this by
|
||||
* closing a descriptor rather than by unloading a module, so it
|
||||
* is reported separately.
|
||||
*/
|
||||
|
||||
*open_blocked = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Prefer the extent closest to the hole */
|
||||
|
||||
if (best == NULL || ext->start_block < best->start_block)
|
||||
{
|
||||
best = ext;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_defrag_relocate
|
||||
*
|
||||
* Description:
|
||||
* Perform one atomic relocation of 'ext' to 'dest'.
|
||||
*
|
||||
* The order is load bearing: copy the data in full, commit the metadata
|
||||
* that names the new location, and only then erase the old blocks. A
|
||||
* power loss before the commit leaves the old generation live and the old
|
||||
* data intact, so the copy is simply forgotten. A power loss after it
|
||||
* leaves the new location live and the old blocks merely stale, which the
|
||||
* next mount reclaims because nothing references them.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int xipfs_defrag_relocate(FAR struct xipfs_mount_s *fs,
|
||||
FAR struct xipfs_extent_s *ext,
|
||||
uint32_t dest)
|
||||
{
|
||||
uint32_t old_start = ext->start_block;
|
||||
uint32_t nblocks = ext->nblocks;
|
||||
uint32_t pages_per_block;
|
||||
uint32_t block;
|
||||
uint32_t page;
|
||||
int ret;
|
||||
|
||||
pages_per_block = fs->geo.erasesize / fs->geo.blocksize;
|
||||
|
||||
/* The destination must already be erased. Erasing it here rather than
|
||||
* assuming keeps the invariant local and cheap: it is free space, so
|
||||
* nothing can be lost by erasing it.
|
||||
*/
|
||||
|
||||
ret = xipfs_flash_erase(fs, dest, nblocks);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Copy a page at a time. Block granular allocation means no source block
|
||||
* ever holds a mix of live and dead data, so the staging buffer is one
|
||||
* program page rather than a whole erase block.
|
||||
*/
|
||||
|
||||
for (block = 0; block < nblocks; block++)
|
||||
{
|
||||
for (page = 0; page < pages_per_block; page++)
|
||||
{
|
||||
uint32_t offset = page * fs->geo.blocksize;
|
||||
|
||||
ret = xipfs_flash_read(fs, old_start + block, offset, fs->stage,
|
||||
fs->geo.blocksize);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = xipfs_flash_write(fs, dest + block, offset, fs->stage,
|
||||
fs->geo.blocksize);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Commit point. Until this succeeds the extent still officially lives at
|
||||
* its old location and everything written above is invisible.
|
||||
*/
|
||||
|
||||
ext->start_block = dest;
|
||||
|
||||
ret = xipfs_meta_commit(fs);
|
||||
if (ret < 0)
|
||||
{
|
||||
ext->start_block = old_start;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* The move is now durable. Erasing the vacated blocks is pure hygiene:
|
||||
* if it fails or is interrupted, the layout is still correct and the
|
||||
* blocks are simply unreferenced until something reuses them.
|
||||
*/
|
||||
|
||||
xipfs_alloc_rebuild(fs);
|
||||
xipfs_flash_erase(fs, old_start, nblocks);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_defrag
|
||||
*
|
||||
* Description:
|
||||
* Run one best-effort compaction pass. Called with the mount unlocked.
|
||||
*
|
||||
* Returned Value:
|
||||
* OK on any clean stop, including one that made no progress at all. A
|
||||
* negative errno only for a failure that prevented the pass from running.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_defrag(FAR struct xipfs_mount_s *fs, uint32_t max_ms,
|
||||
FAR struct xipfs_defrag_result_s *result)
|
||||
{
|
||||
FAR struct xipfs_extent_s *victim;
|
||||
clock_t start_tick;
|
||||
uint32_t free_start;
|
||||
uint32_t free_len;
|
||||
uint32_t pinned_blocks;
|
||||
bool pin_blocked;
|
||||
bool open_blocked;
|
||||
int ret;
|
||||
|
||||
memset(result, 0, sizeof(*result));
|
||||
result->reason = XIPFS_DEFRAG_DONE;
|
||||
|
||||
ret = xipfs_lock(fs);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
start_tick = clock_systime_ticks();
|
||||
|
||||
for (; ; )
|
||||
{
|
||||
/* Check the caller's budget between moves, never during one */
|
||||
|
||||
if (max_ms > 0 &&
|
||||
TICK2MSEC(clock_systime_ticks() - start_tick) >= max_ms)
|
||||
{
|
||||
result->reason = XIPFS_DEFRAG_TIME_BUDGET;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!xipfs_defrag_findlowestfree(fs, &free_start, &free_len))
|
||||
{
|
||||
/* No free space at all; nothing to compact into */
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
pinned_blocks = 0;
|
||||
victim = xipfs_defrag_pickvictim(fs, free_start, free_len,
|
||||
&pin_blocked, &open_blocked,
|
||||
&pinned_blocks);
|
||||
if (victim == NULL)
|
||||
{
|
||||
/* A live mapping is the more serious obstruction, so it wins the
|
||||
* report when both are present.
|
||||
*/
|
||||
|
||||
if (pin_blocked)
|
||||
{
|
||||
result->reason = XIPFS_DEFRAG_BLOCKED_PINS;
|
||||
result->blocks_pinned = pinned_blocks;
|
||||
}
|
||||
else if (open_blocked)
|
||||
{
|
||||
result->reason = XIPFS_DEFRAG_BLOCKED_OPEN;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ret = xipfs_defrag_relocate(fs, victim, free_start);
|
||||
if (ret < 0)
|
||||
{
|
||||
/* Stopped at a valid intermediate layout. Nothing is partially
|
||||
* applied, so this is a clean stop rather than an error the
|
||||
* caller has to recover from.
|
||||
*/
|
||||
|
||||
result->reason = (ret == -ENOMEM) ? XIPFS_DEFRAG_BLOCKED_RAM
|
||||
: XIPFS_DEFRAG_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
result->extents_moved++;
|
||||
result->blocks_reclaimed += victim->nblocks;
|
||||
}
|
||||
|
||||
result->largest_free_run = (size_t)xipfs_alloc_largestrun(fs) *
|
||||
fs->geo.erasesize;
|
||||
|
||||
finfo("xipfs: defrag moved %" PRIu32 " extents, largest run %zu, "
|
||||
"reason %d\n", result->extents_moved, result->largest_free_run,
|
||||
result->reason);
|
||||
|
||||
xipfs_unlock(fs);
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_listpinned
|
||||
*
|
||||
* Description:
|
||||
* Report the extents currently holding XIP pins. Without this, a caller
|
||||
* told that defrag is blocked by pins has no way to find out which module
|
||||
* to unload, and the report is a dead end.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_listpinned(FAR struct xipfs_mount_s *fs,
|
||||
FAR struct xipfs_pinned_entry_s *entries,
|
||||
size_t nentries, FAR size_t *count)
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
size_t n = 0;
|
||||
int ret;
|
||||
|
||||
ret = xipfs_lock(fs);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (ext = fs->extents; ext != NULL; ext = ext->flink)
|
||||
{
|
||||
if (ext->pincount == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (n < nentries)
|
||||
{
|
||||
xipfs_meta_path(fs, ext, entries[n].path,
|
||||
sizeof(entries[n].path));
|
||||
entries[n].start_block = ext->start_block;
|
||||
entries[n].nblocks = ext->nblocks;
|
||||
entries[n].pincount = ext->pincount;
|
||||
}
|
||||
|
||||
n++;
|
||||
}
|
||||
|
||||
/* Report the true total even when the caller's array was too small, so a
|
||||
* short buffer is detectable rather than silently truncating.
|
||||
*/
|
||||
|
||||
*count = n;
|
||||
|
||||
xipfs_unlock(fs);
|
||||
return n > nentries ? -ENOSPC : OK;
|
||||
}
|
||||
455
fs/xipfs/xipfs_flash.c
Normal file
455
fs/xipfs/xipfs_flash.c
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
/****************************************************************************
|
||||
* fs/xipfs/xipfs_flash.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
*
|
||||
* Every media access in xipfs funnels through this file. That is
|
||||
* deliberate: on a single NOR die without read-while-write, an erase stalls
|
||||
* all fetches from the die, so erase slicing, running the erase routine
|
||||
* from RAM and erase-suspend/resume all have to be introduced here once the
|
||||
* actual NOR part is known. Keeping the chokepoint from the start means
|
||||
* adding them later is a local change rather than a refactor.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <debug.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nuttx/fs/ioctl.h>
|
||||
#include <nuttx/kmalloc.h>
|
||||
|
||||
#include "xipfs.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_flash_fault
|
||||
*
|
||||
* Description:
|
||||
* Test hook modelling a power loss at an arbitrary flash operation. When
|
||||
* the countdown reaches zero the operation fails and the medium latches
|
||||
* dead for the remainder of the run, which is what a real power cut looks
|
||||
* like to the code above: no further write or erase ever completes.
|
||||
*
|
||||
* A real cut tears exactly the one operation that was in flight at the
|
||||
* instant power dropped; everything nominally after it simply never runs.
|
||||
* So only the first failing operation reports XIPFS_OP_FAIL_TORN, and only
|
||||
* when the caller armed torn mode -- every operation past the dead latch
|
||||
* fails clean, having never touched the medium.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_FS_XIPFS_FAULT_INJECT
|
||||
enum xipfs_fault_e
|
||||
{
|
||||
XIPFS_OP_PROCEED = 0, /* Not the injected point; run the operation */
|
||||
XIPFS_OP_FAIL_CLEAN, /* Fail with the medium left untouched */
|
||||
XIPFS_OP_FAIL_TORN /* Fail after half of the operation has landed */
|
||||
};
|
||||
|
||||
static enum xipfs_fault_e xipfs_flash_fault(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
if (fs->media_dead)
|
||||
{
|
||||
return XIPFS_OP_FAIL_CLEAN;
|
||||
}
|
||||
|
||||
if (fs->fault_countdown < 0)
|
||||
{
|
||||
return XIPFS_OP_PROCEED;
|
||||
}
|
||||
|
||||
if (fs->fault_countdown == 0)
|
||||
{
|
||||
finfo("xipfs: injected %s media failure\n",
|
||||
fs->fault_mode == XIPFS_FAULT_TORN ? "torn" : "clean");
|
||||
fs->media_dead = true;
|
||||
return fs->fault_mode == XIPFS_FAULT_TORN ?
|
||||
XIPFS_OP_FAIL_TORN : XIPFS_OP_FAIL_CLEAN;
|
||||
}
|
||||
|
||||
fs->fault_countdown--;
|
||||
return XIPFS_OP_PROCEED;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_flash_tear_write
|
||||
*
|
||||
* Description:
|
||||
* Model a NOR page program cut short by power loss: the first half of the
|
||||
* page reaches the medium and the rest is left at the erased value, so a
|
||||
* reader sees a page that is neither its old contents nor the intended new
|
||||
* ones -- which is what makes a torn metadata generation fail its CRC
|
||||
* rather than look valid. Every xipfs write is a single read/write block,
|
||||
* so the tear is expressed by reprogramming that block with its tail
|
||||
* replaced by the erased value; a transient allocation failure simply
|
||||
* leaves the operation a clean one, still a legitimate power-loss outcome.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void xipfs_flash_tear_write(FAR struct xipfs_mount_s *fs,
|
||||
uint32_t block, uint32_t offset,
|
||||
FAR const void *buffer, size_t nbytes)
|
||||
{
|
||||
off_t byteoff = (off_t)block * fs->geo.erasesize + offset;
|
||||
size_t half = nbytes / 2;
|
||||
FAR uint8_t *torn;
|
||||
|
||||
torn = kmm_malloc(nbytes);
|
||||
if (torn == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(torn, buffer, half);
|
||||
memset(torn + half, 0xff, nbytes - half);
|
||||
|
||||
MTD_BWRITE(fs->mtd, byteoff / fs->geo.blocksize,
|
||||
nbytes / fs->geo.blocksize, torn);
|
||||
|
||||
kmm_free(torn);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_flash_tear_erase
|
||||
*
|
||||
* Description:
|
||||
* Model a sector erase cut short: the leading half of the sector reaches
|
||||
* the erased state while the rest still holds its old contents. Expressed
|
||||
* by programming the erased value over the first half of the read/write
|
||||
* blocks, which is a simulation the RAM-backed test medium accepts even
|
||||
* though real NOR could not be erased this way.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void xipfs_flash_tear_erase(FAR struct xipfs_mount_s *fs,
|
||||
uint32_t block)
|
||||
{
|
||||
uint32_t blks_per_sector = fs->geo.erasesize / fs->geo.blocksize;
|
||||
off_t startblk = (off_t)block * blks_per_sector;
|
||||
uint32_t half = blks_per_sector / 2;
|
||||
FAR uint8_t *ones;
|
||||
uint32_t i;
|
||||
|
||||
if (half == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ones = kmm_malloc(fs->geo.blocksize);
|
||||
if (ones == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
memset(ones, 0xff, fs->geo.blocksize);
|
||||
|
||||
for (i = 0; i < half; i++)
|
||||
{
|
||||
MTD_BWRITE(fs->mtd, startblk + i, 1, ones);
|
||||
}
|
||||
|
||||
kmm_free(ones);
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_flash_probe
|
||||
*
|
||||
* Description:
|
||||
* Read the MTD geometry and resolve the direct address of the media.
|
||||
*
|
||||
* A media that cannot answer BIOC_XIPBASE is still perfectly usable as a
|
||||
* filesystem; it simply cannot serve execute-in-place mappings, and
|
||||
* strict XIP requests against it fail loudly rather than silently
|
||||
* degrading into a RAM copy.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_flash_probe(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
FAR void *xipbase = NULL;
|
||||
int ret;
|
||||
|
||||
ret = MTD_IOCTL(fs->mtd, MTDIOC_GEOMETRY,
|
||||
(unsigned long)(uintptr_t)&fs->geo);
|
||||
if (ret < 0)
|
||||
{
|
||||
ferr("ERROR: MTDIOC_GEOMETRY failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (fs->geo.blocksize == 0 || fs->geo.erasesize == 0 ||
|
||||
(fs->geo.erasesize % fs->geo.blocksize) != 0)
|
||||
{
|
||||
ferr("ERROR: Bad geometry: blocksize=%" PRIu32 " erasesize=%" PRIu32
|
||||
"\n", fs->geo.blocksize, fs->geo.erasesize);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (fs->geo.neraseblocks < XIPFS_MIN_BLOCKS)
|
||||
{
|
||||
ferr("ERROR: Volume too small: %" PRIu32 " blocks\n",
|
||||
fs->geo.neraseblocks);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* The metadata generation header occupies the first read/write block of a
|
||||
* ring block and the dirents follow it, so a ring block must hold at
|
||||
* least the header plus one dirent.
|
||||
*/
|
||||
|
||||
if (fs->geo.erasesize <= fs->geo.blocksize)
|
||||
{
|
||||
ferr("ERROR: erasesize must exceed blocksize for the metadata ring\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = MTD_IOCTL(fs->mtd, BIOC_XIPBASE, (unsigned long)(uintptr_t)&xipbase);
|
||||
if (ret >= 0 && xipbase != NULL)
|
||||
{
|
||||
fs->xipbase = (FAR uint8_t *)xipbase;
|
||||
finfo("xipfs: XIP base %p\n", fs->xipbase);
|
||||
}
|
||||
else
|
||||
{
|
||||
fs->xipbase = NULL;
|
||||
fwarn("xipfs: media is not memory mapped; XIP unavailable\n");
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_flash_addr
|
||||
*
|
||||
* Description:
|
||||
* Return the direct address of an erase block, or NULL when the media is
|
||||
* not memory mapped.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
FAR uint8_t *xipfs_flash_addr(FAR struct xipfs_mount_s *fs, uint32_t block)
|
||||
{
|
||||
if (fs->xipbase == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return fs->xipbase + (size_t)block * fs->geo.erasesize;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_flash_read
|
||||
*
|
||||
* Description:
|
||||
* Read nbytes at 'offset' within erase block 'block'.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_flash_read(FAR struct xipfs_mount_s *fs, uint32_t block,
|
||||
uint32_t offset, FAR void *buffer, size_t nbytes)
|
||||
{
|
||||
off_t byteoff;
|
||||
ssize_t nread;
|
||||
|
||||
DEBUGASSERT(offset + nbytes <= fs->geo.erasesize);
|
||||
|
||||
if (nbytes == 0)
|
||||
{
|
||||
return OK;
|
||||
}
|
||||
|
||||
byteoff = (off_t)block * fs->geo.erasesize + offset;
|
||||
|
||||
/* Prefer the byte oriented read when the driver offers one. Otherwise
|
||||
* fall back to reading whole read/write blocks through the staging
|
||||
* buffer.
|
||||
*/
|
||||
|
||||
if (fs->mtd->read != NULL)
|
||||
{
|
||||
nread = MTD_READ(fs->mtd, byteoff, nbytes, buffer);
|
||||
if (nread < 0)
|
||||
{
|
||||
return (int)nread;
|
||||
}
|
||||
|
||||
return nread == (ssize_t)nbytes ? OK : -EIO;
|
||||
}
|
||||
|
||||
/* Block oriented fallback. Only whole read/write blocks can be
|
||||
* transferred, so the caller's range must be aligned. Every internal
|
||||
* caller either reads a whole block or reads from a byte capable driver,
|
||||
* so this is an assertion rather than a supported partial path.
|
||||
*/
|
||||
|
||||
if ((offset % fs->geo.blocksize) != 0 ||
|
||||
(nbytes % fs->geo.blocksize) != 0)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
nread = MTD_BREAD(fs->mtd, byteoff / fs->geo.blocksize,
|
||||
nbytes / fs->geo.blocksize, buffer);
|
||||
if (nread < 0)
|
||||
{
|
||||
return (int)nread;
|
||||
}
|
||||
|
||||
return nread == (ssize_t)(nbytes / fs->geo.blocksize) ? OK : -EIO;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_flash_write
|
||||
*
|
||||
* Description:
|
||||
* Write nbytes at 'offset' within erase block 'block'. Both must be
|
||||
* multiples of the read/write block size: NOR is programmed in whole
|
||||
* pages, and every xipfs write path is structured to respect that.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_flash_write(FAR struct xipfs_mount_s *fs, uint32_t block,
|
||||
uint32_t offset, FAR const void *buffer,
|
||||
size_t nbytes)
|
||||
{
|
||||
off_t byteoff;
|
||||
ssize_t nwritten;
|
||||
|
||||
DEBUGASSERT(offset + nbytes <= fs->geo.erasesize);
|
||||
|
||||
if (nbytes == 0)
|
||||
{
|
||||
return OK;
|
||||
}
|
||||
|
||||
if ((offset % fs->geo.blocksize) != 0 ||
|
||||
(nbytes % fs->geo.blocksize) != 0)
|
||||
{
|
||||
ferr("ERROR: Unaligned write: offset=%" PRIu32 " nbytes=%zu\n",
|
||||
offset, nbytes);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FS_XIPFS_FAULT_INJECT
|
||||
switch (xipfs_flash_fault(fs))
|
||||
{
|
||||
case XIPFS_OP_FAIL_TORN:
|
||||
xipfs_flash_tear_write(fs, block, offset, buffer, nbytes);
|
||||
|
||||
/* Fall through: half the page has landed; report the cut. */
|
||||
|
||||
case XIPFS_OP_FAIL_CLEAN:
|
||||
return -EIO;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
byteoff = (off_t)block * fs->geo.erasesize + offset;
|
||||
|
||||
nwritten = MTD_BWRITE(fs->mtd, byteoff / fs->geo.blocksize,
|
||||
nbytes / fs->geo.blocksize, buffer);
|
||||
if (nwritten < 0)
|
||||
{
|
||||
return (int)nwritten;
|
||||
}
|
||||
|
||||
return nwritten == (ssize_t)(nbytes / fs->geo.blocksize) ? OK : -EIO;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_flash_erase
|
||||
*
|
||||
* Description:
|
||||
* Erase nblocks erase blocks starting at 'block'.
|
||||
*
|
||||
* On a single NOR die this is the operation that stalls instruction
|
||||
* fetches, so this is where erase slicing and erase-suspend belong once
|
||||
* the part is known. Callers must already have guaranteed that no live
|
||||
* XIP mapping covers the range: defrag does so by refusing to touch a
|
||||
* pinned extent.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_flash_erase(FAR struct xipfs_mount_s *fs, uint32_t block,
|
||||
uint32_t nblocks)
|
||||
{
|
||||
uint32_t i;
|
||||
int ret;
|
||||
|
||||
if (nblocks == 0)
|
||||
{
|
||||
return OK;
|
||||
}
|
||||
|
||||
DEBUGASSERT(block + nblocks <= fs->geo.neraseblocks);
|
||||
|
||||
/* Erase one block at a time. On real hardware this loop is the natural
|
||||
* place to bound interrupt latency; keeping the granularity here from the
|
||||
* beginning means the callers already tolerate a partial erase followed
|
||||
* by an error.
|
||||
*/
|
||||
|
||||
for (i = 0; i < nblocks; i++)
|
||||
{
|
||||
#ifdef CONFIG_FS_XIPFS_FAULT_INJECT
|
||||
switch (xipfs_flash_fault(fs))
|
||||
{
|
||||
case XIPFS_OP_FAIL_TORN:
|
||||
xipfs_flash_tear_erase(fs, block + i);
|
||||
|
||||
/* Fall through: half the sector is erased; report the cut. */
|
||||
|
||||
case XIPFS_OP_FAIL_CLEAN:
|
||||
return -EIO;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = MTD_ERASE(fs->mtd, block + i, 1);
|
||||
if (ret < 0)
|
||||
{
|
||||
ferr("ERROR: Erase of block %" PRIu32 " failed: %d\n",
|
||||
block + i, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
873
fs/xipfs/xipfs_meta.c
Normal file
873
fs/xipfs/xipfs_meta.c
Normal file
|
|
@ -0,0 +1,873 @@
|
|||
/****************************************************************************
|
||||
* fs/xipfs/xipfs_meta.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
*
|
||||
* Power-loss atomicity of the metadata commit lives here. The scheme is a
|
||||
* ping-pong ring of whole generations: each commit erases the next ring
|
||||
* slot, writes the dirent body, and finally writes the header block that
|
||||
* carries the sequence number and the CRC over header plus body.
|
||||
*
|
||||
* That final header write is the commit point, and it is a single
|
||||
* read/write block program -- the smallest unit the media can tear at. A
|
||||
* power loss before it leaves a slot whose header is still erased, so the
|
||||
* generation simply does not exist. A power loss during it leaves a header
|
||||
* that fails its own CRC. Either way mount falls back to the previous
|
||||
* generation, which is untouched because it lives in a different slot.
|
||||
*
|
||||
* The workload is tens of modules, so a whole directory fits in one erase
|
||||
* block. Rewriting all of it per commit is what buys the single-write
|
||||
* commit point and removes any need for journal replay logic.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <debug.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <nuttx/crc32.h>
|
||||
#include <nuttx/kmalloc.h>
|
||||
|
||||
#include "xipfs.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_bodyblocks
|
||||
*
|
||||
* Description:
|
||||
* Number of read/write blocks the dirent body of a generation occupies.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static uint32_t xipfs_meta_bodyblocks(FAR struct xipfs_mount_s *fs,
|
||||
uint32_t nentries)
|
||||
{
|
||||
if (nentries == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (nentries + fs->entries_per_blk - 1) / fs->entries_per_blk;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_verify
|
||||
*
|
||||
* Description:
|
||||
* Read the generation in ring slot 'slot' and verify it. On success
|
||||
* returns OK and reports the sequence number and entry count.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int xipfs_meta_verify(FAR struct xipfs_mount_s *fs, uint32_t slot,
|
||||
FAR uint32_t *seq, FAR uint32_t *nentries)
|
||||
{
|
||||
FAR struct xipfs_genhdr_s *hdr;
|
||||
uint32_t hdrfields[3];
|
||||
uint32_t running;
|
||||
uint32_t nbody;
|
||||
uint32_t crc;
|
||||
uint32_t i;
|
||||
int ret;
|
||||
|
||||
ret = xipfs_flash_read(fs, fs->meta_start + slot, 0, fs->metabuf,
|
||||
fs->geo.blocksize);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
hdr = (FAR struct xipfs_genhdr_s *)fs->metabuf;
|
||||
if (hdr->magic != XIPFS_GEN_MAGIC)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (hdr->nentries > fs->max_entries)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Snapshot the header before the body reads reuse the buffer */
|
||||
|
||||
hdrfields[0] = hdr->magic;
|
||||
hdrfields[1] = hdr->seq;
|
||||
hdrfields[2] = hdr->nentries;
|
||||
crc = hdr->crc;
|
||||
|
||||
/* Recompute the CRC over the header fields followed by every body block.
|
||||
* An empty generation is legitimate and simply has no body blocks.
|
||||
*/
|
||||
|
||||
running = crc32part((FAR const uint8_t *)hdrfields, sizeof(hdrfields), 0);
|
||||
nbody = xipfs_meta_bodyblocks(fs, hdrfields[2]);
|
||||
|
||||
for (i = 0; i < nbody; i++)
|
||||
{
|
||||
ret = xipfs_flash_read(fs, fs->meta_start + slot,
|
||||
(i + 1) * fs->geo.blocksize, fs->metabuf,
|
||||
fs->geo.blocksize);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
running = crc32part(fs->metabuf, fs->geo.blocksize, running);
|
||||
}
|
||||
|
||||
if (running != crc)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*seq = hdrfields[1];
|
||||
*nentries = hdrfields[2];
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_validate
|
||||
*
|
||||
* Description:
|
||||
* Check that the entries just loaded form a tree. Two identities must not
|
||||
* collide, every parent must name a live directory, and following parents
|
||||
* must reach the root -- a cycle on the medium would otherwise hang the
|
||||
* path walk rather than merely returning the wrong answer. Bounding the
|
||||
* climb by the entry count is what makes that terminate.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int xipfs_meta_validate(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
FAR struct xipfs_extent_s *other;
|
||||
FAR struct xipfs_extent_s *up;
|
||||
uint32_t steps;
|
||||
|
||||
for (ext = fs->extents; ext != NULL; ext = ext->flink)
|
||||
{
|
||||
for (other = ext->flink; other != NULL; other = other->flink)
|
||||
{
|
||||
if (other->id == ext->id)
|
||||
{
|
||||
ferr("ERROR: Duplicate id %u ('%s' and '%s')\n",
|
||||
ext->id, ext->name, other->name);
|
||||
return -EFTYPE;
|
||||
}
|
||||
|
||||
if (other->parent == ext->parent &&
|
||||
strcmp(other->name, ext->name) == 0)
|
||||
{
|
||||
ferr("ERROR: Duplicate name '%s' in directory %u\n",
|
||||
ext->name, ext->parent);
|
||||
return -EFTYPE;
|
||||
}
|
||||
}
|
||||
|
||||
up = ext;
|
||||
steps = 0;
|
||||
|
||||
while (up->parent != XIPFS_ROOT_ID)
|
||||
{
|
||||
if (++steps > fs->nextents)
|
||||
{
|
||||
ferr("ERROR: Directory cycle above '%s'\n", ext->name);
|
||||
return -EFTYPE;
|
||||
}
|
||||
|
||||
for (other = fs->extents; other != NULL; other = other->flink)
|
||||
{
|
||||
if (other->id == up->parent)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (other == NULL || !other->isdir)
|
||||
{
|
||||
ferr("ERROR: '%s' has no parent directory %u\n",
|
||||
up->name, up->parent);
|
||||
return -EFTYPE;
|
||||
}
|
||||
|
||||
up = other;
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_load
|
||||
*
|
||||
* Description:
|
||||
* Build the in-RAM entry list from the generation in ring slot 'slot'.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int xipfs_meta_load(FAR struct xipfs_mount_s *fs, uint32_t slot,
|
||||
uint32_t nentries)
|
||||
{
|
||||
FAR struct xipfs_dirent_s *dirent;
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
uint32_t nbody;
|
||||
uint32_t idx = 0;
|
||||
uint32_t i;
|
||||
uint32_t j;
|
||||
int ret;
|
||||
|
||||
xipfs_meta_freeextents(fs);
|
||||
|
||||
nbody = xipfs_meta_bodyblocks(fs, nentries);
|
||||
|
||||
for (i = 0; i < nbody; i++)
|
||||
{
|
||||
ret = xipfs_flash_read(fs, fs->meta_start + slot,
|
||||
(i + 1) * fs->geo.blocksize, fs->metabuf,
|
||||
fs->geo.blocksize);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (j = 0; j < fs->entries_per_blk && idx < nentries; j++, idx++)
|
||||
{
|
||||
dirent = (FAR struct xipfs_dirent_s *)
|
||||
(fs->metabuf + j * XIPFS_DIRENT_SIZE);
|
||||
|
||||
bool isdir = (dirent->flags & XIPFS_DIRENT_DIR) != 0;
|
||||
|
||||
/* The mount path is exactly where a corrupted medium must be
|
||||
* caught rather than trusted. Every entry needs an identity of
|
||||
* its own, a directory must carry no extent, and a file's extent
|
||||
* must lie inside the data region.
|
||||
*/
|
||||
|
||||
if (dirent->id == XIPFS_ROOT_ID ||
|
||||
dirent->name[0] == '\0' ||
|
||||
strchr(dirent->name, '/') != NULL)
|
||||
{
|
||||
ferr("ERROR: Corrupt dirent, id %u name '%s'\n",
|
||||
dirent->id, dirent->name);
|
||||
return -EFTYPE;
|
||||
}
|
||||
|
||||
if (isdir)
|
||||
{
|
||||
if (dirent->start_block != 0 || dirent->nblocks != 0 ||
|
||||
dirent->size != 0)
|
||||
{
|
||||
ferr("ERROR: Directory '%s' carries an extent\n",
|
||||
dirent->name);
|
||||
return -EFTYPE;
|
||||
}
|
||||
}
|
||||
else if (dirent->start_block < fs->data_start ||
|
||||
dirent->nblocks == 0 ||
|
||||
dirent->start_block + dirent->nblocks >
|
||||
fs->data_start + fs->data_nblocks)
|
||||
{
|
||||
ferr("ERROR: Corrupt dirent '%s' at %" PRIu32 "+%" PRIu32 "\n",
|
||||
dirent->name, dirent->start_block, dirent->nblocks);
|
||||
return -EFTYPE;
|
||||
}
|
||||
|
||||
ext = kmm_zalloc(sizeof(struct xipfs_extent_s));
|
||||
if (ext == NULL)
|
||||
{
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
strlcpy(ext->name, dirent->name, sizeof(ext->name));
|
||||
ext->fs = fs;
|
||||
ext->id = dirent->id;
|
||||
ext->parent = dirent->parent;
|
||||
ext->isdir = isdir;
|
||||
ext->size = dirent->size;
|
||||
ext->start_block = dirent->start_block;
|
||||
ext->nblocks = dirent->nblocks;
|
||||
|
||||
ext->flink = fs->extents;
|
||||
fs->extents = ext;
|
||||
fs->nextents++;
|
||||
}
|
||||
}
|
||||
|
||||
return xipfs_meta_validate(fs);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_newid
|
||||
*
|
||||
* Description:
|
||||
* The lowest identity not in use. Identities are stored on the medium
|
||||
* because parent references them, so they have to survive a remount; they
|
||||
* are reused once nothing refers to them. Zero is reserved for the root.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static uint16_t xipfs_meta_newid(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
uint16_t id;
|
||||
|
||||
for (id = 1; id != 0; id++)
|
||||
{
|
||||
for (ext = fs->extents; ext != NULL; ext = ext->flink)
|
||||
{
|
||||
if (ext->id == id)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ext == NULL)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_freeextents
|
||||
****************************************************************************/
|
||||
|
||||
void xipfs_meta_freeextents(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
FAR struct xipfs_extent_s *next;
|
||||
|
||||
for (ext = fs->extents; ext != NULL; ext = next)
|
||||
{
|
||||
next = ext->flink;
|
||||
kmm_free(ext);
|
||||
}
|
||||
|
||||
fs->extents = NULL;
|
||||
fs->nextents = 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_commit
|
||||
*
|
||||
* Description:
|
||||
* Write the current in-RAM directory as a new generation into the next
|
||||
* ring slot. Returns only after the commit point has landed, so a
|
||||
* successful return means the new state is durable and a failure means
|
||||
* the previous generation is still the live one.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_meta_commit(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
FAR struct xipfs_genhdr_s *hdr;
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
FAR struct xipfs_dirent_s *dirent;
|
||||
uint32_t hdrfields[3];
|
||||
uint32_t nentries = 0;
|
||||
uint32_t slot;
|
||||
uint32_t nbody;
|
||||
uint32_t crc;
|
||||
uint32_t i;
|
||||
uint32_t j;
|
||||
int ret;
|
||||
|
||||
/* Count the entries that will be committed. Unlinked extents are
|
||||
* already detached from the list, so they are simply absent.
|
||||
*/
|
||||
|
||||
for (ext = fs->extents; ext != NULL; ext = ext->flink)
|
||||
{
|
||||
nentries++;
|
||||
}
|
||||
|
||||
if (nentries > fs->max_entries)
|
||||
{
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
slot = (fs->meta_slot + 1) % fs->meta_nblocks;
|
||||
nbody = xipfs_meta_bodyblocks(fs, nentries);
|
||||
|
||||
/* Erase the target slot. Until the header write below lands, this slot
|
||||
* holds nothing valid and the live generation remains the old one.
|
||||
*/
|
||||
|
||||
ret = xipfs_flash_erase(fs, fs->meta_start + slot, 1);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
hdrfields[0] = XIPFS_GEN_MAGIC;
|
||||
hdrfields[1] = fs->meta_seq + 1;
|
||||
hdrfields[2] = nentries;
|
||||
crc = crc32part((FAR const uint8_t *)hdrfields, sizeof(hdrfields), 0);
|
||||
|
||||
/* Write the body first, accumulating the CRC as we go */
|
||||
|
||||
ext = fs->extents;
|
||||
for (i = 0; i < nbody; i++)
|
||||
{
|
||||
memset(fs->metabuf, 0xff, fs->geo.blocksize);
|
||||
|
||||
for (j = 0; j < fs->entries_per_blk && ext != NULL; j++)
|
||||
{
|
||||
dirent = (FAR struct xipfs_dirent_s *)
|
||||
(fs->metabuf + j * XIPFS_DIRENT_SIZE);
|
||||
|
||||
memset(dirent, 0, sizeof(*dirent));
|
||||
strlcpy(dirent->name, ext->name, sizeof(dirent->name));
|
||||
dirent->size = ext->size;
|
||||
dirent->start_block = ext->start_block;
|
||||
dirent->nblocks = ext->nblocks;
|
||||
dirent->flags = ext->isdir ? XIPFS_DIRENT_DIR : 0;
|
||||
dirent->id = ext->id;
|
||||
dirent->parent = ext->parent;
|
||||
|
||||
ext = ext->flink;
|
||||
}
|
||||
|
||||
crc = crc32part(fs->metabuf, fs->geo.blocksize, crc);
|
||||
|
||||
ret = xipfs_flash_write(fs, fs->meta_start + slot,
|
||||
(i + 1) * fs->geo.blocksize, fs->metabuf,
|
||||
fs->geo.blocksize);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* The commit point: a single read/write block program */
|
||||
|
||||
memset(fs->metabuf, 0xff, fs->geo.blocksize);
|
||||
hdr = (FAR struct xipfs_genhdr_s *)fs->metabuf;
|
||||
hdr->magic = hdrfields[0];
|
||||
hdr->seq = hdrfields[1];
|
||||
hdr->nentries = hdrfields[2];
|
||||
hdr->crc = crc;
|
||||
|
||||
ret = xipfs_flash_write(fs, fs->meta_start + slot, 0, fs->metabuf,
|
||||
fs->geo.blocksize);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
fs->meta_slot = slot;
|
||||
fs->meta_seq = hdrfields[1];
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_format
|
||||
*
|
||||
* Description:
|
||||
* Lay down a fresh filesystem: erase the volume, write both superblock
|
||||
* copies, then commit an empty generation.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_meta_format(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
FAR struct xipfs_sblk_s *sblk;
|
||||
int ret;
|
||||
|
||||
ret = xipfs_flash_erase(fs, 0, fs->geo.neraseblocks);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
memset(fs->metabuf, 0xff, fs->geo.blocksize);
|
||||
sblk = (FAR struct xipfs_sblk_s *)fs->metabuf;
|
||||
|
||||
memset(sblk, 0, sizeof(*sblk));
|
||||
sblk->magic = XIPFS_SBLK_MAGIC;
|
||||
sblk->version = XIPFS_VERSION;
|
||||
sblk->erasesize = fs->geo.erasesize;
|
||||
sblk->blocksize = fs->geo.blocksize;
|
||||
sblk->nblocks = fs->geo.neraseblocks;
|
||||
sblk->meta_start = XIPFS_META_START;
|
||||
sblk->meta_nblocks = XIPFS_META_NBLOCKS;
|
||||
sblk->data_start = XIPFS_DATA_START;
|
||||
sblk->data_nblocks = fs->geo.neraseblocks - XIPFS_DATA_START;
|
||||
sblk->crc = crc32((FAR const uint8_t *)sblk,
|
||||
offsetof(struct xipfs_sblk_s, crc));
|
||||
|
||||
ret = xipfs_flash_write(fs, XIPFS_SBLK_A, 0, fs->metabuf,
|
||||
fs->geo.blocksize);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = xipfs_flash_write(fs, XIPFS_SBLK_B, 0, fs->metabuf,
|
||||
fs->geo.blocksize);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Adopt the geometry we just wrote, then commit generation 1 */
|
||||
|
||||
fs->meta_start = XIPFS_META_START;
|
||||
fs->meta_nblocks = XIPFS_META_NBLOCKS;
|
||||
fs->data_start = XIPFS_DATA_START;
|
||||
fs->data_nblocks = fs->geo.neraseblocks - XIPFS_DATA_START;
|
||||
fs->meta_slot = fs->meta_nblocks - 1;
|
||||
fs->meta_seq = 0;
|
||||
|
||||
xipfs_meta_freeextents(fs);
|
||||
|
||||
return xipfs_meta_commit(fs);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_mount
|
||||
*
|
||||
* Description:
|
||||
* Read the superblock, scan the metadata ring and adopt the last fully
|
||||
* valid generation.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_meta_mount(FAR struct xipfs_mount_s *fs)
|
||||
{
|
||||
struct xipfs_sblk_s sblk;
|
||||
uint32_t best_slot = 0;
|
||||
uint32_t best_seq = 0;
|
||||
uint32_t best_nentries = 0;
|
||||
bool found = false;
|
||||
uint32_t copy;
|
||||
uint32_t slot;
|
||||
uint32_t seq;
|
||||
uint32_t nentries;
|
||||
int ret;
|
||||
|
||||
/* Superblock: try copy A, fall back to copy B */
|
||||
|
||||
ret = -EFTYPE;
|
||||
for (copy = XIPFS_SBLK_A; copy <= XIPFS_SBLK_B; copy++)
|
||||
{
|
||||
ret = xipfs_flash_read(fs, copy, 0, fs->metabuf, fs->geo.blocksize);
|
||||
if (ret < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(&sblk, fs->metabuf, sizeof(sblk));
|
||||
|
||||
if (sblk.magic != XIPFS_SBLK_MAGIC ||
|
||||
sblk.version != XIPFS_VERSION)
|
||||
{
|
||||
ret = -EFTYPE;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (crc32((FAR const uint8_t *)&sblk,
|
||||
offsetof(struct xipfs_sblk_s, crc)) != sblk.crc)
|
||||
{
|
||||
fwarn("xipfs: superblock copy %" PRIu32 " failed CRC\n", copy);
|
||||
ret = -EFTYPE;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The geometry recorded at format time must still match the medium,
|
||||
* otherwise every block index in the directory means something
|
||||
* different from what it meant when it was written.
|
||||
*/
|
||||
|
||||
if (sblk.erasesize != fs->geo.erasesize ||
|
||||
sblk.blocksize != fs->geo.blocksize ||
|
||||
sblk.nblocks != fs->geo.neraseblocks)
|
||||
{
|
||||
ferr("ERROR: Superblock geometry does not match the medium\n");
|
||||
return -EFTYPE;
|
||||
}
|
||||
|
||||
ret = OK;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
fs->meta_start = sblk.meta_start;
|
||||
fs->meta_nblocks = sblk.meta_nblocks;
|
||||
fs->data_start = sblk.data_start;
|
||||
fs->data_nblocks = sblk.data_nblocks;
|
||||
|
||||
if (fs->meta_nblocks < 2 ||
|
||||
fs->meta_start + fs->meta_nblocks > fs->data_start ||
|
||||
fs->data_start + fs->data_nblocks > fs->geo.neraseblocks)
|
||||
{
|
||||
ferr("ERROR: Superblock describes an inconsistent layout\n");
|
||||
return -EFTYPE;
|
||||
}
|
||||
|
||||
/* Scan the ring for the highest sequence number that fully verifies */
|
||||
|
||||
for (slot = 0; slot < fs->meta_nblocks; slot++)
|
||||
{
|
||||
if (xipfs_meta_verify(fs, slot, &seq, &nentries) < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!found || seq > best_seq)
|
||||
{
|
||||
found = true;
|
||||
best_seq = seq;
|
||||
best_slot = slot;
|
||||
best_nentries = nentries;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
ferr("ERROR: No valid metadata generation found\n");
|
||||
return -EFTYPE;
|
||||
}
|
||||
|
||||
fs->meta_slot = best_slot;
|
||||
fs->meta_seq = best_seq;
|
||||
|
||||
finfo("xipfs: mounted generation %" PRIu32 " from slot %" PRIu32
|
||||
" with %" PRIu32 " entries\n", best_seq, best_slot, best_nentries);
|
||||
|
||||
return xipfs_meta_load(fs, best_slot, best_nentries);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_find
|
||||
****************************************************************************/
|
||||
|
||||
FAR struct xipfs_extent_s *xipfs_meta_find(FAR struct xipfs_mount_s *fs,
|
||||
uint16_t parent,
|
||||
FAR const char *name,
|
||||
size_t namelen)
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
|
||||
for (ext = fs->extents; ext != NULL; ext = ext->flink)
|
||||
{
|
||||
if (ext->parent == parent &&
|
||||
strncmp(ext->name, name, namelen) == 0 &&
|
||||
ext->name[namelen] == '\0')
|
||||
{
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_path
|
||||
*
|
||||
* Description:
|
||||
* Compose an entry's path relative to the mountpoint by climbing its
|
||||
* parents. Written backwards from the end of the buffer, so a path too
|
||||
* long to fit loses its leading components rather than the name that
|
||||
* identifies it. The climb is bounded by the entry count, which mount has
|
||||
* already proved cycle-free, so this cannot spin.
|
||||
*
|
||||
* The caller must hold the lock.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void xipfs_meta_path(FAR struct xipfs_mount_s *fs,
|
||||
FAR struct xipfs_extent_s *ext,
|
||||
FAR char *buf, size_t buflen)
|
||||
{
|
||||
FAR struct xipfs_extent_s *up = ext;
|
||||
FAR struct xipfs_extent_s *cur;
|
||||
uint32_t steps = 0;
|
||||
size_t pos;
|
||||
|
||||
DEBUGASSERT(buflen > 0);
|
||||
|
||||
pos = buflen - 1;
|
||||
buf[pos] = '\0';
|
||||
|
||||
for (; ; )
|
||||
{
|
||||
size_t len = strlen(up->name);
|
||||
|
||||
if (len > pos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pos -= len;
|
||||
memcpy(&buf[pos], up->name, len);
|
||||
|
||||
if (up->parent == XIPFS_ROOT_ID || pos == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (cur = fs->extents; cur != NULL; cur = cur->flink)
|
||||
{
|
||||
if (cur->id == up->parent)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cur == NULL || ++steps > fs->nextents)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
buf[--pos] = '/';
|
||||
up = cur;
|
||||
}
|
||||
|
||||
if (pos > 0)
|
||||
{
|
||||
memmove(buf, &buf[pos], buflen - pos);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_haschildren
|
||||
*
|
||||
* Description:
|
||||
* Whether anything names 'dirid' as its parent, which is what stops rmdir
|
||||
* removing a directory that still holds something.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
bool xipfs_meta_haschildren(FAR struct xipfs_mount_s *fs, uint16_t dirid)
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
|
||||
for (ext = fs->extents; ext != NULL; ext = ext->flink)
|
||||
{
|
||||
if (ext->parent == dirid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_add
|
||||
*
|
||||
* Description:
|
||||
* Attach a new extent to the in-RAM directory. The caller commits.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
FAR struct xipfs_extent_s *xipfs_meta_add(FAR struct xipfs_mount_s *fs,
|
||||
uint16_t parent,
|
||||
FAR const char *name,
|
||||
size_t namelen, bool isdir,
|
||||
uint32_t start_block,
|
||||
uint32_t nblocks, uint32_t size)
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
uint16_t id;
|
||||
|
||||
if (namelen > XIPFS_NAME_MAX)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
id = xipfs_meta_newid(fs);
|
||||
if (id == 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ext = kmm_zalloc(sizeof(struct xipfs_extent_s));
|
||||
if (ext == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(ext->name, name, namelen);
|
||||
ext->name[namelen] = '\0';
|
||||
|
||||
ext->fs = fs;
|
||||
ext->id = id;
|
||||
ext->parent = parent;
|
||||
ext->isdir = isdir;
|
||||
ext->start_block = start_block;
|
||||
ext->nblocks = nblocks;
|
||||
ext->size = size;
|
||||
|
||||
ext->flink = fs->extents;
|
||||
fs->extents = ext;
|
||||
fs->nextents++;
|
||||
|
||||
return ext;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_meta_detach
|
||||
*
|
||||
* Description:
|
||||
* Remove an extent from the in-RAM directory without freeing it. The
|
||||
* object stays alive while opens or pins reference it; the caller frees
|
||||
* it once the last reference goes away.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void xipfs_meta_detach(FAR struct xipfs_mount_s *fs,
|
||||
FAR struct xipfs_extent_s *ext)
|
||||
{
|
||||
FAR struct xipfs_extent_s **prev;
|
||||
|
||||
for (prev = &fs->extents; *prev != NULL; prev = &(*prev)->flink)
|
||||
{
|
||||
if (*prev == ext)
|
||||
{
|
||||
*prev = ext->flink;
|
||||
ext->flink = NULL;
|
||||
ext->unlinked = true;
|
||||
fs->nextents--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
253
fs/xipfs/xipfs_mmap.c
Normal file
253
fs/xipfs/xipfs_mmap.c
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/****************************************************************************
|
||||
* fs/xipfs/xipfs_mmap.c
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
*
|
||||
* This is what distinguishes xipfs from littlefs, FAT or SPIFFS: the mmap
|
||||
* operation hands back a direct pointer into memory mapped flash rather
|
||||
* than a RAM copy, so a module's text and rodata are executed and read
|
||||
* where they already live.
|
||||
*
|
||||
* Two properties make that safe.
|
||||
*
|
||||
* First, the mapping takes a pin on the extent, and the pin lives on the
|
||||
* extent object rather than on the file descriptor. Three running
|
||||
* instances of one module therefore produce a pin count of three, and the
|
||||
* extent only becomes movable again when the last of them goes away.
|
||||
*
|
||||
* Second, the pin is acquired under the same lock the defragmenter holds
|
||||
* while it decides whether an extent is movable. Without that, a new
|
||||
* mapping could slip in between the compactor observing "pin count zero"
|
||||
* and the compactor starting the move, and the module would be executing
|
||||
* out of blocks that were being erased underneath it.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <debug.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <nuttx/mm/map.h>
|
||||
#include <nuttx/sched.h>
|
||||
|
||||
#include "xipfs.h"
|
||||
|
||||
/****************************************************************************
|
||||
* Private Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
static int xipfs_munmap(FAR struct task_group_s *group,
|
||||
FAR struct mm_map_entry_s *entry,
|
||||
FAR void *start, size_t length);
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_munmap
|
||||
*
|
||||
* Description:
|
||||
* Release an XIP mapping and drop its pin.
|
||||
*
|
||||
* This runs in two situations: an explicit munmap by the module loader,
|
||||
* and the task teardown walk that mm_map_destroy performs when a task
|
||||
* dies. The second case is the one that matters most -- a module that
|
||||
* faults or is killed without unmapping must still release its pin, or
|
||||
* the extent stays immovable forever and the defragmenter slowly stops
|
||||
* being able to reclaim anything.
|
||||
*
|
||||
* Because it can run from teardown, this function must not consult
|
||||
* this_task()->group: the group is being dismantled and is passed as
|
||||
* NULL. Everything it needs is reachable from the entry itself.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int xipfs_munmap(FAR struct task_group_s *group,
|
||||
FAR struct mm_map_entry_s *entry,
|
||||
FAR void *start, size_t length)
|
||||
{
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
FAR struct xipfs_mount_s *fs;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(entry != NULL && entry->priv.p != NULL);
|
||||
|
||||
/* Only whole mappings can be released. A partial unmap of an XIP window
|
||||
* has no meaning -- there is no allocation to shrink, only a refcount --
|
||||
* and accepting one would leave the pin count wrong in a way that is
|
||||
* very hard to trace back later.
|
||||
*/
|
||||
|
||||
if (start != entry->vaddr || length != entry->length)
|
||||
{
|
||||
ferr("ERROR: Partial unmap of an XIP mapping is not supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ext = (FAR struct xipfs_extent_s *)entry->priv.p;
|
||||
fs = ext->fs;
|
||||
|
||||
ret = xipfs_lock(fs);
|
||||
if (ret < 0)
|
||||
{
|
||||
/* Failing here would leak the pin permanently, and the caller has no
|
||||
* way to retry from the teardown path. Drop the count anyway: the
|
||||
* lock only orders us against defrag, and an unpinned-but-still-
|
||||
* mapped extent cannot arise because the mapping is going away.
|
||||
*/
|
||||
|
||||
fwarn("xipfs: unmap could not take the lock; dropping pin anyway\n");
|
||||
}
|
||||
|
||||
DEBUGASSERT(ext->pincount > 0);
|
||||
ext->pincount--;
|
||||
|
||||
/* An extent that was unlinked while mapped is freed once the last
|
||||
* reference goes away.
|
||||
*/
|
||||
|
||||
if (ext->unlinked && ext->pincount == 0 && ext->openrefs == 0)
|
||||
{
|
||||
xipfs_free(fs, ext->start_block, ext->nblocks);
|
||||
kmm_free(ext);
|
||||
}
|
||||
|
||||
if (ret >= 0)
|
||||
{
|
||||
xipfs_unlock(fs);
|
||||
}
|
||||
|
||||
return mm_map_remove(get_group_mm(group), entry);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: xipfs_mmap
|
||||
*
|
||||
* Description:
|
||||
* Map a range of a file directly onto the underlying memory mapped media.
|
||||
*
|
||||
* The error returned when that is impossible is significant. The core
|
||||
* mmap path falls back to copying the file into RAM only when the file
|
||||
* system answers -ENOTTY, so returning anything else suppresses the
|
||||
* fallback. A caller that passed MAP_XIP_STRICT -- a module loader, for
|
||||
* which a silent RAM copy would defeat the entire point of executing in
|
||||
* place -- gets -ENXIO instead, which it can turn into "defragment and
|
||||
* retry" or into a refusal to load. Ordinary data readers that did not
|
||||
* ask for strict behaviour still get the convenience of the copy.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int xipfs_mmap(FAR struct file *filep, FAR struct mm_map_entry_s *map)
|
||||
{
|
||||
FAR struct xipfs_mount_s *fs;
|
||||
FAR struct xipfs_file_s *xf;
|
||||
FAR struct xipfs_extent_s *ext;
|
||||
FAR uint8_t *addr;
|
||||
bool strict;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL);
|
||||
|
||||
xf = filep->f_priv;
|
||||
fs = filep->f_inode->i_private;
|
||||
strict = (map->flags & MAP_XIP_STRICT) != 0;
|
||||
|
||||
ret = xipfs_lock(fs);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
ext = xf->ext;
|
||||
|
||||
/* A file still being written has no stable contents to map yet */
|
||||
|
||||
if (ext->writing)
|
||||
{
|
||||
ret = strict ? -ENXIO : -ENOTTY;
|
||||
goto errout_with_lock;
|
||||
}
|
||||
|
||||
if (map->offset < 0 || map->length == 0 ||
|
||||
(uint64_t)map->offset + map->length > ext->size)
|
||||
{
|
||||
ret = -EINVAL;
|
||||
goto errout_with_lock;
|
||||
}
|
||||
|
||||
addr = xipfs_flash_addr(fs, ext->start_block);
|
||||
if (addr == NULL)
|
||||
{
|
||||
/* The media cannot be addressed directly, so there is no in-place
|
||||
* mapping to be had at any price.
|
||||
*/
|
||||
|
||||
ret = strict ? -ENXIO : -ENOTTY;
|
||||
goto errout_with_lock;
|
||||
}
|
||||
|
||||
map->vaddr = addr + map->offset;
|
||||
|
||||
/* Take the pin before releasing the lock. From this moment the extent
|
||||
* is frozen in place: the defragmenter will skip it, so a mapping can
|
||||
* never be relocated out from under a running module.
|
||||
*/
|
||||
|
||||
ext->pincount++;
|
||||
|
||||
/* Record what kind of mapping this is now, rather than trying to work it
|
||||
* out again at unmap time. priv.p carries the pinned extent and offset
|
||||
* carries the mount, which is all the teardown path can rely on.
|
||||
*/
|
||||
|
||||
map->priv.p = ext;
|
||||
map->offset = (off_t)(uintptr_t)fs;
|
||||
map->munmap = xipfs_munmap;
|
||||
map->msync = NULL;
|
||||
|
||||
ret = mm_map_add(get_current_mm(), map);
|
||||
if (ret < 0)
|
||||
{
|
||||
ext->pincount--;
|
||||
goto errout_with_lock;
|
||||
}
|
||||
|
||||
finfo("xipfs: XIP map '%s' at %p len %zu pins %" PRIu32 "\n",
|
||||
ext->name, map->vaddr, map->length, ext->pincount);
|
||||
|
||||
xipfs_unlock(fs);
|
||||
return OK;
|
||||
|
||||
errout_with_lock:
|
||||
xipfs_unlock(fs);
|
||||
return ret;
|
||||
}
|
||||
1893
fs/xipfs/xipfs_vfs.c
Normal file
1893
fs/xipfs/xipfs_vfs.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -251,6 +251,37 @@
|
|||
* OUT: None
|
||||
*/
|
||||
|
||||
/* XIPFS specific commands. See include/nuttx/fs/xipfs.h for the argument
|
||||
* structures.
|
||||
*/
|
||||
|
||||
#define XIPFSIOC_DEFRAG _FIOC(0x0019) /* IN: FAR struct
|
||||
* xipfs_defrag_arg_s *
|
||||
* OUT: Defragmentation result
|
||||
*/
|
||||
#define XIPFSIOC_LISTPINNED _FIOC(0x001a) /* IN: FAR struct
|
||||
* xipfs_pinned_arg_s *
|
||||
* OUT: Extents holding XIP pins
|
||||
*/
|
||||
#define XIPFSIOC_EXTENTINFO _FIOC(0x001b) /* IN: FAR struct
|
||||
* xipfs_extent_info_s *
|
||||
* OUT: Physical extent placement
|
||||
*/
|
||||
|
||||
/* IN: FAR struct xipfs_fault_s * (op countdown and tear mode)
|
||||
* OUT: None
|
||||
*/
|
||||
|
||||
#define XIPFSIOC_FAULTINJECT _FIOC(0x001c)
|
||||
|
||||
#define XIPFSIOC_PIN _FIOC(0x001d) /* IN: FAR uintptr_t *
|
||||
* OUT: Flash address; pins the
|
||||
* extent in place
|
||||
*/
|
||||
#define XIPFSIOC_UNPIN _FIOC(0x001e) /* IN: None
|
||||
* OUT: Releases one pin
|
||||
*/
|
||||
|
||||
/* NuttX character driver ioctl definitions *********************************/
|
||||
|
||||
#define _DIOCVALID(c) (_IOC_TYPE(c)==_DIOCBASE)
|
||||
|
|
|
|||
168
include/nuttx/fs/xipfs.h
Normal file
168
include/nuttx/fs/xipfs.h
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
/****************************************************************************
|
||||
* include/nuttx/fs/xipfs.h
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __INCLUDE_NUTTX_FS_XIPFS_H
|
||||
#define __INCLUDE_NUTTX_FS_XIPFS_H
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
/* Maximum length of one path component, not including the NUL terminator.
|
||||
* Depth comes from the directory an entry belongs to rather than from its
|
||||
* name, so this bounds a component and not a whole path -- which is what
|
||||
* statfs reports it as, in f_namelen.
|
||||
*/
|
||||
|
||||
#define XIPFS_NAME_MAX 31
|
||||
|
||||
/* Longest path reported back to an application, separators included. A path
|
||||
* is bounded only by the depth of the tree, so this is a reporting limit and
|
||||
* not a filesystem one: a path longer than this is truncated from the front,
|
||||
* which keeps the part that identifies the file.
|
||||
*/
|
||||
|
||||
#define XIPFS_PATH_MAX 127
|
||||
|
||||
/* Reason codes returned in struct xipfs_defrag_result_s.reason.
|
||||
*
|
||||
* The distinction that matters to the caller is transient (PINS, RAM,
|
||||
* BUDGET -- retrying later may help) versus permanent (FULL -- it will
|
||||
* not). The caller normally asked because an allocation returned
|
||||
* -ENOSPC, so largest_free_run tells it directly whether a retry of that
|
||||
* allocation can now succeed.
|
||||
*/
|
||||
|
||||
#define XIPFS_DEFRAG_DONE 0 /* Nothing left to compact */
|
||||
#define XIPFS_DEFRAG_BLOCKED_PINS 1 /* Blocked by a live XIP mapping */
|
||||
#define XIPFS_DEFRAG_BLOCKED_RAM 2 /* Transient resource shortage */
|
||||
#define XIPFS_DEFRAG_TIME_BUDGET 3 /* Ran out of the caller's budget */
|
||||
#define XIPFS_DEFRAG_ERROR 4 /* Media error; stopped cleanly */
|
||||
#define XIPFS_DEFRAG_BLOCKED_OPEN 5 /* Blocked by a merely open file */
|
||||
|
||||
/* BLOCKED_PINS and BLOCKED_OPEN are deliberately distinct because the
|
||||
* caller resolves them differently: a pinned extent needs a module to be
|
||||
* unloaded, whereas an open one only needs a descriptor to be closed. Issue
|
||||
* the ioctl on a descriptor for the mountpoint directory to avoid the
|
||||
* self-inflicted case: a descriptor for a file inside the volume holds that
|
||||
* file open, and the pass then reports BLOCKED_OPEN for the caller's own
|
||||
* file.
|
||||
*/
|
||||
|
||||
/* Fault-injection mode carried in the XIPFSIOC_FAULTINJECT argument.
|
||||
*
|
||||
* CLEAN models a power loss that stops the failing write or erase before it
|
||||
* perturbs the medium at all, leaving the target exactly as it was. TORN
|
||||
* models the harder real case: an interrupted NOR program leaves the first
|
||||
* half of a page written and the rest unprogrammed, and an interrupted erase
|
||||
* leaves the first half of a sector erased and the rest holding old
|
||||
* contents. A torn generation is what forces the mount-time CRC to do real
|
||||
* work -- reject a half-formed generation rather than trust it -- which the
|
||||
* clean model, where an operation either happens whole or not at all, never
|
||||
* exercises.
|
||||
*/
|
||||
|
||||
#define XIPFS_FAULT_CLEAN 0
|
||||
#define XIPFS_FAULT_TORN 1
|
||||
|
||||
/****************************************************************************
|
||||
* Public Types
|
||||
****************************************************************************/
|
||||
|
||||
/* Outcome of one xipfs defragmentation pass. Returned by the
|
||||
* XIPFSIOC_DEFRAG ioctl.
|
||||
*/
|
||||
|
||||
struct xipfs_defrag_result_s
|
||||
{
|
||||
size_t largest_free_run; /* Bytes in the largest contiguous free run */
|
||||
uint32_t blocks_reclaimed; /* Erase blocks coalesced into free space */
|
||||
uint32_t blocks_pinned; /* Blocks skipped because pin count > 0 */
|
||||
uint32_t extents_moved; /* Number of atomic relocations performed */
|
||||
int reason; /* One of XIPFS_DEFRAG_* */
|
||||
};
|
||||
|
||||
/* Argument to the XIPFSIOC_DEFRAG ioctl */
|
||||
|
||||
struct xipfs_defrag_arg_s
|
||||
{
|
||||
uint32_t max_ms; /* 0 means "no time budget" */
|
||||
struct xipfs_defrag_result_s result; /* Filled in on return */
|
||||
};
|
||||
|
||||
/* One entry returned by the XIPFSIOC_LISTPINNED ioctl. Without this,
|
||||
* "blocked by pins" is a dead end the caller cannot resolve; with it the
|
||||
* application can unload an idle module and re-trigger defrag.
|
||||
*/
|
||||
|
||||
struct xipfs_pinned_entry_s
|
||||
{
|
||||
char path[XIPFS_PATH_MAX + 1]; /* Relative to the mountpoint */
|
||||
uint32_t start_block;
|
||||
uint32_t nblocks;
|
||||
uint32_t pincount;
|
||||
};
|
||||
|
||||
/* Argument to the XIPFSIOC_LISTPINNED ioctl */
|
||||
|
||||
struct xipfs_pinned_arg_s
|
||||
{
|
||||
FAR struct xipfs_pinned_entry_s *entries; /* Caller supplied array */
|
||||
size_t nentries; /* Capacity of that array */
|
||||
size_t count; /* OUT: entries filled in */
|
||||
};
|
||||
|
||||
/* Argument to the XIPFSIOC_EXTENTINFO ioctl. Reports the physical
|
||||
* placement of the file behind the file descriptor, which is what the
|
||||
* contiguity invariant tests assert against.
|
||||
*/
|
||||
|
||||
struct xipfs_extent_info_s
|
||||
{
|
||||
uint32_t start_block;
|
||||
uint32_t nblocks;
|
||||
uint32_t erasesize;
|
||||
uint32_t size;
|
||||
uint32_t pincount;
|
||||
uint32_t data_start; /* First block of the data region */
|
||||
uint32_t data_nblocks; /* Length of the data region */
|
||||
uintptr_t xipaddr; /* Direct flash address, or 0 if not XIP capable */
|
||||
};
|
||||
|
||||
/* Argument to the XIPFSIOC_FAULTINJECT ioctl */
|
||||
|
||||
struct xipfs_fault_s
|
||||
{
|
||||
int32_t count; /* Operations to allow before failing; negative disables */
|
||||
uint8_t mode; /* XIPFS_FAULT_CLEAN or XIPFS_FAULT_TORN */
|
||||
};
|
||||
|
||||
#endif /* __INCLUDE_NUTTX_FS_XIPFS_H */
|
||||
|
|
@ -70,6 +70,16 @@
|
|||
|
||||
#define MAP_UNINITIALIZED (1 << 26) /* Bit 26: Do not clear the anonymous pages */
|
||||
|
||||
/* NuttX specific. Requests a strict execute-in-place mapping: the file
|
||||
* system must resolve the mapping directly onto the memory mapped media or
|
||||
* fail with ENXIO. It must never fall back to copying the file into RAM,
|
||||
* even when CONFIG_FS_RAMMAP is enabled for the benefit of other consumers.
|
||||
* This is what an XIP module loader must use: a silent RAM copy would
|
||||
* defeat the entire purpose of executing in place.
|
||||
*/
|
||||
|
||||
#define MAP_XIP_STRICT (1 << 27) /* Bit 27: In-place or fail */
|
||||
|
||||
/* Failure return */
|
||||
|
||||
#define MAP_FAILED ((FAR void*)-1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue