This commit is contained in:
Dan-guanghua
2025-12-15 12:05:06 +08:00
parent 749e9926e5
commit 90c69ea943
18 changed files with 9373 additions and 866 deletions

View File

@@ -0,0 +1,797 @@
/*
* The little filesystem
*
* Copyright (c) 2022, The littlefs authors.
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS_H
#define LFS_H
#include "lfs_util.h"
#ifdef __cplusplus
extern "C"
{
#endif
/// Version info ///
// Software library version
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS_VERSION 0x0002000a
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
// Version of On-disk data structures
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS_DISK_VERSION 0x00020001
#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0))
/// Definitions ///
// Type definitions
typedef uint32_t lfs_size_t;
typedef uint32_t lfs_off_t;
typedef int32_t lfs_ssize_t;
typedef int32_t lfs_soff_t;
typedef uint32_t lfs_block_t;
// Maximum name size in bytes, may be redefined to reduce the size of the
// info struct. Limited to <= 1022. Stored in superblock and must be
// respected by other littlefs drivers.
#ifndef LFS_NAME_MAX
#define LFS_NAME_MAX 255
#endif
// Maximum size of a file in bytes, may be redefined to limit to support other
// drivers. Limited on disk to <= 2147483647. Stored in superblock and must be
// respected by other littlefs drivers.
#ifndef LFS_FILE_MAX
#define LFS_FILE_MAX 2147483647
#endif
// Maximum size of custom attributes in bytes, may be redefined, but there is
// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. Stored
// in superblock and must be respected by other littlefs drivers.
#ifndef LFS_ATTR_MAX
#define LFS_ATTR_MAX 1022
#endif
// Possible error codes, these are negative to allow
// valid positive return values
enum lfs_error {
LFS_ERR_OK = 0, // No error
LFS_ERR_IO = -5, // Error during device operation
LFS_ERR_CORRUPT = -84, // Corrupted
LFS_ERR_NOENT = -2, // No directory entry
LFS_ERR_EXIST = -17, // Entry already exists
LFS_ERR_NOTDIR = -20, // Entry is not a dir
LFS_ERR_ISDIR = -21, // Entry is a dir
LFS_ERR_NOTEMPTY = -39, // Dir is not empty
LFS_ERR_BADF = -9, // Bad file number
LFS_ERR_FBIG = -27, // File too large
LFS_ERR_INVAL = -22, // Invalid parameter
LFS_ERR_NOSPC = -28, // No space left on device
LFS_ERR_NOMEM = -12, // No more memory available
LFS_ERR_NOATTR = -61, // No data/attr available
LFS_ERR_NAMETOOLONG = -36, // File name too long
};
// File types
enum lfs_type {
// file types
LFS_TYPE_REG = 0x001,
LFS_TYPE_DIR = 0x002,
// internally used types
LFS_TYPE_SPLICE = 0x400,
LFS_TYPE_NAME = 0x000,
LFS_TYPE_STRUCT = 0x200,
LFS_TYPE_USERATTR = 0x300,
LFS_TYPE_FROM = 0x100,
LFS_TYPE_TAIL = 0x600,
LFS_TYPE_GLOBALS = 0x700,
LFS_TYPE_CRC = 0x500,
// internally used type specializations
LFS_TYPE_CREATE = 0x401,
LFS_TYPE_DELETE = 0x4ff,
LFS_TYPE_SUPERBLOCK = 0x0ff,
LFS_TYPE_DIRSTRUCT = 0x200,
LFS_TYPE_CTZSTRUCT = 0x202,
LFS_TYPE_INLINESTRUCT = 0x201,
LFS_TYPE_SOFTTAIL = 0x600,
LFS_TYPE_HARDTAIL = 0x601,
LFS_TYPE_MOVESTATE = 0x7ff,
LFS_TYPE_CCRC = 0x500,
LFS_TYPE_FCRC = 0x5ff,
// internal chip sources
LFS_FROM_NOOP = 0x000,
LFS_FROM_MOVE = 0x101,
LFS_FROM_USERATTRS = 0x102,
};
// File open flags
enum lfs_open_flags {
// open flags
LFS_O_RDONLY = 1, // Open a file as read only
#ifndef LFS_READONLY
LFS_O_WRONLY = 2, // Open a file as write only
LFS_O_RDWR = 3, // Open a file as read and write
LFS_O_CREAT = 0x0100, // Create a file if it does not exist
LFS_O_EXCL = 0x0200, // Fail if a file already exists
LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
LFS_O_APPEND = 0x0800, // Move to end of file on every write
#endif
// internally used flags
#ifndef LFS_READONLY
LFS_F_DIRTY = 0x010000, // File does not match storage
LFS_F_WRITING = 0x020000, // File has been written since last flush
#endif
LFS_F_READING = 0x040000, // File has been read since last flush
#ifndef LFS_READONLY
LFS_F_ERRED = 0x080000, // An error occurred during write
#endif
LFS_F_INLINE = 0x100000, // Currently inlined in directory entry
};
// File seek flags
enum lfs_whence_flags {
LFS_SEEK_SET = 0, // Seek relative to an absolute position
LFS_SEEK_CUR = 1, // Seek relative to the current file position
LFS_SEEK_END = 2, // Seek relative to the end of the file
};
// Configuration provided during initialization of the littlefs
struct lfs_config {
// Opaque user provided context that can be used to pass
// information to the block device operations
void *context;
// Read a region in a block. Negative error codes are propagated
// to the user.
int (*read)(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size);
// Program a region in a block. The block must have previously
// been erased. Negative error codes are propagated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
int (*prog)(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size);
// Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes
// are propagated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
int (*erase)(const struct lfs_config *c, lfs_block_t block);
// Sync the state of the underlying block device. Negative error codes
// are propagated to the user.
int (*sync)(const struct lfs_config *c);
#ifdef LFS_THREADSAFE
// Lock the underlying block device. Negative error codes
// are propagated to the user.
int (*lock)(const struct lfs_config *c);
// Unlock the underlying block device. Negative error codes
// are propagated to the user.
int (*unlock)(const struct lfs_config *c);
#endif
// Minimum size of a block read in bytes. All read operations will be a
// multiple of this value.
lfs_size_t read_size;
// Minimum size of a block program in bytes. All program operations will be
// a multiple of this value.
lfs_size_t prog_size;
// Size of an erasable block in bytes. This does not impact ram consumption
// and may be larger than the physical erase size. However, non-inlined
// files take up at minimum one block. Must be a multiple of the read and
// program sizes.
lfs_size_t block_size;
// Number of erasable blocks on the device. Defaults to block_count stored
// on disk when zero.
lfs_size_t block_count;
// Number of erase cycles before littlefs evicts metadata logs and moves
// the metadata to another block. Suggested values are in the
// range 100-1000, with large values having better performance at the cost
// of less consistent wear distribution.
//
// Set to -1 to disable block-level wear-leveling.
int32_t block_cycles;
// Size of block caches in bytes. Each cache buffers a portion of a block in
// RAM. The littlefs needs a read cache, a program cache, and one additional
// cache per file. Larger caches can improve performance by storing more
// data and reducing the number of disk accesses. Must be a multiple of the
// read and program sizes, and a factor of the block size.
lfs_size_t cache_size;
// Size of the lookahead buffer in bytes. A larger lookahead buffer
// increases the number of blocks found during an allocation pass. The
// lookahead buffer is stored as a compact bitmap, so each byte of RAM
// can track 8 blocks.
lfs_size_t lookahead_size;
// Threshold for metadata compaction during lfs_fs_gc in bytes. Metadata
// pairs that exceed this threshold will be compacted during lfs_fs_gc.
// Defaults to ~88% block_size when zero, though the default may change
// in the future.
//
// Note this only affects lfs_fs_gc. Normal compactions still only occur
// when full.
//
// Set to -1 to disable metadata compaction during lfs_fs_gc.
lfs_size_t compact_thresh;
// Optional statically allocated read buffer. Must be cache_size.
// By default lfs_malloc is used to allocate this buffer.
void *read_buffer;
// Optional statically allocated program buffer. Must be cache_size.
// By default lfs_malloc is used to allocate this buffer.
void *prog_buffer;
// Optional statically allocated lookahead buffer. Must be lookahead_size.
// By default lfs_malloc is used to allocate this buffer.
void *lookahead_buffer;
// Optional upper limit on length of file names in bytes. No downside for
// larger names except the size of the info struct which is controlled by
// the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX or name_max stored on
// disk when zero.
lfs_size_t name_max;
// Optional upper limit on files in bytes. No downside for larger files
// but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX or file_max stored
// on disk when zero.
lfs_size_t file_max;
// Optional upper limit on custom attributes in bytes. No downside for
// larger attributes size but must be <= LFS_ATTR_MAX. Defaults to
// LFS_ATTR_MAX or attr_max stored on disk when zero.
lfs_size_t attr_max;
// Optional upper limit on total space given to metadata pairs in bytes. On
// devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB)
// can help bound the metadata compaction time. Must be <= block_size.
// Defaults to block_size when zero.
lfs_size_t metadata_max;
// Optional upper limit on inlined files in bytes. Inlined files live in
// metadata and decrease storage requirements, but may be limited to
// improve metadata-related performance. Must be <= cache_size, <=
// attr_max, and <= block_size/8. Defaults to the largest possible
// inline_max when zero.
//
// Set to -1 to disable inlined files.
lfs_size_t inline_max;
#ifdef LFS_MULTIVERSION
// On-disk version to use when writing in the form of 16-bit major version
// + 16-bit minor version. This limiting metadata to what is supported by
// older minor versions. Note that some features will be lost. Defaults to
// to the most recent minor version when zero.
uint32_t disk_version;
#endif
};
// File info structure
struct lfs_info {
// Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
uint8_t type;
// Size of the file, only valid for REG files. Limited to 32-bits.
lfs_size_t size;
// Name of the file stored as a null-terminated string. Limited to
// LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to
// reduce RAM. LFS_NAME_MAX is stored in superblock and must be
// respected by other littlefs drivers.
char name[LFS_NAME_MAX+1];
};
// Filesystem info structure
struct lfs_fsinfo {
// On-disk version.
uint32_t disk_version;
// Size of a logical block in bytes.
lfs_size_t block_size;
// Number of logical blocks in filesystem.
lfs_size_t block_count;
// Upper limit on the length of file names in bytes.
lfs_size_t name_max;
// Upper limit on the size of files in bytes.
lfs_size_t file_max;
// Upper limit on the size of custom attributes in bytes.
lfs_size_t attr_max;
};
// Custom attribute structure, used to describe custom attributes
// committed atomically during file writes.
struct lfs_attr {
// 8-bit type of attribute, provided by user and used to
// identify the attribute
uint8_t type;
// Pointer to buffer containing the attribute
void *buffer;
// Size of attribute in bytes, limited to LFS_ATTR_MAX
lfs_size_t size;
};
// Optional configuration provided during lfs_file_opencfg
struct lfs_file_config {
// Optional statically allocated file buffer. Must be cache_size.
// By default lfs_malloc is used to allocate this buffer.
void *buffer;
// Optional list of custom attributes related to the file. If the file
// is opened with read access, these attributes will be read from disk
// during the open call. If the file is opened with write access, the
// attributes will be written to disk every file sync or close. This
// write occurs atomically with update to the file's contents.
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller
// than the buffer, it will be padded with zeros. If the stored attribute
// is larger, then it will be silently truncated. If the attribute is not
// found, it will be created implicitly.
struct lfs_attr *attrs;
// Number of custom attributes in the list
lfs_size_t attr_count;
};
/// internal littlefs data structures ///
typedef struct lfs_cache {
lfs_block_t block;
lfs_off_t off;
lfs_size_t size;
uint8_t *buffer;
} lfs_cache_t;
typedef struct lfs_mdir {
lfs_block_t pair[2];
uint32_t rev;
lfs_off_t off;
uint32_t etag;
uint16_t count;
bool erased;
bool split;
lfs_block_t tail[2];
} lfs_mdir_t;
// littlefs directory type
typedef struct lfs_dir {
struct lfs_dir *next;
uint16_t id;
uint8_t type;
lfs_mdir_t m;
lfs_off_t pos;
lfs_block_t head[2];
} lfs_dir_t;
// littlefs file type
typedef struct lfs_file {
struct lfs_file *next;
uint16_t id;
uint8_t type;
lfs_mdir_t m;
struct lfs_ctz {
lfs_block_t head;
lfs_size_t size;
} ctz;
uint32_t flags;
lfs_off_t pos;
lfs_block_t block;
lfs_off_t off;
lfs_cache_t cache;
const struct lfs_file_config *cfg;
} lfs_file_t;
typedef struct lfs_superblock {
uint32_t version;
lfs_size_t block_size;
lfs_size_t block_count;
lfs_size_t name_max;
lfs_size_t file_max;
lfs_size_t attr_max;
} lfs_superblock_t;
typedef struct lfs_gstate {
uint32_t tag;
lfs_block_t pair[2];
} lfs_gstate_t;
// The littlefs filesystem type
typedef struct lfs {
lfs_cache_t rcache;
lfs_cache_t pcache;
lfs_block_t root[2];
struct lfs_mlist {
struct lfs_mlist *next;
uint16_t id;
uint8_t type;
lfs_mdir_t m;
} *mlist;
uint32_t seed;
lfs_gstate_t gstate;
lfs_gstate_t gdisk;
lfs_gstate_t gdelta;
struct lfs_lookahead {
lfs_block_t start;
lfs_block_t size;
lfs_block_t next;
lfs_block_t ckpoint;
uint8_t *buffer;
} lookahead;
const struct lfs_config *cfg;
lfs_size_t block_count;
lfs_size_t name_max;
lfs_size_t file_max;
lfs_size_t attr_max;
lfs_size_t inline_max;
#ifdef LFS_MIGRATE
struct lfs1 *lfs1;
#endif
} lfs_t;
/// Filesystem functions ///
#ifndef LFS_READONLY
// Format a block device with the littlefs
//
// Requires a littlefs object and config struct. This clobbers the littlefs
// object, and does not leave the filesystem mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs_format(lfs_t *lfs, const struct lfs_config *config);
#endif
// Mounts a littlefs
//
// Requires a littlefs object and config struct. Multiple filesystems
// may be mounted simultaneously with multiple littlefs objects. Both
// lfs and config must be allocated while mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs_mount(lfs_t *lfs, const struct lfs_config *config);
// Unmounts a littlefs
//
// Does nothing besides releasing any allocated resources.
// Returns a negative error code on failure.
int lfs_unmount(lfs_t *lfs);
/// General operations ///
#ifndef LFS_READONLY
// Removes a file or directory
//
// If removing a directory, the directory must be empty.
// Returns a negative error code on failure.
int lfs_remove(lfs_t *lfs, const char *path);
#endif
#ifndef LFS_READONLY
// Rename or move a file or directory
//
// If the destination exists, it must match the source in type.
// If the destination is a directory, the directory must be empty.
//
// Returns a negative error code on failure.
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
#endif
// Find info about a file or directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a negative error code on failure.
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
// Get a custom attribute
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than
// the buffer, it will be padded with zeros. If the stored attribute is larger,
// then it will be silently truncated. If no attribute is found, the error
// LFS_ERR_NOATTR is returned and the buffer is filled with zeros.
//
// Returns the size of the attribute, or a negative error code on failure.
// Note, the returned size is the size of the attribute on disk, irrespective
// of the size of the buffer. This can be used to dynamically allocate a buffer
// or check for existence.
lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
uint8_t type, void *buffer, lfs_size_t size);
#ifndef LFS_READONLY
// Set custom attributes
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be
// implicitly created.
//
// Returns a negative error code on failure.
int lfs_setattr(lfs_t *lfs, const char *path,
uint8_t type, const void *buffer, lfs_size_t size);
#endif
#ifndef LFS_READONLY
// Removes a custom attribute
//
// If an attribute is not found, nothing happens.
//
// Returns a negative error code on failure.
int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type);
#endif
/// File operations ///
#ifndef LFS_NO_MALLOC
// Open a file
//
// The mode that the file is opened in is determined by the flags, which
// are values from the enum lfs_open_flags that are bitwise-ored together.
//
// Returns a negative error code on failure.
int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
const char *path, int flags);
// if LFS_NO_MALLOC is defined, lfs_file_open() will fail with LFS_ERR_NOMEM
// thus use lfs_file_opencfg() with config.buffer set.
#endif
// Open a file with extra configuration
//
// The mode that the file is opened in is determined by the flags, which
// are values from the enum lfs_open_flags that are bitwise-ored together.
//
// The config struct provides additional config options per file as described
// above. The config struct must remain allocated while the file is open, and
// the config struct must be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
const char *path, int flags,
const struct lfs_file_config *config);
// Close a file
//
// Any pending writes are written out to storage as though
// sync had been called and releases any allocated resources.
//
// Returns a negative error code on failure.
int lfs_file_close(lfs_t *lfs, lfs_file_t *file);
// Synchronize a file on storage
//
// Any pending writes are written out to storage.
// Returns a negative error code on failure.
int lfs_file_sync(lfs_t *lfs, lfs_file_t *file);
// Read data from file
//
// Takes a buffer and size indicating where to store the read data.
// Returns the number of bytes read, or a negative error code on failure.
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
void *buffer, lfs_size_t size);
#ifndef LFS_READONLY
// Write data to file
//
// Takes a buffer and size indicating the data to write. The file will not
// actually be updated on the storage until either sync or close is called.
//
// Returns the number of bytes written, or a negative error code on failure.
lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
const void *buffer, lfs_size_t size);
#endif
// Change the position of the file
//
// The change in position is determined by the offset and whence flag.
// Returns the new position of the file, or a negative error code on failure.
lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
lfs_soff_t off, int whence);
#ifndef LFS_READONLY
// Truncates the size of the file to the specified size
//
// Returns a negative error code on failure.
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size);
#endif
// Return the position of the file
//
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
// Returns the position of the file, or a negative error code on failure.
lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file);
// Change the position of the file to the beginning of the file
//
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET)
// Returns a negative error code on failure.
int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file);
// Return the size of the file
//
// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END)
// Returns the size of the file, or a negative error code on failure.
lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file);
/// Directory operations ///
#ifndef LFS_READONLY
// Create a directory
//
// Returns a negative error code on failure.
int lfs_mkdir(lfs_t *lfs, const char *path);
#endif
// Open a directory
//
// Once open a directory can be used with read to iterate over files.
// Returns a negative error code on failure.
int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path);
// Close a directory
//
// Releases any allocated resources.
// Returns a negative error code on failure.
int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir);
// Read an entry in the directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a positive value on success, 0 at the end of directory,
// or a negative error code on failure.
int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info);
// Change the position of the directory
//
// The new off must be a value previous returned from tell and specifies
// an absolute offset in the directory seek.
//
// Returns a negative error code on failure.
int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off);
// Return the position of the directory
//
// The returned offset is only meant to be consumed by seek and may not make
// sense, but does indicate the current position in the directory iteration.
//
// Returns the position of the directory, or a negative error code on failure.
lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir);
// Change the position of the directory to the beginning of the directory
//
// Returns a negative error code on failure.
int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
/// Filesystem-level filesystem operations
// Find on-disk info about the filesystem
//
// Fills out the fsinfo structure based on the filesystem found on-disk.
// Returns a negative error code on failure.
int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo);
// Finds the current size of the filesystem
//
// Note: Result is best effort. If files share COW structures, the returned
// size may be larger than the filesystem actually is.
//
// Returns the number of allocated blocks, or a negative error code on failure.
lfs_ssize_t lfs_fs_size(lfs_t *lfs);
// Traverse through all blocks in use by the filesystem
//
// The provided callback will be called with each block address that is
// currently in use by the filesystem. This can be used to determine which
// blocks are in use or how much of the storage is available.
//
// Returns a negative error code on failure.
int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
#ifndef LFS_READONLY
// Attempt to make the filesystem consistent and ready for writing
//
// Calling this function is not required, consistency will be implicitly
// enforced on the first operation that writes to the filesystem, but this
// function allows the work to be performed earlier and without other
// filesystem changes.
//
// Returns a negative error code on failure.
int lfs_fs_mkconsistent(lfs_t *lfs);
#endif
#ifndef LFS_READONLY
// Attempt any janitorial work
//
// This currently:
// 1. Calls mkconsistent if not already consistent
// 2. Compacts metadata > compact_thresh
// 3. Populates the block allocator
//
// Though additional janitorial work may be added in the future.
//
// Calling this function is not required, but may allow the offloading of
// expensive janitorial work to a less time-critical code path.
//
// Returns a negative error code on failure. Accomplishing nothing is not
// an error.
int lfs_fs_gc(lfs_t *lfs);
#endif
#ifndef LFS_READONLY
// Grows the filesystem to a new size, updating the superblock with the new
// block count.
//
// Note: This is irreversible.
//
// Returns a negative error code on failure.
int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count);
#endif
#ifndef LFS_READONLY
#ifdef LFS_MIGRATE
// Attempts to migrate a previous version of littlefs
//
// Behaves similarly to the lfs_format function. Attempts to mount
// the previous version of littlefs and update the filesystem so it can be
// mounted with the current version of littlefs.
//
// Requires a littlefs object and config struct. This clobbers the littlefs
// object, and does not leave the filesystem mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg);
#endif
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -0,0 +1,275 @@
/*
* lfs utility functions
*
* Copyright (c) 2022, The littlefs authors.
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS_UTIL_H
#define LFS_UTIL_H
#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
#define LFS_STRINGIZE2(x) #x
// Users can override lfs_util.h with their own configuration by defining
// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
//
// If LFS_CONFIG is used, none of the default utils will be emitted and must be
// provided by the config file. To start, I would suggest copying lfs_util.h
// and modifying as needed.
#ifdef LFS_CONFIG
#include LFS_STRINGIZE(LFS_CONFIG)
#else
// Alternatively, users can provide a header file which defines
// macros and other things consumed by littlefs.
//
// For example, provide my_defines.h, which contains
// something like:
//
// #include <stddef.h>
// extern void *my_malloc(size_t sz);
// #define LFS_MALLOC(sz) my_malloc(sz)
//
// And build littlefs with the header by defining LFS_DEFINES.
// (-DLFS_DEFINES=my_defines.h)
#ifdef LFS_DEFINES
#include LFS_STRINGIZE(LFS_DEFINES)
#endif
// System includes
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#ifndef LFS_NO_MALLOC
#include <stdlib.h>
#endif
#ifndef LFS_NO_ASSERT
#include <assert.h>
#endif
#if !defined(LFS_NO_DEBUG) || \
!defined(LFS_NO_WARN) || \
!defined(LFS_NO_ERROR) || \
defined(LFS_YES_TRACE)
#include <stdio.h>
#endif
#ifdef __cplusplus
extern "C"
{
#endif
// Macros, may be replaced by system specific wrappers. Arguments to these
// macros must not have side-effects as the macros can be removed for a smaller
// code footprint
// Logging functions
#ifndef LFS_TRACE
#ifdef LFS_YES_TRACE
#define LFS_TRACE_(fmt, ...) \
printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "")
#else
#define LFS_TRACE(...)
#endif
#endif
#ifndef LFS_DEBUG
#ifndef LFS_NO_DEBUG
#define LFS_DEBUG_(fmt, ...) \
printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "")
#else
#define LFS_DEBUG(...)
#endif
#endif
#ifndef LFS_WARN
#ifndef LFS_NO_WARN
#define LFS_WARN_(fmt, ...) \
printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "")
#else
#define LFS_WARN(...)
#endif
#endif
#ifndef LFS_ERROR
#ifndef LFS_NO_ERROR
#define LFS_ERROR_(fmt, ...) \
printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "")
#else
#define LFS_ERROR(...)
#endif
#endif
// Runtime assertions
#ifndef LFS_ASSERT
#ifndef LFS_NO_ASSERT
#define LFS_ASSERT(test) assert(test)
#else
#define LFS_ASSERT(test)
#endif
#endif
// Builtin functions, these may be replaced by more efficient
// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more
// expensive basic C implementation for debugging purposes
// Min/max functions for unsigned 32-bit numbers
static inline uint32_t lfs_max(uint32_t a, uint32_t b) {
return (a > b) ? a : b;
}
static inline uint32_t lfs_min(uint32_t a, uint32_t b) {
return (a < b) ? a : b;
}
// Align to nearest multiple of a size
static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) {
return a - (a % alignment);
}
static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) {
return lfs_aligndown(a + alignment-1, alignment);
}
// Find the smallest power of 2 greater than or equal to a
static inline uint32_t lfs_npw2(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return 32 - __builtin_clz(a-1);
#else
uint32_t r = 0;
uint32_t s;
a -= 1;
s = (a > 0xffff) << 4; a >>= s; r |= s;
s = (a > 0xff ) << 3; a >>= s; r |= s;
s = (a > 0xf ) << 2; a >>= s; r |= s;
s = (a > 0x3 ) << 1; a >>= s; r |= s;
return (r | (a >> 1)) + 1;
#endif
}
// Count the number of trailing binary zeros in a
// lfs_ctz(0) may be undefined
static inline uint32_t lfs_ctz(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__)
return __builtin_ctz(a);
#else
return lfs_npw2((a & -a) + 1) - 1;
#endif
}
// Count the number of binary ones in a
static inline uint32_t lfs_popc(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return __builtin_popcount(a);
#else
a = a - ((a >> 1) & 0x55555555);
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
#endif
}
// Find the sequence comparison of a and b, this is the distance
// between a and b ignoring overflow
static inline int lfs_scmp(uint32_t a, uint32_t b) {
return (int)(unsigned)(a - b);
}
// Convert between 32-bit little-endian and native order
static inline uint32_t lfs_fromle32(uint32_t a) {
#if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
return a;
#elif !defined(LFS_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
return __builtin_bswap32(a);
#else
return (((uint8_t*)&a)[0] << 0) |
(((uint8_t*)&a)[1] << 8) |
(((uint8_t*)&a)[2] << 16) |
(((uint8_t*)&a)[3] << 24);
#endif
}
static inline uint32_t lfs_tole32(uint32_t a) {
return lfs_fromle32(a);
}
// Convert between 32-bit big-endian and native order
static inline uint32_t lfs_frombe32(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
return __builtin_bswap32(a);
#elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
return a;
#else
return (((uint8_t*)&a)[0] << 24) |
(((uint8_t*)&a)[1] << 16) |
(((uint8_t*)&a)[2] << 8) |
(((uint8_t*)&a)[3] << 0);
#endif
}
static inline uint32_t lfs_tobe32(uint32_t a) {
return lfs_frombe32(a);
}
// Calculate CRC-32 with polynomial = 0x04c11db7
#ifdef LFS_CRC
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
return LFS_CRC(crc, buffer, size)
}
#else
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size);
#endif
// Allocate memory, only used if buffers are not provided to littlefs
//
// littlefs current has no alignment requirements, as it only allocates
// byte-level buffers.
static inline void *lfs_malloc(size_t size) {
#if defined(LFS_MALLOC)
return LFS_MALLOC(size);
#elif !defined(LFS_NO_MALLOC)
return malloc(size);
#else
(void)size;
return NULL;
#endif
}
// Deallocate memory, only used if buffers are not provided to littlefs
static inline void lfs_free(void *p) {
#if defined(LFS_FREE)
LFS_FREE(p);
#elif !defined(LFS_NO_MALLOC)
if (!p) {
free(p);
}
#else
(void)p;
#endif
}
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif
#endif

View File

@@ -0,0 +1,10 @@
set(LIB_NAME lib_littlefs)
set(LIB_VERSION 0.0.1)
set(LIB_INCLUDES api)
set(LIB_DEPENDENT_MODULES "lib_logging(3.2.0)")
set(LIB_COMPILER_FLAGS -O3 -DREF_CLK_FREQ=100 -fasm-linenum -fcomment-asm)
list(APPEND LIB_COMPILER_FLAGS -DXASSERT_ENABLE_ASSERTIONS=0
-DXASSERT_ENABLE_DEBUG=0
-DXASSERT_ENABLE_LINE_NUMBERS=0)
XMOS_REGISTER_MODULE()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
/*
* lfs util functions
*
* Copyright (c) 2022, The littlefs authors.
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "lfs_util.h"
// Only compile if user does not provide custom config
#ifndef LFS_CONFIG
// If user provides their own CRC impl we don't need this
#ifndef LFS_CRC
// Software CRC implementation with small lookup table
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
static const uint32_t rtable[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
};
const uint8_t *data = buffer;
for (size_t i = 0; i < size; i++) {
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf];
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf];
}
return crc;
}
#endif
#endif

View File

@@ -0,0 +1,30 @@
if((${CMAKE_SYSTEM_NAME} STREQUAL XCORE_XS3A) OR (${CMAKE_SYSTEM_NAME} STREQUAL XCORE_XS2A))
## Create library target
add_library(framework_rtos_drivers_qspi_io INTERFACE)
target_sources(framework_rtos_drivers_qspi_io
INTERFACE
src/rtos_qspi_flash.c
src/rtos_qspi_flash_rpc.c
)
target_include_directories(framework_rtos_drivers_qspi_io
INTERFACE
api
)
target_link_libraries(framework_rtos_drivers_qspi_io
INTERFACE
lib_qspi_fast_read
rtos::osal
)
target_compile_options(framework_rtos_drivers_qspi_io
INTERFACE
-lquadflash
)
target_link_options(framework_rtos_drivers_qspi_io
INTERFACE
-lquadflash
)
## Create an alias
add_library(rtos::drivers::qspi_io ALIAS framework_rtos_drivers_qspi_io)
endif()

View File

@@ -0,0 +1,394 @@
// Copyright 2020-2023 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#ifndef RTOS_QSPI_FLASH_H_
#define RTOS_QSPI_FLASH_H_
/**
* \addtogroup rtos_qspi_flash_driver rtos_qspi_flash_driver
*
* The public API for using the RTOS QSPI flash driver.
* @{
*/
#include <quadflash.h>
#include <quadflashlib.h>
#include "xs1.h"
#include "xcore/clock.h"
#include "xcore/port.h"
#define RTOS_QSPI_FLASH_READ_CHUNK_SIZE (24*1024)
/**
* Typedef to the RTOS QSPI flash driver instance struct.
*/
typedef struct rtos_qspi_flash_struct rtos_qspi_flash_t;
/**
* Struct representing an RTOS QSPI flash driver instance.
*
* The members in this struct should not be accessed directly.
*/
struct rtos_qspi_flash_struct {
__attribute__((fptrgroup("rtos_qspi_flash_read_fptr_grp")))
void (*read)(rtos_qspi_flash_t *, uint8_t *, unsigned, size_t);
__attribute__((fptrgroup("rtos_qspi_flash_read_mode_fptr_grp")))
void (*read_mode)(rtos_qspi_flash_t *, uint8_t *, unsigned, size_t, qspi_fast_flash_read_transfer_mode_t);
__attribute__((fptrgroup("rtos_qspi_flash_write_fptr_grp")))
void (*write)(rtos_qspi_flash_t *, const uint8_t *, unsigned, size_t);
__attribute__((fptrgroup("rtos_qspi_flash_erase_fptr_grp")))
void (*erase)(rtos_qspi_flash_t *, unsigned, size_t);
__attribute__((fptrgroup("rtos_qspi_flash_lock_fptr_grp")))
void (*lock)(rtos_qspi_flash_t *);
__attribute__((fptrgroup("rtos_qspi_flash_unlock_fptr_grp")))
void (*unlock)(rtos_qspi_flash_t *);
fl_QSPIPorts qspi_ports;
fl_QuadDeviceSpec qspi_spec;
size_t flash_size;
unsigned calibration_valid;
unsigned last_op;
volatile int spinlock;
volatile int ll_req_flag;
};
/**
* \addtogroup rtos_qspi_flash_driver_core rtos_qspi_flash_driver_core
*
* The core functions for using an RTOS QSPI flash driver instance after
* it has been initialized and started. These functions may be used
* by both the host and any client tiles that RPC has been enabled for.
* @{
*/
/**
* Obtains a lock for exclusive access to the QSPI flash. This allows
* a thread to perform a sequence of operations (such as read, modify, erase,
* write) without the risk of another thread issuing a command in the middle of
* the sequence and corrupting the data in the flash.
*
* If only a single atomic operation needs to be performed, such as a read, it
* is not necessary to call this to obtain the lock first. Each individual operation
* obtains and releases the lock automatically so that they cannot run while another
* thread has the lock.
*
* The lock MUST be released when it is no longer needed by calling
* rtos_qspi_flash_unlock().
*
* \param ctx A pointer to the QSPI flash driver instance to lock.
*/
inline void rtos_qspi_flash_lock(
rtos_qspi_flash_t *ctx)
{
ctx->lock(ctx);
}
/**
* Releases a lock for exclusive access to the QSPI flash. The lock
* must have already been obtained by calling rtos_qspi_flash_lock().
*
* \param ctx A pointer to the QSPI flash driver instance to unlock.
*/
inline void rtos_qspi_flash_unlock(
rtos_qspi_flash_t *ctx)
{
ctx->unlock(ctx);
}
/**
* This reads data from the flash in quad I/O mode. All four lines are
* used to send the address and to read the data.
*
* \param ctx A pointer to the QSPI flash driver instance to use.
* \param data Pointer to the buffer to save the read data to.
* \param address The byte address in the flash to begin reading at.
* Only bits 23:0 contain the address. Bits 31:24 are
* ignored.
* \param len The number of bytes to read and save to \p data.
*/
inline void rtos_qspi_flash_read(
rtos_qspi_flash_t *ctx,
uint8_t *data,
unsigned address,
size_t len)
{
ctx->read(ctx, data, address, len);
}
/**
* This is a lower level version of rtos_qspi_flash_read() that is safe
* to call from within ISRs. If a task currently own the flash lock, or
* if another core is actively doing a read with this function, then the
* read will not be performed and an error returned. It is up to the
* application to determine what it should do in this situation and to
* avoid a potential deadlock.
*
* This function may only be called on the same tile as the underlying
* peripheral.
*
* This function uses the lib_quadflash API to perform the read. It is up
* to the application to ensure that XCORE resources are properly configured.
*
* \note It is not possible to call this from a task that currently owns
* the flash lock taken with rtos_qspi_flash_lock(). In general it is not
* advisable to call this from an RTOS task unless the small amount of
* overhead time that is introduced by rtos_qspi_flash_read() is unacceptable.
*
* \param ctx A pointer to the QSPI flash driver instance to use.
* \param data Pointer to the buffer to save the read data to.
* \param address The byte address in the flash to begin reading at.
* Only bits 23:0 contain the address. Bits 31:24 are
* ignored.
* \param len The number of bytes to read and save to \p data.
*
* \retval 0 if the flash was available and the read operation was performed.
* \retval -1 if the flash was unavailable and the read could not be performed.
*/
int rtos_qspi_flash_read_ll(
rtos_qspi_flash_t *ctx,
uint8_t *data,
unsigned address,
size_t len);
/**
* This writes data to the QSPI flash. The standard page program command
* is sent and only SIO0 (MOSI) is used to send the address and data.
*
* The driver handles sending the write enable command, as well as waiting for
* the write to complete.
*
* This function may return before the write operation is complete, as the actual
* write operation is queued and executed by a thread created by the driver.
*
* \note this function does NOT erase the flash first. Erase operations must be
* explicitly requested by the application.
*
* \param ctx A pointer to the QSPI flash driver instance to use.
* \param data Pointer to the data to write to the flash.
* \param address The byte address in the flash to begin writing at.
* Only bits 23:0 contain the address. The byte in bits 31:24 is
* not sent.
* \param len The number of bytes to write to the flash.
*/
inline void rtos_qspi_flash_write(
rtos_qspi_flash_t *ctx,
const uint8_t *data,
unsigned address,
size_t len)
{
ctx->write(ctx, data, address, len);
}
/**
* This erases data from the QSPI flash. If the address range to erase
* spans multiple sectors, then all of these sectors will be erased by issuing
* multiple erase commands.
*
* The driver handles sending the write enable command, as well as waiting for
* the write to complete.
*
* This function may return before the write operation is complete, as the actual
* erase operation is queued and executed by a thread created by the driver.
*
* \note The smallest amount of data that can be erased is a 4k sector.
* This means that data outside the address range specified by \p address
* and \p len will be erased if the address range does not both begin and
* end at 4k sector boundaries.
*
* \param ctx A pointer to the QSPI flash driver instance to use.
* \param address The byte address to begin erasing. This does not need to begin
* at a sector boundary, but if it does not, note that the entire
* sector that contains this address will still be erased.
* \param len The minimum number of bytes to erase. If \p address + \p len - 1
* does not correspond to the last address within a sector, note that
* the entire sector that contains this address will still be erased.
*/
inline void rtos_qspi_flash_erase(
rtos_qspi_flash_t *ctx,
unsigned address,
size_t len)
{
ctx->erase(ctx, address, len);
}
/**
* This gets the size in bytes of the flash chip.
*
* \param A pointer to the QSPI flash driver instance to query.
*
* \returns the size in bytes of the flash chip.
*/
inline size_t rtos_qspi_flash_size_get(
rtos_qspi_flash_t *qspi_flash_ctx)
{
return qspi_flash_ctx->flash_size;
}
/**
* This gets the size in bytes of each page in the flash chip.
*
* \param A pointer to the QSPI flash driver instance to query.
*
* \returns the size in bytes of the flash page.
*/
inline size_t rtos_qspi_flash_page_size_get(
rtos_qspi_flash_t *qspi_flash_ctx)
{
return qspi_flash_ctx->qspi_spec.pageSize;
}
/**
* This gets the number of pages in the flash chip.
*
* \param A pointer to the QSPI flash driver instance to query.
*
* \returns the number of pages in the flash chip.
*/
inline size_t rtos_qspi_flash_page_count_get(
rtos_qspi_flash_t *qspi_flash_ctx)
{
return qspi_flash_ctx->qspi_spec.numPages;
}
/**
* This gets the sector size of the flash chip
*
* \param A pointer to the QSPI flash driver instance to query.
*
* \returns the size in bytes of the smallest sector
*/
inline size_t rtos_qspi_flash_sector_size_get(
rtos_qspi_flash_t *qspi_flash_ctx)
{
return qspi_flash_ctx->qspi_spec.sectorEraseSize;
}
/**
* Gets the value of the calibration valid.
*
* \param A pointer to the QSPI flash driver instance to query.
*
* \returns 1 if calibration was successful
* 0 otherwise
*/
inline unsigned rtos_qspi_flash_calibration_valid_get(
rtos_qspi_flash_t *qspi_flash_ctx)
{
return qspi_flash_ctx->calibration_valid;
}
/**@}*/
/**
* Starts an RTOS QSPI flash driver instance. This must only be called by the tile that
* owns the driver instance. It may be called either before or after starting
* the RTOS, but must be called before any of the core QSPI flash driver functions are
* called with this instance.
*
* rtos_qspi_flash_init() must be called on this QSPI flash driver instance prior to calling this.
*
* \param ctx A pointer to the QSPI flash driver instance to start.
* \param priority The priority of the task that gets created by the driver to
* handle the QSPI flash interface.
*/
void rtos_qspi_flash_start(
rtos_qspi_flash_t *ctx,
unsigned priority);
/**
* Sets the core affinity for a RTOS QSPI flash driver instance.
* This must only be called by the tile that owns the driver instance.
* It may be called either before or after starting the RTOS, and should
* be called before any of the core QSPI flash driver functions are
* called with this instance.
*
* Since interrupts are disabled during the QSPI transaction on the op thread, a
* core mask is provided to allow users to avoid collisions with application ISRs.
*
* rtos_qspi_flash_start() must be called on this QSPI flash driver instance prior to calling this.
*
* \param ctx A pointer to the QSPI flash driver instance to start.
* \param op_core_mask A bitmask representing the cores on which the QSPI I/O thread
* created by the driver is allowed to run. Bit 0 is core 0, bit 1 is core 1,
* etc.
*/
void rtos_qspi_flash_op_core_affinity_set(
rtos_qspi_flash_t *ctx,
uint32_t op_core_mask);
/**
* Initializes an RTOS QSPI flash driver instance.
* This must only be called by the tile that owns the driver instance. It may be
* called either before or after starting the RTOS, but must be called before calling
* rtos_qspi_flash_start() or any of the core QSPI flash driver functions with this instance.
*
* This function will initialize a flash driver using lib_quadflash for
* all operations.
*
* \param ctx A pointer to the QSPI flash driver instance to initialize.
* \param clock_block The clock block to use for the qspi_io interface.
* \param cs_port The chip select port. MUST be a 1-bit port.
* \param sclk_port The SCLK port. MUST be a 1-bit port.
* \param sio_port The SIO port. MUST be a 4-bit port.
* \param spec A pointer to the flash part specification.
* This may be set to NULL to use the XTC default
*/
void rtos_qspi_flash_init(
rtos_qspi_flash_t *ctx,
xclock_t clock_block,
port_t cs_port,
port_t sclk_port,
port_t sio_port,
fl_QuadDeviceSpec *spec);
/**
* Initializes an RTOS QSPI flash driver instance.
* This must only be called by the tile that owns the driver instance. It may be
* called either before or after starting the RTOS, but must be called before calling
* rtos_qspi_flash_start() or any of the core QSPI flash driver functions with this instance.
*
* This function will initialize a flash driver using lib_quadflash for
* erase and writes, and lib_qspi_fast_read for reads. If calibration
* fails the driver will enable lib_quadflash for reads and allow the
* application to decide what to do about the failed calibration. The
* status of the calibration can be checked at runtime by calling
* rtos_qspi_flash_calibration_valid_get().
*
* \param ctx A pointer to the QSPI flash driver instance to initialize.
* \param clock_block The clock block to use for the qspi_io interface.
* \param cs_port The chip select port. MUST be a 1-bit port.
* \param sclk_port The SCLK port. MUST be a 1-bit port.
* \param sio_port The SIO port. MUST be a 4-bit port.
* \param spec A pointer to the flash part specification.
* This may be set to NULL to use the XTC default
* \param read_mode The transfer mode to use for port reads.
* Invalid values will default to qspi_fast_flash_read_transfer_raw
* \param read_divide The divisor to use for QSPI SCLK.
* \param calibration_pattern_addr The address of the default calibration pattern.
* This driver requires the default calibration pattern
* supplied with lib_qspi_fast_read and does not support
* custom patterns.
*/
void rtos_qspi_flash_fast_read_init(
rtos_qspi_flash_t *ctx,
xclock_t clock_block,
port_t cs_port,
port_t sclk_port,
port_t sio_port,
fl_QuadDeviceSpec *spec,
uint8_t read_divide,
uint32_t calibration_pattern_addr);
/**@}*/
#endif /* RTOS_QSPI_FLASH_H_ */

View File

@@ -0,0 +1,10 @@
set(LIB_NAME lib_qspi_flash)
set(LIB_VERSION 0.0.1)
set(LIB_INCLUDES api)
set(LIB_DEPENDENT_MODULES "lib_logging(3.2.0)")
set(LIB_COMPILER_FLAGS -O3 -DREF_CLK_FREQ=100 -fasm-linenum -fcomment-asm)
list(APPEND LIB_COMPILER_FLAGS -DXASSERT_ENABLE_ASSERTIONS=0
-DXASSERT_ENABLE_DEBUG=0
-DXASSERT_ENABLE_LINE_NUMBERS=0)
XMOS_REGISTER_MODULE()

View File

@@ -0,0 +1,399 @@
// Copyright 2020-2023 XMOS LIMITED.
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
#define DEBUG_UNIT RTOS_QSPI_FLASH
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <xcore/assert.h>
#include <xcore/lock.h>
#include "rtos_qspi_flash.h"
#ifndef QSPI_FL_RETRY_DELAY_TICKS
#define QSPI_FL_RETRY_DELAY_TICKS 1000
#endif
#ifndef QSPI_FL_RETRY_ATTEMPT_CNT
#define QSPI_FL_RETRY_ATTEMPT_CNT 5
#endif
/* TODO, these will be removed once moved to the public API */
#define ERASE_CHIP 0xC7
extern void fl_int_read(
unsigned char cmd,
unsigned int address,
unsigned char * destination,
unsigned int num_bytes);
extern void fl_int_write(
unsigned char cmd,
unsigned int pageAddress,
const unsigned char data[],
unsigned int num_bytes);
extern void fl_int_sendSingleByteCommand(unsigned char cmd);
extern void fl_int_eraseSector(
unsigned char cmd,
unsigned int sectorAddress);
/* end TODO */
/* Library only supports 4096 sector size*/
#define QSPI_ERASE_TYPE_SIZE_LOG2 12
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define FLASH_OP_NONE 0
#define FLASH_OP_READ 1
#define FLASH_OP_WRITE 2
#define FLASH_OP_ERASE 3
#define FLASH_OP_READ_FAST_RAW 4
#define FLASH_OP_READ_FAST_NIBBLE_SWAP 5
#define FLASH_OP_LL_SETUP 6
extern unsigned __libc_hwlock;
typedef struct {
int op;
uint8_t *data;
unsigned address;
size_t len;
unsigned priority;
} qspi_flash_op_req_t;
/*
* Returns true if the spinlock is
* acquired, false if not available.
* NOT recursive.
*/
static bool spinlock_get(volatile int *lock)
{
bool ret;
lock_acquire(__libc_hwlock);
{
if (*lock == 0) {
*lock = 1;
ret = true;
} else {
ret = false;
}
}
lock_release(__libc_hwlock);
return ret;
}
/*
* Releases the lock. It MUST be owned
* by the caller.
*/
static void spinlock_release(volatile int *lock)
{
*lock = 0;
}
static void rtos_qspi_flash_fl_connect_with_retry(
rtos_qspi_flash_t *ctx)
{
if (fl_connectToDevice(&ctx->qspi_ports, &ctx->qspi_spec, 1) == 0) {
return;
} else {
int cnt = 0;
while (fl_connectToDevice(&ctx->qspi_ports, &ctx->qspi_spec, 1) != 0) {
delay_ticks(QSPI_FL_RETRY_DELAY_TICKS);
if (cnt++ >= QSPI_FL_RETRY_ATTEMPT_CNT) {
xassert(0); /* fl_connectToDevice failed too many times */
}
}
}
}
int rtos_qspi_flash_read_ll(
rtos_qspi_flash_t *ctx,
uint8_t *data,
unsigned address,
size_t len)
{
uint32_t irq_mask;
bool lock_acquired;
// printf("Asked to ll read %d bytes at address 0x%08x\n", len, address);
lock_acquired = spinlock_get(&ctx->spinlock);
while (lock_acquired && len > 0) {
size_t read_len = MIN(len, RTOS_QSPI_FLASH_READ_CHUNK_SIZE);
/*
* Cap the address at the size of the flash.
* This ensures the correction below will work if
* address is outside the flash's address space.
*/
if (address >= ctx->flash_size) {
address = ctx->flash_size;
}
if (address + read_len > ctx->flash_size) {
int original_len = read_len;
/* Don't read past the end of the flash */
read_len = ctx->flash_size - address;
/* Return all 0xFF bytes for addresses beyond the end of the flash */
memset(&data[read_len], 0xFF, original_len - read_len);
}
// printf("Read %d bytes from flash at address 0x%x\n", read_len, address);
fl_int_read(ctx->qspi_spec.readCommand, address, data, read_len);
len -= read_len;
data += read_len;
address += read_len;
}
if (lock_acquired) {
spinlock_release(&ctx->spinlock);
}
return lock_acquired ? 0 : -1;
}
static void read_op(
rtos_qspi_flash_t *ctx,
uint8_t *data,
unsigned address,
size_t len)
{
// printf("Asked to read %d bytes at address 0x%08x\n", len, address);
while (len > 0) {
size_t read_len = MIN(len, RTOS_QSPI_FLASH_READ_CHUNK_SIZE);
/*
* Cap the address at the size of the flash.
* This ensures the correction below will work if
* address is outside the flash's address space.
*/
if (address >= ctx->flash_size) {
address = ctx->flash_size;
}
if (address + read_len > ctx->flash_size) {
int original_len = read_len;
/* Don't read past the end of the flash */
read_len = ctx->flash_size - address;
/* Return all 0xFF bytes for addresses beyond the end of the flash */
memset(&data[read_len], 0xFF, original_len - read_len);
}
// printf("Read %d bytes from flash at address 0x%x\n", read_len, address);
fl_int_read(ctx->qspi_spec.readCommand, address, data, read_len);
len -= read_len;
data += read_len;
address += read_len;
}
}
static void while_busy(void)
{
bool busy;
do {
busy = fl_getBusyStatus();
} while (busy);
}
static void write_op(
rtos_qspi_flash_t *ctx,
const uint8_t *data,
unsigned address,
size_t len)
{
size_t bytes_left_to_write = len;
unsigned address_to_write = address;
const uint8_t *write_buf = data;
// printf("Asked to write %d bytes at address 0x%08x\n", bytes_left_to_write, address_to_write);
while (bytes_left_to_write > 0) {
/* compute the maximum number of bytes that can be written to the current page. */
size_t max_bytes_to_write = fl_getPageSize() - (address_to_write & (fl_getPageSize() - 1));
size_t bytes_to_write = bytes_left_to_write <= max_bytes_to_write ? bytes_left_to_write : max_bytes_to_write;
if (address_to_write >= ctx->flash_size) {
break; /* do not write past the end of the flash */
}
// printf("Write %d bytes from flash at address 0x%x\n", bytes_to_write, address_to_write);
fl_int_sendSingleByteCommand(ctx->qspi_spec.writeEnableCommand);
fl_int_write(ctx->qspi_spec.programPageCommand, address_to_write, write_buf, bytes_to_write);
while_busy();
fl_int_sendSingleByteCommand(ctx->qspi_spec.writeDisableCommand);
bytes_left_to_write -= bytes_to_write;
write_buf += bytes_to_write;
address_to_write += bytes_to_write;
}
}
#define SECTORS_TO_BYTES(s, ss_log2) ((s) << (ss_log2))
#define BYTES_TO_SECTORS(b, ss_log2) (((b) + (1 << ss_log2) - 1) >> (ss_log2))
#define SECTOR_TO_BYTE_ADDRESS(s, ss_log2) SECTORS_TO_BYTES(s, ss_log2)
#define BYTE_TO_SECTOR_ADDRESS(b, ss_log2) ((b) >> (ss_log2))
static void erase_op(
rtos_qspi_flash_t *ctx,
unsigned address,
size_t len)
{
size_t bytes_left_to_erase = len;
unsigned address_to_erase = address;
// printf("Asked to erase %d bytes at address 0x%08x\n", bytes_left_to_erase, address_to_erase);
if (address_to_erase == 0 && bytes_left_to_erase >= ctx->flash_size) {
/* Use chip erase when being asked to erase the entire address range */
// printf("Erasing entire chip\n");
fl_int_sendSingleByteCommand(ctx->qspi_spec.writeEnableCommand);
fl_int_sendSingleByteCommand(ERASE_CHIP);
while_busy();
fl_int_sendSingleByteCommand(ctx->qspi_spec.writeDisableCommand);
} else {
if (SECTOR_TO_BYTE_ADDRESS(BYTE_TO_SECTOR_ADDRESS(address_to_erase, QSPI_ERASE_TYPE_SIZE_LOG2), QSPI_ERASE_TYPE_SIZE_LOG2) != address_to_erase) {
/*
* If the provided starting erase address does not begin on the smallest
* sector boundary, then update the starting address and number of bytes
* to erase so that it does.
*/
unsigned sector_address;
sector_address = BYTE_TO_SECTOR_ADDRESS(address_to_erase, QSPI_ERASE_TYPE_SIZE_LOG2);
bytes_left_to_erase += address_to_erase - SECTOR_TO_BYTE_ADDRESS(sector_address, QSPI_ERASE_TYPE_SIZE_LOG2);
address_to_erase = SECTOR_TO_BYTE_ADDRESS(sector_address, QSPI_ERASE_TYPE_SIZE_LOG2);
// printf("adjusted starting erase address to %d\n", address_to_erase);
}
while (bytes_left_to_erase > 0) {
int erase_length;
int erase_length_log2 = QSPI_ERASE_TYPE_SIZE_LOG2;
if (address_to_erase >= ctx->flash_size) {
break; /* do not erase past the end of the flash */
}
erase_length = 1 << erase_length_log2;
xassert(address_to_erase == SECTOR_TO_BYTE_ADDRESS(BYTE_TO_SECTOR_ADDRESS(address_to_erase, erase_length_log2), erase_length_log2));
// printf("Erasing %d bytes (%d) at byte address %d, sector %d\n", erase_length, bytes_left_to_erase, address_to_erase, BYTE_TO_SECTOR_ADDRESS(address_to_erase, erase_length_log2));
fl_int_sendSingleByteCommand(ctx->qspi_spec.writeEnableCommand);
fl_int_eraseSector(ctx->qspi_spec.sectorEraseCommand, address_to_erase);
while_busy();
fl_int_sendSingleByteCommand(ctx->qspi_spec.writeDisableCommand);
address_to_erase += erase_length;
bytes_left_to_erase -= erase_length < bytes_left_to_erase ? erase_length : bytes_left_to_erase;
}
}
// printf("Erasing complete\n");
}
__attribute__((fptrgroup("rtos_qspi_flash_read_fptr_grp")))
static void qspi_flash_local_read(
rtos_qspi_flash_t *ctx,
uint8_t *data,
unsigned address,
size_t len)
{
qspi_flash_op_req_t op = {
.op = FLASH_OP_READ,
.data = data,
.address = address,
.len = len
};
read_op(ctx, op.data, op.address, op.len);
}
__attribute__((fptrgroup("rtos_qspi_flash_write_fptr_grp")))
static void qspi_flash_local_write(
rtos_qspi_flash_t *ctx,
const uint8_t *data,
unsigned address,
size_t len)
{
qspi_flash_op_req_t op = {
.op = FLASH_OP_WRITE,
.address = address,
.len = len
};
write_op(ctx, data, op.address, op.len);
}
__attribute__((fptrgroup("rtos_qspi_flash_erase_fptr_grp")))
static void qspi_flash_local_erase(
rtos_qspi_flash_t *ctx,
unsigned address,
size_t len)
{
qspi_flash_op_req_t op = {
.op = FLASH_OP_ERASE,
.address = address,
.len = len
};
erase_op(ctx, op.address, op.len);
}
void rtos_qspi_flash_init(
rtos_qspi_flash_t *ctx,
xclock_t clock_block,
port_t cs_port,
port_t sclk_port,
port_t sio_port,
fl_QuadDeviceSpec *spec)
{
ctx->qspi_ports.qspiCS = cs_port;
ctx->qspi_ports.qspiSCLK = sclk_port;
ctx->qspi_ports.qspiSIO = sio_port;
ctx->qspi_ports.qspiClkblk = clock_block;
fl_QuadDeviceSpec default_spec = FL_QUADDEVICE_DEFAULT;
if (spec == NULL) {
memcpy(&ctx->qspi_spec, &default_spec, sizeof(fl_QuadDeviceSpec));
} else {
memcpy(&ctx->qspi_spec, spec, sizeof(fl_QuadDeviceSpec));
}
xassert(fl_connectToDevice(&ctx->qspi_ports, &ctx->qspi_spec, 1) == 0);
/* Copy the spec back in case one was provided which has params populated by sfdp */
xassert(fl_copySpec(&ctx->qspi_spec) == 0);
ctx->flash_size = fl_getFlashSize();
/* Driver currently only supports 4096 sector size */
xassert(rtos_qspi_flash_sector_size_get(ctx) == (1 << QSPI_ERASE_TYPE_SIZE_LOG2));
/* Enable quad flash */
xassert(fl_quadEnable() == 0);
ctx->calibration_valid = 0;
ctx->last_op = FLASH_OP_NONE;
ctx->read = qspi_flash_local_read;
ctx->write = qspi_flash_local_write;
ctx->erase = qspi_flash_local_erase;
}