0xc3 hai 1 semana
pai
achega
33bd597375

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
.mxproject


+ 66 - 0
.settings/bundles-lock.store.json

@@ -121,6 +121,39 @@
         }
       ]
     },
+    {
+      "name": "programmer",
+      "version": "2.21.0",
+      "platform": "darwin",
+      "selected_by": [
+        {
+          "name": "programmer",
+          "version": "2.21.0"
+        }
+      ]
+    },
+    {
+      "name": "programmer",
+      "version": "2.21.0",
+      "platform": "x86_64-linux",
+      "selected_by": [
+        {
+          "name": "programmer",
+          "version": "2.21.0"
+        }
+      ]
+    },
+    {
+      "name": "programmer",
+      "version": "2.21.0",
+      "platform": "x86_64-windows",
+      "selected_by": [
+        {
+          "name": "programmer",
+          "version": "2.21.0"
+        }
+      ]
+    },
     {
       "name": "st-arm-clangd",
       "version": "19.1.2+st.3",
@@ -153,6 +186,39 @@
           "version": "19.1.2+st.3"
         }
       ]
+    },
+    {
+      "name": "stlink-gdbserver",
+      "version": "7.12.0+st.2",
+      "platform": "darwin",
+      "selected_by": [
+        {
+          "name": "stlink-gdbserver",
+          "version": "7.12.0+st.2"
+        }
+      ]
+    },
+    {
+      "name": "stlink-gdbserver",
+      "version": "7.12.0+st.2",
+      "platform": "x86_64-linux",
+      "selected_by": [
+        {
+          "name": "stlink-gdbserver",
+          "version": "7.12.0+st.2"
+        }
+      ]
+    },
+    {
+      "name": "stlink-gdbserver",
+      "version": "7.12.0+st.2",
+      "platform": "x86_64-windows",
+      "selected_by": [
+        {
+          "name": "stlink-gdbserver",
+          "version": "7.12.0+st.2"
+        }
+      ]
     }
   ]
 }

+ 8 - 0
.settings/bundles.store.json

@@ -15,6 +15,14 @@
     {
       "name": "st-arm-clangd",
       "version": "19.1.2+st.3"
+    },
+    {
+      "name": "programmer",
+      "version": "2.21.0"
+    },
+    {
+      "name": "stlink-gdbserver",
+      "version": "7.12.0+st.2"
     }
   ]
 }

+ 22 - 0
.vscode/launch.json

@@ -0,0 +1,22 @@
+{
+  // Use IntelliSense to learn about possible attributes.
+  // Hover to view descriptions of existing attributes.
+  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "type": "stlinkgdbtarget",
+      "request": "launch",
+      "name": "STM32Cube: STM32 Launch ST-Link GDB Server",
+      "origin": "snippet",
+      "cwd": "${workspaceFolder}",
+      "preBuild": "${command:st-stm32-ide-debug-launch.build}",
+      "runEntry": "main",
+      "imagesAndSymbols": [
+        {
+          "imageFileName": "${command:st-stm32-ide-debug-launch.get-projects-binary-from-context1}"
+        }
+      ]
+    }
+  ]
+}

+ 2 - 0
CMakeLists.txt

@@ -45,6 +45,8 @@ target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
 # Add sources to executable
 target_sources(${CMAKE_PROJECT_NAME} PRIVATE
     # Add user sources here
+    ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/lfs.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/Core/Src/lfs_util.c
 )
 
 # Add include paths

+ 49 - 0
Core/Inc/gpio.h

@@ -0,0 +1,49 @@
+/* USER CODE BEGIN Header */
+/**
+  ******************************************************************************
+  * @file    gpio.h
+  * @brief   This file contains all the function prototypes for
+  *          the gpio.c file
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2026 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  */
+/* USER CODE END Header */
+/* Define to prevent recursive inclusion -------------------------------------*/
+#ifndef __GPIO_H__
+#define __GPIO_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
+
+/* USER CODE BEGIN Includes */
+
+/* USER CODE END Includes */
+
+/* USER CODE BEGIN Private defines */
+
+/* USER CODE END Private defines */
+
+void MX_GPIO_Init(void);
+
+/* USER CODE BEGIN Prototypes */
+
+/* USER CODE END Prototypes */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /*__ GPIO_H__ */
+

+ 801 - 0
Core/Inc/lfs.h

@@ -0,0 +1,801 @@
+/*
+ * 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 0x0002000b
+#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.
+//
+// If LFS_SHRINKNONRELOCATING is defined, this function will also accept
+// block_counts smaller than the current configuration, after checking
+// that none of the blocks that are being removed are in use.
+// Note that littlefs's pseudorandom block allocation means that
+// this is very unlikely to work in the general case.
+//
+// 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

+ 273 - 0
Core/Inc/lfs_util.h

@@ -0,0 +1,273 @@
+/*
+ * 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 ((uint32_t)((uint8_t*)&a)[0] <<  0) |
+           ((uint32_t)((uint8_t*)&a)[1] <<  8) |
+           ((uint32_t)((uint8_t*)&a)[2] << 16) |
+           ((uint32_t)((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 ((uint32_t)((uint8_t*)&a)[0] << 24) |
+           ((uint32_t)((uint8_t*)&a)[1] << 16) |
+           ((uint32_t)((uint8_t*)&a)[2] <<  8) |
+           ((uint32_t)((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
+static inline 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)
+    free(p);
+#else
+    (void)p;
+#endif
+}
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
+#endif

+ 2 - 8
Core/Inc/main.h

@@ -57,20 +57,14 @@ void Error_Handler(void);
 /* USER CODE END EFP */
 
 /* Private defines -----------------------------------------------------------*/
-#define B1_Pin GPIO_PIN_13
-#define B1_GPIO_Port GPIOC
 #define USART_TX_Pin GPIO_PIN_2
 #define USART_TX_GPIO_Port GPIOA
 #define USART_RX_Pin GPIO_PIN_3
 #define USART_RX_GPIO_Port GPIOA
-#define LD2_Pin GPIO_PIN_13
-#define LD2_GPIO_Port GPIOB
-#define TMS_Pin GPIO_PIN_13
-#define TMS_GPIO_Port GPIOA
 #define TCK_Pin GPIO_PIN_14
 #define TCK_GPIO_Port GPIOA
-#define SWO_Pin GPIO_PIN_3
-#define SWO_GPIO_Port GPIOB
+#define SPI_CS_Pin GPIO_PIN_6
+#define SPI_CS_GPIO_Port GPIOB
 
 /* USER CODE BEGIN Private defines */
 

+ 52 - 0
Core/Inc/spi.h

@@ -0,0 +1,52 @@
+/* USER CODE BEGIN Header */
+/**
+  ******************************************************************************
+  * @file    spi.h
+  * @brief   This file contains all the function prototypes for
+  *          the spi.c file
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2026 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  */
+/* USER CODE END Header */
+/* Define to prevent recursive inclusion -------------------------------------*/
+#ifndef __SPI_H__
+#define __SPI_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
+
+/* USER CODE BEGIN Includes */
+
+/* USER CODE END Includes */
+
+extern SPI_HandleTypeDef hspi2;
+
+/* USER CODE BEGIN Private defines */
+
+/* USER CODE END Private defines */
+
+void MX_SPI2_Init(void);
+
+/* USER CODE BEGIN Prototypes */
+
+/* USER CODE END Prototypes */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SPI_H__ */
+

+ 1 - 1
Core/Inc/stm32f3xx_hal_conf.h

@@ -56,7 +56,7 @@
 /*#define HAL_LPTIM_MODULE_ENABLED   */
 /*#define HAL_RNG_MODULE_ENABLED   */
 /*#define HAL_RTC_MODULE_ENABLED   */
-/*#define HAL_SPI_MODULE_ENABLED   */
+#define HAL_SPI_MODULE_ENABLED
 /*#define HAL_TIM_MODULE_ENABLED   */
 #define HAL_UART_MODULE_ENABLED
 /*#define HAL_USART_MODULE_ENABLED   */

+ 52 - 0
Core/Inc/usart.h

@@ -0,0 +1,52 @@
+/* USER CODE BEGIN Header */
+/**
+  ******************************************************************************
+  * @file    usart.h
+  * @brief   This file contains all the function prototypes for
+  *          the usart.c file
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2026 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  */
+/* USER CODE END Header */
+/* Define to prevent recursive inclusion -------------------------------------*/
+#ifndef __USART_H__
+#define __USART_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
+
+/* USER CODE BEGIN Includes */
+
+/* USER CODE END Includes */
+
+extern UART_HandleTypeDef huart2;
+
+/* USER CODE BEGIN Private defines */
+
+/* USER CODE END Private defines */
+
+void MX_USART2_UART_Init(void);
+
+/* USER CODE BEGIN Prototypes */
+
+/* USER CODE END Prototypes */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __USART_H__ */
+

+ 67 - 0
Core/Src/gpio.c

@@ -0,0 +1,67 @@
+/* USER CODE BEGIN Header */
+/**
+  ******************************************************************************
+  * @file    gpio.c
+  * @brief   This file provides code for the configuration
+  *          of all used GPIO pins.
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2026 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  */
+/* USER CODE END Header */
+
+/* Includes ------------------------------------------------------------------*/
+#include "gpio.h"
+
+/* USER CODE BEGIN 0 */
+
+/* USER CODE END 0 */
+
+/*----------------------------------------------------------------------------*/
+/* Configure GPIO                                                             */
+/*----------------------------------------------------------------------------*/
+/* USER CODE BEGIN 1 */
+
+/* USER CODE END 1 */
+
+/** Configure pins as
+        * Analog
+        * Input
+        * Output
+        * EVENT_OUT
+        * EXTI
+*/
+void MX_GPIO_Init(void)
+{
+
+  GPIO_InitTypeDef GPIO_InitStruct = {0};
+
+  /* GPIO Ports Clock Enable */
+  __HAL_RCC_GPIOC_CLK_ENABLE();
+  __HAL_RCC_GPIOF_CLK_ENABLE();
+  __HAL_RCC_GPIOA_CLK_ENABLE();
+  __HAL_RCC_GPIOB_CLK_ENABLE();
+
+  /*Configure GPIO pin Output Level */
+  HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, GPIO_PIN_SET);
+
+  /*Configure GPIO pin : SPI_CS_Pin */
+  GPIO_InitStruct.Pin = SPI_CS_Pin;
+  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
+  GPIO_InitStruct.Pull = GPIO_NOPULL;
+  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
+  HAL_GPIO_Init(SPI_CS_GPIO_Port, &GPIO_InitStruct);
+
+}
+
+/* USER CODE BEGIN 2 */
+
+/* USER CODE END 2 */

+ 6549 - 0
Core/Src/lfs.c

@@ -0,0 +1,6549 @@
+/*
+ * The little filesystem
+ *
+ * Copyright (c) 2022, The littlefs authors.
+ * Copyright (c) 2017, Arm Limited. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+#include "lfs.h"
+#include "lfs_util.h"
+
+
+// some constants used throughout the code
+#define LFS_BLOCK_NULL ((lfs_block_t)-1)
+#define LFS_BLOCK_INLINE ((lfs_block_t)-2)
+
+enum {
+    LFS_OK_RELOCATED = 1,
+    LFS_OK_DROPPED   = 2,
+    LFS_OK_ORPHANED  = 3,
+};
+
+enum {
+    LFS_CMP_EQ = 0,
+    LFS_CMP_LT = 1,
+    LFS_CMP_GT = 2,
+};
+
+
+/// Caching block device operations ///
+
+static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) {
+    // do not zero, cheaper if cache is readonly or only going to be
+    // written with identical data (during relocates)
+    (void)lfs;
+    rcache->block = LFS_BLOCK_NULL;
+}
+
+static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) {
+    // zero to avoid information leak
+    memset(pcache->buffer, 0xff, lfs->cfg->cache_size);
+    pcache->block = LFS_BLOCK_NULL;
+}
+
+static int lfs_bd_read(lfs_t *lfs,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint,
+        lfs_block_t block, lfs_off_t off,
+        void *buffer, lfs_size_t size) {
+    uint8_t *data = buffer;
+    if (off+size > lfs->cfg->block_size
+            || (lfs->block_count && block >= lfs->block_count)) {
+        return LFS_ERR_CORRUPT;
+    }
+
+    while (size > 0) {
+        lfs_size_t diff = size;
+
+        if (pcache && block == pcache->block &&
+                off < pcache->off + pcache->size) {
+            if (off >= pcache->off) {
+                // is already in pcache?
+                diff = lfs_min(diff, pcache->size - (off-pcache->off));
+                memcpy(data, &pcache->buffer[off-pcache->off], diff);
+
+                data += diff;
+                off += diff;
+                size -= diff;
+                continue;
+            }
+
+            // pcache takes priority
+            diff = lfs_min(diff, pcache->off-off);
+        }
+
+        if (block == rcache->block &&
+                off < rcache->off + rcache->size) {
+            if (off >= rcache->off) {
+                // is already in rcache?
+                diff = lfs_min(diff, rcache->size - (off-rcache->off));
+                memcpy(data, &rcache->buffer[off-rcache->off], diff);
+
+                data += diff;
+                off += diff;
+                size -= diff;
+                continue;
+            }
+
+            // rcache takes priority
+            diff = lfs_min(diff, rcache->off-off);
+        }
+
+        if (size >= hint && off % lfs->cfg->read_size == 0 &&
+                size >= lfs->cfg->read_size) {
+            // bypass cache?
+            diff = lfs_aligndown(diff, lfs->cfg->read_size);
+            int err = lfs->cfg->read(lfs->cfg, block, off, data, diff);
+            LFS_ASSERT(err <= 0);
+            if (err) {
+                return err;
+            }
+
+            data += diff;
+            off += diff;
+            size -= diff;
+            continue;
+        }
+
+        // load to cache, first condition can no longer fail
+        LFS_ASSERT(!lfs->block_count || block < lfs->block_count);
+        rcache->block = block;
+        rcache->off = lfs_aligndown(off, lfs->cfg->read_size);
+        rcache->size = lfs_min(
+                lfs_min(
+                    lfs_alignup(off+hint, lfs->cfg->read_size),
+                    lfs->cfg->block_size)
+                - rcache->off,
+                lfs->cfg->cache_size);
+        int err = lfs->cfg->read(lfs->cfg, rcache->block,
+                rcache->off, rcache->buffer, rcache->size);
+        LFS_ASSERT(err <= 0);
+        if (err) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+
+static int lfs_bd_cmp(lfs_t *lfs,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint,
+        lfs_block_t block, lfs_off_t off,
+        const void *buffer, lfs_size_t size) {
+    const uint8_t *data = buffer;
+    lfs_size_t diff = 0;
+
+    for (lfs_off_t i = 0; i < size; i += diff) {
+        uint8_t dat[8];
+
+        diff = lfs_min(size-i, sizeof(dat));
+        int err = lfs_bd_read(lfs,
+                pcache, rcache, hint-i,
+                block, off+i, &dat, diff);
+        if (err) {
+            return err;
+        }
+
+        int res = memcmp(dat, data + i, diff);
+        if (res) {
+            return res < 0 ? LFS_CMP_LT : LFS_CMP_GT;
+        }
+    }
+
+    return LFS_CMP_EQ;
+}
+
+static int lfs_bd_crc(lfs_t *lfs,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint,
+        lfs_block_t block, lfs_off_t off, lfs_size_t size, uint32_t *crc) {
+    lfs_size_t diff = 0;
+
+    for (lfs_off_t i = 0; i < size; i += diff) {
+        uint8_t dat[8];
+        diff = lfs_min(size-i, sizeof(dat));
+        int err = lfs_bd_read(lfs,
+                pcache, rcache, hint-i,
+                block, off+i, &dat, diff);
+        if (err) {
+            return err;
+        }
+
+        *crc = lfs_crc(*crc, &dat, diff);
+    }
+
+    return 0;
+}
+
+#ifndef LFS_READONLY
+static int lfs_bd_flush(lfs_t *lfs,
+        lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) {
+    if (pcache->block != LFS_BLOCK_NULL && pcache->block != LFS_BLOCK_INLINE) {
+        LFS_ASSERT(pcache->block < lfs->block_count);
+        lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size);
+        int err = lfs->cfg->prog(lfs->cfg, pcache->block,
+                pcache->off, pcache->buffer, diff);
+        LFS_ASSERT(err <= 0);
+        if (err) {
+            return err;
+        }
+
+        if (validate) {
+            // check data on disk
+            lfs_cache_drop(lfs, rcache);
+            int res = lfs_bd_cmp(lfs,
+                    NULL, rcache, diff,
+                    pcache->block, pcache->off, pcache->buffer, diff);
+            if (res < 0) {
+                return res;
+            }
+
+            if (res != LFS_CMP_EQ) {
+                return LFS_ERR_CORRUPT;
+            }
+        }
+
+        lfs_cache_zero(lfs, pcache);
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_bd_sync(lfs_t *lfs,
+        lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) {
+    lfs_cache_drop(lfs, rcache);
+
+    int err = lfs_bd_flush(lfs, pcache, rcache, validate);
+    if (err) {
+        return err;
+    }
+
+    err = lfs->cfg->sync(lfs->cfg);
+    LFS_ASSERT(err <= 0);
+    return err;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_bd_prog(lfs_t *lfs,
+        lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate,
+        lfs_block_t block, lfs_off_t off,
+        const void *buffer, lfs_size_t size) {
+    const uint8_t *data = buffer;
+    LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->block_count);
+    LFS_ASSERT(off + size <= lfs->cfg->block_size);
+
+    while (size > 0) {
+        if (block == pcache->block &&
+                off >= pcache->off &&
+                off < pcache->off + lfs->cfg->cache_size) {
+            // already fits in pcache?
+            lfs_size_t diff = lfs_min(size,
+                    lfs->cfg->cache_size - (off-pcache->off));
+            memcpy(&pcache->buffer[off-pcache->off], data, diff);
+
+            data += diff;
+            off += diff;
+            size -= diff;
+
+            pcache->size = lfs_max(pcache->size, off - pcache->off);
+            if (pcache->size == lfs->cfg->cache_size) {
+                // eagerly flush out pcache if we fill up
+                int err = lfs_bd_flush(lfs, pcache, rcache, validate);
+                if (err) {
+                    return err;
+                }
+            }
+
+            continue;
+        }
+
+        // pcache must have been flushed, either by programming and
+        // entire block or manually flushing the pcache
+        LFS_ASSERT(pcache->block == LFS_BLOCK_NULL);
+
+        // prepare pcache, first condition can no longer fail
+        pcache->block = block;
+        pcache->off = lfs_aligndown(off, lfs->cfg->prog_size);
+        pcache->size = 0;
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
+    LFS_ASSERT(block < lfs->block_count);
+    int err = lfs->cfg->erase(lfs->cfg, block);
+    LFS_ASSERT(err <= 0);
+    return err;
+}
+#endif
+
+
+/// Small type-level utilities ///
+
+// some operations on paths
+static inline lfs_size_t lfs_path_namelen(const char *path) {
+    return strcspn(path, "/");
+}
+
+static inline bool lfs_path_islast(const char *path) {
+    lfs_size_t namelen = lfs_path_namelen(path);
+    return path[namelen + strspn(path + namelen, "/")] == '\0';
+}
+
+static inline bool lfs_path_isdir(const char *path) {
+    return path[lfs_path_namelen(path)] != '\0';
+}
+
+// operations on block pairs
+static inline void lfs_pair_swap(lfs_block_t pair[2]) {
+    lfs_block_t t = pair[0];
+    pair[0] = pair[1];
+    pair[1] = t;
+}
+
+static inline bool lfs_pair_isnull(const lfs_block_t pair[2]) {
+    return pair[0] == LFS_BLOCK_NULL || pair[1] == LFS_BLOCK_NULL;
+}
+
+static inline int lfs_pair_cmp(
+        const lfs_block_t paira[2],
+        const lfs_block_t pairb[2]) {
+    return !(paira[0] == pairb[0] || paira[1] == pairb[1] ||
+             paira[0] == pairb[1] || paira[1] == pairb[0]);
+}
+
+static inline bool lfs_pair_issync(
+        const lfs_block_t paira[2],
+        const lfs_block_t pairb[2]) {
+    return (paira[0] == pairb[0] && paira[1] == pairb[1]) ||
+           (paira[0] == pairb[1] && paira[1] == pairb[0]);
+}
+
+static inline void lfs_pair_fromle32(lfs_block_t pair[2]) {
+    pair[0] = lfs_fromle32(pair[0]);
+    pair[1] = lfs_fromle32(pair[1]);
+}
+
+#ifndef LFS_READONLY
+static inline void lfs_pair_tole32(lfs_block_t pair[2]) {
+    pair[0] = lfs_tole32(pair[0]);
+    pair[1] = lfs_tole32(pair[1]);
+}
+#endif
+
+// operations on 32-bit entry tags
+typedef uint32_t lfs_tag_t;
+typedef int32_t lfs_stag_t;
+
+#define LFS_MKTAG(type, id, size) \
+    (((lfs_tag_t)(type) << 20) | ((lfs_tag_t)(id) << 10) | (lfs_tag_t)(size))
+
+#define LFS_MKTAG_IF(cond, type, id, size) \
+    ((cond) ? LFS_MKTAG(type, id, size) : LFS_MKTAG(LFS_FROM_NOOP, 0, 0))
+
+#define LFS_MKTAG_IF_ELSE(cond, type1, id1, size1, type2, id2, size2) \
+    ((cond) ? LFS_MKTAG(type1, id1, size1) : LFS_MKTAG(type2, id2, size2))
+
+static inline bool lfs_tag_isvalid(lfs_tag_t tag) {
+    return !(tag & 0x80000000);
+}
+
+static inline bool lfs_tag_isdelete(lfs_tag_t tag) {
+    return ((int32_t)(tag << 22) >> 22) == -1;
+}
+
+static inline uint16_t lfs_tag_type1(lfs_tag_t tag) {
+    return (tag & 0x70000000) >> 20;
+}
+
+static inline uint16_t lfs_tag_type2(lfs_tag_t tag) {
+    return (tag & 0x78000000) >> 20;
+}
+
+static inline uint16_t lfs_tag_type3(lfs_tag_t tag) {
+    return (tag & 0x7ff00000) >> 20;
+}
+
+static inline uint8_t lfs_tag_chunk(lfs_tag_t tag) {
+    return (tag & 0x0ff00000) >> 20;
+}
+
+static inline int8_t lfs_tag_splice(lfs_tag_t tag) {
+    return (int8_t)lfs_tag_chunk(tag);
+}
+
+static inline uint16_t lfs_tag_id(lfs_tag_t tag) {
+    return (tag & 0x000ffc00) >> 10;
+}
+
+static inline lfs_size_t lfs_tag_size(lfs_tag_t tag) {
+    return tag & 0x000003ff;
+}
+
+static inline lfs_size_t lfs_tag_dsize(lfs_tag_t tag) {
+    return sizeof(tag) + lfs_tag_size(tag + lfs_tag_isdelete(tag));
+}
+
+// operations on attributes in attribute lists
+struct lfs_mattr {
+    lfs_tag_t tag;
+    const void *buffer;
+};
+
+struct lfs_diskoff {
+    lfs_block_t block;
+    lfs_off_t off;
+};
+
+#define LFS_MKATTRS(...) \
+    (struct lfs_mattr[]){__VA_ARGS__}, \
+    sizeof((struct lfs_mattr[]){__VA_ARGS__}) / sizeof(struct lfs_mattr)
+
+// operations on global state
+static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) {
+    a->tag ^= b->tag;
+    a->pair[0] ^= b->pair[0];
+    a->pair[1] ^= b->pair[1];
+}
+
+static inline bool lfs_gstate_iszero(const lfs_gstate_t *a) {
+    return a->tag == 0
+            && a->pair[0] == 0
+            && a->pair[1] == 0;
+}
+
+#ifndef LFS_READONLY
+static inline bool lfs_gstate_hasorphans(const lfs_gstate_t *a) {
+    return lfs_tag_size(a->tag);
+}
+
+static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) {
+    return lfs_tag_size(a->tag) & 0x1ff;
+}
+
+static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) {
+    return lfs_tag_type1(a->tag);
+}
+#endif
+
+static inline bool lfs_gstate_needssuperblock(const lfs_gstate_t *a) {
+    return lfs_tag_size(a->tag) >> 9;
+}
+
+static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a,
+        const lfs_block_t *pair) {
+    return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0;
+}
+
+static inline void lfs_gstate_fromle32(lfs_gstate_t *a) {
+    a->tag     = lfs_fromle32(a->tag);
+    a->pair[0] = lfs_fromle32(a->pair[0]);
+    a->pair[1] = lfs_fromle32(a->pair[1]);
+}
+
+#ifndef LFS_READONLY
+static inline void lfs_gstate_tole32(lfs_gstate_t *a) {
+    a->tag     = lfs_tole32(a->tag);
+    a->pair[0] = lfs_tole32(a->pair[0]);
+    a->pair[1] = lfs_tole32(a->pair[1]);
+}
+#endif
+
+// operations on forward-CRCs used to track erased state
+struct lfs_fcrc {
+    lfs_size_t size;
+    uint32_t crc;
+};
+
+static void lfs_fcrc_fromle32(struct lfs_fcrc *fcrc) {
+    fcrc->size = lfs_fromle32(fcrc->size);
+    fcrc->crc = lfs_fromle32(fcrc->crc);
+}
+
+#ifndef LFS_READONLY
+static void lfs_fcrc_tole32(struct lfs_fcrc *fcrc) {
+    fcrc->size = lfs_tole32(fcrc->size);
+    fcrc->crc = lfs_tole32(fcrc->crc);
+}
+#endif
+
+// other endianness operations
+static void lfs_ctz_fromle32(struct lfs_ctz *ctz) {
+    ctz->head = lfs_fromle32(ctz->head);
+    ctz->size = lfs_fromle32(ctz->size);
+}
+
+#ifndef LFS_READONLY
+static void lfs_ctz_tole32(struct lfs_ctz *ctz) {
+    ctz->head = lfs_tole32(ctz->head);
+    ctz->size = lfs_tole32(ctz->size);
+}
+#endif
+
+static inline void lfs_superblock_fromle32(lfs_superblock_t *superblock) {
+    superblock->version     = lfs_fromle32(superblock->version);
+    superblock->block_size  = lfs_fromle32(superblock->block_size);
+    superblock->block_count = lfs_fromle32(superblock->block_count);
+    superblock->name_max    = lfs_fromle32(superblock->name_max);
+    superblock->file_max    = lfs_fromle32(superblock->file_max);
+    superblock->attr_max    = lfs_fromle32(superblock->attr_max);
+}
+
+#ifndef LFS_READONLY
+static inline void lfs_superblock_tole32(lfs_superblock_t *superblock) {
+    superblock->version     = lfs_tole32(superblock->version);
+    superblock->block_size  = lfs_tole32(superblock->block_size);
+    superblock->block_count = lfs_tole32(superblock->block_count);
+    superblock->name_max    = lfs_tole32(superblock->name_max);
+    superblock->file_max    = lfs_tole32(superblock->file_max);
+    superblock->attr_max    = lfs_tole32(superblock->attr_max);
+}
+#endif
+
+#ifndef LFS_NO_ASSERT
+static bool lfs_mlist_isopen(struct lfs_mlist *head,
+        struct lfs_mlist *node) {
+    for (struct lfs_mlist **p = &head; *p; p = &(*p)->next) {
+        if (*p == (struct lfs_mlist*)node) {
+            return true;
+        }
+    }
+
+    return false;
+}
+#endif
+
+static void lfs_mlist_remove(lfs_t *lfs, struct lfs_mlist *mlist) {
+    for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) {
+        if (*p == mlist) {
+            *p = (*p)->next;
+            break;
+        }
+    }
+}
+
+static void lfs_mlist_append(lfs_t *lfs, struct lfs_mlist *mlist) {
+    mlist->next = lfs->mlist;
+    lfs->mlist = mlist;
+}
+
+// some other filesystem operations
+static uint32_t lfs_fs_disk_version(lfs_t *lfs) {
+    (void)lfs;
+#ifdef LFS_MULTIVERSION
+    if (lfs->cfg->disk_version) {
+        return lfs->cfg->disk_version;
+    } else
+#endif
+    {
+        return LFS_DISK_VERSION;
+    }
+}
+
+static uint16_t lfs_fs_disk_version_major(lfs_t *lfs) {
+    return 0xffff & (lfs_fs_disk_version(lfs) >> 16);
+
+}
+
+static uint16_t lfs_fs_disk_version_minor(lfs_t *lfs) {
+    return 0xffff & (lfs_fs_disk_version(lfs) >> 0);
+}
+
+
+/// Internal operations predeclared here ///
+#ifndef LFS_READONLY
+static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
+        const struct lfs_mattr *attrs, int attrcount);
+static int lfs_dir_compact(lfs_t *lfs,
+        lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount,
+        lfs_mdir_t *source, uint16_t begin, uint16_t end);
+static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file,
+        const void *buffer, lfs_size_t size);
+static lfs_ssize_t lfs_file_write_(lfs_t *lfs, lfs_file_t *file,
+        const void *buffer, lfs_size_t size);
+static int lfs_file_sync_(lfs_t *lfs, lfs_file_t *file);
+static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file);
+static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file);
+
+static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss);
+static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans);
+static void lfs_fs_prepmove(lfs_t *lfs,
+        uint16_t id, const lfs_block_t pair[2]);
+static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t dir[2],
+        lfs_mdir_t *pdir);
+static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2],
+        lfs_mdir_t *parent);
+static int lfs_fs_forceconsistency(lfs_t *lfs);
+#endif
+
+static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock);
+
+#ifdef LFS_MIGRATE
+static int lfs1_traverse(lfs_t *lfs,
+        int (*cb)(void*, lfs_block_t), void *data);
+#endif
+
+static int lfs_dir_rewind_(lfs_t *lfs, lfs_dir_t *dir);
+
+static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file,
+        void *buffer, lfs_size_t size);
+static lfs_ssize_t lfs_file_read_(lfs_t *lfs, lfs_file_t *file,
+        void *buffer, lfs_size_t size);
+static int lfs_file_close_(lfs_t *lfs, lfs_file_t *file);
+static lfs_soff_t lfs_file_size_(lfs_t *lfs, lfs_file_t *file);
+
+static lfs_ssize_t lfs_fs_size_(lfs_t *lfs);
+static int lfs_fs_traverse_(lfs_t *lfs,
+        int (*cb)(void *data, lfs_block_t block), void *data,
+        bool includeorphans);
+
+static int lfs_deinit(lfs_t *lfs);
+static int lfs_unmount_(lfs_t *lfs);
+
+
+/// Block allocator ///
+
+// allocations should call this when all allocated blocks are committed to
+// the filesystem
+//
+// after a checkpoint, the block allocator may realloc any untracked blocks
+static void lfs_alloc_ckpoint(lfs_t *lfs) {
+    lfs->lookahead.ckpoint = lfs->block_count;
+}
+
+// drop the lookahead buffer, this is done during mounting and failed
+// traversals in order to avoid invalid lookahead state
+static void lfs_alloc_drop(lfs_t *lfs) {
+    lfs->lookahead.size = 0;
+    lfs->lookahead.next = 0;
+    lfs_alloc_ckpoint(lfs);
+}
+
+#ifndef LFS_READONLY
+static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
+    lfs_t *lfs = (lfs_t*)p;
+    lfs_block_t off = ((block - lfs->lookahead.start)
+            + lfs->block_count) % lfs->block_count;
+
+    if (off < lfs->lookahead.size) {
+        lfs->lookahead.buffer[off / 8] |= 1U << (off % 8);
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_alloc_scan(lfs_t *lfs) {
+    // move lookahead buffer to the first unused block
+    //
+    // note we limit the lookahead buffer to at most the amount of blocks
+    // checkpointed, this prevents the math in lfs_alloc from underflowing
+    lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.next) 
+            % lfs->block_count;
+    lfs->lookahead.next = 0;
+    lfs->lookahead.size = lfs_min(
+            8*lfs->cfg->lookahead_size,
+            lfs->lookahead.ckpoint);
+
+    // find mask of free blocks from tree
+    memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size);
+    int err = lfs_fs_traverse_(lfs, lfs_alloc_lookahead, lfs, true);
+    if (err) {
+        lfs_alloc_drop(lfs);
+        return err;
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
+    while (true) {
+        // scan our lookahead buffer for free blocks
+        while (lfs->lookahead.next < lfs->lookahead.size) {
+            if (!(lfs->lookahead.buffer[lfs->lookahead.next / 8]
+                    & (1U << (lfs->lookahead.next % 8)))) {
+                // found a free block
+                *block = (lfs->lookahead.start + lfs->lookahead.next)
+                        % lfs->block_count;
+
+                // eagerly find next free block to maximize how many blocks
+                // lfs_alloc_ckpoint makes available for scanning
+                while (true) {
+                    lfs->lookahead.next += 1;
+                    lfs->lookahead.ckpoint -= 1;
+
+                    if (lfs->lookahead.next >= lfs->lookahead.size
+                            || !(lfs->lookahead.buffer[lfs->lookahead.next / 8]
+                                & (1U << (lfs->lookahead.next % 8)))) {
+                        return 0;
+                    }
+                }
+            }
+
+            lfs->lookahead.next += 1;
+            lfs->lookahead.ckpoint -= 1;
+        }
+
+        // In order to keep our block allocator from spinning forever when our
+        // filesystem is full, we mark points where there are no in-flight
+        // allocations with a checkpoint before starting a set of allocations.
+        //
+        // If we've looked at all blocks since the last checkpoint, we report
+        // the filesystem as out of storage.
+        //
+        if (lfs->lookahead.ckpoint <= 0) {
+            LFS_ERROR("No more free space 0x%"PRIx32,
+                    (lfs->lookahead.start + lfs->lookahead.next)
+                        % lfs->block_count);
+            return LFS_ERR_NOSPC;
+        }
+
+        // No blocks in our lookahead buffer, we need to scan the filesystem for
+        // unused blocks in the next lookahead window.
+        int err = lfs_alloc_scan(lfs);
+        if(err) {
+            return err;
+        }
+    }
+}
+#endif
+
+/// Metadata pair and directory operations ///
+static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir,
+        lfs_tag_t gmask, lfs_tag_t gtag,
+        lfs_off_t goff, void *gbuffer, lfs_size_t gsize) {
+    lfs_off_t off = dir->off;
+    lfs_tag_t ntag = dir->etag;
+    lfs_stag_t gdiff = 0;
+
+    // synthetic moves
+    if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair) &&
+            lfs_tag_id(gmask) != 0) {
+        if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(gtag)) {
+            return LFS_ERR_NOENT;
+        } else if (lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(gtag)) {
+            gdiff -= LFS_MKTAG(0, 1, 0);
+        }
+    }
+
+    // iterate over dir block backwards (for faster lookups)
+    while (off >= sizeof(lfs_tag_t) + lfs_tag_dsize(ntag)) {
+        off -= lfs_tag_dsize(ntag);
+        lfs_tag_t tag = ntag;
+        int err = lfs_bd_read(lfs,
+                NULL, &lfs->rcache, sizeof(ntag),
+                dir->pair[0], off, &ntag, sizeof(ntag));
+        LFS_ASSERT(err <= 0);
+        if (err) {
+            return err;
+        }
+
+        ntag = (lfs_frombe32(ntag) ^ tag) & 0x7fffffff;
+
+        if (lfs_tag_id(gmask) != 0 &&
+                lfs_tag_type1(tag) == LFS_TYPE_SPLICE &&
+                lfs_tag_id(tag) <= lfs_tag_id(gtag - gdiff)) {
+            if (tag == (LFS_MKTAG(LFS_TYPE_CREATE, 0, 0) |
+                    (LFS_MKTAG(0, 0x3ff, 0) & (gtag - gdiff)))) {
+                // found where we were created
+                return LFS_ERR_NOENT;
+            }
+
+            // move around splices
+            gdiff += LFS_MKTAG(0, lfs_tag_splice(tag), 0);
+        }
+
+        if ((gmask & tag) == (gmask & (gtag - gdiff))) {
+            if (lfs_tag_isdelete(tag)) {
+                return LFS_ERR_NOENT;
+            }
+
+            lfs_size_t diff = lfs_min(lfs_tag_size(tag), gsize);
+            err = lfs_bd_read(lfs,
+                    NULL, &lfs->rcache, diff,
+                    dir->pair[0], off+sizeof(tag)+goff, gbuffer, diff);
+            LFS_ASSERT(err <= 0);
+            if (err) {
+                return err;
+            }
+
+            memset((uint8_t*)gbuffer + diff, 0, gsize - diff);
+
+            return tag + gdiff;
+        }
+    }
+
+    return LFS_ERR_NOENT;
+}
+
+static lfs_stag_t lfs_dir_get(lfs_t *lfs, const lfs_mdir_t *dir,
+        lfs_tag_t gmask, lfs_tag_t gtag, void *buffer) {
+    return lfs_dir_getslice(lfs, dir,
+            gmask, gtag,
+            0, buffer, lfs_tag_size(gtag));
+}
+
+static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint,
+        lfs_tag_t gmask, lfs_tag_t gtag,
+        lfs_off_t off, void *buffer, lfs_size_t size) {
+    uint8_t *data = buffer;
+    if (off+size > lfs->cfg->block_size) {
+        return LFS_ERR_CORRUPT;
+    }
+
+    while (size > 0) {
+        lfs_size_t diff = size;
+
+        if (pcache && pcache->block == LFS_BLOCK_INLINE &&
+                off < pcache->off + pcache->size) {
+            if (off >= pcache->off) {
+                // is already in pcache?
+                diff = lfs_min(diff, pcache->size - (off-pcache->off));
+                memcpy(data, &pcache->buffer[off-pcache->off], diff);
+
+                data += diff;
+                off += diff;
+                size -= diff;
+                continue;
+            }
+
+            // pcache takes priority
+            diff = lfs_min(diff, pcache->off-off);
+        }
+
+        if (rcache->block == LFS_BLOCK_INLINE &&
+                off < rcache->off + rcache->size) {
+            if (off >= rcache->off) {
+                // is already in rcache?
+                diff = lfs_min(diff, rcache->size - (off-rcache->off));
+                memcpy(data, &rcache->buffer[off-rcache->off], diff);
+
+                data += diff;
+                off += diff;
+                size -= diff;
+                continue;
+            }
+        }
+
+        // load to cache, first condition can no longer fail
+        rcache->block = LFS_BLOCK_INLINE;
+        rcache->off = lfs_aligndown(off, lfs->cfg->read_size);
+        rcache->size = lfs_min(lfs_alignup(off+hint, lfs->cfg->read_size),
+                lfs->cfg->cache_size);
+        int err = lfs_dir_getslice(lfs, dir, gmask, gtag,
+                rcache->off, rcache->buffer, rcache->size);
+        if (err < 0) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+
+#ifndef LFS_READONLY
+static int lfs_dir_traverse_filter(void *p,
+        lfs_tag_t tag, const void *buffer) {
+    lfs_tag_t *filtertag = p;
+    (void)buffer;
+
+    // which mask depends on unique bit in tag structure
+    uint32_t mask = (tag & LFS_MKTAG(0x100, 0, 0))
+            ? LFS_MKTAG(0x7ff, 0x3ff, 0)
+            : LFS_MKTAG(0x700, 0x3ff, 0);
+
+    // check for redundancy
+    if ((mask & tag) == (mask & *filtertag) ||
+            lfs_tag_isdelete(*filtertag) ||
+            (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == (
+                LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) |
+                    (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) {
+        *filtertag = LFS_MKTAG(LFS_FROM_NOOP, 0, 0);
+        return true;
+    }
+
+    // check if we need to adjust for created/deleted tags
+    if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE &&
+            lfs_tag_id(tag) <= lfs_tag_id(*filtertag)) {
+        *filtertag += LFS_MKTAG(0, lfs_tag_splice(tag), 0);
+    }
+
+    return false;
+}
+#endif
+
+#ifndef LFS_READONLY
+// maximum recursive depth of lfs_dir_traverse, the deepest call:
+//
+// traverse with commit
+// '-> traverse with move
+//     '-> traverse with filter
+//
+#define LFS_DIR_TRAVERSE_DEPTH 3
+
+struct lfs_dir_traverse {
+    const lfs_mdir_t *dir;
+    lfs_off_t off;
+    lfs_tag_t ptag;
+    const struct lfs_mattr *attrs;
+    int attrcount;
+
+    lfs_tag_t tmask;
+    lfs_tag_t ttag;
+    uint16_t begin;
+    uint16_t end;
+    int16_t diff;
+
+    int (*cb)(void *data, lfs_tag_t tag, const void *buffer);
+    void *data;
+
+    lfs_tag_t tag;
+    const void *buffer;
+    struct lfs_diskoff disk;
+};
+
+static int lfs_dir_traverse(lfs_t *lfs,
+        const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag,
+        const struct lfs_mattr *attrs, int attrcount,
+        lfs_tag_t tmask, lfs_tag_t ttag,
+        uint16_t begin, uint16_t end, int16_t diff,
+        int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) {
+    // This function in inherently recursive, but bounded. To allow tool-based
+    // analysis without unnecessary code-cost we use an explicit stack
+    struct lfs_dir_traverse stack[LFS_DIR_TRAVERSE_DEPTH-1];
+    unsigned sp = 0;
+    int res;
+
+    // iterate over directory and attrs
+    lfs_tag_t tag;
+    const void *buffer;
+    struct lfs_diskoff disk = {0};
+    while (true) {
+        {
+            if (off+lfs_tag_dsize(ptag) < dir->off) {
+                off += lfs_tag_dsize(ptag);
+                int err = lfs_bd_read(lfs,
+                        NULL, &lfs->rcache, sizeof(tag),
+                        dir->pair[0], off, &tag, sizeof(tag));
+                if (err) {
+                    return err;
+                }
+
+                tag = (lfs_frombe32(tag) ^ ptag) | 0x80000000;
+                disk.block = dir->pair[0];
+                disk.off = off+sizeof(lfs_tag_t);
+                buffer = &disk;
+                ptag = tag;
+            } else if (attrcount > 0) {
+                tag = attrs[0].tag;
+                buffer = attrs[0].buffer;
+                attrs += 1;
+                attrcount -= 1;
+            } else {
+                // finished traversal, pop from stack?
+                res = 0;
+                break;
+            }
+
+            // do we need to filter?
+            lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0);
+            if ((mask & tmask & tag) != (mask & tmask & ttag)) {
+                continue;
+            }
+
+            if (lfs_tag_id(tmask) != 0) {
+                LFS_ASSERT(sp < LFS_DIR_TRAVERSE_DEPTH);
+                // recurse, scan for duplicates, and update tag based on
+                // creates/deletes
+                stack[sp] = (struct lfs_dir_traverse){
+                    .dir        = dir,
+                    .off        = off,
+                    .ptag       = ptag,
+                    .attrs      = attrs,
+                    .attrcount  = attrcount,
+                    .tmask      = tmask,
+                    .ttag       = ttag,
+                    .begin      = begin,
+                    .end        = end,
+                    .diff       = diff,
+                    .cb         = cb,
+                    .data       = data,
+                    .tag        = tag,
+                    .buffer     = buffer,
+                    .disk       = disk,
+                };
+                sp += 1;
+
+                tmask = 0;
+                ttag = 0;
+                begin = 0;
+                end = 0;
+                diff = 0;
+                cb = lfs_dir_traverse_filter;
+                data = &stack[sp-1].tag;
+                continue;
+            }
+        }
+
+popped:
+        // in filter range?
+        if (lfs_tag_id(tmask) != 0 &&
+                !(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) {
+            continue;
+        }
+
+        // handle special cases for mcu-side operations
+        if (lfs_tag_type3(tag) == LFS_FROM_NOOP) {
+            // do nothing
+        } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) {
+            // Without this condition, lfs_dir_traverse can exhibit an
+            // extremely expensive O(n^3) of nested loops when renaming.
+            // This happens because lfs_dir_traverse tries to filter tags by
+            // the tags in the source directory, triggering a second
+            // lfs_dir_traverse with its own filter operation.
+            //
+            // traverse with commit
+            // '-> traverse with filter
+            //     '-> traverse with move
+            //         '-> traverse with filter
+            //
+            // However we don't actually care about filtering the second set of
+            // tags, since duplicate tags have no effect when filtering.
+            //
+            // This check skips this unnecessary recursive filtering explicitly,
+            // reducing this runtime from O(n^3) to O(n^2).
+            if (cb == lfs_dir_traverse_filter) {
+                continue;
+            }
+
+            // recurse into move
+            stack[sp] = (struct lfs_dir_traverse){
+                .dir        = dir,
+                .off        = off,
+                .ptag       = ptag,
+                .attrs      = attrs,
+                .attrcount  = attrcount,
+                .tmask      = tmask,
+                .ttag       = ttag,
+                .begin      = begin,
+                .end        = end,
+                .diff       = diff,
+                .cb         = cb,
+                .data       = data,
+                .tag        = LFS_MKTAG(LFS_FROM_NOOP, 0, 0),
+            };
+            sp += 1;
+
+            uint16_t fromid = lfs_tag_size(tag);
+            uint16_t toid = lfs_tag_id(tag);
+            dir = buffer;
+            off = 0;
+            ptag = 0xffffffff;
+            attrs = NULL;
+            attrcount = 0;
+            tmask = LFS_MKTAG(0x600, 0x3ff, 0);
+            ttag = LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0);
+            begin = fromid;
+            end = fromid+1;
+            diff = toid-fromid+diff;
+        } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) {
+            for (unsigned i = 0; i < lfs_tag_size(tag); i++) {
+                const struct lfs_attr *a = buffer;
+                res = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type,
+                        lfs_tag_id(tag) + diff, a[i].size), a[i].buffer);
+                if (res < 0) {
+                    return res;
+                }
+
+                if (res) {
+                    break;
+                }
+            }
+        } else {
+            res = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer);
+            if (res < 0) {
+                return res;
+            }
+
+            if (res) {
+                break;
+            }
+        }
+    }
+
+    if (sp > 0) {
+        // pop from the stack and return, fortunately all pops share
+        // a destination
+        dir         = stack[sp-1].dir;
+        off         = stack[sp-1].off;
+        ptag        = stack[sp-1].ptag;
+        attrs       = stack[sp-1].attrs;
+        attrcount   = stack[sp-1].attrcount;
+        tmask       = stack[sp-1].tmask;
+        ttag        = stack[sp-1].ttag;
+        begin       = stack[sp-1].begin;
+        end         = stack[sp-1].end;
+        diff        = stack[sp-1].diff;
+        cb          = stack[sp-1].cb;
+        data        = stack[sp-1].data;
+        tag         = stack[sp-1].tag;
+        buffer      = stack[sp-1].buffer;
+        disk        = stack[sp-1].disk;
+        sp -= 1;
+        goto popped;
+    } else {
+        return res;
+    }
+}
+#endif
+
+static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs,
+        lfs_mdir_t *dir, const lfs_block_t pair[2],
+        lfs_tag_t fmask, lfs_tag_t ftag, uint16_t *id,
+        int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) {
+    // we can find tag very efficiently during a fetch, since we're already
+    // scanning the entire directory
+    lfs_stag_t besttag = -1;
+
+    // if either block address is invalid we return LFS_ERR_CORRUPT here,
+    // otherwise later writes to the pair could fail
+    if (lfs->block_count 
+            && (pair[0] >= lfs->block_count || pair[1] >= lfs->block_count)) {
+        return LFS_ERR_CORRUPT;
+    }
+
+    // find the block with the most recent revision
+    uint32_t revs[2] = {0, 0};
+    int r = 0;
+    for (int i = 0; i < 2; i++) {
+        int err = lfs_bd_read(lfs,
+                NULL, &lfs->rcache, sizeof(revs[i]),
+                pair[i], 0, &revs[i], sizeof(revs[i]));
+        revs[i] = lfs_fromle32(revs[i]);
+        if (err && err != LFS_ERR_CORRUPT) {
+            return err;
+        }
+
+        if (err != LFS_ERR_CORRUPT &&
+                lfs_scmp(revs[i], revs[(i+1)%2]) > 0) {
+            r = i;
+        }
+    }
+
+    dir->pair[0] = pair[(r+0)%2];
+    dir->pair[1] = pair[(r+1)%2];
+    dir->rev = revs[(r+0)%2];
+    dir->off = 0; // nonzero = found some commits
+
+    // now scan tags to fetch the actual dir and find possible match
+    for (int i = 0; i < 2; i++) {
+        lfs_off_t off = 0;
+        lfs_tag_t ptag = 0xffffffff;
+
+        uint16_t tempcount = 0;
+        lfs_block_t temptail[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL};
+        bool tempsplit = false;
+        lfs_stag_t tempbesttag = besttag;
+
+        // assume not erased until proven otherwise
+        bool maybeerased = false;
+        bool hasfcrc = false;
+        struct lfs_fcrc fcrc;
+
+        dir->rev = lfs_tole32(dir->rev);
+        uint32_t crc = lfs_crc(0xffffffff, &dir->rev, sizeof(dir->rev));
+        dir->rev = lfs_fromle32(dir->rev);
+
+        while (true) {
+            // extract next tag
+            lfs_tag_t tag;
+            off += lfs_tag_dsize(ptag);
+            int err = lfs_bd_read(lfs,
+                    NULL, &lfs->rcache, lfs->cfg->block_size,
+                    dir->pair[0], off, &tag, sizeof(tag));
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    // can't continue?
+                    break;
+                }
+                return err;
+            }
+
+            crc = lfs_crc(crc, &tag, sizeof(tag));
+            tag = lfs_frombe32(tag) ^ ptag;
+
+            // next commit not yet programmed?
+            if (!lfs_tag_isvalid(tag)) {
+                // we only might be erased if the last tag was a crc
+                maybeerased = (lfs_tag_type2(ptag) == LFS_TYPE_CCRC);
+                break;
+            // out of range?
+            } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) {
+                break;
+            }
+
+            ptag = tag;
+
+            if (lfs_tag_type2(tag) == LFS_TYPE_CCRC) {
+                // check the crc attr
+                uint32_t dcrc;
+                err = lfs_bd_read(lfs,
+                        NULL, &lfs->rcache, lfs->cfg->block_size,
+                        dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc));
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        break;
+                    }
+                    return err;
+                }
+                dcrc = lfs_fromle32(dcrc);
+
+                if (crc != dcrc) {
+                    break;
+                }
+
+                // reset the next bit if we need to
+                ptag ^= (lfs_tag_t)(lfs_tag_chunk(tag) & 1U) << 31;
+
+                // toss our crc into the filesystem seed for
+                // pseudorandom numbers, note we use another crc here
+                // as a collection function because it is sufficiently
+                // random and convenient
+                lfs->seed = lfs_crc(lfs->seed, &crc, sizeof(crc));
+
+                // update with what's found so far
+                besttag = tempbesttag;
+                dir->off = off + lfs_tag_dsize(tag);
+                dir->etag = ptag;
+                dir->count = tempcount;
+                dir->tail[0] = temptail[0];
+                dir->tail[1] = temptail[1];
+                dir->split = tempsplit;
+
+                // reset crc, hasfcrc
+                crc = 0xffffffff;
+                continue;
+            }
+
+            // crc the entry first, hopefully leaving it in the cache
+            err = lfs_bd_crc(lfs,
+                    NULL, &lfs->rcache, lfs->cfg->block_size,
+                    dir->pair[0], off+sizeof(tag),
+                    lfs_tag_dsize(tag)-sizeof(tag), &crc);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    break;
+                }
+                return err;
+            }
+
+            // directory modification tags?
+            if (lfs_tag_type1(tag) == LFS_TYPE_NAME) {
+                // increase count of files if necessary
+                if (lfs_tag_id(tag) >= tempcount) {
+                    tempcount = lfs_tag_id(tag) + 1;
+                }
+            } else if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE) {
+                tempcount += lfs_tag_splice(tag);
+
+                if (tag == (LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) |
+                        (LFS_MKTAG(0, 0x3ff, 0) & tempbesttag))) {
+                    tempbesttag |= 0x80000000;
+                } else if (tempbesttag != -1 &&
+                        lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) {
+                    tempbesttag += LFS_MKTAG(0, lfs_tag_splice(tag), 0);
+                }
+            } else if (lfs_tag_type1(tag) == LFS_TYPE_TAIL) {
+                tempsplit = (lfs_tag_chunk(tag) & 1);
+
+                err = lfs_bd_read(lfs,
+                        NULL, &lfs->rcache, lfs->cfg->block_size,
+                        dir->pair[0], off+sizeof(tag), &temptail, 8);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        break;
+                    }
+                    return err;
+                }
+                lfs_pair_fromle32(temptail);
+            } else if (lfs_tag_type3(tag) == LFS_TYPE_FCRC) {
+                err = lfs_bd_read(lfs,
+                        NULL, &lfs->rcache, lfs->cfg->block_size,
+                        dir->pair[0], off+sizeof(tag),
+                        &fcrc, sizeof(fcrc));
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        break;
+                    }
+                    return err;
+                }
+
+                lfs_fcrc_fromle32(&fcrc);
+                hasfcrc = true;
+            }
+
+            // found a match for our fetcher?
+            if ((fmask & tag) == (fmask & ftag)) {
+                int res = cb(data, tag, &(struct lfs_diskoff){
+                        dir->pair[0], off+sizeof(tag)});
+                if (res < 0) {
+                    if (res == LFS_ERR_CORRUPT) {
+                        break;
+                    }
+                    return res;
+                }
+
+                if (res == LFS_CMP_EQ) {
+                    // found a match
+                    tempbesttag = tag;
+                } else if ((LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) ==
+                        (LFS_MKTAG(0x7ff, 0x3ff, 0) & tempbesttag)) {
+                    // found an identical tag, but contents didn't match
+                    // this must mean that our besttag has been overwritten
+                    tempbesttag = -1;
+                } else if (res == LFS_CMP_GT &&
+                        lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) {
+                    // found a greater match, keep track to keep things sorted
+                    tempbesttag = tag | 0x80000000;
+                }
+            }
+        }
+
+        // found no valid commits?
+        if (dir->off == 0) {
+            // try the other block?
+            lfs_pair_swap(dir->pair);
+            dir->rev = revs[(r+1)%2];
+            continue;
+        }
+
+        // did we end on a valid commit? we may have an erased block
+        dir->erased = false;
+        if (maybeerased && dir->off % lfs->cfg->prog_size == 0) {
+        #ifdef LFS_MULTIVERSION
+            // note versions < lfs2.1 did not have fcrc tags, if
+            // we're < lfs2.1 treat missing fcrc as erased data
+            //
+            // we don't strictly need to do this, but otherwise writing
+            // to lfs2.0 disks becomes very inefficient
+            if (lfs_fs_disk_version(lfs) < 0x00020001) {
+                dir->erased = true;
+
+            } else
+        #endif
+            if (hasfcrc) {
+                // check for an fcrc matching the next prog's erased state, if
+                // this failed most likely a previous prog was interrupted, we
+                // need a new erase
+                uint32_t fcrc_ = 0xffffffff;
+                int err = lfs_bd_crc(lfs,
+                        NULL, &lfs->rcache, lfs->cfg->block_size,
+                        dir->pair[0], dir->off, fcrc.size, &fcrc_);
+                if (err && err != LFS_ERR_CORRUPT) {
+                    return err;
+                }
+
+                // found beginning of erased part?
+                dir->erased = (fcrc_ == fcrc.crc);
+            }
+        }
+
+        // synthetic move
+        if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair)) {
+            if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(besttag)) {
+                besttag |= 0x80000000;
+            } else if (besttag != -1 &&
+                    lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(besttag)) {
+                besttag -= LFS_MKTAG(0, 1, 0);
+            }
+        }
+
+        // found tag? or found best id?
+        if (id) {
+            *id = lfs_min(lfs_tag_id(besttag), dir->count);
+        }
+
+        if (lfs_tag_isvalid(besttag)) {
+            return besttag;
+        } else if (lfs_tag_id(besttag) < dir->count) {
+            return LFS_ERR_NOENT;
+        } else {
+            return 0;
+        }
+    }
+
+    LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}",
+            dir->pair[0], dir->pair[1]);
+    return LFS_ERR_CORRUPT;
+}
+
+static int lfs_dir_fetch(lfs_t *lfs,
+        lfs_mdir_t *dir, const lfs_block_t pair[2]) {
+    // note, mask=-1, tag=-1 can never match a tag since this
+    // pattern has the invalid bit set
+    return (int)lfs_dir_fetchmatch(lfs, dir, pair,
+            (lfs_tag_t)-1, (lfs_tag_t)-1, NULL, NULL, NULL);
+}
+
+static int lfs_dir_getgstate(lfs_t *lfs, const lfs_mdir_t *dir,
+        lfs_gstate_t *gstate) {
+    lfs_gstate_t temp;
+    lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x7ff, 0, 0),
+            LFS_MKTAG(LFS_TYPE_MOVESTATE, 0, sizeof(temp)), &temp);
+    if (res < 0 && res != LFS_ERR_NOENT) {
+        return res;
+    }
+
+    if (res != LFS_ERR_NOENT) {
+        // xor together to find resulting gstate
+        lfs_gstate_fromle32(&temp);
+        lfs_gstate_xor(gstate, &temp);
+    }
+
+    return 0;
+}
+
+static int lfs_dir_getinfo(lfs_t *lfs, lfs_mdir_t *dir,
+        uint16_t id, struct lfs_info *info) {
+    if (id == 0x3ff) {
+        // special case for root
+        strcpy(info->name, "/");
+        info->type = LFS_TYPE_DIR;
+        return 0;
+    }
+
+    lfs_stag_t tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x780, 0x3ff, 0),
+            LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_max+1), info->name);
+    if (tag < 0) {
+        return (int)tag;
+    }
+
+    info->type = lfs_tag_type3(tag);
+
+    struct lfs_ctz ctz;
+    tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0),
+            LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz);
+    if (tag < 0) {
+        return (int)tag;
+    }
+    lfs_ctz_fromle32(&ctz);
+
+    if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) {
+        info->size = ctz.size;
+    } else if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) {
+        info->size = lfs_tag_size(tag);
+    }
+
+    return 0;
+}
+
+struct lfs_dir_find_match {
+    lfs_t *lfs;
+    const void *name;
+    lfs_size_t size;
+};
+
+static int lfs_dir_find_match(void *data,
+        lfs_tag_t tag, const void *buffer) {
+    struct lfs_dir_find_match *name = data;
+    lfs_t *lfs = name->lfs;
+    const struct lfs_diskoff *disk = buffer;
+
+    // compare with disk
+    lfs_size_t diff = lfs_min(name->size, lfs_tag_size(tag));
+    int res = lfs_bd_cmp(lfs,
+            NULL, &lfs->rcache, diff,
+            disk->block, disk->off, name->name, diff);
+    if (res != LFS_CMP_EQ) {
+        return res;
+    }
+
+    // only equal if our size is still the same
+    if (name->size != lfs_tag_size(tag)) {
+        return (name->size < lfs_tag_size(tag)) ? LFS_CMP_LT : LFS_CMP_GT;
+    }
+
+    // found a match!
+    return LFS_CMP_EQ;
+}
+
+// lfs_dir_find tries to set path and id even if file is not found
+//
+// returns:
+// - 0                  if file is found
+// - LFS_ERR_NOENT      if file or parent is not found
+// - LFS_ERR_NOTDIR     if parent is not a dir
+static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir,
+        const char **path, uint16_t *id) {
+    // we reduce path to a single name if we can find it
+    const char *name = *path;
+
+    // default to root dir
+    lfs_stag_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0);
+    dir->tail[0] = lfs->root[0];
+    dir->tail[1] = lfs->root[1];
+
+    // empty paths are not allowed
+    if (*name == '\0') {
+        return LFS_ERR_INVAL;
+    }
+
+    while (true) {
+nextname:
+        // skip slashes if we're a directory
+        if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
+            name += strspn(name, "/");
+        }
+        lfs_size_t namelen = strcspn(name, "/");
+
+        // skip '.'
+        if (namelen == 1 && memcmp(name, ".", 1) == 0) {
+            name += namelen;
+            goto nextname;
+        }
+
+        // error on unmatched '..', trying to go above root?
+        if (namelen == 2 && memcmp(name, "..", 2) == 0) {
+            return LFS_ERR_INVAL;
+        }
+
+        // skip if matched by '..' in name
+        const char *suffix = name + namelen;
+        lfs_size_t sufflen;
+        int depth = 1;
+        while (true) {
+            suffix += strspn(suffix, "/");
+            sufflen = strcspn(suffix, "/");
+            if (sufflen == 0) {
+                break;
+            }
+
+            if (sufflen == 1 && memcmp(suffix, ".", 1) == 0) {
+                // noop
+            } else if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) {
+                depth -= 1;
+                if (depth == 0) {
+                    name = suffix + sufflen;
+                    goto nextname;
+                }
+            } else {
+                depth += 1;
+            }
+
+            suffix += sufflen;
+        }
+
+        // found path
+        if (*name == '\0') {
+            return tag;
+        }
+
+        // update what we've found so far
+        *path = name;
+
+        // only continue if we're a directory
+        if (lfs_tag_type3(tag) != LFS_TYPE_DIR) {
+            return LFS_ERR_NOTDIR;
+        }
+
+        // grab the entry data
+        if (lfs_tag_id(tag) != 0x3ff) {
+            lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), dir->tail);
+            if (res < 0) {
+                return res;
+            }
+            lfs_pair_fromle32(dir->tail);
+        }
+
+        // find entry matching name
+        while (true) {
+            tag = lfs_dir_fetchmatch(lfs, dir, dir->tail,
+                    LFS_MKTAG(0x780, 0, 0),
+                    LFS_MKTAG(LFS_TYPE_NAME, 0, namelen),
+                    id,
+                    lfs_dir_find_match, &(struct lfs_dir_find_match){
+                        lfs, name, namelen});
+            if (tag < 0) {
+                return tag;
+            }
+
+            if (tag) {
+                break;
+            }
+
+            if (!dir->split) {
+                return LFS_ERR_NOENT;
+            }
+        }
+
+        // to next name
+        name += namelen;
+    }
+}
+
+// commit logic
+struct lfs_commit {
+    lfs_block_t block;
+    lfs_off_t off;
+    lfs_tag_t ptag;
+    uint32_t crc;
+
+    lfs_off_t begin;
+    lfs_off_t end;
+};
+
+#ifndef LFS_READONLY
+static int lfs_dir_commitprog(lfs_t *lfs, struct lfs_commit *commit,
+        const void *buffer, lfs_size_t size) {
+    int err = lfs_bd_prog(lfs,
+            &lfs->pcache, &lfs->rcache, false,
+            commit->block, commit->off ,
+            (const uint8_t*)buffer, size);
+    if (err) {
+        return err;
+    }
+
+    commit->crc = lfs_crc(commit->crc, buffer, size);
+    commit->off += size;
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit,
+        lfs_tag_t tag, const void *buffer) {
+    // check if we fit
+    lfs_size_t dsize = lfs_tag_dsize(tag);
+    if (commit->off + dsize > commit->end) {
+        return LFS_ERR_NOSPC;
+    }
+
+    // write out tag
+    lfs_tag_t ntag = lfs_tobe32((tag & 0x7fffffff) ^ commit->ptag);
+    int err = lfs_dir_commitprog(lfs, commit, &ntag, sizeof(ntag));
+    if (err) {
+        return err;
+    }
+
+    if (!(tag & 0x80000000)) {
+        // from memory
+        err = lfs_dir_commitprog(lfs, commit, buffer, dsize-sizeof(tag));
+        if (err) {
+            return err;
+        }
+    } else {
+        // from disk
+        const struct lfs_diskoff *disk = buffer;
+        for (lfs_off_t i = 0; i < dsize-sizeof(tag); i++) {
+            // rely on caching to make this efficient
+            uint8_t dat;
+            err = lfs_bd_read(lfs,
+                    NULL, &lfs->rcache, dsize-sizeof(tag)-i,
+                    disk->block, disk->off+i, &dat, 1);
+            if (err) {
+                return err;
+            }
+
+            err = lfs_dir_commitprog(lfs, commit, &dat, 1);
+            if (err) {
+                return err;
+            }
+        }
+    }
+
+    commit->ptag = tag & 0x7fffffff;
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+
+static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
+    // align to program units
+    //
+    // this gets a bit complex as we have two types of crcs:
+    // - 5-word crc with fcrc to check following prog (middle of block)
+    // - 2-word crc with no following prog (end of block)
+    const lfs_off_t end = lfs_alignup(
+            lfs_min(commit->off + 5*sizeof(uint32_t), lfs->cfg->block_size),
+            lfs->cfg->prog_size);
+
+    lfs_off_t off1 = 0;
+    uint32_t crc1 = 0;
+
+    // create crc tags to fill up remainder of commit, note that
+    // padding is not crced, which lets fetches skip padding but
+    // makes committing a bit more complicated
+    while (commit->off < end) {
+        lfs_off_t noff = (
+                lfs_min(end - (commit->off+sizeof(lfs_tag_t)), 0x3fe)
+                + (commit->off+sizeof(lfs_tag_t)));
+        // too large for crc tag? need padding commits
+        if (noff < end) {
+            noff = lfs_min(noff, end - 5*sizeof(uint32_t));
+        }
+
+        // space for fcrc?
+        uint8_t eperturb = (uint8_t)-1;
+        if (noff >= end && noff <= lfs->cfg->block_size - lfs->cfg->prog_size) {
+            // first read the leading byte, this always contains a bit
+            // we can perturb to avoid writes that don't change the fcrc
+            int err = lfs_bd_read(lfs,
+                    NULL, &lfs->rcache, lfs->cfg->prog_size,
+                    commit->block, noff, &eperturb, 1);
+            if (err && err != LFS_ERR_CORRUPT) {
+                return err;
+            }
+
+        #ifdef LFS_MULTIVERSION
+            // unfortunately fcrcs break mdir fetching < lfs2.1, so only write
+            // these if we're a >= lfs2.1 filesystem
+            if (lfs_fs_disk_version(lfs) <= 0x00020000) {
+                // don't write fcrc
+            } else
+        #endif
+            {
+                // find the expected fcrc, don't bother avoiding a reread
+                // of the eperturb, it should still be in our cache
+                struct lfs_fcrc fcrc = {
+                    .size = lfs->cfg->prog_size,
+                    .crc = 0xffffffff
+                };
+                err = lfs_bd_crc(lfs,
+                        NULL, &lfs->rcache, lfs->cfg->prog_size,
+                        commit->block, noff, fcrc.size, &fcrc.crc);
+                if (err && err != LFS_ERR_CORRUPT) {
+                    return err;
+                }
+
+                lfs_fcrc_tole32(&fcrc);
+                err = lfs_dir_commitattr(lfs, commit,
+                        LFS_MKTAG(LFS_TYPE_FCRC, 0x3ff, sizeof(struct lfs_fcrc)),
+                        &fcrc);
+                if (err) {
+                    return err;
+                }
+            }
+        }
+
+        // build commit crc
+        struct {
+            lfs_tag_t tag;
+            uint32_t crc;
+        } ccrc;
+        lfs_tag_t ntag = LFS_MKTAG(
+                LFS_TYPE_CCRC + (((uint8_t)~eperturb) >> 7), 0x3ff,
+                noff - (commit->off+sizeof(lfs_tag_t)));
+        ccrc.tag = lfs_tobe32(ntag ^ commit->ptag);
+        commit->crc = lfs_crc(commit->crc, &ccrc.tag, sizeof(lfs_tag_t));
+        ccrc.crc = lfs_tole32(commit->crc);
+
+        int err = lfs_bd_prog(lfs,
+                &lfs->pcache, &lfs->rcache, false,
+                commit->block, commit->off, &ccrc, sizeof(ccrc));
+        if (err) {
+            return err;
+        }
+
+        // keep track of non-padding checksum to verify
+        if (off1 == 0) {
+            off1 = commit->off + sizeof(lfs_tag_t);
+            crc1 = commit->crc;
+        }
+
+        commit->off = noff;
+        // perturb valid bit?
+        commit->ptag = ntag ^ ((0x80UL & ~eperturb) << 24);
+        // reset crc for next commit
+        commit->crc = 0xffffffff;
+
+        // manually flush here since we don't prog the padding, this confuses
+        // the caching layer
+        if (noff >= end || noff >= lfs->pcache.off + lfs->cfg->cache_size) {
+            // flush buffers
+            int err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false);
+            if (err) {
+                return err;
+            }
+        }
+    }
+
+    // successful commit, check checksums to make sure
+    //
+    // note that we don't need to check padding commits, worst
+    // case if they are corrupted we would have had to compact anyways
+    lfs_off_t off = commit->begin;
+    uint32_t crc = 0xffffffff;
+    int err = lfs_bd_crc(lfs,
+            NULL, &lfs->rcache, off1+sizeof(uint32_t),
+            commit->block, off, off1-off, &crc);
+    if (err) {
+        return err;
+    }
+
+    // check non-padding commits against known crc
+    if (crc != crc1) {
+        return LFS_ERR_CORRUPT;
+    }
+
+    // make sure to check crc in case we happen to pick
+    // up an unrelated crc (frozen block?)
+    err = lfs_bd_crc(lfs,
+            NULL, &lfs->rcache, sizeof(uint32_t),
+            commit->block, off1, sizeof(uint32_t), &crc);
+    if (err) {
+        return err;
+    }
+
+    if (crc != 0) {
+        return LFS_ERR_CORRUPT;
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) {
+    // allocate pair of dir blocks (backwards, so we write block 1 first)
+    for (int i = 0; i < 2; i++) {
+        int err = lfs_alloc(lfs, &dir->pair[(i+1)%2]);
+        if (err) {
+            return err;
+        }
+    }
+
+    // zero for reproducibility in case initial block is unreadable
+    dir->rev = 0;
+
+    // rather than clobbering one of the blocks we just pretend
+    // the revision may be valid
+    int err = lfs_bd_read(lfs,
+            NULL, &lfs->rcache, sizeof(dir->rev),
+            dir->pair[0], 0, &dir->rev, sizeof(dir->rev));
+    dir->rev = lfs_fromle32(dir->rev);
+    if (err && err != LFS_ERR_CORRUPT) {
+        return err;
+    }
+
+    // to make sure we don't immediately evict, align the new revision count
+    // to our block_cycles modulus, see lfs_dir_compact for why our modulus
+    // is tweaked this way
+    if (lfs->cfg->block_cycles > 0) {
+        dir->rev = lfs_alignup(dir->rev, ((lfs->cfg->block_cycles+1)|1));
+    }
+
+    // set defaults
+    dir->off = sizeof(dir->rev);
+    dir->etag = 0xffffffff;
+    dir->count = 0;
+    dir->tail[0] = LFS_BLOCK_NULL;
+    dir->tail[1] = LFS_BLOCK_NULL;
+    dir->erased = false;
+    dir->split = false;
+
+    // don't write out yet, let caller take care of that
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_drop(lfs_t *lfs, lfs_mdir_t *dir, lfs_mdir_t *tail) {
+    // steal state
+    int err = lfs_dir_getgstate(lfs, tail, &lfs->gdelta);
+    if (err) {
+        return err;
+    }
+
+    // steal tail
+    lfs_pair_tole32(tail->tail);
+    err = lfs_dir_commit(lfs, dir, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_TAIL + tail->split, 0x3ff, 8), tail->tail}));
+    lfs_pair_fromle32(tail->tail);
+    if (err) {
+        return err;
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_split(lfs_t *lfs,
+        lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount,
+        lfs_mdir_t *source, uint16_t split, uint16_t end) {
+    // create tail metadata pair
+    lfs_mdir_t tail;
+    int err = lfs_dir_alloc(lfs, &tail);
+    if (err) {
+        return err;
+    }
+
+    tail.split = dir->split;
+    tail.tail[0] = dir->tail[0];
+    tail.tail[1] = dir->tail[1];
+
+    // note we don't care about LFS_OK_RELOCATED
+    int res = lfs_dir_compact(lfs, &tail, attrs, attrcount, source, split, end);
+    if (res < 0) {
+        return res;
+    }
+
+    dir->tail[0] = tail.pair[0];
+    dir->tail[1] = tail.pair[1];
+    dir->split = true;
+
+    // update root if needed
+    if (lfs_pair_cmp(dir->pair, lfs->root) == 0 && split == 0) {
+        lfs->root[0] = tail.pair[0];
+        lfs->root[1] = tail.pair[1];
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_commit_size(void *p, lfs_tag_t tag, const void *buffer) {
+    lfs_size_t *size = p;
+    (void)buffer;
+
+    *size += lfs_tag_dsize(tag);
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+struct lfs_dir_commit_commit {
+    lfs_t *lfs;
+    struct lfs_commit *commit;
+};
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_commit_commit(void *p, lfs_tag_t tag, const void *buffer) {
+    struct lfs_dir_commit_commit *commit = p;
+    return lfs_dir_commitattr(commit->lfs, commit->commit, tag, buffer);
+}
+#endif
+
+#ifndef LFS_READONLY
+static bool lfs_dir_needsrelocation(lfs_t *lfs, lfs_mdir_t *dir) {
+    // If our revision count == n * block_cycles, we should force a relocation,
+    // this is how littlefs wear-levels at the metadata-pair level. Note that we
+    // actually use (block_cycles+1)|1, this is to avoid two corner cases:
+    // 1. block_cycles = 1, which would prevent relocations from terminating
+    // 2. block_cycles = 2n, which, due to aliasing, would only ever relocate
+    //    one metadata block in the pair, effectively making this useless
+    return (lfs->cfg->block_cycles > 0
+            && ((dir->rev + 1) % ((lfs->cfg->block_cycles+1)|1) == 0));
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_compact(lfs_t *lfs,
+        lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount,
+        lfs_mdir_t *source, uint16_t begin, uint16_t end) {
+    // save some state in case block is bad
+    bool relocated = false;
+    bool tired = lfs_dir_needsrelocation(lfs, dir);
+
+    // increment revision count
+    dir->rev += 1;
+
+    // do not proactively relocate blocks during migrations, this
+    // can cause a number of failure states such: clobbering the
+    // v1 superblock if we relocate root, and invalidating directory
+    // pointers if we relocate the head of a directory. On top of
+    // this, relocations increase the overall complexity of
+    // lfs_migration, which is already a delicate operation.
+#ifdef LFS_MIGRATE
+    if (lfs->lfs1) {
+        tired = false;
+    }
+#endif
+
+    if (tired && lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) != 0) {
+        // we're writing too much, time to relocate
+        goto relocate;
+    }
+
+    // begin loop to commit compaction to blocks until a compact sticks
+    while (true) {
+        {
+            // setup commit state
+            struct lfs_commit commit = {
+                .block = dir->pair[1],
+                .off = 0,
+                .ptag = 0xffffffff,
+                .crc = 0xffffffff,
+
+                .begin = 0,
+                .end = (lfs->cfg->metadata_max ?
+                    lfs->cfg->metadata_max : lfs->cfg->block_size) - 8,
+            };
+
+            // erase block to write to
+            int err = lfs_bd_erase(lfs, dir->pair[1]);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+
+            // write out header
+            dir->rev = lfs_tole32(dir->rev);
+            err = lfs_dir_commitprog(lfs, &commit,
+                    &dir->rev, sizeof(dir->rev));
+            dir->rev = lfs_fromle32(dir->rev);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+
+            // traverse the directory, this time writing out all unique tags
+            err = lfs_dir_traverse(lfs,
+                    source, 0, 0xffffffff, attrs, attrcount,
+                    LFS_MKTAG(0x400, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_NAME, 0, 0),
+                    begin, end, -begin,
+                    lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){
+                        lfs, &commit});
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+
+            // commit tail, which may be new after last size check
+            if (!lfs_pair_isnull(dir->tail)) {
+                lfs_pair_tole32(dir->tail);
+                err = lfs_dir_commitattr(lfs, &commit,
+                        LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8),
+                        dir->tail);
+                lfs_pair_fromle32(dir->tail);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        goto relocate;
+                    }
+                    return err;
+                }
+            }
+
+            // bring over gstate?
+            lfs_gstate_t delta = {0};
+            if (!relocated) {
+                lfs_gstate_xor(&delta, &lfs->gdisk);
+                lfs_gstate_xor(&delta, &lfs->gstate);
+            }
+            lfs_gstate_xor(&delta, &lfs->gdelta);
+            delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff);
+
+            err = lfs_dir_getgstate(lfs, dir, &delta);
+            if (err) {
+                return err;
+            }
+
+            if (!lfs_gstate_iszero(&delta)) {
+                lfs_gstate_tole32(&delta);
+                err = lfs_dir_commitattr(lfs, &commit,
+                        LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff,
+                            sizeof(delta)), &delta);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        goto relocate;
+                    }
+                    return err;
+                }
+            }
+
+            // complete commit with crc
+            err = lfs_dir_commitcrc(lfs, &commit);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+
+            // successful compaction, swap dir pair to indicate most recent
+            LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0);
+            lfs_pair_swap(dir->pair);
+            dir->count = end - begin;
+            dir->off = commit.off;
+            dir->etag = commit.ptag;
+            // update gstate
+            lfs->gdelta = (lfs_gstate_t){0};
+            if (!relocated) {
+                lfs->gdisk = lfs->gstate;
+            }
+        }
+        break;
+
+relocate:
+        // commit was corrupted, drop caches and prepare to relocate block
+        relocated = true;
+        lfs_cache_drop(lfs, &lfs->pcache);
+        if (!tired) {
+            LFS_DEBUG("Bad block at 0x%"PRIx32, dir->pair[1]);
+        }
+
+        // can't relocate superblock, filesystem is now frozen
+        if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) {
+            LFS_WARN("Superblock 0x%"PRIx32" has become unwritable",
+                    dir->pair[1]);
+            return LFS_ERR_NOSPC;
+        }
+
+        // relocate half of pair
+        int err = lfs_alloc(lfs, &dir->pair[1]);
+        if (err && (err != LFS_ERR_NOSPC || !tired)) {
+            return err;
+        }
+
+        tired = false;
+        continue;
+    }
+
+    return relocated ? LFS_OK_RELOCATED : 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_splittingcompact(lfs_t *lfs, lfs_mdir_t *dir,
+        const struct lfs_mattr *attrs, int attrcount,
+        lfs_mdir_t *source, uint16_t begin, uint16_t end) {
+    while (true) {
+        // find size of first split, we do this by halving the split until
+        // the metadata is guaranteed to fit
+        //
+        // Note that this isn't a true binary search, we never increase the
+        // split size. This may result in poorly distributed metadata but isn't
+        // worth the extra code size or performance hit to fix.
+        lfs_size_t split = begin;
+        while (end - split > 1) {
+            lfs_size_t size = 0;
+            int err = lfs_dir_traverse(lfs,
+                    source, 0, 0xffffffff, attrs, attrcount,
+                    LFS_MKTAG(0x400, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_NAME, 0, 0),
+                    split, end, -split,
+                    lfs_dir_commit_size, &size);
+            if (err) {
+                return err;
+            }
+
+            // space is complicated, we need room for:
+            //
+            // - tail:         4+2*4 = 12 bytes
+            // - gstate:       4+3*4 = 16 bytes
+            // - move delete:  4     = 4 bytes
+            // - crc:          4+4   = 8 bytes
+            //                 total = 40 bytes
+            //
+            // And we cap at half a block to avoid degenerate cases with
+            // nearly-full metadata blocks.
+            //
+            lfs_size_t metadata_max = (lfs->cfg->metadata_max)
+                    ? lfs->cfg->metadata_max
+                    : lfs->cfg->block_size;
+            if (end - split < 0xff
+                    && size <= lfs_min(
+                        metadata_max - 40,
+                        lfs_alignup(
+                            metadata_max/2,
+                            lfs->cfg->prog_size))) {
+                break;
+            }
+
+            split = split + ((end - split) / 2);
+        }
+
+        if (split == begin) {
+            // no split needed
+            break;
+        }
+
+        // split into two metadata pairs and continue
+        int err = lfs_dir_split(lfs, dir, attrs, attrcount,
+                source, split, end);
+        if (err && err != LFS_ERR_NOSPC) {
+            return err;
+        }
+
+        if (err) {
+            // we can't allocate a new block, try to compact with degraded
+            // performance
+            LFS_WARN("Unable to split {0x%"PRIx32", 0x%"PRIx32"}",
+                    dir->pair[0], dir->pair[1]);
+            break;
+        } else {
+            end = split;
+        }
+    }
+
+    if (lfs_dir_needsrelocation(lfs, dir)
+            && lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) {
+        // oh no! we're writing too much to the superblock,
+        // should we expand?
+        lfs_ssize_t size = lfs_fs_size_(lfs);
+        if (size < 0) {
+            return size;
+        }
+
+        // littlefs cannot reclaim expanded superblocks, so expand cautiously
+        //
+        // if our filesystem is more than ~88% full, don't expand, this is
+        // somewhat arbitrary
+        if (lfs->block_count - size > lfs->block_count/8) {
+            LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev);
+            int err = lfs_dir_split(lfs, dir, attrs, attrcount,
+                    source, begin, end);
+            if (err && err != LFS_ERR_NOSPC) {
+                return err;
+            }
+
+            if (err) {
+                // welp, we tried, if we ran out of space there's not much
+                // we can do, we'll error later if we've become frozen
+                LFS_WARN("Unable to expand superblock");
+            } else {
+                // duplicate the superblock entry into the new superblock
+                end = 1;
+            }
+        }
+    }
+
+    return lfs_dir_compact(lfs, dir, attrs, attrcount, source, begin, end);
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_relocatingcommit(lfs_t *lfs, lfs_mdir_t *dir,
+        const lfs_block_t pair[2],
+        const struct lfs_mattr *attrs, int attrcount,
+        lfs_mdir_t *pdir) {
+    int state = 0;
+
+    // calculate changes to the directory
+    bool hasdelete = false;
+    for (int i = 0; i < attrcount; i++) {
+        if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) {
+            dir->count += 1;
+        } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE) {
+            LFS_ASSERT(dir->count > 0);
+            dir->count -= 1;
+            hasdelete = true;
+        } else if (lfs_tag_type1(attrs[i].tag) == LFS_TYPE_TAIL) {
+            dir->tail[0] = ((lfs_block_t*)attrs[i].buffer)[0];
+            dir->tail[1] = ((lfs_block_t*)attrs[i].buffer)[1];
+            dir->split = (lfs_tag_chunk(attrs[i].tag) & 1);
+            lfs_pair_fromle32(dir->tail);
+        }
+    }
+
+    // should we actually drop the directory block?
+    if (hasdelete && dir->count == 0) {
+        LFS_ASSERT(pdir);
+        int err = lfs_fs_pred(lfs, dir->pair, pdir);
+        if (err && err != LFS_ERR_NOENT) {
+            return err;
+        }
+
+        if (err != LFS_ERR_NOENT && pdir->split) {
+            state = LFS_OK_DROPPED;
+            goto fixmlist;
+        }
+    }
+
+    if (dir->erased && dir->count < 0xff) {
+        // try to commit
+        struct lfs_commit commit = {
+            .block = dir->pair[0],
+            .off = dir->off,
+            .ptag = dir->etag,
+            .crc = 0xffffffff,
+
+            .begin = dir->off,
+            .end = (lfs->cfg->metadata_max ?
+                lfs->cfg->metadata_max : lfs->cfg->block_size) - 8,
+        };
+
+        // traverse attrs that need to be written out
+        lfs_pair_tole32(dir->tail);
+        int err = lfs_dir_traverse(lfs,
+                dir, dir->off, dir->etag, attrs, attrcount,
+                0, 0, 0, 0, 0,
+                lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){
+                    lfs, &commit});
+        lfs_pair_fromle32(dir->tail);
+        if (err) {
+            if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
+                goto compact;
+            }
+            return err;
+        }
+
+        // commit any global diffs if we have any
+        lfs_gstate_t delta = {0};
+        lfs_gstate_xor(&delta, &lfs->gstate);
+        lfs_gstate_xor(&delta, &lfs->gdisk);
+        lfs_gstate_xor(&delta, &lfs->gdelta);
+        delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff);
+        if (!lfs_gstate_iszero(&delta)) {
+            err = lfs_dir_getgstate(lfs, dir, &delta);
+            if (err) {
+                return err;
+            }
+
+            lfs_gstate_tole32(&delta);
+            err = lfs_dir_commitattr(lfs, &commit,
+                    LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff,
+                        sizeof(delta)), &delta);
+            if (err) {
+                if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
+                    goto compact;
+                }
+                return err;
+            }
+        }
+
+        // finalize commit with the crc
+        err = lfs_dir_commitcrc(lfs, &commit);
+        if (err) {
+            if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
+                goto compact;
+            }
+            return err;
+        }
+
+        // successful commit, update dir
+        LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0);
+        dir->off = commit.off;
+        dir->etag = commit.ptag;
+        // and update gstate
+        lfs->gdisk = lfs->gstate;
+        lfs->gdelta = (lfs_gstate_t){0};
+
+        goto fixmlist;
+    }
+
+compact:
+    // fall back to compaction
+    lfs_cache_drop(lfs, &lfs->pcache);
+
+    state = lfs_dir_splittingcompact(lfs, dir, attrs, attrcount,
+            dir, 0, dir->count);
+    if (state < 0) {
+        return state;
+    }
+
+    goto fixmlist;
+
+fixmlist:;
+    // this complicated bit of logic is for fixing up any active
+    // metadata-pairs that we may have affected
+    //
+    // note we have to make two passes since the mdir passed to
+    // lfs_dir_commit could also be in this list, and even then
+    // we need to copy the pair so they don't get clobbered if we refetch
+    // our mdir.
+    lfs_block_t oldpair[2] = {pair[0], pair[1]};
+    for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) {
+        if (lfs_pair_cmp(d->m.pair, oldpair) == 0) {
+            d->m = *dir;
+            if (d->m.pair != pair) {
+                for (int i = 0; i < attrcount; i++) {
+                    if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE &&
+                            d->id == lfs_tag_id(attrs[i].tag) &&
+                            d->type != LFS_TYPE_DIR) {
+                        d->m.pair[0] = LFS_BLOCK_NULL;
+                        d->m.pair[1] = LFS_BLOCK_NULL;
+                    } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE &&
+                            d->id > lfs_tag_id(attrs[i].tag)) {
+                        d->id -= 1;
+                        if (d->type == LFS_TYPE_DIR) {
+                            ((lfs_dir_t*)d)->pos -= 1;
+                        }
+                    } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE &&
+                            d->id >= lfs_tag_id(attrs[i].tag)) {
+                        d->id += 1;
+                        if (d->type == LFS_TYPE_DIR) {
+                            ((lfs_dir_t*)d)->pos += 1;
+                        }
+                    }
+                }
+            }
+
+            while (d->id >= d->m.count && d->m.split) {
+                // we split and id is on tail now
+                if (lfs_pair_cmp(d->m.tail, lfs->root) != 0) {
+                    d->id -= d->m.count;
+                }
+                int err = lfs_dir_fetch(lfs, &d->m, d->m.tail);
+                if (err) {
+                    return err;
+                }
+            }
+        }
+    }
+
+    return state;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_orphaningcommit(lfs_t *lfs, lfs_mdir_t *dir,
+        const struct lfs_mattr *attrs, int attrcount) {
+    // check for any inline files that aren't RAM backed and
+    // forcefully evict them, needed for filesystem consistency
+    for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) {
+        if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 &&
+                f->type == LFS_TYPE_REG && (f->flags & LFS_F_INLINE) &&
+                f->ctz.size > lfs->cfg->cache_size) {
+            int err = lfs_file_outline(lfs, f);
+            if (err) {
+                return err;
+            }
+
+            err = lfs_file_flush(lfs, f);
+            if (err) {
+                return err;
+            }
+        }
+    }
+
+    lfs_block_t lpair[2] = {dir->pair[0], dir->pair[1]};
+    lfs_mdir_t ldir = *dir;
+    lfs_mdir_t pdir;
+    int state = lfs_dir_relocatingcommit(lfs, &ldir, dir->pair,
+            attrs, attrcount, &pdir);
+    if (state < 0) {
+        return state;
+    }
+
+    // update if we're not in mlist, note we may have already been
+    // updated if we are in mlist
+    if (lfs_pair_cmp(dir->pair, lpair) == 0) {
+        *dir = ldir;
+    }
+
+    // commit was successful, but may require other changes in the
+    // filesystem, these would normally be tail recursive, but we have
+    // flattened them here avoid unbounded stack usage
+
+    // need to drop?
+    if (state == LFS_OK_DROPPED) {
+        // steal state
+        int err = lfs_dir_getgstate(lfs, dir, &lfs->gdelta);
+        if (err) {
+            return err;
+        }
+
+        // steal tail, note that this can't create a recursive drop
+        lpair[0] = pdir.pair[0];
+        lpair[1] = pdir.pair[1];
+        lfs_pair_tole32(dir->tail);
+        state = lfs_dir_relocatingcommit(lfs, &pdir, lpair, LFS_MKATTRS(
+                    {LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8),
+                        dir->tail}),
+                NULL);
+        lfs_pair_fromle32(dir->tail);
+        if (state < 0) {
+            return state;
+        }
+
+        ldir = pdir;
+    }
+
+    // need to relocate?
+    bool orphans = false;
+    while (state == LFS_OK_RELOCATED) {
+        LFS_DEBUG("Relocating {0x%"PRIx32", 0x%"PRIx32"} "
+                    "-> {0x%"PRIx32", 0x%"PRIx32"}",
+                lpair[0], lpair[1], ldir.pair[0], ldir.pair[1]);
+        state = 0;
+
+        // update internal root
+        if (lfs_pair_cmp(lpair, lfs->root) == 0) {
+            lfs->root[0] = ldir.pair[0];
+            lfs->root[1] = ldir.pair[1];
+        }
+
+        // update internally tracked dirs
+        for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) {
+            if (lfs_pair_cmp(lpair, d->m.pair) == 0) {
+                d->m.pair[0] = ldir.pair[0];
+                d->m.pair[1] = ldir.pair[1];
+            }
+
+            if (d->type == LFS_TYPE_DIR &&
+                    lfs_pair_cmp(lpair, ((lfs_dir_t*)d)->head) == 0) {
+                ((lfs_dir_t*)d)->head[0] = ldir.pair[0];
+                ((lfs_dir_t*)d)->head[1] = ldir.pair[1];
+            }
+        }
+
+        // find parent
+        lfs_stag_t tag = lfs_fs_parent(lfs, lpair, &pdir);
+        if (tag < 0 && tag != LFS_ERR_NOENT) {
+            return tag;
+        }
+
+        bool hasparent = (tag != LFS_ERR_NOENT);
+        if (tag != LFS_ERR_NOENT) {
+            // note that if we have a parent, we must have a pred, so this will
+            // always create an orphan
+            int err = lfs_fs_preporphans(lfs, +1);
+            if (err) {
+                return err;
+            }
+
+            // fix pending move in this pair? this looks like an optimization but
+            // is in fact _required_ since relocating may outdate the move.
+            uint16_t moveid = 0x3ff;
+            if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) {
+                moveid = lfs_tag_id(lfs->gstate.tag);
+                LFS_DEBUG("Fixing move while relocating "
+                        "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n",
+                        pdir.pair[0], pdir.pair[1], moveid);
+                lfs_fs_prepmove(lfs, 0x3ff, NULL);
+                if (moveid < lfs_tag_id(tag)) {
+                    tag -= LFS_MKTAG(0, 1, 0);
+                }
+            }
+
+            lfs_block_t ppair[2] = {pdir.pair[0], pdir.pair[1]};
+            lfs_pair_tole32(ldir.pair);
+            state = lfs_dir_relocatingcommit(lfs, &pdir, ppair, LFS_MKATTRS(
+                        {LFS_MKTAG_IF(moveid != 0x3ff,
+                            LFS_TYPE_DELETE, moveid, 0), NULL},
+                        {tag, ldir.pair}),
+                    NULL);
+            lfs_pair_fromle32(ldir.pair);
+            if (state < 0) {
+                return state;
+            }
+
+            if (state == LFS_OK_RELOCATED) {
+                lpair[0] = ppair[0];
+                lpair[1] = ppair[1];
+                ldir = pdir;
+                orphans = true;
+                continue;
+            }
+        }
+
+        // find pred
+        int err = lfs_fs_pred(lfs, lpair, &pdir);
+        if (err && err != LFS_ERR_NOENT) {
+            return err;
+        }
+        LFS_ASSERT(!(hasparent && err == LFS_ERR_NOENT));
+
+        // if we can't find dir, it must be new
+        if (err != LFS_ERR_NOENT) {
+            if (lfs_gstate_hasorphans(&lfs->gstate)) {
+                // next step, clean up orphans
+                err = lfs_fs_preporphans(lfs, -(int8_t)hasparent);
+                if (err) {
+                    return err;
+                }
+            }
+
+            // fix pending move in this pair? this looks like an optimization
+            // but is in fact _required_ since relocating may outdate the move.
+            uint16_t moveid = 0x3ff;
+            if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) {
+                moveid = lfs_tag_id(lfs->gstate.tag);
+                LFS_DEBUG("Fixing move while relocating "
+                        "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n",
+                        pdir.pair[0], pdir.pair[1], moveid);
+                lfs_fs_prepmove(lfs, 0x3ff, NULL);
+            }
+
+            // replace bad pair, either we clean up desync, or no desync occured
+            lpair[0] = pdir.pair[0];
+            lpair[1] = pdir.pair[1];
+            lfs_pair_tole32(ldir.pair);
+            state = lfs_dir_relocatingcommit(lfs, &pdir, lpair, LFS_MKATTRS(
+                        {LFS_MKTAG_IF(moveid != 0x3ff,
+                            LFS_TYPE_DELETE, moveid, 0), NULL},
+                        {LFS_MKTAG(LFS_TYPE_TAIL + pdir.split, 0x3ff, 8),
+                            ldir.pair}),
+                    NULL);
+            lfs_pair_fromle32(ldir.pair);
+            if (state < 0) {
+                return state;
+            }
+
+            ldir = pdir;
+        }
+    }
+
+    return orphans ? LFS_OK_ORPHANED : 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
+        const struct lfs_mattr *attrs, int attrcount) {
+    int orphans = lfs_dir_orphaningcommit(lfs, dir, attrs, attrcount);
+    if (orphans < 0) {
+        return orphans;
+    }
+
+    if (orphans) {
+        // make sure we've removed all orphans, this is a noop if there
+        // are none, but if we had nested blocks failures we may have
+        // created some
+        int err = lfs_fs_deorphan(lfs, false);
+        if (err) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+#endif
+
+
+/// Top level directory operations ///
+#ifndef LFS_READONLY
+static int lfs_mkdir_(lfs_t *lfs, const char *path) {
+    // deorphan if we haven't yet, needed at most once after poweron
+    int err = lfs_fs_forceconsistency(lfs);
+    if (err) {
+        return err;
+    }
+
+    struct lfs_mlist cwd;
+    cwd.next = lfs->mlist;
+    uint16_t id;
+    err = lfs_dir_find(lfs, &cwd.m, &path, &id);
+    if (!(err == LFS_ERR_NOENT && lfs_path_islast(path))) {
+        return (err < 0) ? err : LFS_ERR_EXIST;
+    }
+
+    // check that name fits
+    lfs_size_t nlen = lfs_path_namelen(path);
+    if (nlen > lfs->name_max) {
+        return LFS_ERR_NAMETOOLONG;
+    }
+
+    // build up new directory
+    lfs_alloc_ckpoint(lfs);
+    lfs_mdir_t dir;
+    err = lfs_dir_alloc(lfs, &dir);
+    if (err) {
+        return err;
+    }
+
+    // find end of list
+    lfs_mdir_t pred = cwd.m;
+    while (pred.split) {
+        err = lfs_dir_fetch(lfs, &pred, pred.tail);
+        if (err) {
+            return err;
+        }
+    }
+
+    // setup dir
+    lfs_pair_tole32(pred.tail);
+    err = lfs_dir_commit(lfs, &dir, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail}));
+    lfs_pair_fromle32(pred.tail);
+    if (err) {
+        return err;
+    }
+
+    // current block not end of list?
+    if (cwd.m.split) {
+        // update tails, this creates a desync
+        err = lfs_fs_preporphans(lfs, +1);
+        if (err) {
+            return err;
+        }
+
+        // it's possible our predecessor has to be relocated, and if
+        // our parent is our predecessor's predecessor, this could have
+        // caused our parent to go out of date, fortunately we can hook
+        // ourselves into littlefs to catch this
+        cwd.type = 0;
+        cwd.id = 0;
+        lfs->mlist = &cwd;
+
+        lfs_pair_tole32(dir.pair);
+        err = lfs_dir_commit(lfs, &pred, LFS_MKATTRS(
+                {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair}));
+        lfs_pair_fromle32(dir.pair);
+        if (err) {
+            lfs->mlist = cwd.next;
+            return err;
+        }
+
+        lfs->mlist = cwd.next;
+        err = lfs_fs_preporphans(lfs, -1);
+        if (err) {
+            return err;
+        }
+    }
+
+    // now insert into our parent block
+    lfs_pair_tole32(dir.pair);
+    err = lfs_dir_commit(lfs, &cwd.m, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL},
+            {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path},
+            {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair},
+            {LFS_MKTAG_IF(!cwd.m.split,
+                LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair}));
+    lfs_pair_fromle32(dir.pair);
+    if (err) {
+        return err;
+    }
+
+    return 0;
+}
+#endif
+
+static int lfs_dir_open_(lfs_t *lfs, lfs_dir_t *dir, const char *path) {
+    lfs_stag_t tag = lfs_dir_find(lfs, &dir->m, &path, NULL);
+    if (tag < 0) {
+        return tag;
+    }
+
+    if (lfs_tag_type3(tag) != LFS_TYPE_DIR) {
+        return LFS_ERR_NOTDIR;
+    }
+
+    lfs_block_t pair[2];
+    if (lfs_tag_id(tag) == 0x3ff) {
+        // handle root dir separately
+        pair[0] = lfs->root[0];
+        pair[1] = lfs->root[1];
+    } else {
+        // get dir pair from parent
+        lfs_stag_t res = lfs_dir_get(lfs, &dir->m, LFS_MKTAG(0x700, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair);
+        if (res < 0) {
+            return res;
+        }
+        lfs_pair_fromle32(pair);
+    }
+
+    // fetch first pair
+    int err = lfs_dir_fetch(lfs, &dir->m, pair);
+    if (err) {
+        return err;
+    }
+
+    // setup entry
+    dir->head[0] = dir->m.pair[0];
+    dir->head[1] = dir->m.pair[1];
+    dir->id = 0;
+    dir->pos = 0;
+
+    // add to list of mdirs
+    dir->type = LFS_TYPE_DIR;
+    lfs_mlist_append(lfs, (struct lfs_mlist *)dir);
+
+    return 0;
+}
+
+static int lfs_dir_close_(lfs_t *lfs, lfs_dir_t *dir) {
+    // remove from list of mdirs
+    lfs_mlist_remove(lfs, (struct lfs_mlist *)dir);
+
+    return 0;
+}
+
+static int lfs_dir_read_(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) {
+    memset(info, 0, sizeof(*info));
+
+    // special offset for '.' and '..'
+    if (dir->pos == 0) {
+        info->type = LFS_TYPE_DIR;
+        strcpy(info->name, ".");
+        dir->pos += 1;
+        return true;
+    } else if (dir->pos == 1) {
+        info->type = LFS_TYPE_DIR;
+        strcpy(info->name, "..");
+        dir->pos += 1;
+        return true;
+    }
+
+    while (true) {
+        if (dir->id == dir->m.count) {
+            if (!dir->m.split) {
+                return false;
+            }
+
+            int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail);
+            if (err) {
+                return err;
+            }
+
+            dir->id = 0;
+        }
+
+        int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info);
+        if (err && err != LFS_ERR_NOENT) {
+            return err;
+        }
+
+        dir->id += 1;
+        if (err != LFS_ERR_NOENT) {
+            break;
+        }
+    }
+
+    dir->pos += 1;
+    return true;
+}
+
+static int lfs_dir_seek_(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
+    // simply walk from head dir
+    int err = lfs_dir_rewind_(lfs, dir);
+    if (err) {
+        return err;
+    }
+
+    // first two for ./..
+    dir->pos = lfs_min(2, off);
+    off -= dir->pos;
+
+    // skip superblock entry
+    dir->id = (off > 0 && lfs_pair_cmp(dir->head, lfs->root) == 0);
+
+    while (off > 0) {
+        if (dir->id == dir->m.count) {
+            if (!dir->m.split) {
+                return LFS_ERR_INVAL;
+            }
+
+            err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail);
+            if (err) {
+                return err;
+            }
+
+            dir->id = 0;
+        }
+
+        int diff = lfs_min(dir->m.count - dir->id, off);
+        dir->id += diff;
+        dir->pos += diff;
+        off -= diff;
+    }
+
+    return 0;
+}
+
+static lfs_soff_t lfs_dir_tell_(lfs_t *lfs, lfs_dir_t *dir) {
+    (void)lfs;
+    return dir->pos;
+}
+
+static int lfs_dir_rewind_(lfs_t *lfs, lfs_dir_t *dir) {
+    // reload the head dir
+    int err = lfs_dir_fetch(lfs, &dir->m, dir->head);
+    if (err) {
+        return err;
+    }
+
+    dir->id = 0;
+    dir->pos = 0;
+    return 0;
+}
+
+
+/// File index list operations ///
+static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) {
+    lfs_off_t size = *off;
+    lfs_off_t b = lfs->cfg->block_size - 2*4;
+    lfs_off_t i = size / b;
+    if (i == 0) {
+        return 0;
+    }
+
+    i = (size - 4*(lfs_popc(i-1)+2)) / b;
+    *off = size - b*i - 4*lfs_popc(i);
+    return i;
+}
+
+static int lfs_ctz_find(lfs_t *lfs,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache,
+        lfs_block_t head, lfs_size_t size,
+        lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) {
+    if (size == 0) {
+        *block = LFS_BLOCK_NULL;
+        *off = 0;
+        return 0;
+    }
+
+    lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1});
+    lfs_off_t target = lfs_ctz_index(lfs, &pos);
+
+    while (current > target) {
+        lfs_size_t skip = lfs_min(
+                lfs_npw2(current-target+1) - 1,
+                lfs_ctz(current));
+
+        int err = lfs_bd_read(lfs,
+                pcache, rcache, sizeof(head),
+                head, 4*skip, &head, sizeof(head));
+        head = lfs_fromle32(head);
+        if (err) {
+            return err;
+        }
+
+        current -= 1 << skip;
+    }
+
+    *block = head;
+    *off = pos;
+    return 0;
+}
+
+#ifndef LFS_READONLY
+static int lfs_ctz_extend(lfs_t *lfs,
+        lfs_cache_t *pcache, lfs_cache_t *rcache,
+        lfs_block_t head, lfs_size_t size,
+        lfs_block_t *block, lfs_off_t *off) {
+    while (true) {
+        // go ahead and grab a block
+        lfs_block_t nblock;
+        int err = lfs_alloc(lfs, &nblock);
+        if (err) {
+            return err;
+        }
+
+        {
+            err = lfs_bd_erase(lfs, nblock);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+
+            if (size == 0) {
+                *block = nblock;
+                *off = 0;
+                return 0;
+            }
+
+            lfs_size_t noff = size - 1;
+            lfs_off_t index = lfs_ctz_index(lfs, &noff);
+            noff = noff + 1;
+
+            // just copy out the last block if it is incomplete
+            if (noff != lfs->cfg->block_size) {
+                for (lfs_off_t i = 0; i < noff; i++) {
+                    uint8_t data;
+                    err = lfs_bd_read(lfs,
+                            NULL, rcache, noff-i,
+                            head, i, &data, 1);
+                    if (err) {
+                        return err;
+                    }
+
+                    err = lfs_bd_prog(lfs,
+                            pcache, rcache, true,
+                            nblock, i, &data, 1);
+                    if (err) {
+                        if (err == LFS_ERR_CORRUPT) {
+                            goto relocate;
+                        }
+                        return err;
+                    }
+                }
+
+                *block = nblock;
+                *off = noff;
+                return 0;
+            }
+
+            // append block
+            index += 1;
+            lfs_size_t skips = lfs_ctz(index) + 1;
+            lfs_block_t nhead = head;
+            for (lfs_off_t i = 0; i < skips; i++) {
+                nhead = lfs_tole32(nhead);
+                err = lfs_bd_prog(lfs, pcache, rcache, true,
+                        nblock, 4*i, &nhead, 4);
+                nhead = lfs_fromle32(nhead);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        goto relocate;
+                    }
+                    return err;
+                }
+
+                if (i != skips-1) {
+                    err = lfs_bd_read(lfs,
+                            NULL, rcache, sizeof(nhead),
+                            nhead, 4*i, &nhead, sizeof(nhead));
+                    nhead = lfs_fromle32(nhead);
+                    if (err) {
+                        return err;
+                    }
+                }
+            }
+
+            *block = nblock;
+            *off = 4*skips;
+            return 0;
+        }
+
+relocate:
+        LFS_DEBUG("Bad block at 0x%"PRIx32, nblock);
+
+        // just clear cache and try a new block
+        lfs_cache_drop(lfs, pcache);
+    }
+}
+#endif
+
+static int lfs_ctz_traverse(lfs_t *lfs,
+        const lfs_cache_t *pcache, lfs_cache_t *rcache,
+        lfs_block_t head, lfs_size_t size,
+        int (*cb)(void*, lfs_block_t), void *data) {
+    if (size == 0) {
+        return 0;
+    }
+
+    lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1});
+
+    while (true) {
+        int err = cb(data, head);
+        if (err) {
+            return err;
+        }
+
+        if (index == 0) {
+            return 0;
+        }
+
+        lfs_block_t heads[2];
+        int count = 2 - (index & 1);
+        err = lfs_bd_read(lfs,
+                pcache, rcache, count*sizeof(head),
+                head, 0, &heads, count*sizeof(head));
+        heads[0] = lfs_fromle32(heads[0]);
+        heads[1] = lfs_fromle32(heads[1]);
+        if (err) {
+            return err;
+        }
+
+        for (int i = 0; i < count-1; i++) {
+            err = cb(data, heads[i]);
+            if (err) {
+                return err;
+            }
+        }
+
+        head = heads[count-1];
+        index -= count;
+    }
+}
+
+
+/// Top level file operations ///
+static int lfs_file_opencfg_(lfs_t *lfs, lfs_file_t *file,
+        const char *path, int flags,
+        const struct lfs_file_config *cfg) {
+#ifndef LFS_READONLY
+    // deorphan if we haven't yet, needed at most once after poweron
+    if ((flags & LFS_O_WRONLY) == LFS_O_WRONLY) {
+        int err = lfs_fs_forceconsistency(lfs);
+        if (err) {
+            return err;
+        }
+    }
+#else
+    LFS_ASSERT((flags & LFS_O_RDONLY) == LFS_O_RDONLY);
+#endif
+
+    // setup simple file details
+    int err;
+    file->cfg = cfg;
+    file->flags = flags;
+    file->pos = 0;
+    file->off = 0;
+    file->cache.buffer = NULL;
+
+    // allocate entry for file if it doesn't exist
+    lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id);
+    if (tag < 0 && !(tag == LFS_ERR_NOENT && lfs_path_islast(path))) {
+        err = tag;
+        goto cleanup;
+    }
+
+    // get id, add to list of mdirs to catch update changes
+    file->type = LFS_TYPE_REG;
+    lfs_mlist_append(lfs, (struct lfs_mlist *)file);
+
+#ifdef LFS_READONLY
+    if (tag == LFS_ERR_NOENT) {
+        err = LFS_ERR_NOENT;
+        goto cleanup;
+#else
+    if (tag == LFS_ERR_NOENT) {
+        if (!(flags & LFS_O_CREAT)) {
+            err = LFS_ERR_NOENT;
+            goto cleanup;
+        }
+
+        // don't allow trailing slashes
+        if (lfs_path_isdir(path)) {
+            err = LFS_ERR_NOTDIR;
+            goto cleanup;
+        }
+
+        // check that name fits
+        lfs_size_t nlen = lfs_path_namelen(path);
+        if (nlen > lfs->name_max) {
+            err = LFS_ERR_NAMETOOLONG;
+            goto cleanup;
+        }
+
+        // get next slot and create entry to remember name
+        err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS(
+                {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL},
+                {LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path},
+                {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), NULL}));
+
+        // it may happen that the file name doesn't fit in the metadata blocks, e.g., a 256 byte file name will
+        // not fit in a 128 byte block.
+        err = (err == LFS_ERR_NOSPC) ? LFS_ERR_NAMETOOLONG : err;
+        if (err) {
+            goto cleanup;
+        }
+
+        tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0);
+    } else if (flags & LFS_O_EXCL) {
+        err = LFS_ERR_EXIST;
+        goto cleanup;
+#endif
+    } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) {
+        err = LFS_ERR_ISDIR;
+        goto cleanup;
+#ifndef LFS_READONLY
+    } else if (flags & LFS_O_TRUNC) {
+        // truncate if requested
+        tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0);
+        file->flags |= LFS_F_DIRTY;
+#endif
+    } else {
+        // try to load what's on disk, if it's inlined we'll fix it later
+        tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz);
+        if (tag < 0) {
+            err = tag;
+            goto cleanup;
+        }
+        lfs_ctz_fromle32(&file->ctz);
+    }
+
+    // fetch attrs
+    for (unsigned i = 0; i < file->cfg->attr_count; i++) {
+        // if opened for read / read-write operations
+        if ((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY) {
+            lfs_stag_t res = lfs_dir_get(lfs, &file->m,
+                    LFS_MKTAG(0x7ff, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type,
+                        file->id, file->cfg->attrs[i].size),
+                        file->cfg->attrs[i].buffer);
+            if (res < 0 && res != LFS_ERR_NOENT) {
+                err = res;
+                goto cleanup;
+            }
+        }
+
+#ifndef LFS_READONLY
+        // if opened for write / read-write operations
+        if ((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY) {
+            if (file->cfg->attrs[i].size > lfs->attr_max) {
+                err = LFS_ERR_NOSPC;
+                goto cleanup;
+            }
+
+            file->flags |= LFS_F_DIRTY;
+        }
+#endif
+    }
+
+    // allocate buffer if needed
+    if (file->cfg->buffer) {
+        file->cache.buffer = file->cfg->buffer;
+    } else {
+        file->cache.buffer = lfs_malloc(lfs->cfg->cache_size);
+        if (!file->cache.buffer) {
+            err = LFS_ERR_NOMEM;
+            goto cleanup;
+        }
+    }
+
+    // zero to avoid information leak
+    lfs_cache_zero(lfs, &file->cache);
+
+    if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) {
+        // load inline files
+        file->ctz.head = LFS_BLOCK_INLINE;
+        file->ctz.size = lfs_tag_size(tag);
+        file->flags |= LFS_F_INLINE;
+        file->cache.block = file->ctz.head;
+        file->cache.off = 0;
+        file->cache.size = lfs->cfg->cache_size;
+
+        // don't always read (may be new/trunc file)
+        if (file->ctz.size > 0) {
+            lfs_stag_t res = lfs_dir_get(lfs, &file->m,
+                    LFS_MKTAG(0x700, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_STRUCT, file->id,
+                        lfs_min(file->cache.size, 0x3fe)),
+                    file->cache.buffer);
+            if (res < 0) {
+                err = res;
+                goto cleanup;
+            }
+        }
+    }
+
+    return 0;
+
+cleanup:
+    // clean up lingering resources
+#ifndef LFS_READONLY
+    file->flags |= LFS_F_ERRED;
+#endif
+    lfs_file_close_(lfs, file);
+    return err;
+}
+
+#ifndef LFS_NO_MALLOC
+static int lfs_file_open_(lfs_t *lfs, lfs_file_t *file,
+        const char *path, int flags) {
+    static const struct lfs_file_config defaults = {0};
+    int err = lfs_file_opencfg_(lfs, file, path, flags, &defaults);
+    return err;
+}
+#endif
+
+static int lfs_file_close_(lfs_t *lfs, lfs_file_t *file) {
+#ifndef LFS_READONLY
+    int err = lfs_file_sync_(lfs, file);
+#else
+    int err = 0;
+#endif
+
+    // remove from list of mdirs
+    lfs_mlist_remove(lfs, (struct lfs_mlist*)file);
+
+    // clean up memory
+    if (!file->cfg->buffer) {
+        lfs_free(file->cache.buffer);
+    }
+
+    return err;
+}
+
+
+#ifndef LFS_READONLY
+static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) {
+    while (true) {
+        // just relocate what exists into new block
+        lfs_block_t nblock;
+        int err = lfs_alloc(lfs, &nblock);
+        if (err) {
+            return err;
+        }
+
+        err = lfs_bd_erase(lfs, nblock);
+        if (err) {
+            if (err == LFS_ERR_CORRUPT) {
+                goto relocate;
+            }
+            return err;
+        }
+
+        // either read from dirty cache or disk
+        for (lfs_off_t i = 0; i < file->off; i++) {
+            uint8_t data;
+            if (file->flags & LFS_F_INLINE) {
+                err = lfs_dir_getread(lfs, &file->m,
+                        // note we evict inline files before they can be dirty
+                        NULL, &file->cache, file->off-i,
+                        LFS_MKTAG(0xfff, 0x1ff, 0),
+                        LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0),
+                        i, &data, 1);
+                if (err) {
+                    return err;
+                }
+            } else {
+                err = lfs_bd_read(lfs,
+                        &file->cache, &lfs->rcache, file->off-i,
+                        file->block, i, &data, 1);
+                if (err) {
+                    return err;
+                }
+            }
+
+            err = lfs_bd_prog(lfs,
+                    &lfs->pcache, &lfs->rcache, true,
+                    nblock, i, &data, 1);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                return err;
+            }
+        }
+
+        // copy over new state of file
+        memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->cache_size);
+        file->cache.block = lfs->pcache.block;
+        file->cache.off = lfs->pcache.off;
+        file->cache.size = lfs->pcache.size;
+        lfs_cache_zero(lfs, &lfs->pcache);
+
+        file->block = nblock;
+        file->flags |= LFS_F_WRITING;
+        return 0;
+
+relocate:
+        LFS_DEBUG("Bad block at 0x%"PRIx32, nblock);
+
+        // just clear cache and try a new block
+        lfs_cache_drop(lfs, &lfs->pcache);
+    }
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file) {
+    file->off = file->pos;
+    lfs_alloc_ckpoint(lfs);
+    int err = lfs_file_relocate(lfs, file);
+    if (err) {
+        return err;
+    }
+
+    file->flags &= ~LFS_F_INLINE;
+    return 0;
+}
+#endif
+
+static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
+    if (file->flags & LFS_F_READING) {
+        if (!(file->flags & LFS_F_INLINE)) {
+            lfs_cache_drop(lfs, &file->cache);
+        }
+        file->flags &= ~LFS_F_READING;
+    }
+
+#ifndef LFS_READONLY
+    if (file->flags & LFS_F_WRITING) {
+        lfs_off_t pos = file->pos;
+
+        if (!(file->flags & LFS_F_INLINE)) {
+            // copy over anything after current branch
+            lfs_file_t orig = {
+                .ctz.head = file->ctz.head,
+                .ctz.size = file->ctz.size,
+                .flags = LFS_O_RDONLY,
+                .pos = file->pos,
+                .cache = lfs->rcache,
+            };
+            lfs_cache_drop(lfs, &lfs->rcache);
+
+            while (file->pos < file->ctz.size) {
+                // copy over a byte at a time, leave it up to caching
+                // to make this efficient
+                uint8_t data;
+                lfs_ssize_t res = lfs_file_flushedread(lfs, &orig, &data, 1);
+                if (res < 0) {
+                    return res;
+                }
+
+                res = lfs_file_flushedwrite(lfs, file, &data, 1);
+                if (res < 0) {
+                    return res;
+                }
+
+                // keep our reference to the rcache in sync
+                if (lfs->rcache.block != LFS_BLOCK_NULL) {
+                    lfs_cache_drop(lfs, &orig.cache);
+                    lfs_cache_drop(lfs, &lfs->rcache);
+                }
+            }
+
+            // write out what we have
+            while (true) {
+                int err = lfs_bd_flush(lfs, &file->cache, &lfs->rcache, true);
+                if (err) {
+                    if (err == LFS_ERR_CORRUPT) {
+                        goto relocate;
+                    }
+                    return err;
+                }
+
+                break;
+
+relocate:
+                LFS_DEBUG("Bad block at 0x%"PRIx32, file->block);
+                err = lfs_file_relocate(lfs, file);
+                if (err) {
+                    return err;
+                }
+            }
+        } else {
+            file->pos = lfs_max(file->pos, file->ctz.size);
+        }
+
+        // actual file updates
+        file->ctz.head = file->block;
+        file->ctz.size = file->pos;
+        file->flags &= ~LFS_F_WRITING;
+        file->flags |= LFS_F_DIRTY;
+
+        file->pos = pos;
+    }
+#endif
+
+    return 0;
+}
+
+#ifndef LFS_READONLY
+static int lfs_file_sync_(lfs_t *lfs, lfs_file_t *file) {
+    if (file->flags & LFS_F_ERRED) {
+        // it's not safe to do anything if our file errored
+        return 0;
+    }
+
+    int err = lfs_file_flush(lfs, file);
+    if (err) {
+        file->flags |= LFS_F_ERRED;
+        return err;
+    }
+
+
+    if ((file->flags & LFS_F_DIRTY) &&
+            !lfs_pair_isnull(file->m.pair)) {
+        // before we commit metadata, we need sync the disk to make sure
+        // data writes don't complete after metadata writes
+        if (!(file->flags & LFS_F_INLINE)) {
+            err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false);
+            if (err) {
+                return err;
+            }
+        }
+
+        // update dir entry
+        uint16_t type;
+        const void *buffer;
+        lfs_size_t size;
+        struct lfs_ctz ctz;
+        if (file->flags & LFS_F_INLINE) {
+            // inline the whole file
+            type = LFS_TYPE_INLINESTRUCT;
+            buffer = file->cache.buffer;
+            size = file->ctz.size;
+        } else {
+            // update the ctz reference
+            type = LFS_TYPE_CTZSTRUCT;
+            // copy ctz so alloc will work during a relocate
+            ctz = file->ctz;
+            lfs_ctz_tole32(&ctz);
+            buffer = &ctz;
+            size = sizeof(ctz);
+        }
+
+        // commit file data and attributes
+        err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS(
+                {LFS_MKTAG(type, file->id, size), buffer},
+                {LFS_MKTAG(LFS_FROM_USERATTRS, file->id,
+                    file->cfg->attr_count), file->cfg->attrs}));
+        if (err) {
+            file->flags |= LFS_F_ERRED;
+            return err;
+        }
+
+        file->flags &= ~LFS_F_DIRTY;
+    }
+
+    return 0;
+}
+#endif
+
+static lfs_ssize_t lfs_file_flushedread(lfs_t *lfs, lfs_file_t *file,
+        void *buffer, lfs_size_t size) {
+    uint8_t *data = buffer;
+    lfs_size_t nsize = size;
+
+    if (file->pos >= file->ctz.size) {
+        // eof if past end
+        return 0;
+    }
+
+    size = lfs_min(size, file->ctz.size - file->pos);
+    nsize = size;
+
+    while (nsize > 0) {
+        // check if we need a new block
+        if (!(file->flags & LFS_F_READING) ||
+                file->off == lfs->cfg->block_size) {
+            if (!(file->flags & LFS_F_INLINE)) {
+                int err = lfs_ctz_find(lfs, NULL, &file->cache,
+                        file->ctz.head, file->ctz.size,
+                        file->pos, &file->block, &file->off);
+                if (err) {
+                    return err;
+                }
+            } else {
+                file->block = LFS_BLOCK_INLINE;
+                file->off = file->pos;
+            }
+
+            file->flags |= LFS_F_READING;
+        }
+
+        // read as much as we can in current block
+        lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
+        if (file->flags & LFS_F_INLINE) {
+            int err = lfs_dir_getread(lfs, &file->m,
+                    NULL, &file->cache, lfs->cfg->block_size,
+                    LFS_MKTAG(0xfff, 0x1ff, 0),
+                    LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0),
+                    file->off, data, diff);
+            if (err) {
+                return err;
+            }
+        } else {
+            int err = lfs_bd_read(lfs,
+                    NULL, &file->cache, lfs->cfg->block_size,
+                    file->block, file->off, data, diff);
+            if (err) {
+                return err;
+            }
+        }
+
+        file->pos += diff;
+        file->off += diff;
+        data += diff;
+        nsize -= diff;
+    }
+
+    return size;
+}
+
+static lfs_ssize_t lfs_file_read_(lfs_t *lfs, lfs_file_t *file,
+        void *buffer, lfs_size_t size) {
+    LFS_ASSERT((file->flags & LFS_O_RDONLY) == LFS_O_RDONLY);
+
+#ifndef LFS_READONLY
+    if (file->flags & LFS_F_WRITING) {
+        // flush out any writes
+        int err = lfs_file_flush(lfs, file);
+        if (err) {
+            return err;
+        }
+    }
+#endif
+
+    return lfs_file_flushedread(lfs, file, buffer, size);
+}
+
+
+#ifndef LFS_READONLY
+static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file,
+        const void *buffer, lfs_size_t size) {
+    const uint8_t *data = buffer;
+    lfs_size_t nsize = size;
+
+    if ((file->flags & LFS_F_INLINE) &&
+            lfs_max(file->pos+nsize, file->ctz.size) > lfs->inline_max) {
+        // inline file doesn't fit anymore
+        int err = lfs_file_outline(lfs, file);
+        if (err) {
+            file->flags |= LFS_F_ERRED;
+            return err;
+        }
+    }
+
+    while (nsize > 0) {
+        // check if we need a new block
+        if (!(file->flags & LFS_F_WRITING) ||
+                file->off == lfs->cfg->block_size) {
+            if (!(file->flags & LFS_F_INLINE)) {
+                if (!(file->flags & LFS_F_WRITING) && file->pos > 0) {
+                    // find out which block we're extending from
+                    int err = lfs_ctz_find(lfs, NULL, &file->cache,
+                            file->ctz.head, file->ctz.size,
+                            file->pos-1, &file->block, &(lfs_off_t){0});
+                    if (err) {
+                        file->flags |= LFS_F_ERRED;
+                        return err;
+                    }
+
+                    // mark cache as dirty since we may have read data into it
+                    lfs_cache_zero(lfs, &file->cache);
+                }
+
+                // extend file with new blocks
+                lfs_alloc_ckpoint(lfs);
+                int err = lfs_ctz_extend(lfs, &file->cache, &lfs->rcache,
+                        file->block, file->pos,
+                        &file->block, &file->off);
+                if (err) {
+                    file->flags |= LFS_F_ERRED;
+                    return err;
+                }
+            } else {
+                file->block = LFS_BLOCK_INLINE;
+                file->off = file->pos;
+            }
+
+            file->flags |= LFS_F_WRITING;
+        }
+
+        // program as much as we can in current block
+        lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
+        while (true) {
+            int err = lfs_bd_prog(lfs, &file->cache, &lfs->rcache, true,
+                    file->block, file->off, data, diff);
+            if (err) {
+                if (err == LFS_ERR_CORRUPT) {
+                    goto relocate;
+                }
+                file->flags |= LFS_F_ERRED;
+                return err;
+            }
+
+            break;
+relocate:
+            err = lfs_file_relocate(lfs, file);
+            if (err) {
+                file->flags |= LFS_F_ERRED;
+                return err;
+            }
+        }
+
+        file->pos += diff;
+        file->off += diff;
+        data += diff;
+        nsize -= diff;
+
+        lfs_alloc_ckpoint(lfs);
+    }
+
+    return size;
+}
+
+static lfs_ssize_t lfs_file_write_(lfs_t *lfs, lfs_file_t *file,
+        const void *buffer, lfs_size_t size) {
+    LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY);
+
+    if (file->flags & LFS_F_READING) {
+        // drop any reads
+        int err = lfs_file_flush(lfs, file);
+        if (err) {
+            return err;
+        }
+    }
+
+    if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) {
+        file->pos = file->ctz.size;
+    }
+
+    if (file->pos + size > lfs->file_max) {
+        // Larger than file limit?
+        return LFS_ERR_FBIG;
+    }
+
+    if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) {
+        // fill with zeros
+        lfs_off_t pos = file->pos;
+        file->pos = file->ctz.size;
+
+        while (file->pos < pos) {
+            lfs_ssize_t res = lfs_file_flushedwrite(lfs, file, &(uint8_t){0}, 1);
+            if (res < 0) {
+                return res;
+            }
+        }
+    }
+
+    lfs_ssize_t nsize = lfs_file_flushedwrite(lfs, file, buffer, size);
+    if (nsize < 0) {
+        return nsize;
+    }
+
+    file->flags &= ~LFS_F_ERRED;
+    return nsize;
+}
+#endif
+
+static lfs_soff_t lfs_file_seek_(lfs_t *lfs, lfs_file_t *file,
+        lfs_soff_t off, int whence) {
+    // find new pos
+    //
+    // fortunately for us, littlefs is limited to 31-bit file sizes, so we
+    // don't have to worry too much about integer overflow
+    lfs_off_t npos = file->pos;
+    if (whence == LFS_SEEK_SET) {
+        npos = off;
+    } else if (whence == LFS_SEEK_CUR) {
+        npos = file->pos + (lfs_off_t)off;
+    } else if (whence == LFS_SEEK_END) {
+        npos = (lfs_off_t)lfs_file_size_(lfs, file) + (lfs_off_t)off;
+    }
+
+    if (npos > lfs->file_max) {
+        // file position out of range
+        return LFS_ERR_INVAL;
+    }
+
+    if (file->pos == npos) {
+        // noop - position has not changed
+        return npos;
+    }
+
+    // if we're only reading and our new offset is still in the file's cache
+    // we can avoid flushing and needing to reread the data
+    if ((file->flags & LFS_F_READING)
+            && file->off != lfs->cfg->block_size) {
+        int oindex = lfs_ctz_index(lfs, &(lfs_off_t){file->pos});
+        lfs_off_t noff = npos;
+        int nindex = lfs_ctz_index(lfs, &noff);
+        if (oindex == nindex
+                && noff >= file->cache.off
+                && noff < file->cache.off + file->cache.size) {
+            file->pos = npos;
+            file->off = noff;
+            return npos;
+        }
+    }
+
+    // write out everything beforehand, may be noop if rdonly
+    int err = lfs_file_flush(lfs, file);
+    if (err) {
+        return err;
+    }
+
+    // update pos
+    file->pos = npos;
+    return npos;
+}
+
+#ifndef LFS_READONLY
+static int lfs_file_truncate_(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
+    LFS_ASSERT((file->flags & LFS_O_WRONLY) == LFS_O_WRONLY);
+
+    if (size > LFS_FILE_MAX) {
+        return LFS_ERR_INVAL;
+    }
+
+    lfs_off_t pos = file->pos;
+    lfs_off_t oldsize = lfs_file_size_(lfs, file);
+    if (size < oldsize) {
+        // revert to inline file?
+        if (size <= lfs->inline_max) {
+            // flush+seek to head
+            lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_SET);
+            if (res < 0) {
+                return (int)res;
+            }
+
+            // read our data into rcache temporarily
+            lfs_cache_drop(lfs, &lfs->rcache);
+            res = lfs_file_flushedread(lfs, file,
+                    lfs->rcache.buffer, size);
+            if (res < 0) {
+                return (int)res;
+            }
+
+            file->ctz.head = LFS_BLOCK_INLINE;
+            file->ctz.size = size;
+            file->flags |= LFS_F_DIRTY | LFS_F_READING | LFS_F_INLINE;
+            file->cache.block = file->ctz.head;
+            file->cache.off = 0;
+            file->cache.size = lfs->cfg->cache_size;
+            memcpy(file->cache.buffer, lfs->rcache.buffer, size);
+
+        } else {
+            // need to flush since directly changing metadata
+            int err = lfs_file_flush(lfs, file);
+            if (err) {
+                return err;
+            }
+
+            // lookup new head in ctz skip list
+            err = lfs_ctz_find(lfs, NULL, &file->cache,
+                    file->ctz.head, file->ctz.size,
+                    size-1, &file->block, &(lfs_off_t){0});
+            if (err) {
+                return err;
+            }
+
+            // need to set pos/block/off consistently so seeking back to
+            // the old position does not get confused
+            file->pos = size;
+            file->ctz.head = file->block;
+            file->ctz.size = size;
+            file->flags |= LFS_F_DIRTY | LFS_F_READING;
+        }
+    } else if (size > oldsize) {
+        // flush+seek if not already at end
+        lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_END);
+        if (res < 0) {
+            return (int)res;
+        }
+
+        // fill with zeros
+        while (file->pos < size) {
+            res = lfs_file_write_(lfs, file, &(uint8_t){0}, 1);
+            if (res < 0) {
+                return (int)res;
+            }
+        }
+    }
+
+    // restore pos
+    lfs_soff_t res = lfs_file_seek_(lfs, file, pos, LFS_SEEK_SET);
+    if (res < 0) {
+      return (int)res;
+    }
+
+    return 0;
+}
+#endif
+
+static lfs_soff_t lfs_file_tell_(lfs_t *lfs, lfs_file_t *file) {
+    (void)lfs;
+    return file->pos;
+}
+
+static int lfs_file_rewind_(lfs_t *lfs, lfs_file_t *file) {
+    lfs_soff_t res = lfs_file_seek_(lfs, file, 0, LFS_SEEK_SET);
+    if (res < 0) {
+        return (int)res;
+    }
+
+    return 0;
+}
+
+static lfs_soff_t lfs_file_size_(lfs_t *lfs, lfs_file_t *file) {
+    (void)lfs;
+
+#ifndef LFS_READONLY
+    if (file->flags & LFS_F_WRITING) {
+        return lfs_max(file->pos, file->ctz.size);
+    }
+#endif
+
+    return file->ctz.size;
+}
+
+
+/// General fs operations ///
+static int lfs_stat_(lfs_t *lfs, const char *path, struct lfs_info *info) {
+    lfs_mdir_t cwd;
+    lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL);
+    if (tag < 0) {
+        return (int)tag;
+    }
+
+    // only allow trailing slashes on dirs
+    if (strchr(path, '/') != NULL
+            && lfs_tag_type3(tag) != LFS_TYPE_DIR) {
+        return LFS_ERR_NOTDIR;
+    }
+
+    return lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info);
+}
+
+#ifndef LFS_READONLY
+static int lfs_remove_(lfs_t *lfs, const char *path) {
+    // deorphan if we haven't yet, needed at most once after poweron
+    int err = lfs_fs_forceconsistency(lfs);
+    if (err) {
+        return err;
+    }
+
+    lfs_mdir_t cwd;
+    lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL);
+    if (tag < 0 || lfs_tag_id(tag) == 0x3ff) {
+        return (tag < 0) ? (int)tag : LFS_ERR_INVAL;
+    }
+
+    struct lfs_mlist dir;
+    dir.next = lfs->mlist;
+    if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
+        // must be empty before removal
+        lfs_block_t pair[2];
+        lfs_stag_t res = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x700, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair);
+        if (res < 0) {
+            return (int)res;
+        }
+        lfs_pair_fromle32(pair);
+
+        err = lfs_dir_fetch(lfs, &dir.m, pair);
+        if (err) {
+            return err;
+        }
+
+        if (dir.m.count > 0 || dir.m.split) {
+            return LFS_ERR_NOTEMPTY;
+        }
+
+        // mark fs as orphaned
+        err = lfs_fs_preporphans(lfs, +1);
+        if (err) {
+            return err;
+        }
+
+        // I know it's crazy but yes, dir can be changed by our parent's
+        // commit (if predecessor is child)
+        dir.type = 0;
+        dir.id = 0;
+        lfs->mlist = &dir;
+    }
+
+    // delete the entry
+    err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL}));
+    if (err) {
+        lfs->mlist = dir.next;
+        return err;
+    }
+
+    lfs->mlist = dir.next;
+    if (lfs_gstate_hasorphans(&lfs->gstate)) {
+        LFS_ASSERT(lfs_tag_type3(tag) == LFS_TYPE_DIR);
+
+        // fix orphan
+        err = lfs_fs_preporphans(lfs, -1);
+        if (err) {
+            return err;
+        }
+
+        err = lfs_fs_pred(lfs, dir.m.pair, &cwd);
+        if (err) {
+            return err;
+        }
+
+        err = lfs_dir_drop(lfs, &cwd, &dir.m);
+        if (err) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
+    // deorphan if we haven't yet, needed at most once after poweron
+    int err = lfs_fs_forceconsistency(lfs);
+    if (err) {
+        return err;
+    }
+
+    // find old entry
+    lfs_mdir_t oldcwd;
+    lfs_stag_t oldtag = lfs_dir_find(lfs, &oldcwd, &oldpath, NULL);
+    if (oldtag < 0 || lfs_tag_id(oldtag) == 0x3ff) {
+        return (oldtag < 0) ? (int)oldtag : LFS_ERR_INVAL;
+    }
+
+    // find new entry
+    lfs_mdir_t newcwd;
+    uint16_t newid;
+    lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid);
+    if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) &&
+            !(prevtag == LFS_ERR_NOENT && lfs_path_islast(newpath))) {
+        return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL;
+    }
+
+    // if we're in the same pair there's a few special cases...
+    bool samepair = (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0);
+    uint16_t newoldid = lfs_tag_id(oldtag);
+
+    struct lfs_mlist prevdir;
+    prevdir.next = lfs->mlist;
+    if (prevtag == LFS_ERR_NOENT) {
+        // if we're a file, don't allow trailing slashes
+        if (lfs_path_isdir(newpath)
+                && lfs_tag_type3(oldtag) != LFS_TYPE_DIR) {
+            return LFS_ERR_NOTDIR;
+        }
+
+        // check that name fits
+        lfs_size_t nlen = lfs_path_namelen(newpath);
+        if (nlen > lfs->name_max) {
+            return LFS_ERR_NAMETOOLONG;
+        }
+
+        // there is a small chance we are being renamed in the same
+        // directory/ to an id less than our old id, the global update
+        // to handle this is a bit messy
+        if (samepair && newid <= newoldid) {
+            newoldid += 1;
+        }
+    } else if (lfs_tag_type3(prevtag) != lfs_tag_type3(oldtag)) {
+        return (lfs_tag_type3(prevtag) == LFS_TYPE_DIR)
+                ? LFS_ERR_ISDIR
+                : LFS_ERR_NOTDIR;
+    } else if (samepair && newid == newoldid) {
+        // we're renaming to ourselves??
+        return 0;
+    } else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) {
+        // must be empty before removal
+        lfs_block_t prevpair[2];
+        lfs_stag_t res = lfs_dir_get(lfs, &newcwd, LFS_MKTAG(0x700, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair);
+        if (res < 0) {
+            return (int)res;
+        }
+        lfs_pair_fromle32(prevpair);
+
+        // must be empty before removal
+        err = lfs_dir_fetch(lfs, &prevdir.m, prevpair);
+        if (err) {
+            return err;
+        }
+
+        if (prevdir.m.count > 0 || prevdir.m.split) {
+            return LFS_ERR_NOTEMPTY;
+        }
+
+        // mark fs as orphaned
+        err = lfs_fs_preporphans(lfs, +1);
+        if (err) {
+            return err;
+        }
+
+        // I know it's crazy but yes, dir can be changed by our parent's
+        // commit (if predecessor is child)
+        prevdir.type = 0;
+        prevdir.id = 0;
+        lfs->mlist = &prevdir;
+    }
+
+    if (!samepair) {
+        lfs_fs_prepmove(lfs, newoldid, oldcwd.pair);
+    }
+
+    // move over all attributes
+    err = lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS(
+            {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT,
+                LFS_TYPE_DELETE, newid, 0), NULL},
+            {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL},
+            {LFS_MKTAG(lfs_tag_type3(oldtag),
+                newid, lfs_path_namelen(newpath)), newpath},
+            {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd},
+            {LFS_MKTAG_IF(samepair,
+                LFS_TYPE_DELETE, newoldid, 0), NULL}));
+    if (err) {
+        lfs->mlist = prevdir.next;
+        return err;
+    }
+
+    // let commit clean up after move (if we're different! otherwise move
+    // logic already fixed it for us)
+    if (!samepair && lfs_gstate_hasmove(&lfs->gstate)) {
+        // prep gstate and delete move id
+        lfs_fs_prepmove(lfs, 0x3ff, NULL);
+        err = lfs_dir_commit(lfs, &oldcwd, LFS_MKATTRS(
+                {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(oldtag), 0), NULL}));
+        if (err) {
+            lfs->mlist = prevdir.next;
+            return err;
+        }
+    }
+
+    lfs->mlist = prevdir.next;
+    if (lfs_gstate_hasorphans(&lfs->gstate)) {
+        LFS_ASSERT(prevtag != LFS_ERR_NOENT
+                && lfs_tag_type3(prevtag) == LFS_TYPE_DIR);
+
+        // fix orphan
+        err = lfs_fs_preporphans(lfs, -1);
+        if (err) {
+            return err;
+        }
+
+        err = lfs_fs_pred(lfs, prevdir.m.pair, &newcwd);
+        if (err) {
+            return err;
+        }
+
+        err = lfs_dir_drop(lfs, &newcwd, &prevdir.m);
+        if (err) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+#endif
+
+static lfs_ssize_t lfs_getattr_(lfs_t *lfs, const char *path,
+        uint8_t type, void *buffer, lfs_size_t size) {
+    lfs_mdir_t cwd;
+    lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL);
+    if (tag < 0) {
+        return tag;
+    }
+
+    uint16_t id = lfs_tag_id(tag);
+    if (id == 0x3ff) {
+        // special case for root
+        id = 0;
+        int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
+        if (err) {
+            return err;
+        }
+    }
+
+    tag = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x7ff, 0x3ff, 0),
+            LFS_MKTAG(LFS_TYPE_USERATTR + type,
+                id, lfs_min(size, lfs->attr_max)),
+            buffer);
+    if (tag < 0) {
+        if (tag == LFS_ERR_NOENT) {
+            return LFS_ERR_NOATTR;
+        }
+
+        return tag;
+    }
+
+    return lfs_tag_size(tag);
+}
+
+#ifndef LFS_READONLY
+static int lfs_commitattr(lfs_t *lfs, const char *path,
+        uint8_t type, const void *buffer, lfs_size_t size) {
+    lfs_mdir_t cwd;
+    lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL);
+    if (tag < 0) {
+        return tag;
+    }
+
+    uint16_t id = lfs_tag_id(tag);
+    if (id == 0x3ff) {
+        // special case for root
+        id = 0;
+        int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
+        if (err) {
+            return err;
+        }
+    }
+
+    return lfs_dir_commit(lfs, &cwd, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_USERATTR + type, id, size), buffer}));
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_setattr_(lfs_t *lfs, const char *path,
+        uint8_t type, const void *buffer, lfs_size_t size) {
+    if (size > lfs->attr_max) {
+        return LFS_ERR_NOSPC;
+    }
+
+    return lfs_commitattr(lfs, path, type, buffer, size);
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_removeattr_(lfs_t *lfs, const char *path, uint8_t type) {
+    return lfs_commitattr(lfs, path, type, NULL, 0x3ff);
+}
+#endif
+
+
+/// Filesystem operations ///
+
+// compile time checks, see lfs.h for why these limits exist
+#if LFS_NAME_MAX > 1022
+#error "Invalid LFS_NAME_MAX, must be <= 1022"
+#endif
+
+#if LFS_FILE_MAX > 2147483647
+#error "Invalid LFS_FILE_MAX, must be <= 2147483647"
+#endif
+
+#if LFS_ATTR_MAX > 1022
+#error "Invalid LFS_ATTR_MAX, must be <= 1022"
+#endif
+
+// common filesystem initialization
+static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
+    lfs->cfg = cfg;
+    lfs->block_count = cfg->block_count;  // May be 0
+    int err = 0;
+
+#ifdef LFS_MULTIVERSION
+    // this driver only supports minor version < current minor version
+    LFS_ASSERT(!lfs->cfg->disk_version || (
+            (0xffff & (lfs->cfg->disk_version >> 16))
+                    == LFS_DISK_VERSION_MAJOR
+                && (0xffff & (lfs->cfg->disk_version >> 0))
+                    <= LFS_DISK_VERSION_MINOR));
+#endif
+
+    // check that bool is a truthy-preserving type
+    //
+    // note the most common reason for this failure is a before-c99 compiler,
+    // which littlefs currently does not support
+    LFS_ASSERT((bool)0x80000000);
+
+    // check that the required io functions are provided
+    LFS_ASSERT(lfs->cfg->read != NULL);
+#ifndef LFS_READONLY
+    LFS_ASSERT(lfs->cfg->prog != NULL);
+    LFS_ASSERT(lfs->cfg->erase != NULL);
+    LFS_ASSERT(lfs->cfg->sync != NULL);
+#endif
+
+    // validate that the lfs-cfg sizes were initiated properly before
+    // performing any arithmetic logics with them
+    LFS_ASSERT(lfs->cfg->read_size != 0);
+    LFS_ASSERT(lfs->cfg->prog_size != 0);
+    LFS_ASSERT(lfs->cfg->cache_size != 0);
+
+    // check that block size is a multiple of cache size is a multiple
+    // of prog and read sizes
+    LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0);
+    LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0);
+    LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0);
+
+    // check that the block size is large enough to fit all ctz pointers
+    LFS_ASSERT(lfs->cfg->block_size >= 128);
+    // this is the exact calculation for all ctz pointers, if this fails
+    // and the simpler assert above does not, math must be broken
+    LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4))
+            <= lfs->cfg->block_size);
+
+    // block_cycles = 0 is no longer supported.
+    //
+    // block_cycles is the number of erase cycles before littlefs evicts
+    // metadata logs as a part of wear leveling. Suggested values are in the
+    // range of 100-1000, or set block_cycles to -1 to disable block-level
+    // wear-leveling.
+    LFS_ASSERT(lfs->cfg->block_cycles != 0);
+
+    // check that compact_thresh makes sense
+    //
+    // metadata can't be compacted below block_size/2, and metadata can't
+    // exceed a block_size
+    LFS_ASSERT(lfs->cfg->compact_thresh == 0
+            || lfs->cfg->compact_thresh >= lfs->cfg->block_size/2);
+    LFS_ASSERT(lfs->cfg->compact_thresh == (lfs_size_t)-1
+            || lfs->cfg->compact_thresh <= lfs->cfg->block_size);
+
+    // check that metadata_max is a multiple of read_size and prog_size,
+    // and a factor of the block_size
+    LFS_ASSERT(!lfs->cfg->metadata_max
+            || lfs->cfg->metadata_max % lfs->cfg->read_size == 0);
+    LFS_ASSERT(!lfs->cfg->metadata_max
+            || lfs->cfg->metadata_max % lfs->cfg->prog_size == 0);
+    LFS_ASSERT(!lfs->cfg->metadata_max
+            || lfs->cfg->block_size % lfs->cfg->metadata_max == 0);
+
+    // setup read cache
+    if (lfs->cfg->read_buffer) {
+        lfs->rcache.buffer = lfs->cfg->read_buffer;
+    } else {
+        lfs->rcache.buffer = lfs_malloc(lfs->cfg->cache_size);
+        if (!lfs->rcache.buffer) {
+            err = LFS_ERR_NOMEM;
+            goto cleanup;
+        }
+    }
+
+    // setup program cache
+    if (lfs->cfg->prog_buffer) {
+        lfs->pcache.buffer = lfs->cfg->prog_buffer;
+    } else {
+        lfs->pcache.buffer = lfs_malloc(lfs->cfg->cache_size);
+        if (!lfs->pcache.buffer) {
+            err = LFS_ERR_NOMEM;
+            goto cleanup;
+        }
+    }
+
+    // zero to avoid information leaks
+    lfs_cache_zero(lfs, &lfs->rcache);
+    lfs_cache_zero(lfs, &lfs->pcache);
+
+    // setup lookahead buffer, note mount finishes initializing this after
+    // we establish a decent pseudo-random seed
+    LFS_ASSERT(lfs->cfg->lookahead_size > 0);
+    if (lfs->cfg->lookahead_buffer) {
+        lfs->lookahead.buffer = lfs->cfg->lookahead_buffer;
+    } else {
+        lfs->lookahead.buffer = lfs_malloc(lfs->cfg->lookahead_size);
+        if (!lfs->lookahead.buffer) {
+            err = LFS_ERR_NOMEM;
+            goto cleanup;
+        }
+    }
+
+    // check that the size limits are sane
+    LFS_ASSERT(lfs->cfg->name_max <= LFS_NAME_MAX);
+    lfs->name_max = lfs->cfg->name_max;
+    if (!lfs->name_max) {
+        lfs->name_max = LFS_NAME_MAX;
+    }
+
+    LFS_ASSERT(lfs->cfg->file_max <= LFS_FILE_MAX);
+    lfs->file_max = lfs->cfg->file_max;
+    if (!lfs->file_max) {
+        lfs->file_max = LFS_FILE_MAX;
+    }
+
+    LFS_ASSERT(lfs->cfg->attr_max <= LFS_ATTR_MAX);
+    lfs->attr_max = lfs->cfg->attr_max;
+    if (!lfs->attr_max) {
+        lfs->attr_max = LFS_ATTR_MAX;
+    }
+
+    LFS_ASSERT(lfs->cfg->metadata_max <= lfs->cfg->block_size);
+
+    LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1
+            || lfs->cfg->inline_max <= lfs->cfg->cache_size);
+    LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1
+            || lfs->cfg->inline_max <= lfs->attr_max);
+    LFS_ASSERT(lfs->cfg->inline_max == (lfs_size_t)-1
+            || lfs->cfg->inline_max <= ((lfs->cfg->metadata_max)
+                ? lfs->cfg->metadata_max
+                : lfs->cfg->block_size)/8);
+    lfs->inline_max = lfs->cfg->inline_max;
+    if (lfs->inline_max == (lfs_size_t)-1) {
+        lfs->inline_max = 0;
+    } else if (lfs->inline_max == 0) {
+        lfs->inline_max = lfs_min(
+                lfs->cfg->cache_size,
+                lfs_min(
+                    lfs->attr_max,
+                    ((lfs->cfg->metadata_max)
+                        ? lfs->cfg->metadata_max
+                        : lfs->cfg->block_size)/8));
+    }
+
+    // setup default state
+    lfs->root[0] = LFS_BLOCK_NULL;
+    lfs->root[1] = LFS_BLOCK_NULL;
+    lfs->mlist = NULL;
+    lfs->seed = 0;
+    lfs->gdisk = (lfs_gstate_t){0};
+    lfs->gstate = (lfs_gstate_t){0};
+    lfs->gdelta = (lfs_gstate_t){0};
+#ifdef LFS_MIGRATE
+    lfs->lfs1 = NULL;
+#endif
+
+    return 0;
+
+cleanup:
+    lfs_deinit(lfs);
+    return err;
+}
+
+static int lfs_deinit(lfs_t *lfs) {
+    // free allocated memory
+    if (!lfs->cfg->read_buffer) {
+        lfs_free(lfs->rcache.buffer);
+    }
+
+    if (!lfs->cfg->prog_buffer) {
+        lfs_free(lfs->pcache.buffer);
+    }
+
+    if (!lfs->cfg->lookahead_buffer) {
+        lfs_free(lfs->lookahead.buffer);
+    }
+
+    return 0;
+}
+
+
+
+#ifndef LFS_READONLY
+static int lfs_format_(lfs_t *lfs, const struct lfs_config *cfg) {
+    int err = 0;
+    {
+        err = lfs_init(lfs, cfg);
+        if (err) {
+            return err;
+        }
+
+        LFS_ASSERT(cfg->block_count != 0);
+
+        // create free lookahead
+        memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size);
+        lfs->lookahead.start = 0;
+        lfs->lookahead.size = lfs_min(8*lfs->cfg->lookahead_size,
+                lfs->block_count);
+        lfs->lookahead.next = 0;
+        lfs_alloc_ckpoint(lfs);
+
+        // create root dir
+        lfs_mdir_t root;
+        err = lfs_dir_alloc(lfs, &root);
+        if (err) {
+            goto cleanup;
+        }
+
+        // write one superblock
+        lfs_superblock_t superblock = {
+            .version     = lfs_fs_disk_version(lfs),
+            .block_size  = lfs->cfg->block_size,
+            .block_count = lfs->block_count,
+            .name_max    = lfs->name_max,
+            .file_max    = lfs->file_max,
+            .attr_max    = lfs->attr_max,
+        };
+
+        lfs_superblock_tole32(&superblock);
+        err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
+                {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL},
+                {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"},
+                {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                    &superblock}));
+        if (err) {
+            goto cleanup;
+        }
+
+        // force compaction to prevent accidentally mounting any
+        // older version of littlefs that may live on disk
+        root.erased = false;
+        err = lfs_dir_commit(lfs, &root, NULL, 0);
+        if (err) {
+            goto cleanup;
+        }
+
+        // sanity check that fetch works
+        err = lfs_dir_fetch(lfs, &root, (const lfs_block_t[2]){0, 1});
+        if (err) {
+            goto cleanup;
+        }
+    }
+
+cleanup:
+    lfs_deinit(lfs);
+    return err;
+
+}
+#endif
+
+struct lfs_tortoise_t {
+    lfs_block_t pair[2];
+    lfs_size_t i;
+    lfs_size_t period;
+};
+
+static int lfs_tortoise_detectcycles(
+    const lfs_mdir_t *dir, struct lfs_tortoise_t *tortoise) {
+    // detect cycles with Brent's algorithm
+    if (lfs_pair_issync(dir->tail, tortoise->pair)) {
+        LFS_WARN("Cycle detected in tail list");
+        return LFS_ERR_CORRUPT;
+    }
+    if (tortoise->i == tortoise->period) {
+        tortoise->pair[0] = dir->tail[0];
+        tortoise->pair[1] = dir->tail[1];
+        tortoise->i = 0;
+        tortoise->period *= 2;
+    }
+    tortoise->i += 1;
+
+    return LFS_ERR_OK;
+}
+
+static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) {
+    int err = lfs_init(lfs, cfg);
+    if (err) {
+        return err;
+    }
+
+    // scan directory blocks for superblock and any global updates
+    lfs_mdir_t dir = {.tail = {0, 1}};
+    struct lfs_tortoise_t tortoise = {
+        .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
+        .i = 1,
+        .period = 1,
+    };
+    while (!lfs_pair_isnull(dir.tail)) {
+        err = lfs_tortoise_detectcycles(&dir, &tortoise);
+        if (err < 0) {
+            goto cleanup;
+        }
+
+        // fetch next block in tail list
+        lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail,
+                LFS_MKTAG(0x7ff, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8),
+                NULL,
+                lfs_dir_find_match, &(struct lfs_dir_find_match){
+                    lfs, "littlefs", 8});
+        if (tag < 0) {
+            err = tag;
+            goto cleanup;
+        }
+
+        // has superblock?
+        if (tag && !lfs_tag_isdelete(tag)) {
+            // update root
+            lfs->root[0] = dir.pair[0];
+            lfs->root[1] = dir.pair[1];
+
+            // grab superblock
+            lfs_superblock_t superblock;
+            tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                    &superblock);
+            if (tag < 0) {
+                err = tag;
+                goto cleanup;
+            }
+            lfs_superblock_fromle32(&superblock);
+
+            // check version
+            uint16_t major_version = (0xffff & (superblock.version >> 16));
+            uint16_t minor_version = (0xffff & (superblock.version >>  0));
+            if (major_version != lfs_fs_disk_version_major(lfs)
+                    || minor_version > lfs_fs_disk_version_minor(lfs)) {
+                LFS_ERROR("Invalid version "
+                        "v%"PRIu16".%"PRIu16" != v%"PRIu16".%"PRIu16,
+                        major_version,
+                        minor_version,
+                        lfs_fs_disk_version_major(lfs),
+                        lfs_fs_disk_version_minor(lfs));
+                err = LFS_ERR_INVAL;
+                goto cleanup;
+            }
+
+            // found older minor version? set an in-device only bit in the
+            // gstate so we know we need to rewrite the superblock before
+            // the first write
+            bool needssuperblock = false;
+            if (minor_version < lfs_fs_disk_version_minor(lfs)) {
+                LFS_DEBUG("Found older minor version "
+                        "v%"PRIu16".%"PRIu16" < v%"PRIu16".%"PRIu16,
+                        major_version,
+                        minor_version,
+                        lfs_fs_disk_version_major(lfs),
+                        lfs_fs_disk_version_minor(lfs));
+                needssuperblock = true;
+            }
+            // note this bit is reserved on disk, so fetching more gstate
+            // will not interfere here
+            lfs_fs_prepsuperblock(lfs, needssuperblock);
+
+            // check superblock configuration
+            if (superblock.name_max) {
+                if (superblock.name_max > lfs->name_max) {
+                    LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")",
+                            superblock.name_max, lfs->name_max);
+                    err = LFS_ERR_INVAL;
+                    goto cleanup;
+                }
+
+                lfs->name_max = superblock.name_max;
+            }
+
+            if (superblock.file_max) {
+                if (superblock.file_max > lfs->file_max) {
+                    LFS_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")",
+                            superblock.file_max, lfs->file_max);
+                    err = LFS_ERR_INVAL;
+                    goto cleanup;
+                }
+
+                lfs->file_max = superblock.file_max;
+            }
+
+            if (superblock.attr_max) {
+                if (superblock.attr_max > lfs->attr_max) {
+                    LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")",
+                            superblock.attr_max, lfs->attr_max);
+                    err = LFS_ERR_INVAL;
+                    goto cleanup;
+                }
+
+                lfs->attr_max = superblock.attr_max;
+
+                // we also need to update inline_max in case attr_max changed
+                lfs->inline_max = lfs_min(lfs->inline_max, lfs->attr_max);
+            }
+
+            // this is where we get the block_count from disk if block_count=0
+            if (lfs->cfg->block_count
+                    && superblock.block_count != lfs->cfg->block_count) {
+                LFS_ERROR("Invalid block count (%"PRIu32" != %"PRIu32")",
+                        superblock.block_count, lfs->cfg->block_count);
+                err = LFS_ERR_INVAL;
+                goto cleanup;
+            }
+
+            lfs->block_count = superblock.block_count;
+
+            if (superblock.block_size != lfs->cfg->block_size) {
+                LFS_ERROR("Invalid block size (%"PRIu32" != %"PRIu32")",
+                        superblock.block_size, lfs->cfg->block_size);
+                err = LFS_ERR_INVAL;
+                goto cleanup;
+            }
+        }
+
+        // has gstate?
+        err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate);
+        if (err) {
+            goto cleanup;
+        }
+    }
+
+    // update littlefs with gstate
+    if (!lfs_gstate_iszero(&lfs->gstate)) {
+        LFS_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32,
+                lfs->gstate.tag,
+                lfs->gstate.pair[0],
+                lfs->gstate.pair[1]);
+    }
+    lfs->gstate.tag += !lfs_tag_isvalid(lfs->gstate.tag);
+    lfs->gdisk = lfs->gstate;
+
+    // setup free lookahead, to distribute allocations uniformly across
+    // boots, we start the allocator at a random location
+    lfs->lookahead.start = lfs->seed % lfs->block_count;
+    lfs_alloc_drop(lfs);
+
+    return 0;
+
+cleanup:
+    lfs_unmount_(lfs);
+    return err;
+}
+
+static int lfs_unmount_(lfs_t *lfs) {
+    return lfs_deinit(lfs);
+}
+
+
+/// Filesystem filesystem operations ///
+static int lfs_fs_stat_(lfs_t *lfs, struct lfs_fsinfo *fsinfo) {
+    // if the superblock is up-to-date, we must be on the most recent
+    // minor version of littlefs
+    if (!lfs_gstate_needssuperblock(&lfs->gstate)) {
+        fsinfo->disk_version = lfs_fs_disk_version(lfs);
+
+    // otherwise we need to read the minor version on disk
+    } else {
+        // fetch the superblock
+        lfs_mdir_t dir;
+        int err = lfs_dir_fetch(lfs, &dir, lfs->root);
+        if (err) {
+            return err;
+        }
+
+        lfs_superblock_t superblock;
+        lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0),
+                LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                &superblock);
+        if (tag < 0) {
+            return tag;
+        }
+        lfs_superblock_fromle32(&superblock);
+
+        // read the on-disk version
+        fsinfo->disk_version = superblock.version;
+    }
+
+    // filesystem geometry
+    fsinfo->block_size = lfs->cfg->block_size;
+    fsinfo->block_count = lfs->block_count;
+
+    // other on-disk configuration, we cache all of these for internal use
+    fsinfo->name_max = lfs->name_max;
+    fsinfo->file_max = lfs->file_max;
+    fsinfo->attr_max = lfs->attr_max;
+
+    return 0;
+}
+
+int lfs_fs_traverse_(lfs_t *lfs,
+        int (*cb)(void *data, lfs_block_t block), void *data,
+        bool includeorphans) {
+    // iterate over metadata pairs
+    lfs_mdir_t dir = {.tail = {0, 1}};
+
+#ifdef LFS_MIGRATE
+    // also consider v1 blocks during migration
+    if (lfs->lfs1) {
+        int err = lfs1_traverse(lfs, cb, data);
+        if (err) {
+            return err;
+        }
+
+        dir.tail[0] = lfs->root[0];
+        dir.tail[1] = lfs->root[1];
+    }
+#endif
+
+    struct lfs_tortoise_t tortoise = {
+        .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
+        .i = 1,
+        .period = 1,
+    };
+    int err = LFS_ERR_OK;
+    while (!lfs_pair_isnull(dir.tail)) {
+        err = lfs_tortoise_detectcycles(&dir, &tortoise);
+        if (err < 0) {
+            return LFS_ERR_CORRUPT;
+        }
+
+        for (int i = 0; i < 2; i++) {
+            int err = cb(data, dir.tail[i]);
+            if (err) {
+                return err;
+            }
+        }
+
+        // iterate through ids in directory
+        int err = lfs_dir_fetch(lfs, &dir, dir.tail);
+        if (err) {
+            return err;
+        }
+
+        for (uint16_t id = 0; id < dir.count; id++) {
+            struct lfs_ctz ctz;
+            lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x700, 0x3ff, 0),
+                    LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz);
+            if (tag < 0) {
+                if (tag == LFS_ERR_NOENT) {
+                    continue;
+                }
+                return tag;
+            }
+            lfs_ctz_fromle32(&ctz);
+
+            if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) {
+                err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache,
+                        ctz.head, ctz.size, cb, data);
+                if (err) {
+                    return err;
+                }
+            } else if (includeorphans &&
+                    lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) {
+                for (int i = 0; i < 2; i++) {
+                    err = cb(data, (&ctz.head)[i]);
+                    if (err) {
+                        return err;
+                    }
+                }
+            }
+        }
+    }
+
+#ifndef LFS_READONLY
+    // iterate over any open files
+    for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) {
+        if (f->type != LFS_TYPE_REG) {
+            continue;
+        }
+
+        if ((f->flags & LFS_F_DIRTY) && !(f->flags & LFS_F_INLINE)) {
+            int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache,
+                    f->ctz.head, f->ctz.size, cb, data);
+            if (err) {
+                return err;
+            }
+        }
+
+        if ((f->flags & LFS_F_WRITING) && !(f->flags & LFS_F_INLINE)) {
+            int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache,
+                    f->block, f->pos, cb, data);
+            if (err) {
+                return err;
+            }
+        }
+    }
+#endif
+
+    return 0;
+}
+
+#ifndef LFS_READONLY
+static int lfs_fs_pred(lfs_t *lfs,
+        const lfs_block_t pair[2], lfs_mdir_t *pdir) {
+    // iterate over all directory directory entries
+    pdir->tail[0] = 0;
+    pdir->tail[1] = 1;
+    struct lfs_tortoise_t tortoise = {
+        .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
+        .i = 1,
+        .period = 1,
+    };
+    int err = LFS_ERR_OK;
+    while (!lfs_pair_isnull(pdir->tail)) {
+        err = lfs_tortoise_detectcycles(pdir, &tortoise);
+        if (err < 0) {
+            return LFS_ERR_CORRUPT;
+        }
+
+        if (lfs_pair_cmp(pdir->tail, pair) == 0) {
+            return 0;
+        }
+
+        int err = lfs_dir_fetch(lfs, pdir, pdir->tail);
+        if (err) {
+            return err;
+        }
+    }
+
+    return LFS_ERR_NOENT;
+}
+#endif
+
+#ifndef LFS_READONLY
+struct lfs_fs_parent_match {
+    lfs_t *lfs;
+    const lfs_block_t pair[2];
+};
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_fs_parent_match(void *data,
+        lfs_tag_t tag, const void *buffer) {
+    struct lfs_fs_parent_match *find = data;
+    lfs_t *lfs = find->lfs;
+    const struct lfs_diskoff *disk = buffer;
+    (void)tag;
+
+    lfs_block_t child[2];
+    int err = lfs_bd_read(lfs,
+            &lfs->pcache, &lfs->rcache, lfs->cfg->block_size,
+            disk->block, disk->off, &child, sizeof(child));
+    if (err) {
+        return err;
+    }
+
+    lfs_pair_fromle32(child);
+    return (lfs_pair_cmp(child, find->pair) == 0) ? LFS_CMP_EQ : LFS_CMP_LT;
+}
+#endif
+
+#ifndef LFS_READONLY
+static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
+        lfs_mdir_t *parent) {
+    // use fetchmatch with callback to find pairs
+    parent->tail[0] = 0;
+    parent->tail[1] = 1;
+    struct lfs_tortoise_t tortoise = {
+        .pair = {LFS_BLOCK_NULL, LFS_BLOCK_NULL},
+        .i = 1,
+        .period = 1,
+    };
+    int err = LFS_ERR_OK;
+    while (!lfs_pair_isnull(parent->tail)) {
+        err = lfs_tortoise_detectcycles(parent, &tortoise);
+        if (err < 0) {
+            return err;
+        }
+
+        lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail,
+                LFS_MKTAG(0x7ff, 0, 0x3ff),
+                LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8),
+                NULL,
+                lfs_fs_parent_match, &(struct lfs_fs_parent_match){
+                    lfs, {pair[0], pair[1]}});
+        if (tag && tag != LFS_ERR_NOENT) {
+            return tag;
+        }
+    }
+
+    return LFS_ERR_NOENT;
+}
+#endif
+
+static void lfs_fs_prepsuperblock(lfs_t *lfs, bool needssuperblock) {
+    lfs->gstate.tag = (lfs->gstate.tag & ~LFS_MKTAG(0, 0, 0x200))
+            | (uint32_t)needssuperblock << 9;
+}
+
+#ifndef LFS_READONLY
+static int lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) {
+    LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0x000 || orphans >= 0);
+    LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) < 0x1ff || orphans <= 0);
+    lfs->gstate.tag += orphans;
+    lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) |
+            ((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31));
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static void lfs_fs_prepmove(lfs_t *lfs,
+        uint16_t id, const lfs_block_t pair[2]) {
+    lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x7ff, 0x3ff, 0)) |
+            ((id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0));
+    lfs->gstate.pair[0] = (id != 0x3ff) ? pair[0] : 0;
+    lfs->gstate.pair[1] = (id != 0x3ff) ? pair[1] : 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_fs_desuperblock(lfs_t *lfs) {
+    if (!lfs_gstate_needssuperblock(&lfs->gstate)) {
+        return 0;
+    }
+
+    LFS_DEBUG("Rewriting superblock {0x%"PRIx32", 0x%"PRIx32"}",
+            lfs->root[0],
+            lfs->root[1]);
+
+    lfs_mdir_t root;
+    int err = lfs_dir_fetch(lfs, &root, lfs->root);
+    if (err) {
+        return err;
+    }
+
+    // write a new superblock
+    lfs_superblock_t superblock = {
+        .version     = lfs_fs_disk_version(lfs),
+        .block_size  = lfs->cfg->block_size,
+        .block_count = lfs->block_count,
+        .name_max    = lfs->name_max,
+        .file_max    = lfs->file_max,
+        .attr_max    = lfs->attr_max,
+    };
+
+    lfs_superblock_tole32(&superblock);
+    err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                &superblock}));
+    if (err) {
+        return err;
+    }
+
+    lfs_fs_prepsuperblock(lfs, false);
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_fs_demove(lfs_t *lfs) {
+    if (!lfs_gstate_hasmove(&lfs->gdisk)) {
+        return 0;
+    }
+
+    // Fix bad moves
+    LFS_DEBUG("Fixing move {0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16,
+            lfs->gdisk.pair[0],
+            lfs->gdisk.pair[1],
+            lfs_tag_id(lfs->gdisk.tag));
+
+    // no other gstate is supported at this time, so if we found something else
+    // something most likely went wrong in gstate calculation
+    LFS_ASSERT(lfs_tag_type3(lfs->gdisk.tag) == LFS_TYPE_DELETE);
+
+    // fetch and delete the moved entry
+    lfs_mdir_t movedir;
+    int err = lfs_dir_fetch(lfs, &movedir, lfs->gdisk.pair);
+    if (err) {
+        return err;
+    }
+
+    // prep gstate and delete move id
+    uint16_t moveid = lfs_tag_id(lfs->gdisk.tag);
+    lfs_fs_prepmove(lfs, 0x3ff, NULL);
+    err = lfs_dir_commit(lfs, &movedir, LFS_MKATTRS(
+            {LFS_MKTAG(LFS_TYPE_DELETE, moveid, 0), NULL}));
+    if (err) {
+        return err;
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_fs_deorphan(lfs_t *lfs, bool powerloss) {
+    if (!lfs_gstate_hasorphans(&lfs->gstate)) {
+        return 0;
+    }
+
+    // Check for orphans in two separate passes:
+    // - 1 for half-orphans (relocations)
+    // - 2 for full-orphans (removes/renames)
+    //
+    // Two separate passes are needed as half-orphans can contain outdated
+    // references to full-orphans, effectively hiding them from the deorphan
+    // search.
+    //
+    int pass = 0;
+    while (pass < 2) {
+        // Fix any orphans
+        lfs_mdir_t pdir = {.split = true, .tail = {0, 1}};
+        lfs_mdir_t dir;
+        bool moreorphans = false;
+
+        // iterate over all directory directory entries
+        while (!lfs_pair_isnull(pdir.tail)) {
+            int err = lfs_dir_fetch(lfs, &dir, pdir.tail);
+            if (err) {
+                return err;
+            }
+
+            // check head blocks for orphans
+            if (!pdir.split) {
+                // check if we have a parent
+                lfs_mdir_t parent;
+                lfs_stag_t tag = lfs_fs_parent(lfs, pdir.tail, &parent);
+                if (tag < 0 && tag != LFS_ERR_NOENT) {
+                    return tag;
+                }
+
+                if (pass == 0 && tag != LFS_ERR_NOENT) {
+                    lfs_block_t pair[2];
+                    lfs_stag_t state = lfs_dir_get(lfs, &parent,
+                            LFS_MKTAG(0x7ff, 0x3ff, 0), tag, pair);
+                    if (state < 0) {
+                        return state;
+                    }
+                    lfs_pair_fromle32(pair);
+
+                    if (!lfs_pair_issync(pair, pdir.tail)) {
+                        // we have desynced
+                        LFS_DEBUG("Fixing half-orphan "
+                                "{0x%"PRIx32", 0x%"PRIx32"} "
+                                "-> {0x%"PRIx32", 0x%"PRIx32"}",
+                                pdir.tail[0], pdir.tail[1], pair[0], pair[1]);
+
+                        // fix pending move in this pair? this looks like an
+                        // optimization but is in fact _required_ since
+                        // relocating may outdate the move.
+                        uint16_t moveid = 0x3ff;
+                        if (lfs_gstate_hasmovehere(&lfs->gstate, pdir.pair)) {
+                            moveid = lfs_tag_id(lfs->gstate.tag);
+                            LFS_DEBUG("Fixing move while fixing orphans "
+                                    "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n",
+                                    pdir.pair[0], pdir.pair[1], moveid);
+                            lfs_fs_prepmove(lfs, 0x3ff, NULL);
+                        }
+
+                        lfs_pair_tole32(pair);
+                        state = lfs_dir_orphaningcommit(lfs, &pdir, LFS_MKATTRS(
+                                {LFS_MKTAG_IF(moveid != 0x3ff,
+                                    LFS_TYPE_DELETE, moveid, 0), NULL},
+                                {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8),
+                                    pair}));
+                        lfs_pair_fromle32(pair);
+                        if (state < 0) {
+                            return state;
+                        }
+
+                        // did our commit create more orphans?
+                        if (state == LFS_OK_ORPHANED) {
+                            moreorphans = true;
+                        }
+
+                        // refetch tail
+                        continue;
+                    }
+                }
+
+                // note we only check for full orphans if we may have had a
+                // power-loss, otherwise orphans are created intentionally
+                // during operations such as lfs_mkdir
+                if (pass == 1 && tag == LFS_ERR_NOENT && powerloss) {
+                    // we are an orphan
+                    LFS_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}",
+                            pdir.tail[0], pdir.tail[1]);
+
+                    // steal state
+                    err = lfs_dir_getgstate(lfs, &dir, &lfs->gdelta);
+                    if (err) {
+                        return err;
+                    }
+
+                    // steal tail
+                    lfs_pair_tole32(dir.tail);
+                    int state = lfs_dir_orphaningcommit(lfs, &pdir, LFS_MKATTRS(
+                            {LFS_MKTAG(LFS_TYPE_TAIL + dir.split, 0x3ff, 8),
+                                dir.tail}));
+                    lfs_pair_fromle32(dir.tail);
+                    if (state < 0) {
+                        return state;
+                    }
+
+                    // did our commit create more orphans?
+                    if (state == LFS_OK_ORPHANED) {
+                        moreorphans = true;
+                    }
+
+                    // refetch tail
+                    continue;
+                }
+            }
+
+            pdir = dir;
+        }
+
+        pass = moreorphans ? 0 : pass+1;
+    }
+
+    // mark orphans as fixed
+    return lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate));
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_fs_forceconsistency(lfs_t *lfs) {
+    int err = lfs_fs_desuperblock(lfs);
+    if (err) {
+        return err;
+    }
+
+    err = lfs_fs_demove(lfs);
+    if (err) {
+        return err;
+    }
+
+    err = lfs_fs_deorphan(lfs, true);
+    if (err) {
+        return err;
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+static int lfs_fs_mkconsistent_(lfs_t *lfs) {
+    // lfs_fs_forceconsistency does most of the work here
+    int err = lfs_fs_forceconsistency(lfs);
+    if (err) {
+        return err;
+    }
+
+    // do we have any pending gstate?
+    lfs_gstate_t delta = {0};
+    lfs_gstate_xor(&delta, &lfs->gdisk);
+    lfs_gstate_xor(&delta, &lfs->gstate);
+    if (!lfs_gstate_iszero(&delta)) {
+        // lfs_dir_commit will implicitly write out any pending gstate
+        lfs_mdir_t root;
+        err = lfs_dir_fetch(lfs, &root, lfs->root);
+        if (err) {
+            return err;
+        }
+
+        err = lfs_dir_commit(lfs, &root, NULL, 0);
+        if (err) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+#endif
+
+static int lfs_fs_size_count(void *p, lfs_block_t block) {
+    (void)block;
+    lfs_size_t *size = p;
+    *size += 1;
+    return 0;
+}
+
+static lfs_ssize_t lfs_fs_size_(lfs_t *lfs) {
+    lfs_size_t size = 0;
+    int err = lfs_fs_traverse_(lfs, lfs_fs_size_count, &size, false);
+    if (err) {
+        return err;
+    }
+
+    return size;
+}
+
+// explicit garbage collection
+#ifndef LFS_READONLY
+static int lfs_fs_gc_(lfs_t *lfs) {
+    // force consistency, even if we're not necessarily going to write,
+    // because this function is supposed to take care of janitorial work
+    // isn't it?
+    int err = lfs_fs_forceconsistency(lfs);
+    if (err) {
+        return err;
+    }
+
+    // try to compact metadata pairs, note we can't really accomplish
+    // anything if compact_thresh doesn't at least leave a prog_size
+    // available
+    if (lfs->cfg->compact_thresh
+            < lfs->cfg->block_size - lfs->cfg->prog_size) {
+        // iterate over all mdirs
+        lfs_mdir_t mdir = {.tail = {0, 1}};
+        while (!lfs_pair_isnull(mdir.tail)) {
+            err = lfs_dir_fetch(lfs, &mdir, mdir.tail);
+            if (err) {
+                return err;
+            }
+
+            // not erased? exceeds our compaction threshold?
+            if (!mdir.erased || ((lfs->cfg->compact_thresh == 0)
+                    ? mdir.off > lfs->cfg->block_size - lfs->cfg->block_size/8
+                    : mdir.off > lfs->cfg->compact_thresh)) {
+                // the easiest way to trigger a compaction is to mark
+                // the mdir as unerased and add an empty commit
+                mdir.erased = false;
+                err = lfs_dir_commit(lfs, &mdir, NULL, 0);
+                if (err) {
+                    return err;
+                }
+            }
+        }
+    }
+
+    // try to populate the lookahead buffer, unless it's already full
+    if (lfs->lookahead.size < lfs_min(
+            8 * lfs->cfg->lookahead_size,
+            lfs->block_count)) {
+        err = lfs_alloc_scan(lfs);
+        if (err) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+#endif
+
+#ifndef LFS_READONLY
+#ifdef LFS_SHRINKNONRELOCATING
+static int lfs_shrink_checkblock(void *data, lfs_block_t block) {
+    lfs_size_t threshold = *((lfs_size_t*)data);
+    if (block >= threshold) {
+        return LFS_ERR_NOTEMPTY;
+    }
+    return 0;
+}
+#endif
+
+static int lfs_fs_grow_(lfs_t *lfs, lfs_size_t block_count) {
+    int err;
+
+    if (block_count == lfs->block_count) {
+        return 0;
+    }
+
+    
+#ifndef LFS_SHRINKNONRELOCATING
+    // shrinking is not supported
+    LFS_ASSERT(block_count >= lfs->block_count);
+#endif
+#ifdef LFS_SHRINKNONRELOCATING
+    if (block_count < lfs->block_count) {
+        err = lfs_fs_traverse_(lfs, lfs_shrink_checkblock, &block_count, true);
+        if (err) {
+            return err;
+        }
+    }
+#endif
+
+    lfs->block_count = block_count;
+
+    // fetch the root
+    lfs_mdir_t root;
+    err = lfs_dir_fetch(lfs, &root, lfs->root);
+    if (err) {
+        return err;
+    }
+
+    // update the superblock
+    lfs_superblock_t superblock;
+    lfs_stag_t tag = lfs_dir_get(lfs, &root, LFS_MKTAG(0x7ff, 0x3ff, 0),
+            LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+            &superblock);
+    if (tag < 0) {
+        return tag;
+    }
+    lfs_superblock_fromle32(&superblock);
+
+    superblock.block_count = lfs->block_count;
+
+    lfs_superblock_tole32(&superblock);
+    err = lfs_dir_commit(lfs, &root, LFS_MKATTRS(
+            {tag, &superblock}));
+    if (err) {
+        return err;
+    }
+    return 0;
+}
+#endif
+
+#ifdef LFS_MIGRATE
+////// Migration from littelfs v1 below this //////
+
+/// Version info ///
+
+// Software library version
+// Major (top-nibble), incremented on backwards incompatible changes
+// Minor (bottom-nibble), incremented on feature additions
+#define LFS1_VERSION 0x00010007
+#define LFS1_VERSION_MAJOR (0xffff & (LFS1_VERSION >> 16))
+#define LFS1_VERSION_MINOR (0xffff & (LFS1_VERSION >>  0))
+
+// Version of On-disk data structures
+// Major (top-nibble), incremented on backwards incompatible changes
+// Minor (bottom-nibble), incremented on feature additions
+#define LFS1_DISK_VERSION 0x00010001
+#define LFS1_DISK_VERSION_MAJOR (0xffff & (LFS1_DISK_VERSION >> 16))
+#define LFS1_DISK_VERSION_MINOR (0xffff & (LFS1_DISK_VERSION >>  0))
+
+
+/// v1 Definitions ///
+
+// File types
+enum lfs1_type {
+    LFS1_TYPE_REG        = 0x11,
+    LFS1_TYPE_DIR        = 0x22,
+    LFS1_TYPE_SUPERBLOCK = 0x2e,
+};
+
+typedef struct lfs1 {
+    lfs_block_t root[2];
+} lfs1_t;
+
+typedef struct lfs1_entry {
+    lfs_off_t off;
+
+    struct lfs1_disk_entry {
+        uint8_t type;
+        uint8_t elen;
+        uint8_t alen;
+        uint8_t nlen;
+        union {
+            struct {
+                lfs_block_t head;
+                lfs_size_t size;
+            } file;
+            lfs_block_t dir[2];
+        } u;
+    } d;
+} lfs1_entry_t;
+
+typedef struct lfs1_dir {
+    struct lfs1_dir *next;
+    lfs_block_t pair[2];
+    lfs_off_t off;
+
+    lfs_block_t head[2];
+    lfs_off_t pos;
+
+    struct lfs1_disk_dir {
+        uint32_t rev;
+        lfs_size_t size;
+        lfs_block_t tail[2];
+    } d;
+} lfs1_dir_t;
+
+typedef struct lfs1_superblock {
+    lfs_off_t off;
+
+    struct lfs1_disk_superblock {
+        uint8_t type;
+        uint8_t elen;
+        uint8_t alen;
+        uint8_t nlen;
+        lfs_block_t root[2];
+        uint32_t block_size;
+        uint32_t block_count;
+        uint32_t version;
+        char magic[8];
+    } d;
+} lfs1_superblock_t;
+
+
+/// Low-level wrappers v1->v2 ///
+static void lfs1_crc(uint32_t *crc, const void *buffer, size_t size) {
+    *crc = lfs_crc(*crc, buffer, size);
+}
+
+static int lfs1_bd_read(lfs_t *lfs, lfs_block_t block,
+        lfs_off_t off, void *buffer, lfs_size_t size) {
+    // if we ever do more than writes to alternating pairs,
+    // this may need to consider pcache
+    return lfs_bd_read(lfs, &lfs->pcache, &lfs->rcache, size,
+            block, off, buffer, size);
+}
+
+static int lfs1_bd_crc(lfs_t *lfs, lfs_block_t block,
+        lfs_off_t off, lfs_size_t size, uint32_t *crc) {
+    for (lfs_off_t i = 0; i < size; i++) {
+        uint8_t c;
+        int err = lfs1_bd_read(lfs, block, off+i, &c, 1);
+        if (err) {
+            return err;
+        }
+
+        lfs1_crc(crc, &c, 1);
+    }
+
+    return 0;
+}
+
+
+/// Endian swapping functions ///
+static void lfs1_dir_fromle32(struct lfs1_disk_dir *d) {
+    d->rev     = lfs_fromle32(d->rev);
+    d->size    = lfs_fromle32(d->size);
+    d->tail[0] = lfs_fromle32(d->tail[0]);
+    d->tail[1] = lfs_fromle32(d->tail[1]);
+}
+
+static void lfs1_dir_tole32(struct lfs1_disk_dir *d) {
+    d->rev     = lfs_tole32(d->rev);
+    d->size    = lfs_tole32(d->size);
+    d->tail[0] = lfs_tole32(d->tail[0]);
+    d->tail[1] = lfs_tole32(d->tail[1]);
+}
+
+static void lfs1_entry_fromle32(struct lfs1_disk_entry *d) {
+    d->u.dir[0] = lfs_fromle32(d->u.dir[0]);
+    d->u.dir[1] = lfs_fromle32(d->u.dir[1]);
+}
+
+static void lfs1_entry_tole32(struct lfs1_disk_entry *d) {
+    d->u.dir[0] = lfs_tole32(d->u.dir[0]);
+    d->u.dir[1] = lfs_tole32(d->u.dir[1]);
+}
+
+static void lfs1_superblock_fromle32(struct lfs1_disk_superblock *d) {
+    d->root[0]     = lfs_fromle32(d->root[0]);
+    d->root[1]     = lfs_fromle32(d->root[1]);
+    d->block_size  = lfs_fromle32(d->block_size);
+    d->block_count = lfs_fromle32(d->block_count);
+    d->version     = lfs_fromle32(d->version);
+}
+
+
+///// Metadata pair and directory operations ///
+static inline lfs_size_t lfs1_entry_size(const lfs1_entry_t *entry) {
+    return 4 + entry->d.elen + entry->d.alen + entry->d.nlen;
+}
+
+static int lfs1_dir_fetch(lfs_t *lfs,
+        lfs1_dir_t *dir, const lfs_block_t pair[2]) {
+    // copy out pair, otherwise may be aliasing dir
+    const lfs_block_t tpair[2] = {pair[0], pair[1]};
+    bool valid = false;
+
+    // check both blocks for the most recent revision
+    for (int i = 0; i < 2; i++) {
+        struct lfs1_disk_dir test;
+        int err = lfs1_bd_read(lfs, tpair[i], 0, &test, sizeof(test));
+        lfs1_dir_fromle32(&test);
+        if (err) {
+            if (err == LFS_ERR_CORRUPT) {
+                continue;
+            }
+            return err;
+        }
+
+        if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) {
+            continue;
+        }
+
+        if ((0x7fffffff & test.size) < sizeof(test)+4 ||
+            (0x7fffffff & test.size) > lfs->cfg->block_size) {
+            continue;
+        }
+
+        uint32_t crc = 0xffffffff;
+        lfs1_dir_tole32(&test);
+        lfs1_crc(&crc, &test, sizeof(test));
+        lfs1_dir_fromle32(&test);
+        err = lfs1_bd_crc(lfs, tpair[i], sizeof(test),
+                (0x7fffffff & test.size) - sizeof(test), &crc);
+        if (err) {
+            if (err == LFS_ERR_CORRUPT) {
+                continue;
+            }
+            return err;
+        }
+
+        if (crc != 0) {
+            continue;
+        }
+
+        valid = true;
+
+        // setup dir in case it's valid
+        dir->pair[0] = tpair[(i+0) % 2];
+        dir->pair[1] = tpair[(i+1) % 2];
+        dir->off = sizeof(dir->d);
+        dir->d = test;
+    }
+
+    if (!valid) {
+        LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}",
+                tpair[0], tpair[1]);
+        return LFS_ERR_CORRUPT;
+    }
+
+    return 0;
+}
+
+static int lfs1_dir_next(lfs_t *lfs, lfs1_dir_t *dir, lfs1_entry_t *entry) {
+    while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) {
+        if (!(0x80000000 & dir->d.size)) {
+            entry->off = dir->off;
+            return LFS_ERR_NOENT;
+        }
+
+        int err = lfs1_dir_fetch(lfs, dir, dir->d.tail);
+        if (err) {
+            return err;
+        }
+
+        dir->off = sizeof(dir->d);
+        dir->pos += sizeof(dir->d) + 4;
+    }
+
+    int err = lfs1_bd_read(lfs, dir->pair[0], dir->off,
+            &entry->d, sizeof(entry->d));
+    lfs1_entry_fromle32(&entry->d);
+    if (err) {
+        return err;
+    }
+
+    entry->off = dir->off;
+    dir->off += lfs1_entry_size(entry);
+    dir->pos += lfs1_entry_size(entry);
+    return 0;
+}
+
+/// littlefs v1 specific operations ///
+int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
+    if (lfs_pair_isnull(lfs->lfs1->root)) {
+        return 0;
+    }
+
+    // iterate over metadata pairs
+    lfs1_dir_t dir;
+    lfs1_entry_t entry;
+    lfs_block_t cwd[2] = {0, 1};
+
+    while (true) {
+        for (int i = 0; i < 2; i++) {
+            int err = cb(data, cwd[i]);
+            if (err) {
+                return err;
+            }
+        }
+
+        int err = lfs1_dir_fetch(lfs, &dir, cwd);
+        if (err) {
+            return err;
+        }
+
+        // iterate over contents
+        while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) {
+            err = lfs1_bd_read(lfs, dir.pair[0], dir.off,
+                    &entry.d, sizeof(entry.d));
+            lfs1_entry_fromle32(&entry.d);
+            if (err) {
+                return err;
+            }
+
+            dir.off += lfs1_entry_size(&entry);
+            if ((0x70 & entry.d.type) == (0x70 & LFS1_TYPE_REG)) {
+                err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache,
+                        entry.d.u.file.head, entry.d.u.file.size, cb, data);
+                if (err) {
+                    return err;
+                }
+            }
+        }
+
+        // we also need to check if we contain a threaded v2 directory
+        lfs_mdir_t dir2 = {.split=true, .tail={cwd[0], cwd[1]}};
+        while (dir2.split) {
+            err = lfs_dir_fetch(lfs, &dir2, dir2.tail);
+            if (err) {
+                break;
+            }
+
+            for (int i = 0; i < 2; i++) {
+                err = cb(data, dir2.pair[i]);
+                if (err) {
+                    return err;
+                }
+            }
+        }
+
+        cwd[0] = dir.d.tail[0];
+        cwd[1] = dir.d.tail[1];
+
+        if (lfs_pair_isnull(cwd)) {
+            break;
+        }
+    }
+
+    return 0;
+}
+
+static int lfs1_moved(lfs_t *lfs, const void *e) {
+    if (lfs_pair_isnull(lfs->lfs1->root)) {
+        return 0;
+    }
+
+    // skip superblock
+    lfs1_dir_t cwd;
+    int err = lfs1_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1});
+    if (err) {
+        return err;
+    }
+
+    // iterate over all directory directory entries
+    lfs1_entry_t entry;
+    while (!lfs_pair_isnull(cwd.d.tail)) {
+        err = lfs1_dir_fetch(lfs, &cwd, cwd.d.tail);
+        if (err) {
+            return err;
+        }
+
+        while (true) {
+            err = lfs1_dir_next(lfs, &cwd, &entry);
+            if (err && err != LFS_ERR_NOENT) {
+                return err;
+            }
+
+            if (err == LFS_ERR_NOENT) {
+                break;
+            }
+
+            if (!(0x80 & entry.d.type) &&
+                 memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+/// Filesystem operations ///
+static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1,
+        const struct lfs_config *cfg) {
+    int err = 0;
+    {
+        err = lfs_init(lfs, cfg);
+        if (err) {
+            return err;
+        }
+
+        lfs->lfs1 = lfs1;
+        lfs->lfs1->root[0] = LFS_BLOCK_NULL;
+        lfs->lfs1->root[1] = LFS_BLOCK_NULL;
+
+        // setup free lookahead
+        lfs->lookahead.start = 0;
+        lfs->lookahead.size = 0;
+        lfs->lookahead.next = 0;
+        lfs_alloc_ckpoint(lfs);
+
+        // load superblock
+        lfs1_dir_t dir;
+        lfs1_superblock_t superblock;
+        err = lfs1_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1});
+        if (err && err != LFS_ERR_CORRUPT) {
+            goto cleanup;
+        }
+
+        if (!err) {
+            err = lfs1_bd_read(lfs, dir.pair[0], sizeof(dir.d),
+                    &superblock.d, sizeof(superblock.d));
+            lfs1_superblock_fromle32(&superblock.d);
+            if (err) {
+                goto cleanup;
+            }
+
+            lfs->lfs1->root[0] = superblock.d.root[0];
+            lfs->lfs1->root[1] = superblock.d.root[1];
+        }
+
+        if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) {
+            LFS_ERROR("Invalid superblock at {0x%"PRIx32", 0x%"PRIx32"}",
+                    0, 1);
+            err = LFS_ERR_CORRUPT;
+            goto cleanup;
+        }
+
+        uint16_t major_version = (0xffff & (superblock.d.version >> 16));
+        uint16_t minor_version = (0xffff & (superblock.d.version >>  0));
+        if ((major_version != LFS1_DISK_VERSION_MAJOR ||
+             minor_version > LFS1_DISK_VERSION_MINOR)) {
+            LFS_ERROR("Invalid version v%d.%d", major_version, minor_version);
+            err = LFS_ERR_INVAL;
+            goto cleanup;
+        }
+
+        return 0;
+    }
+
+cleanup:
+    lfs_deinit(lfs);
+    return err;
+}
+
+static int lfs1_unmount(lfs_t *lfs) {
+    return lfs_deinit(lfs);
+}
+
+/// v1 migration ///
+static int lfs_migrate_(lfs_t *lfs, const struct lfs_config *cfg) {
+    struct lfs1 lfs1;
+
+    // Indeterminate filesystem size not allowed for migration.
+    LFS_ASSERT(cfg->block_count != 0);
+
+    int err = lfs1_mount(lfs, &lfs1, cfg);
+    if (err) {
+        return err;
+    }
+
+    {
+        // iterate through each directory, copying over entries
+        // into new directory
+        lfs1_dir_t dir1;
+        lfs_mdir_t dir2;
+        dir1.d.tail[0] = lfs->lfs1->root[0];
+        dir1.d.tail[1] = lfs->lfs1->root[1];
+        while (!lfs_pair_isnull(dir1.d.tail)) {
+            // iterate old dir
+            err = lfs1_dir_fetch(lfs, &dir1, dir1.d.tail);
+            if (err) {
+                goto cleanup;
+            }
+
+            // create new dir and bind as temporary pretend root
+            err = lfs_dir_alloc(lfs, &dir2);
+            if (err) {
+                goto cleanup;
+            }
+
+            dir2.rev = dir1.d.rev;
+            dir1.head[0] = dir1.pair[0];
+            dir1.head[1] = dir1.pair[1];
+            lfs->root[0] = dir2.pair[0];
+            lfs->root[1] = dir2.pair[1];
+
+            err = lfs_dir_commit(lfs, &dir2, NULL, 0);
+            if (err) {
+                goto cleanup;
+            }
+
+            while (true) {
+                lfs1_entry_t entry1;
+                err = lfs1_dir_next(lfs, &dir1, &entry1);
+                if (err && err != LFS_ERR_NOENT) {
+                    goto cleanup;
+                }
+
+                if (err == LFS_ERR_NOENT) {
+                    break;
+                }
+
+                // check that entry has not been moved
+                if (entry1.d.type & 0x80) {
+                    int moved = lfs1_moved(lfs, &entry1.d.u);
+                    if (moved < 0) {
+                        err = moved;
+                        goto cleanup;
+                    }
+
+                    if (moved) {
+                        continue;
+                    }
+
+                    entry1.d.type &= ~0x80;
+                }
+
+                // also fetch name
+                char name[LFS_NAME_MAX+1];
+                memset(name, 0, sizeof(name));
+                err = lfs1_bd_read(lfs, dir1.pair[0],
+                        entry1.off + 4+entry1.d.elen+entry1.d.alen,
+                        name, entry1.d.nlen);
+                if (err) {
+                    goto cleanup;
+                }
+
+                bool isdir = (entry1.d.type == LFS1_TYPE_DIR);
+
+                // create entry in new dir
+                err = lfs_dir_fetch(lfs, &dir2, lfs->root);
+                if (err) {
+                    goto cleanup;
+                }
+
+                uint16_t id;
+                err = lfs_dir_find(lfs, &dir2, &(const char*){name}, &id);
+                if (!(err == LFS_ERR_NOENT && id != 0x3ff)) {
+                    err = (err < 0) ? err : LFS_ERR_EXIST;
+                    goto cleanup;
+                }
+
+                lfs1_entry_tole32(&entry1.d);
+                err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS(
+                        {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL},
+                        {LFS_MKTAG_IF_ELSE(isdir,
+                            LFS_TYPE_DIR, id, entry1.d.nlen,
+                            LFS_TYPE_REG, id, entry1.d.nlen),
+                                name},
+                        {LFS_MKTAG_IF_ELSE(isdir,
+                            LFS_TYPE_DIRSTRUCT, id, sizeof(entry1.d.u),
+                            LFS_TYPE_CTZSTRUCT, id, sizeof(entry1.d.u)),
+                                &entry1.d.u}));
+                lfs1_entry_fromle32(&entry1.d);
+                if (err) {
+                    goto cleanup;
+                }
+            }
+
+            if (!lfs_pair_isnull(dir1.d.tail)) {
+                // find last block and update tail to thread into fs
+                err = lfs_dir_fetch(lfs, &dir2, lfs->root);
+                if (err) {
+                    goto cleanup;
+                }
+
+                while (dir2.split) {
+                    err = lfs_dir_fetch(lfs, &dir2, dir2.tail);
+                    if (err) {
+                        goto cleanup;
+                    }
+                }
+
+                lfs_pair_tole32(dir2.pair);
+                err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS(
+                        {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir1.d.tail}));
+                lfs_pair_fromle32(dir2.pair);
+                if (err) {
+                    goto cleanup;
+                }
+            }
+
+            // Copy over first block to thread into fs. Unfortunately
+            // if this fails there is not much we can do.
+            LFS_DEBUG("Migrating {0x%"PRIx32", 0x%"PRIx32"} "
+                        "-> {0x%"PRIx32", 0x%"PRIx32"}",
+                    lfs->root[0], lfs->root[1], dir1.head[0], dir1.head[1]);
+
+            err = lfs_bd_erase(lfs, dir1.head[1]);
+            if (err) {
+                goto cleanup;
+            }
+
+            err = lfs_dir_fetch(lfs, &dir2, lfs->root);
+            if (err) {
+                goto cleanup;
+            }
+
+            for (lfs_off_t i = 0; i < dir2.off; i++) {
+                uint8_t dat;
+                err = lfs_bd_read(lfs,
+                        NULL, &lfs->rcache, dir2.off,
+                        dir2.pair[0], i, &dat, 1);
+                if (err) {
+                    goto cleanup;
+                }
+
+                err = lfs_bd_prog(lfs,
+                        &lfs->pcache, &lfs->rcache, true,
+                        dir1.head[1], i, &dat, 1);
+                if (err) {
+                    goto cleanup;
+                }
+            }
+
+            err = lfs_bd_flush(lfs, &lfs->pcache, &lfs->rcache, true);
+            if (err) {
+                goto cleanup;
+            }
+        }
+
+        // Create new superblock. This marks a successful migration!
+        err = lfs1_dir_fetch(lfs, &dir1, (const lfs_block_t[2]){0, 1});
+        if (err) {
+            goto cleanup;
+        }
+
+        dir2.pair[0] = dir1.pair[0];
+        dir2.pair[1] = dir1.pair[1];
+        dir2.rev = dir1.d.rev;
+        dir2.off = sizeof(dir2.rev);
+        dir2.etag = 0xffffffff;
+        dir2.count = 0;
+        dir2.tail[0] = lfs->lfs1->root[0];
+        dir2.tail[1] = lfs->lfs1->root[1];
+        dir2.erased = false;
+        dir2.split = true;
+
+        lfs_superblock_t superblock = {
+            .version     = LFS_DISK_VERSION,
+            .block_size  = lfs->cfg->block_size,
+            .block_count = lfs->cfg->block_count,
+            .name_max    = lfs->name_max,
+            .file_max    = lfs->file_max,
+            .attr_max    = lfs->attr_max,
+        };
+
+        lfs_superblock_tole32(&superblock);
+        err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS(
+                {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL},
+                {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"},
+                {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)),
+                    &superblock}));
+        if (err) {
+            goto cleanup;
+        }
+
+        // sanity check that fetch works
+        err = lfs_dir_fetch(lfs, &dir2, (const lfs_block_t[2]){0, 1});
+        if (err) {
+            goto cleanup;
+        }
+
+        // force compaction to prevent accidentally mounting v1
+        dir2.erased = false;
+        err = lfs_dir_commit(lfs, &dir2, NULL, 0);
+        if (err) {
+            goto cleanup;
+        }
+    }
+
+cleanup:
+    lfs1_unmount(lfs);
+    return err;
+}
+
+#endif
+
+
+/// Public API wrappers ///
+
+// Here we can add tracing/thread safety easily
+
+// Thread-safe wrappers if enabled
+#ifdef LFS_THREADSAFE
+#define LFS_LOCK(cfg)   cfg->lock(cfg)
+#define LFS_UNLOCK(cfg) cfg->unlock(cfg)
+#else
+#define LFS_LOCK(cfg)   ((void)cfg, 0)
+#define LFS_UNLOCK(cfg) ((void)cfg)
+#endif
+
+// Public API
+#ifndef LFS_READONLY
+int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
+    int err = LFS_LOCK(cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_format(%p, %p {.context=%p, "
+                ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
+                ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
+                ".block_size=%"PRIu32", .block_count=%"PRIu32", "
+                ".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
+                ".lookahead_size=%"PRIu32", .read_buffer=%p, "
+                ".prog_buffer=%p, .lookahead_buffer=%p, "
+                ".name_max=%"PRIu32", .file_max=%"PRIu32", "
+                ".attr_max=%"PRIu32"})",
+            (void*)lfs, (void*)cfg, cfg->context,
+            (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
+            (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
+            cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
+            cfg->block_cycles, cfg->cache_size, cfg->lookahead_size,
+            cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer,
+            cfg->name_max, cfg->file_max, cfg->attr_max);
+
+    err = lfs_format_(lfs, cfg);
+
+    LFS_TRACE("lfs_format -> %d", err);
+    LFS_UNLOCK(cfg);
+    return err;
+}
+#endif
+
+int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
+    int err = LFS_LOCK(cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_mount(%p, %p {.context=%p, "
+                ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
+                ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
+                ".block_size=%"PRIu32", .block_count=%"PRIu32", "
+                ".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
+                ".lookahead_size=%"PRIu32", .read_buffer=%p, "
+                ".prog_buffer=%p, .lookahead_buffer=%p, "
+                ".name_max=%"PRIu32", .file_max=%"PRIu32", "
+                ".attr_max=%"PRIu32"})",
+            (void*)lfs, (void*)cfg, cfg->context,
+            (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
+            (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
+            cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
+            cfg->block_cycles, cfg->cache_size, cfg->lookahead_size,
+            cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer,
+            cfg->name_max, cfg->file_max, cfg->attr_max);
+
+    err = lfs_mount_(lfs, cfg);
+
+    LFS_TRACE("lfs_mount -> %d", err);
+    LFS_UNLOCK(cfg);
+    return err;
+}
+
+int lfs_unmount(lfs_t *lfs) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_unmount(%p)", (void*)lfs);
+
+    err = lfs_unmount_(lfs);
+
+    LFS_TRACE("lfs_unmount -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+#ifndef LFS_READONLY
+int lfs_remove(lfs_t *lfs, const char *path) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path);
+
+    err = lfs_remove_(lfs, path);
+
+    LFS_TRACE("lfs_remove -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+#ifndef LFS_READONLY
+int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath);
+
+    err = lfs_rename_(lfs, oldpath, newpath);
+
+    LFS_TRACE("lfs_rename -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_stat(%p, \"%s\", %p)", (void*)lfs, path, (void*)info);
+
+    err = lfs_stat_(lfs, path, info);
+
+    LFS_TRACE("lfs_stat -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
+        uint8_t type, void *buffer, lfs_size_t size) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")",
+            (void*)lfs, path, type, buffer, size);
+
+    lfs_ssize_t res = lfs_getattr_(lfs, path, type, buffer, size);
+
+    LFS_TRACE("lfs_getattr -> %"PRId32, res);
+    LFS_UNLOCK(lfs->cfg);
+    return res;
+}
+
+#ifndef LFS_READONLY
+int lfs_setattr(lfs_t *lfs, const char *path,
+        uint8_t type, const void *buffer, lfs_size_t size) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")",
+            (void*)lfs, path, type, buffer, size);
+
+    err = lfs_setattr_(lfs, path, type, buffer, size);
+
+    LFS_TRACE("lfs_setattr -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+#ifndef LFS_READONLY
+int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs, path, type);
+
+    err = lfs_removeattr_(lfs, path, type);
+
+    LFS_TRACE("lfs_removeattr -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+#ifndef LFS_NO_MALLOC
+int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)",
+            (void*)lfs, (void*)file, path, (unsigned)flags);
+    LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
+
+    err = lfs_file_open_(lfs, file, path, flags);
+
+    LFS_TRACE("lfs_file_open -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
+        const char *path, int flags,
+        const struct lfs_file_config *cfg) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {"
+                 ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})",
+            (void*)lfs, (void*)file, path, (unsigned)flags,
+            (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count);
+    LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
+
+    err = lfs_file_opencfg_(lfs, file, path, flags, cfg);
+
+    LFS_TRACE("lfs_file_opencfg -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file);
+    LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
+
+    err = lfs_file_close_(lfs, file);
+
+    LFS_TRACE("lfs_file_close -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+#ifndef LFS_READONLY
+int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file);
+    LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
+
+    err = lfs_file_sync_(lfs, file);
+
+    LFS_TRACE("lfs_file_sync -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
+        void *buffer, lfs_size_t size) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")",
+            (void*)lfs, (void*)file, buffer, size);
+    LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
+
+    lfs_ssize_t res = lfs_file_read_(lfs, file, buffer, size);
+
+    LFS_TRACE("lfs_file_read -> %"PRId32, res);
+    LFS_UNLOCK(lfs->cfg);
+    return res;
+}
+
+#ifndef LFS_READONLY
+lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
+        const void *buffer, lfs_size_t size) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")",
+            (void*)lfs, (void*)file, buffer, size);
+    LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
+
+    lfs_ssize_t res = lfs_file_write_(lfs, file, buffer, size);
+
+    LFS_TRACE("lfs_file_write -> %"PRId32, res);
+    LFS_UNLOCK(lfs->cfg);
+    return res;
+}
+#endif
+
+lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
+        lfs_soff_t off, int whence) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)",
+            (void*)lfs, (void*)file, off, whence);
+    LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
+
+    lfs_soff_t res = lfs_file_seek_(lfs, file, off, whence);
+
+    LFS_TRACE("lfs_file_seek -> %"PRId32, res);
+    LFS_UNLOCK(lfs->cfg);
+    return res;
+}
+
+#ifndef LFS_READONLY
+int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")",
+            (void*)lfs, (void*)file, size);
+    LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
+
+    err = lfs_file_truncate_(lfs, file, size);
+
+    LFS_TRACE("lfs_file_truncate -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file);
+    LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
+
+    lfs_soff_t res = lfs_file_tell_(lfs, file);
+
+    LFS_TRACE("lfs_file_tell -> %"PRId32, res);
+    LFS_UNLOCK(lfs->cfg);
+    return res;
+}
+
+int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_rewind(%p, %p)", (void*)lfs, (void*)file);
+
+    err = lfs_file_rewind_(lfs, file);
+
+    LFS_TRACE("lfs_file_rewind -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file);
+    LFS_ASSERT(lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));
+
+    lfs_soff_t res = lfs_file_size_(lfs, file);
+
+    LFS_TRACE("lfs_file_size -> %"PRIu32, res);
+    LFS_UNLOCK(lfs->cfg);
+    return res;
+}
+
+#ifndef LFS_READONLY
+int lfs_mkdir(lfs_t *lfs, const char *path) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_mkdir(%p, \"%s\")", (void*)lfs, path);
+
+    err = lfs_mkdir_(lfs, path);
+
+    LFS_TRACE("lfs_mkdir -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_dir_open(%p, %p, \"%s\")", (void*)lfs, (void*)dir, path);
+    LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)dir));
+
+    err = lfs_dir_open_(lfs, dir, path);
+
+    LFS_TRACE("lfs_dir_open -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_dir_close(%p, %p)", (void*)lfs, (void*)dir);
+
+    err = lfs_dir_close_(lfs, dir);
+
+    LFS_TRACE("lfs_dir_close -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_dir_read(%p, %p, %p)",
+            (void*)lfs, (void*)dir, (void*)info);
+
+    err = lfs_dir_read_(lfs, dir, info);
+
+    LFS_TRACE("lfs_dir_read -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_dir_seek(%p, %p, %"PRIu32")",
+            (void*)lfs, (void*)dir, off);
+
+    err = lfs_dir_seek_(lfs, dir, off);
+
+    LFS_TRACE("lfs_dir_seek -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_dir_tell(%p, %p)", (void*)lfs, (void*)dir);
+
+    lfs_soff_t res = lfs_dir_tell_(lfs, dir);
+
+    LFS_TRACE("lfs_dir_tell -> %"PRId32, res);
+    LFS_UNLOCK(lfs->cfg);
+    return res;
+}
+
+int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_dir_rewind(%p, %p)", (void*)lfs, (void*)dir);
+
+    err = lfs_dir_rewind_(lfs, dir);
+
+    LFS_TRACE("lfs_dir_rewind -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_fs_stat(%p, %p)", (void*)lfs, (void*)fsinfo);
+
+    err = lfs_fs_stat_(lfs, fsinfo);
+
+    LFS_TRACE("lfs_fs_stat -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+lfs_ssize_t lfs_fs_size(lfs_t *lfs) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_fs_size(%p)", (void*)lfs);
+
+    lfs_ssize_t res = lfs_fs_size_(lfs);
+
+    LFS_TRACE("lfs_fs_size -> %"PRId32, res);
+    LFS_UNLOCK(lfs->cfg);
+    return res;
+}
+
+int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_fs_traverse(%p, %p, %p)",
+            (void*)lfs, (void*)(uintptr_t)cb, data);
+
+    err = lfs_fs_traverse_(lfs, cb, data, true);
+
+    LFS_TRACE("lfs_fs_traverse -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+
+#ifndef LFS_READONLY
+int lfs_fs_mkconsistent(lfs_t *lfs) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_fs_mkconsistent(%p)", (void*)lfs);
+
+    err = lfs_fs_mkconsistent_(lfs);
+
+    LFS_TRACE("lfs_fs_mkconsistent -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+#ifndef LFS_READONLY
+int lfs_fs_gc(lfs_t *lfs) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_fs_gc(%p)", (void*)lfs);
+
+    err = lfs_fs_gc_(lfs);
+
+    LFS_TRACE("lfs_fs_gc -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+#ifndef LFS_READONLY
+int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count) {
+    int err = LFS_LOCK(lfs->cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_fs_grow(%p, %"PRIu32")", (void*)lfs, block_count);
+
+    err = lfs_fs_grow_(lfs, block_count);
+
+    LFS_TRACE("lfs_fs_grow -> %d", err);
+    LFS_UNLOCK(lfs->cfg);
+    return err;
+}
+#endif
+
+#ifdef LFS_MIGRATE
+int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) {
+    int err = LFS_LOCK(cfg);
+    if (err) {
+        return err;
+    }
+    LFS_TRACE("lfs_migrate(%p, %p {.context=%p, "
+                ".read=%p, .prog=%p, .erase=%p, .sync=%p, "
+                ".read_size=%"PRIu32", .prog_size=%"PRIu32", "
+                ".block_size=%"PRIu32", .block_count=%"PRIu32", "
+                ".block_cycles=%"PRId32", .cache_size=%"PRIu32", "
+                ".lookahead_size=%"PRIu32", .read_buffer=%p, "
+                ".prog_buffer=%p, .lookahead_buffer=%p, "
+                ".name_max=%"PRIu32", .file_max=%"PRIu32", "
+                ".attr_max=%"PRIu32"})",
+            (void*)lfs, (void*)cfg, cfg->context,
+            (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog,
+            (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync,
+            cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count,
+            cfg->block_cycles, cfg->cache_size, cfg->lookahead_size,
+            cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer,
+            cfg->name_max, cfg->file_max, cfg->attr_max);
+
+    err = lfs_migrate_(lfs, cfg);
+
+    LFS_TRACE("lfs_migrate -> %d", err);
+    LFS_UNLOCK(cfg);
+    return err;
+}
+#endif
+

+ 37 - 0
Core/Src/lfs_util.c

@@ -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

+ 284 - 113
Core/Src/main.c

@@ -2,10 +2,17 @@
 /* USER CODE END Header */
 /* Includes ------------------------------------------------------------------*/
 #include "main.h"
+#include "gpio.h"
+#include "spi.h"
+#include "spif.h"
+#include "usart.h"
 
 /* Private includes ----------------------------------------------------------*/
 /* USER CODE BEGIN Includes */
 
+#include "lfs.h"
+#include <stdio.h>
+
 /* USER CODE END Includes */
 
 /* Private typedef -----------------------------------------------------------*/
@@ -24,16 +31,198 @@
 /* USER CODE END PM */
 
 /* Private variables ---------------------------------------------------------*/
-UART_HandleTypeDef huart2;
 
 /* USER CODE BEGIN PV */
 
+uint8_t test_write[256];
+uint8_t test_read[256];
+
+SPIF_HandleTypeDef spif_handle;
+
+// Прототипы функций
+int littlefs_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
+                  void *buffer, lfs_size_t size);
+int littlefs_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
+                  const void *buffer, lfs_size_t size);
+int littlefs_erase(const struct lfs_config *c, lfs_block_t block);
+int littlefs_sync(const struct lfs_config *c);
+
+// Конфигурация LittleFS
+struct lfs_config littlefs_config = {
+    // ... function pointers ...
+    .read = littlefs_read,
+    .prog = littlefs_prog,
+    .erase = littlefs_erase,
+    .sync = littlefs_sync,
+
+    // CONFIGURATION
+    .read_size = 1, // Winbond allows byte-level reads. 1 is more efficient for
+                    // small files.
+    .prog_size = 256,    // Winbond Page Size
+    .block_size = 4096,  // 4KB Sector Size (Must match SPIF_SECTOR_SIZE)
+    .block_count = 4096, // 128Mbit / 4096 bytes = 4096 blocks
+    .cache_size = 256,   // One page cache
+    .lookahead_size =
+        128, // CHANGE THIS: 8 is too small for 4096 blocks. Use 128 or 256.
+    .block_cycles = 500, // CHANGE THIS: 100 is too low, causes excessive wear
+                         // leveling overhead.
+};
+
+lfs_t littlefs;
+
+/**
+ * @brief Инициализация LittleFS с использованием SPIF
+ * @param handle Указатель на инициализированную структуру SPIF_HandleTypeDef
+ */
+int spif_littlefs_init(SPIF_HandleTypeDef *spif_handle) {
+  if (spif_handle == NULL || spif_handle->Inited == 0) {
+    return -1;
+  }
+
+  // Update config based on chip probe
+  littlefs_config.block_count = spif_handle->SectorCnt;
+  // Ensure block_size matches the physical erase size (usually 4096 for
+  // Winbond)
+  littlefs_config.block_size = SPIF_SECTOR_SIZE;
+
+  // 1. Try to mount
+  int err = lfs_mount(&littlefs, &littlefs_config);
+
+  // 2. If mount fails, format and try again
+  if (err) {
+    printf("LittleFS: First boot or corruption detected. Formatting...\r\n");
+
+    err = lfs_format(&littlefs, &littlefs_config);
+    if (err) {
+      printf("LittleFS: Format Failed! Error: %d\r\n", err);
+      return err;
+    }
+
+    err = lfs_mount(&littlefs, &littlefs_config);
+    if (err) {
+      printf("LittleFS: Mount Failed after format! Error: %d\r\n", err);
+      return err;
+    }
+  }
+
+  printf("LittleFS: Mounted Successfully. Size: %lu Bytes\r\n",
+         littlefs_config.block_count * littlefs_config.block_size);
+  return LFS_ERR_OK;
+}
+
+int littlefs_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
+                  void *buffer, lfs_size_t size) {
+  printf("LittleFS Read b = 0x%04lx o = 0x%04lx s = 0x%04lx", block, off, size);
+
+  // Вычисляем абсолютный адрес: (Номер блока * Размер блока) + Смещение
+  uint32_t address = (block * littlefs_config.block_size) + off;
+
+  // SPIF_ReadAddress возвращает true при успехе, false при ошибке
+  if (SPIF_ReadAddress(&spif_handle, address, (uint8_t *)buffer, size) ==
+      true) {
+    return LFS_ERR_OK; // 0
+  }
+  return LFS_ERR_IO; // Отрицательное значение ошибки
+}
+
+int littlefs_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off,
+                  const void *buffer, lfs_size_t size) {
+  printf("LittleFS Prog b = 0x%04lx o = 0x%04lx s = 0x%04lx", block, off, size);
+
+  uint32_t address = (block * littlefs_config.block_size) + off;
+
+  // SPIF_WriteAddress сама обрабатывает запись страниц
+  if (SPIF_WriteAddress(&spif_handle, address, (uint8_t *)buffer, size) ==
+      true) {
+    return LFS_ERR_OK;
+  }
+  return LFS_ERR_IO;
+}
+
+int littlefs_erase(const struct lfs_config *c, lfs_block_t block) {
+  printf("LittleFS Erase b = 0x%04lx", block);
+
+  // SPIF_EraseSector принимает номер сектора, который равен номеру блока LFS
+  // (при условии, что block_size == SPIF_SECTOR_SIZE)
+  if (SPIF_EraseSector(&spif_handle, block) == true) {
+    return LFS_ERR_OK;
+  }
+  return LFS_ERR_IO;
+}
+
+int littlefs_sync(const struct lfs_config *c) {
+  printf("LittleFS Sync");
+  // SPIF библиотека блокирующая, дополнительная синхронизация обычно не
+  // требуется, если не используется кэширование записи на уровне драйвера.
+  return LFS_ERR_OK;
+}
+
+void lfs_test_write_read(void) {
+  lfs_file_t file;
+  int err;
+
+  const char *filename = "boot_count.txt";
+  uint32_t boot_count = 0;
+
+  printf("\r\n--- Starting File Test ---\r\n");
+
+  // 1. Open file for reading and writing. Create if it doesn't exist.
+  err = lfs_file_open(&littlefs, &file, filename, LFS_O_RDWR | LFS_O_CREAT);
+  if (err) {
+    printf("Error opening file: %d\r\n", err);
+    return;
+  }
+
+  // 2. Read current content (if file existed)
+  // We try to read sizeof(uint32_t). If file is empty, it returns 0 bytes.
+  lfs_ssize_t read_size =
+      lfs_file_read(&littlefs, &file, &boot_count, sizeof(boot_count));
+
+  if (read_size < 0) {
+    printf("Error reading file: %ld\r\n", read_size);
+    lfs_file_close(&littlefs, &file);
+    return;
+  } else if (read_size == 0) {
+    printf("File was empty (First run)\r\n");
+    boot_count = 0;
+  } else {
+    printf("Previous boot count: %lu\r\n", boot_count);
+  }
+
+  // 3. Increment count
+  boot_count++;
+
+  // 4. Rewind file to the beginning to overwrite the old value
+  err = lfs_file_rewind(&littlefs, &file);
+  if (err) {
+    printf("Error rewinding: %d\r\n", err);
+    lfs_file_close(&littlefs, &file);
+    return;
+  }
+
+  // 5. Write new value
+  err = lfs_file_write(&littlefs, &file, &boot_count, sizeof(boot_count));
+  if (err < 0) {
+    printf("Error writing: %d\r\n", err);
+    lfs_file_close(&littlefs, &file);
+    return;
+  }
+
+  // 6. Close the file (CRITICAL: This flushes data to the chip)
+  err = lfs_file_close(&littlefs, &file);
+  if (err) {
+    printf("Error closing: %d\r\n", err);
+    return;
+  }
+
+  printf("Updated boot count to: %lu\r\n", boot_count);
+  printf("--- Test Finished ---\r\n");
+}
+
 /* USER CODE END PV */
 
 /* Private function prototypes -----------------------------------------------*/
 void SystemClock_Config(void);
-static void MX_GPIO_Init(void);
-static void MX_USART2_UART_Init(void);
 /* USER CODE BEGIN PFP */
 
 /* USER CODE END PFP */
@@ -41,10 +230,53 @@ static void MX_USART2_UART_Init(void);
 /* Private user code ---------------------------------------------------------*/
 /* USER CODE BEGIN 0 */
 
+int _write(int fd, char *ptr, int len) {
+  HAL_StatusTypeDef hstatus;
+
+  if (fd == 1 || fd == 2) {
+    hstatus = HAL_UART_Transmit(&huart2, (uint8_t *)ptr, len, HAL_MAX_DELAY);
+    if (hstatus == HAL_OK)
+      return len;
+    else
+      return -1;
+  }
+  return -1;
+}
+
+void dump_hex(char *header, uint32_t start, uint8_t *buf, uint32_t len) {
+  uint32_t i = 0;
+
+  printf("%s\n", header);
+
+  for (i = 0; i < len; ++i) {
+
+    if (i % 16 == 0) {
+      printf("0x%08lx: ", start);
+    }
+
+    printf("%02x ", buf[i]);
+
+    if ((i + 1) % 16 == 0) {
+      printf("\n");
+    }
+
+    ++start;
+  }
+}
+
+// int __io_putchar(int ch) {
+//   // Write character to ITM ch.0
+//   ITM_SendChar(ch);
+//   return (ch);
+// }
+
 /* USER CODE END 0 */
 
-int main(void)
-{
+/**
+ * @brief  The application entry point.
+ * @retval int
+ */
+int main(void) {
 
   /* USER CODE BEGIN 1 */
 
@@ -52,7 +284,8 @@ int main(void)
 
   /* MCU Configuration--------------------------------------------------------*/
 
-  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
+  /* Reset of all peripherals, Initializes the Flash interface and the Systick.
+   */
   HAL_Init();
 
   /* USER CODE BEGIN Init */
@@ -69,14 +302,31 @@ int main(void)
   /* Initialize all configured peripherals */
   MX_GPIO_Init();
   MX_USART2_UART_Init();
+  MX_SPI2_Init();
   /* USER CODE BEGIN 2 */
 
+  HAL_Delay(10);
+
+  if (SPIF_Init(&spif_handle, &hspi2, GPIOB, GPIO_PIN_6)) {
+
+    // Initialize and Mount
+    if (spif_littlefs_init(&spif_handle) == LFS_ERR_OK) {
+
+      // Run the test
+      lfs_test_write_read();
+
+    } else {
+      printf("Mount failed!\n");
+    }
+  } else {
+    printf("SPIF Init failed!\n");
+  }
+
   /* USER CODE END 2 */
 
   /* Infinite loop */
   /* USER CODE BEGIN WHILE */
-  while (1)
-  {
+  while (1) {
     /* USER CODE END WHILE */
 
     /* USER CODE BEGIN 3 */
@@ -85,115 +335,38 @@ int main(void)
 }
 
 /**
-  * @brief System Clock Configuration
-  * @retval None
-  */
-void SystemClock_Config(void)
-{
+ * @brief System Clock Configuration
+ * @retval None
+ */
+void SystemClock_Config(void) {
   RCC_OscInitTypeDef RCC_OscInitStruct = {0};
   RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
 
   /** Initializes the RCC Oscillators according to the specified parameters
-  * in the RCC_OscInitTypeDef structure.
-  */
+   * in the RCC_OscInitTypeDef structure.
+   */
   RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
   RCC_OscInitStruct.HSIState = RCC_HSI_ON;
   RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
   RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
   RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
   RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
-  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
-  {
+  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
     Error_Handler();
   }
 
   /** Initializes the CPU, AHB and APB buses clocks
-  */
-  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
-                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
+   */
+  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK |
+                                RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
   RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
   RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
   RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
   RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
 
-  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
-  {
-    Error_Handler();
-  }
-}
-
-/**
-  * @brief USART2 Initialization Function
-  * @param None
-  * @retval None
-  */
-static void MX_USART2_UART_Init(void)
-{
-
-  /* USER CODE BEGIN USART2_Init 0 */
-
-  /* USER CODE END USART2_Init 0 */
-
-  /* USER CODE BEGIN USART2_Init 1 */
-
-  /* USER CODE END USART2_Init 1 */
-  huart2.Instance = USART2;
-  huart2.Init.BaudRate = 38400;
-  huart2.Init.WordLength = UART_WORDLENGTH_8B;
-  huart2.Init.StopBits = UART_STOPBITS_1;
-  huart2.Init.Parity = UART_PARITY_NONE;
-  huart2.Init.Mode = UART_MODE_TX_RX;
-  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
-  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
-  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
-  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
-  if (HAL_UART_Init(&huart2) != HAL_OK)
-  {
+  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
     Error_Handler();
   }
-  /* USER CODE BEGIN USART2_Init 2 */
-
-  /* USER CODE END USART2_Init 2 */
-
-}
-
-/**
-  * @brief GPIO Initialization Function
-  * @param None
-  * @retval None
-  */
-static void MX_GPIO_Init(void)
-{
-  GPIO_InitTypeDef GPIO_InitStruct = {0};
-  /* USER CODE BEGIN MX_GPIO_Init_1 */
-
-  /* USER CODE END MX_GPIO_Init_1 */
-
-  /* GPIO Ports Clock Enable */
-  __HAL_RCC_GPIOC_CLK_ENABLE();
-  __HAL_RCC_GPIOF_CLK_ENABLE();
-  __HAL_RCC_GPIOA_CLK_ENABLE();
-  __HAL_RCC_GPIOB_CLK_ENABLE();
-
-  /*Configure GPIO pin Output Level */
-  HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
-
-  /*Configure GPIO pin : B1_Pin */
-  GPIO_InitStruct.Pin = B1_Pin;
-  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
-  GPIO_InitStruct.Pull = GPIO_NOPULL;
-  HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
-
-  /*Configure GPIO pin : LD2_Pin */
-  GPIO_InitStruct.Pin = LD2_Pin;
-  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
-  GPIO_InitStruct.Pull = GPIO_NOPULL;
-  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
-  HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);
-
-  /* USER CODE BEGIN MX_GPIO_Init_2 */
-
-  /* USER CODE END MX_GPIO_Init_2 */
 }
 
 /* USER CODE BEGIN 4 */
@@ -201,32 +374,30 @@ static void MX_GPIO_Init(void)
 /* USER CODE END 4 */
 
 /**
-  * @brief  This function is executed in case of error occurrence.
-  * @retval None
-  */
-void Error_Handler(void)
-{
+ * @brief  This function is executed in case of error occurrence.
+ * @retval None
+ */
+void Error_Handler(void) {
   /* USER CODE BEGIN Error_Handler_Debug */
   /* User can add his own implementation to report the HAL error return state */
   __disable_irq();
-  while (1)
-  {
+  while (1) {
   }
   /* USER CODE END Error_Handler_Debug */
 }
 #ifdef USE_FULL_ASSERT
 /**
-  * @brief  Reports the name of the source file and the source line number
-  *         where the assert_param error has occurred.
-  * @param  file: pointer to the source file name
-  * @param  line: assert_param error line source number
-  * @retval None
-  */
-void assert_failed(uint8_t *file, uint32_t line)
-{
+ * @brief  Reports the name of the source file and the source line number
+ *         where the assert_param error has occurred.
+ * @param  file: pointer to the source file name
+ * @param  line: assert_param error line source number
+ * @retval None
+ */
+void assert_failed(uint8_t *file, uint32_t line) {
   /* USER CODE BEGIN 6 */
-  /* User can add his own implementation to report the file name and line number,
-     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
+  /* User can add his own implementation to report the file name and line
+     number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file,
+     line) */
   /* USER CODE END 6 */
 }
 #endif /* USE_FULL_ASSERT */

+ 121 - 0
Core/Src/spi.c

@@ -0,0 +1,121 @@
+/* USER CODE BEGIN Header */
+/**
+  ******************************************************************************
+  * @file    spi.c
+  * @brief   This file provides code for the configuration
+  *          of the SPI instances.
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2026 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  */
+/* USER CODE END Header */
+/* Includes ------------------------------------------------------------------*/
+#include "spi.h"
+
+/* USER CODE BEGIN 0 */
+
+/* USER CODE END 0 */
+
+SPI_HandleTypeDef hspi2;
+
+/* SPI2 init function */
+void MX_SPI2_Init(void)
+{
+
+  /* USER CODE BEGIN SPI2_Init 0 */
+
+  /* USER CODE END SPI2_Init 0 */
+
+  /* USER CODE BEGIN SPI2_Init 1 */
+
+  /* USER CODE END SPI2_Init 1 */
+  hspi2.Instance = SPI2;
+  hspi2.Init.Mode = SPI_MODE_MASTER;
+  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
+  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
+  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
+  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
+  hspi2.Init.NSS = SPI_NSS_SOFT;
+  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
+  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
+  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
+  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
+  hspi2.Init.CRCPolynomial = 7;
+  hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
+  hspi2.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
+  if (HAL_SPI_Init(&hspi2) != HAL_OK)
+  {
+    Error_Handler();
+  }
+  /* USER CODE BEGIN SPI2_Init 2 */
+
+  /* USER CODE END SPI2_Init 2 */
+
+}
+
+void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
+{
+
+  GPIO_InitTypeDef GPIO_InitStruct = {0};
+  if(spiHandle->Instance==SPI2)
+  {
+  /* USER CODE BEGIN SPI2_MspInit 0 */
+
+  /* USER CODE END SPI2_MspInit 0 */
+    /* SPI2 clock enable */
+    __HAL_RCC_SPI2_CLK_ENABLE();
+
+    __HAL_RCC_GPIOB_CLK_ENABLE();
+    /**SPI2 GPIO Configuration
+    PB13     ------> SPI2_SCK
+    PB14     ------> SPI2_MISO
+    PB15     ------> SPI2_MOSI
+    */
+    GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
+    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
+    GPIO_InitStruct.Pull = GPIO_NOPULL;
+    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
+    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
+
+  /* USER CODE BEGIN SPI2_MspInit 1 */
+
+  /* USER CODE END SPI2_MspInit 1 */
+  }
+}
+
+void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
+{
+
+  if(spiHandle->Instance==SPI2)
+  {
+  /* USER CODE BEGIN SPI2_MspDeInit 0 */
+
+  /* USER CODE END SPI2_MspDeInit 0 */
+    /* Peripheral clock disable */
+    __HAL_RCC_SPI2_CLK_DISABLE();
+
+    /**SPI2 GPIO Configuration
+    PB13     ------> SPI2_SCK
+    PB14     ------> SPI2_MISO
+    PB15     ------> SPI2_MOSI
+    */
+    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15);
+
+  /* USER CODE BEGIN SPI2_MspDeInit 1 */
+
+  /* USER CODE END SPI2_MspDeInit 1 */
+  }
+}
+
+/* USER CODE BEGIN 1 */
+
+/* USER CODE END 1 */

+ 0 - 66
Core/Src/stm32f3xx_hal_msp.c

@@ -79,72 +79,6 @@ void HAL_MspInit(void)
   /* USER CODE END MspInit 1 */
 }
 
-/**
-  * @brief UART MSP Initialization
-  * This function configures the hardware resources used in this example
-  * @param huart: UART handle pointer
-  * @retval None
-  */
-void HAL_UART_MspInit(UART_HandleTypeDef* huart)
-{
-  GPIO_InitTypeDef GPIO_InitStruct = {0};
-  if(huart->Instance==USART2)
-  {
-    /* USER CODE BEGIN USART2_MspInit 0 */
-
-    /* USER CODE END USART2_MspInit 0 */
-    /* Peripheral clock enable */
-    __HAL_RCC_USART2_CLK_ENABLE();
-
-    __HAL_RCC_GPIOA_CLK_ENABLE();
-    /**USART2 GPIO Configuration
-    PA2     ------> USART2_TX
-    PA3     ------> USART2_RX
-    */
-    GPIO_InitStruct.Pin = USART_TX_Pin|USART_RX_Pin;
-    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
-    GPIO_InitStruct.Pull = GPIO_NOPULL;
-    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
-    GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
-    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
-
-    /* USER CODE BEGIN USART2_MspInit 1 */
-
-    /* USER CODE END USART2_MspInit 1 */
-
-  }
-
-}
-
-/**
-  * @brief UART MSP De-Initialization
-  * This function freeze the hardware resources used in this example
-  * @param huart: UART handle pointer
-  * @retval None
-  */
-void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)
-{
-  if(huart->Instance==USART2)
-  {
-    /* USER CODE BEGIN USART2_MspDeInit 0 */
-
-    /* USER CODE END USART2_MspDeInit 0 */
-    /* Peripheral clock disable */
-    __HAL_RCC_USART2_CLK_DISABLE();
-
-    /**USART2 GPIO Configuration
-    PA2     ------> USART2_TX
-    PA3     ------> USART2_RX
-    */
-    HAL_GPIO_DeInit(GPIOA, USART_TX_Pin|USART_RX_Pin);
-
-    /* USER CODE BEGIN USART2_MspDeInit 1 */
-
-    /* USER CODE END USART2_MspDeInit 1 */
-  }
-
-}
-
 /* USER CODE BEGIN 1 */
 
 /* USER CODE END 1 */

+ 116 - 0
Core/Src/usart.c

@@ -0,0 +1,116 @@
+/* USER CODE BEGIN Header */
+/**
+  ******************************************************************************
+  * @file    usart.c
+  * @brief   This file provides code for the configuration
+  *          of the USART instances.
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2026 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  */
+/* USER CODE END Header */
+/* Includes ------------------------------------------------------------------*/
+#include "usart.h"
+
+/* USER CODE BEGIN 0 */
+
+/* USER CODE END 0 */
+
+UART_HandleTypeDef huart2;
+
+/* USART2 init function */
+
+void MX_USART2_UART_Init(void)
+{
+
+  /* USER CODE BEGIN USART2_Init 0 */
+
+  /* USER CODE END USART2_Init 0 */
+
+  /* USER CODE BEGIN USART2_Init 1 */
+
+  /* USER CODE END USART2_Init 1 */
+  huart2.Instance = USART2;
+  huart2.Init.BaudRate = 115200;
+  huart2.Init.WordLength = UART_WORDLENGTH_8B;
+  huart2.Init.StopBits = UART_STOPBITS_1;
+  huart2.Init.Parity = UART_PARITY_NONE;
+  huart2.Init.Mode = UART_MODE_TX_RX;
+  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
+  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
+  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
+  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
+  if (HAL_UART_Init(&huart2) != HAL_OK)
+  {
+    Error_Handler();
+  }
+  /* USER CODE BEGIN USART2_Init 2 */
+
+  /* USER CODE END USART2_Init 2 */
+
+}
+
+void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
+{
+
+  GPIO_InitTypeDef GPIO_InitStruct = {0};
+  if(uartHandle->Instance==USART2)
+  {
+  /* USER CODE BEGIN USART2_MspInit 0 */
+
+  /* USER CODE END USART2_MspInit 0 */
+    /* USART2 clock enable */
+    __HAL_RCC_USART2_CLK_ENABLE();
+
+    __HAL_RCC_GPIOA_CLK_ENABLE();
+    /**USART2 GPIO Configuration
+    PA2     ------> USART2_TX
+    PA3     ------> USART2_RX
+    */
+    GPIO_InitStruct.Pin = USART_TX_Pin|USART_RX_Pin;
+    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
+    GPIO_InitStruct.Pull = GPIO_NOPULL;
+    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
+    GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
+    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+
+  /* USER CODE BEGIN USART2_MspInit 1 */
+
+  /* USER CODE END USART2_MspInit 1 */
+  }
+}
+
+void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
+{
+
+  if(uartHandle->Instance==USART2)
+  {
+  /* USER CODE BEGIN USART2_MspDeInit 0 */
+
+  /* USER CODE END USART2_MspDeInit 0 */
+    /* Peripheral clock disable */
+    __HAL_RCC_USART2_CLK_DISABLE();
+
+    /**USART2 GPIO Configuration
+    PA2     ------> USART2_TX
+    PA3     ------> USART2_RX
+    */
+    HAL_GPIO_DeInit(GPIOA, USART_TX_Pin|USART_RX_Pin);
+
+  /* USER CODE BEGIN USART2_MspDeInit 1 */
+
+  /* USER CODE END USART2_MspDeInit 1 */
+  }
+}
+
+/* USER CODE BEGIN 1 */
+
+/* USER CODE END 1 */

+ 61 - 0
I-CUBE-SPIF/NimaLTD.I-CUBE-SPIF_conf.h

@@ -0,0 +1,61 @@
+/**
+  ******************************************************************************
+  * File Name          : NimaLTD.I-CUBE-SPIF_conf.h
+  * Description        : This file provides code for the configuration
+  *                      of the NimaLTD.I-CUBE-SPIF_conf.h instances.
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2026 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  */
+/* Define to prevent recursive inclusion -------------------------------------*/
+#ifndef _NIMALTD_I_CUBE_SPIF_CONF_H_
+#define _NIMALTD_I_CUBE_SPIF_CONF_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#define SPIF_DEBUG_DISABLE                    0
+#define SPIF_DEBUG_MIN                        1
+#define SPIF_DEBUG_FULL                       2
+
+#define SPIF_PLATFORM_HAL                     0
+#define SPIF_PLATFORM_HAL_DMA                 1
+
+#define SPIF_RTOS_DISABLE                     0
+#define SPIF_RTOS_CMSIS_V1                    1
+#define SPIF_RTOS_CMSIS_V2                    2
+#define SPIF_RTOS_THREADX                     3
+
+/**
+	MiddleWare name : NimaLTD.I-CUBE-SPIF.2.3.2
+	MiddleWare fileName : NimaLTD.I-CUBE-SPIF_conf.h
+*/
+/*---------- SPIF_DEBUG  -----------*/
+#define SPIF_DEBUG      SPIF_DEBUG_FULL
+
+/*---------- SPIF_PLATFORM  -----------*/
+#define SPIF_PLATFORM      SPIF_PLATFORM_HAL
+
+/*---------- SPIF_RTOS  -----------*/
+#define SPIF_RTOS      SPIF_RTOS_DISABLE
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _NIMALTD_I_CUBE_SPIF_CONF_H_ */
+
+/**
+  * @}
+  */
+
+/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
+

+ 1307 - 0
Middlewares/Third_Party/NimaLTD_Driver/SPIF/spif.c

@@ -0,0 +1,1307 @@
+
+/************************************************************************************************************
+**************    Include Headers
+************************************************************************************************************/
+
+#include "spif.h"
+
+#if SPIF_DEBUG == SPIF_DEBUG_DISABLE
+#define dprintf(...)
+#else
+#include <stdio.h>
+#define dprintf(...) printf(__VA_ARGS__)
+#endif
+
+#if SPIF_RTOS == SPIF_RTOS_DISABLE
+#elif SPIF_RTOS == SPIF_RTOS_CMSIS_V1
+#include "cmsis_os.h"
+#include "freertos.h"
+#elif SPIF_RTOS == SPIF_RTOS_CMSIS_V2
+#include "cmsis_os2.h"
+#include "freertos.h"
+#elif SPIF_RTOS == SPIF_RTOS_THREADX
+#include "app_threadx.h"
+#endif
+
+/************************************************************************************************************
+**************    Private Definitions
+************************************************************************************************************/
+
+#define SPIF_DUMMY_BYTE 0xA5
+
+#define SPIF_CMD_READSFDP 0x5A
+#define SPIF_CMD_ID 0x90
+#define SPIF_CMD_JEDECID 0x9F
+#define SPIF_CMD_UNIQUEID 0x4B
+#define SPIF_CMD_WRITEDISABLE 0x04
+#define SPIF_CMD_READSTATUS1 0x05
+#define SPIF_CMD_READSTATUS2 0x35
+#define SPIF_CMD_READSTATUS3 0x15
+#define SPIF_CMD_WRITESTATUSEN 0x50
+#define SPIF_CMD_WRITESTATUS1 0x01
+#define SPIF_CMD_WRITESTATUS2 0x31
+#define SPIF_CMD_WRITESTATUS3 0x11
+#define SPIF_CMD_WRITEENABLE 0x06
+#define SPIF_CMD_ADDR4BYTE_EN 0xB7
+#define SPIF_CMD_ADDR4BYTE_DIS 0xE9
+#define SPIF_CMD_PAGEPROG3ADD 0x02
+#define SPIF_CMD_PAGEPROG4ADD 0x12
+#define SPIF_CMD_READDATA3ADD 0x03
+#define SPIF_CMD_READDATA4ADD 0x13
+#define SPIF_CMD_FASTREAD3ADD 0x0B
+#define SPIF_CMD_FASTREAD4ADD 0x0C
+#define SPIF_CMD_SECTORERASE3ADD 0x20
+#define SPIF_CMD_SECTORERASE4ADD 0x21
+#define SPIF_CMD_BLOCKERASE3ADD 0xD8
+#define SPIF_CMD_BLOCKERASE4ADD 0xDC
+#define SPIF_CMD_CHIPERASE1 0x60
+#define SPIF_CMD_CHIPERASE2 0xC7
+#define SPIF_CMD_SUSPEND 0x75
+#define SPIF_CMD_RESUME 0x7A
+#define SPIF_CMD_POWERDOWN 0xB9
+#define SPIF_CMD_RELEASE 0xAB
+#define SPIF_CMD_FRAMSERNO 0xC3
+
+#define SPIF_STATUS1_BUSY (1 << 0)
+#define SPIF_STATUS1_WEL (1 << 1)
+#define SPIF_STATUS1_BP0 (1 << 2)
+#define SPIF_STATUS1_BP1 (1 << 3)
+#define SPIF_STATUS1_BP2 (1 << 4)
+#define SPIF_STATUS1_TP (1 << 5)
+#define SPIF_STATUS1_SEC (1 << 6)
+#define SPIF_STATUS1_SRP0 (1 << 7)
+
+#define SPIF_STATUS2_SRP1 (1 << 0)
+#define SPIF_STATUS2_QE (1 << 1)
+#define SPIF_STATUS2_RESERVE1 (1 << 2)
+#define SPIF_STATUS2_LB0 (1 << 3)
+#define SPIF_STATUS2_LB1 (1 << 4)
+#define SPIF_STATUS2_LB2 (1 << 5)
+#define SPIF_STATUS2_CMP (1 << 6)
+#define SPIF_STATUS2_SUS (1 << 7)
+
+#define SPIF_STATUS3_RESERVE1 (1 << 0)
+#define SPIF_STATUS3_RESERVE2 (1 << 1)
+#define SPIF_STATUS3_WPS (1 << 2)
+#define SPIF_STATUS3_RESERVE3 (1 << 3)
+#define SPIF_STATUS3_RESERVE4 (1 << 4)
+#define SPIF_STATUS3_DRV0 (1 << 5)
+#define SPIF_STATUS3_DRV1 (1 << 6)
+#define SPIF_STATUS3_HOLD (1 << 7)
+
+/************************************************************************************************************
+**************    Private Functions
+************************************************************************************************************/
+
+void     SPIF_Delay(uint32_t Delay);
+void     SPIF_Lock(SPIF_HandleTypeDef *Handle);
+void     SPIF_UnLock(SPIF_HandleTypeDef *Handle);
+void     SPIF_CsPin(SPIF_HandleTypeDef *Handle, bool Select);
+bool     SPIF_TransmitReceive(SPIF_HandleTypeDef *Handle, uint8_t *Tx, uint8_t *Rx, size_t Size, uint32_t Timeout);
+bool     SPIF_Transmit(SPIF_HandleTypeDef *Handle, uint8_t *Tx, size_t Size, uint32_t Timeout);
+bool     SPIF_Receive(SPIF_HandleTypeDef *Handle, uint8_t *Rx, size_t Size, uint32_t Timeout);
+bool     SPIF_WriteEnable(SPIF_HandleTypeDef *Handle);
+bool     SPIF_WriteDisable(SPIF_HandleTypeDef *Handle);
+uint8_t  SPIF_ReadReg1(SPIF_HandleTypeDef *Handle);
+uint8_t  SPIF_ReadReg2(SPIF_HandleTypeDef *Handle);
+uint8_t  SPIF_ReadReg3(SPIF_HandleTypeDef *Handle);
+bool     SPIF_WriteReg1(SPIF_HandleTypeDef *Handle, uint8_t Data);
+bool     SPIF_WriteReg2(SPIF_HandleTypeDef *Handle, uint8_t Data);
+bool     SPIF_WriteReg3(SPIF_HandleTypeDef *Handle, uint8_t Data);
+bool     SPIF_WaitForWriting(SPIF_HandleTypeDef *Handle, uint32_t Timeout);
+bool     SPIF_FindChip(SPIF_HandleTypeDef *Handle);
+bool     SPIF_WriteFn(SPIF_HandleTypeDef *Handle, uint32_t PageNumber, uint8_t *Data, uint32_t Size, uint32_t Offset);
+bool     SPIF_ReadFn(SPIF_HandleTypeDef *Handle, uint32_t Address, uint8_t *Data, uint32_t Size);
+
+/***********************************************************************************************************/
+
+void SPIF_Delay(uint32_t Delay)
+{
+#if SPIF_RTOS == SPIF_RTOS_DISABLE
+  HAL_Delay(Delay);
+#elif (SPIF_RTOS == SPIF_RTOS_CMSIS_V1) || (SPIF_RTOS == SPIF_RTOS_CMSIS_V2)
+  uint32_t d = (configTICK_RATE_HZ * Delay) / 1000;
+  if (d == 0)
+      d = 1;
+  osDelay(d);
+#elif SPIF_RTOS == SPIF_RTOS_THREADX
+  uint32_t d = (TX_TIMER_TICKS_PER_SECOND * Delay) / 1000;
+  if (d == 0)
+    d = 1;
+  tx_thread_sleep(d);
+#endif
+}
+
+/***********************************************************************************************************/
+
+void SPIF_Lock(SPIF_HandleTypeDef *Handle)
+{
+  while (Handle->Lock)
+  {
+    SPIF_Delay(1);
+  }
+  Handle->Lock = 1;
+}
+
+/***********************************************************************************************************/
+
+void SPIF_UnLock(SPIF_HandleTypeDef *Handle)
+{
+  Handle->Lock = 0;
+}
+
+/***********************************************************************************************************/
+
+void SPIF_CsPin(SPIF_HandleTypeDef *Handle, bool Select)
+{
+  HAL_GPIO_WritePin(Handle->Gpio, Handle->Pin, (GPIO_PinState)Select);
+  for (int i = 0; i < 10; i++);
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_TransmitReceive(SPIF_HandleTypeDef *Handle, uint8_t *Tx, uint8_t *Rx, size_t Size, uint32_t Timeout)
+{
+  bool retVal = false;
+#if (SPIF_PLATFORM == SPIF_PLATFORM_HAL)
+  if (HAL_SPI_TransmitReceive(Handle->HSpi, Tx, Rx, Size, Timeout) == HAL_OK)
+  {
+    retVal = true;
+  }
+  else
+  {
+    dprintf("SPIF TIMEOUT\r\n");
+  }
+#elif (SPIF_PLATFORM == SPIF_PLATFORM_HAL_DMA)
+  uint32_t startTime = HAL_GetTick();
+  if (HAL_SPI_TransmitReceive_DMA(Handle->HSpi, Tx, Rx, Size) != HAL_OK)
+  {
+    dprintf("SPIF TRANSFER ERROR\r\n");
+  }
+  else
+  {
+    while (1)
+    {
+      SPIF_Delay(1);
+      if (HAL_GetTick() - startTime >= Timeout)
+      {
+        dprintf("SPIF TIMEOUT\r\n");
+        HAL_SPI_DMAStop(Handle->HSpi);
+        break;
+      }
+      if (HAL_SPI_GetState(Handle->HSpi) == HAL_SPI_STATE_READY)
+      {
+        retVal = true;
+        break;
+      }
+    }
+  }
+#endif
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_Transmit(SPIF_HandleTypeDef *Handle, uint8_t *Tx, size_t Size, uint32_t Timeout)
+{
+  bool retVal = false;
+#if (SPIF_PLATFORM == SPIF_PLATFORM_HAL)
+  if (HAL_SPI_Transmit(Handle->HSpi, Tx, Size, Timeout) == HAL_OK)
+  {
+    retVal = true;
+  }
+  else
+  {
+    dprintf("SPIF TIMEOUT\r\n");
+  }
+#elif (SPIF_PLATFORM == SPIF_PLATFORM_HAL_DMA)
+  uint32_t startTime = HAL_GetTick();
+  if (HAL_SPI_Transmit_DMA(Handle->HSpi, Tx, Size) != HAL_OK)
+  {
+    dprintf("SPIF TRANSFER ERROR\r\n");
+  }
+  else
+  {
+    while (1)
+    {
+      SPIF_Delay(1);
+      if (HAL_GetTick() - startTime >= Timeout)
+      {
+        dprintf("SPIF TIMEOUT\r\n");
+        HAL_SPI_DMAStop(Handle->HSpi);
+        break;
+      }
+      if (HAL_SPI_GetState(Handle->HSpi) == HAL_SPI_STATE_READY)
+      {
+        retVal = true;
+        break;
+      }
+    }
+  }
+#endif
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_Receive(SPIF_HandleTypeDef *Handle, uint8_t *Rx, size_t Size, uint32_t Timeout)
+{
+  bool retVal = false;
+#if (SPIF_PLATFORM == SPIF_PLATFORM_HAL)
+  if (HAL_SPI_Receive(Handle->HSpi, Rx, Size, Timeout) == HAL_OK)
+  {
+    retVal = true;
+  }
+  else
+  {
+    dprintf("SPIF TIMEOUT\r\n");
+  }
+#elif (SPIF_PLATFORM == SPIF_PLATFORM_HAL_DMA)
+  uint32_t startTime = HAL_GetTick();
+  if (HAL_SPI_Receive_DMA(Handle->HSpi, Rx, Size) != HAL_OK)
+  {
+    dprintf("SPIF TRANSFER ERROR\r\n");
+  }
+  else
+  {
+    while (1)
+    {
+      SPIF_Delay(1);
+      if (HAL_GetTick() - startTime >= Timeout)
+      {
+        dprintf("SPIF TIMEOUT\r\n");
+        HAL_SPI_DMAStop(Handle->HSpi);
+        break;
+      }
+      if (HAL_SPI_GetState(Handle->HSpi) == HAL_SPI_STATE_READY)
+      {
+        retVal = true;
+        break;
+      }
+    }
+  }
+#endif
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_WriteEnable(SPIF_HandleTypeDef *Handle)
+{
+  bool retVal = true;
+  uint8_t tx[1] = {SPIF_CMD_WRITEENABLE};
+  SPIF_CsPin(Handle, 0);
+  if (SPIF_Transmit(Handle, tx, 1, 100) == false)
+  {
+    retVal = false;
+    dprintf("SPIF_WriteEnable() Error\r\n");
+  }
+  SPIF_CsPin(Handle, 1);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_WriteDisable(SPIF_HandleTypeDef *Handle)
+{
+  bool retVal = true;
+  uint8_t tx[1] = {SPIF_CMD_WRITEDISABLE};
+  SPIF_CsPin(Handle, 0);
+  if (SPIF_Transmit(Handle, tx, 1, 100) == false)
+  {
+    retVal = false;
+    dprintf("SPIF_WriteDisable() Error\r\n");
+  }
+  SPIF_CsPin(Handle, 1);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+uint8_t SPIF_ReadReg1(SPIF_HandleTypeDef *Handle)
+{
+  uint8_t retVal = 0;
+  uint8_t tx[2] = {SPIF_CMD_READSTATUS1, SPIF_DUMMY_BYTE};
+  uint8_t rx[2];
+  SPIF_CsPin(Handle, 0);
+  if (SPIF_TransmitReceive(Handle, tx, rx, 2, 100) == true)
+  {
+    retVal = rx[1];
+  }
+  SPIF_CsPin(Handle, 1);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+uint8_t SPIF_ReadReg2(SPIF_HandleTypeDef *Handle)
+{
+  uint8_t retVal = 0;
+  uint8_t tx[2] = {SPIF_CMD_READSTATUS2, SPIF_DUMMY_BYTE};
+  uint8_t rx[2];
+  SPIF_CsPin(Handle, 0);
+  if (SPIF_TransmitReceive(Handle, tx, rx, 2, 100) == true)
+  {
+    retVal = rx[1];
+  }
+  SPIF_CsPin(Handle, 1);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+uint8_t SPIF_ReadReg3(SPIF_HandleTypeDef *Handle)
+{
+  uint8_t retVal = 0;
+  uint8_t tx[2] = {SPIF_CMD_READSTATUS3, SPIF_DUMMY_BYTE};
+  uint8_t rx[2];
+  SPIF_CsPin(Handle, 0);
+  if (SPIF_TransmitReceive(Handle, tx, rx, 2, 100) == true)
+  {
+    retVal = rx[1];
+  }
+  SPIF_CsPin(Handle, 1);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_WriteReg1(SPIF_HandleTypeDef *Handle, uint8_t Data)
+{
+  bool retVal = true;
+  uint8_t tx[2] = {SPIF_CMD_WRITESTATUS1, Data};
+  uint8_t cmd = SPIF_CMD_WRITESTATUSEN;
+  do
+  {
+    SPIF_CsPin(Handle, 0);
+    if (SPIF_Transmit(Handle, &cmd, 1, 100) == false)
+    {
+      retVal = false;
+      SPIF_CsPin(Handle, 1);
+      break;
+    }
+    SPIF_CsPin(Handle, 1);
+    SPIF_CsPin(Handle, 0);
+    if (SPIF_Transmit(Handle, tx, 2, 100) == false)
+    {
+      retVal = false;
+      SPIF_CsPin(Handle, 1);
+      break;
+    }
+    SPIF_CsPin(Handle, 1);
+  } while (0);
+
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_WriteReg2(SPIF_HandleTypeDef *Handle, uint8_t Data)
+{
+  bool retVal = true;
+  uint8_t tx[2] = {SPIF_CMD_WRITESTATUS2, Data};
+  uint8_t cmd = SPIF_CMD_WRITESTATUSEN;
+  do
+  {
+    SPIF_CsPin(Handle, 0);
+    if (SPIF_Transmit(Handle, &cmd, 1, 100) == false)
+    {
+      retVal = false;
+      SPIF_CsPin(Handle, 1);
+      break;
+    }
+    SPIF_CsPin(Handle, 1);
+    SPIF_CsPin(Handle, 0);
+    if (SPIF_Transmit(Handle, tx, 2, 100) == false)
+    {
+      retVal = false;
+      SPIF_CsPin(Handle, 1);
+      break;
+    }
+    SPIF_CsPin(Handle, 1);
+  } while (0);
+
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_WriteReg3(SPIF_HandleTypeDef *Handle, uint8_t Data)
+{
+  bool retVal = true;
+  uint8_t tx[2] = {SPIF_CMD_WRITESTATUS3, Data};
+  uint8_t cmd = SPIF_CMD_WRITESTATUSEN;
+  do
+  {
+    SPIF_CsPin(Handle, 0);
+    if (SPIF_Transmit(Handle, &cmd, 1, 100) == false)
+    {
+      retVal = false;
+      SPIF_CsPin(Handle, 1);
+      break;
+    }
+    SPIF_CsPin(Handle, 1);
+    SPIF_CsPin(Handle, 0);
+    if (SPIF_Transmit(Handle, tx, 2, 100) == false)
+    {
+      retVal = false;
+      SPIF_CsPin(Handle, 1);
+      break;
+    }
+    SPIF_CsPin(Handle, 1);
+  } while (0);
+
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_WaitForWriting(SPIF_HandleTypeDef *Handle, uint32_t Timeout)
+{
+  bool retVal = false;
+  uint32_t startTime = HAL_GetTick();
+  while (1)
+  {
+    SPIF_Delay(1);
+    if (HAL_GetTick() - startTime >= Timeout)
+    {
+      dprintf("SPIF_WaitForWriting() TIMEOUT\r\n");
+      break;
+    }
+    if ((SPIF_ReadReg1(Handle) & SPIF_STATUS1_BUSY) == 0)
+    {
+      retVal = true;
+      break;
+    }
+  }
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_FindChip(SPIF_HandleTypeDef *Handle)
+{
+  uint8_t tx[4] = {SPIF_CMD_JEDECID, 0xFF, 0xFF, 0xFF};
+  uint8_t rx[4];
+  bool retVal = false;
+  do
+  {
+    dprintf("SPIF_FindChip()\r\n");
+    SPIF_CsPin(Handle, 0);
+    if (SPIF_TransmitReceive(Handle, tx, rx, 4, 100) == false)
+    {
+      SPIF_CsPin(Handle, 1);
+      break;
+    }
+    SPIF_CsPin(Handle, 1);
+    dprintf("CHIP ID: 0x%02X%02X%02X\r\n", rx[1], rx[2], rx[3]);
+    Handle->Manufactor = rx[1];
+    Handle->MemType = rx[2];
+    Handle->Size = rx[3];
+
+    dprintf("SPIF MANUFACTURE: ");
+    switch (Handle->Manufactor)
+    {
+    case SPIF_MANUFACTOR_WINBOND:
+      dprintf("WINBOND");
+      break;
+    case SPIF_MANUFACTOR_SPANSION:
+      dprintf("SPANSION");
+      break;
+    case SPIF_MANUFACTOR_MICRON:
+      dprintf("MICRON");
+      break;
+    case SPIF_MANUFACTOR_MACRONIX:
+      dprintf("MACRONIX");
+      break;
+    case SPIF_MANUFACTOR_ISSI:
+      dprintf("ISSI");
+      break;
+    case SPIF_MANUFACTOR_GIGADEVICE:
+      dprintf("GIGADEVICE");
+      break;
+    case SPIF_MANUFACTOR_AMIC:
+      dprintf("AMIC");
+      break;
+    case SPIF_MANUFACTOR_SST:
+      dprintf("SST");
+      break;
+    case SPIF_MANUFACTOR_HYUNDAI:
+      dprintf("HYUNDAI");
+      break;
+    case SPIF_MANUFACTOR_FUDAN:
+      dprintf("FUDAN");
+      break;
+    case SPIF_MANUFACTOR_ESMT:
+      dprintf("ESMT");
+      break;
+    case SPIF_MANUFACTOR_INTEL:
+      dprintf("INTEL");
+      break;
+    case SPIF_MANUFACTOR_SANYO:
+      dprintf("SANYO");
+      break;
+    case SPIF_MANUFACTOR_FUJITSU:
+      dprintf("FUJITSU");
+      break;
+    case SPIF_MANUFACTOR_EON:
+      dprintf("EON");
+      break;
+    case SPIF_MANUFACTOR_PUYA:
+      dprintf("PUYA");
+      break;
+    default:
+      Handle->Manufactor = SPIF_MANUFACTOR_ERROR;
+      dprintf("ERROR");
+      break;
+    }
+    dprintf(" - MEMTYPE: 0x%02X", Handle->MemType);
+    dprintf(" - SIZE: ");
+    switch (Handle->Size)
+    {
+    case SPIF_SIZE_1MBIT:
+      Handle->BlockCnt = 2;
+      dprintf("1 MBIT\r\n");
+      break;
+    case SPIF_SIZE_2MBIT:
+      Handle->BlockCnt = 4;
+      dprintf("2 MBIT\r\n");
+      break;
+    case SPIF_SIZE_4MBIT:
+      Handle->BlockCnt = 8;
+      dprintf("4 MBIT\r\n");
+      break;
+    case SPIF_SIZE_8MBIT:
+      Handle->BlockCnt = 16;
+      dprintf("8 MBIT\r\n");
+      break;
+    case SPIF_SIZE_16MBIT:
+      Handle->BlockCnt = 32;
+      dprintf("16 MBIT\r\n");
+      break;
+    case SPIF_SIZE_32MBIT:
+      Handle->BlockCnt = 64;
+      dprintf("32 MBIT\r\n");
+      break;
+    case SPIF_SIZE_64MBIT:
+      Handle->BlockCnt = 128;
+      dprintf("64 MBIT\r\n");
+      break;
+    case SPIF_SIZE_128MBIT:
+      Handle->BlockCnt = 256;
+      dprintf("128 MBIT\r\n");
+      break;
+    case SPIF_SIZE_256MBIT:
+      Handle->BlockCnt = 512;
+      dprintf("256 MBIT\r\n");
+      break;
+    case SPIF_SIZE_512MBIT:
+      Handle->BlockCnt = 1024;
+      dprintf("512 MBIT\r\n");
+      break;
+    default:
+      Handle->Size = SPIF_SIZE_ERROR;
+      dprintf("ERROR\r\n");
+      break;
+    }
+
+    Handle->SectorCnt = Handle->BlockCnt * 16;
+    Handle->PageCnt = (Handle->SectorCnt * SPIF_SECTOR_SIZE) / SPIF_PAGE_SIZE;
+    dprintf("SPIF BLOCK CNT: %ld\r\n", Handle->BlockCnt);
+    dprintf("SPIF SECTOR CNT: %ld\r\n", Handle->SectorCnt);
+    dprintf("SPIF PAGE CNT: %ld\r\n", Handle->PageCnt);
+    dprintf("SPIF STATUS1: 0x%02X\r\n", SPIF_ReadReg1(Handle));
+    dprintf("SPIF STATUS2: 0x%02X\r\n", SPIF_ReadReg2(Handle));
+    dprintf("SPIF STATUS3: 0x%02X\r\n", SPIF_ReadReg3(Handle));
+    retVal = true;
+
+  } while (0);
+
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_WriteFn(SPIF_HandleTypeDef *Handle, uint32_t PageNumber, uint8_t *Data, uint32_t Size, uint32_t Offset)
+{
+  bool retVal = false;
+  uint32_t address = 0, maximum = SPIF_PAGE_SIZE - Offset;
+  uint8_t tx[5];
+  do
+  {
+#if SPIF_DEBUG != SPIF_DEBUG_DISABLE
+    uint32_t dbgTime = HAL_GetTick();
+#endif
+    dprintf("SPIF_WritePage() START PAGE %ld\r\n", PageNumber);
+    if (PageNumber >= Handle->PageCnt)
+    {
+      dprintf("SPIF_WritePage() ERROR PageNumber\r\n");
+      break;
+    }
+    if (Offset >= SPIF_PAGE_SIZE)
+    {
+      dprintf("SPIF_WritePage() ERROR Offset\r\n");
+      break;
+    }
+    if (Size > maximum)
+    {
+      Size = maximum;
+    }
+    address = SPIF_PageToAddress(PageNumber) + Offset;
+#if SPIF_DEBUG == SPIF_DEBUG_FULL
+      dprintf("SPIF WRITING {\r\n0x%02X", Data[0]);
+      for (int i = 1; i < Size; i++)
+      {
+        if (i % 8 == 0)
+        {
+          dprintf("\r\n");
+        }
+        dprintf(", 0x%02X", Data[i]);
+      }
+      dprintf("\r\n}\r\n");
+#endif
+    if (SPIF_WriteEnable(Handle) == false)
+    {
+      break;
+    }
+    SPIF_CsPin(Handle, 0);
+    if (Handle->BlockCnt >= 512)
+    {
+      tx[0] = SPIF_CMD_PAGEPROG4ADD;
+      tx[1] = (address & 0xFF000000) >> 24;
+      tx[2] = (address & 0x00FF0000) >> 16;
+      tx[3] = (address & 0x0000FF00) >> 8;
+      tx[4] = (address & 0x000000FF);
+      if (SPIF_Transmit(Handle, tx, 5, 100) == false)
+      {
+        SPIF_CsPin(Handle, 1);
+        break;
+      }
+    }
+    else
+    {
+      tx[0] = SPIF_CMD_PAGEPROG3ADD;
+      tx[1] = (address & 0x00FF0000) >> 16;
+      tx[2] = (address & 0x0000FF00) >> 8;
+      tx[3] = (address & 0x000000FF);
+      if (SPIF_Transmit(Handle, tx, 4, 100) == false)
+      {
+        SPIF_CsPin(Handle, 1);
+        break;
+      }
+    }
+    if (SPIF_Transmit(Handle, Data, Size, 1000) == false)
+    {
+      SPIF_CsPin(Handle, 1);
+      break;
+    }
+    SPIF_CsPin(Handle, 1);
+    if (SPIF_WaitForWriting(Handle, 100))
+    {
+      dprintf("SPIF_WritePage() %d BYTES WITERN DONE AFTER %ld ms\r\n", (uint16_t)Size, HAL_GetTick() - dbgTime);
+      retVal = true;
+    }
+
+  } while (0);
+
+  SPIF_WriteDisable(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+bool SPIF_ReadFn(SPIF_HandleTypeDef *Handle, uint32_t Address, uint8_t *Data, uint32_t Size)
+{
+  bool retVal = false;
+  uint8_t tx[5];
+  do
+  {
+#if SPIF_DEBUG != SPIF_DEBUG_DISABLE
+    uint32_t dbgTime = HAL_GetTick();
+#endif
+    dprintf("SPIF_ReadAddress() START ADDRESS %ld\r\n", Address);
+    SPIF_CsPin(Handle, 0);
+    if (Handle->BlockCnt >= 512)
+    {
+      tx[0] = SPIF_CMD_READDATA4ADD;
+      tx[1] = (Address & 0xFF000000) >> 24;
+      tx[2] = (Address & 0x00FF0000) >> 16;
+      tx[3] = (Address & 0x0000FF00) >> 8;
+      tx[4] = (Address & 0x000000FF);
+      if (SPIF_Transmit(Handle, tx, 5, 100) == false)
+      {
+        SPIF_CsPin(Handle, 1);
+        break;
+      }
+    }
+    else
+    {
+      tx[0] = SPIF_CMD_READDATA3ADD;
+      tx[1] = (Address & 0x00FF0000) >> 16;
+      tx[2] = (Address & 0x0000FF00) >> 8;
+      tx[3] = (Address & 0x000000FF);
+      if (SPIF_Transmit(Handle, tx, 4, 100) == false)
+      {
+        SPIF_CsPin(Handle, 1);
+        break;
+      }
+    }
+    if (SPIF_Receive(Handle, Data, Size, 2000) == false)
+    {
+      SPIF_CsPin(Handle, 1);
+      break;
+    }
+    SPIF_CsPin(Handle, 1);
+    dprintf("SPIF_ReadAddress() %d BYTES READ DONE AFTER %ld ms\r\n", (uint16_t)Size, HAL_GetTick() - dbgTime);
+#if SPIF_DEBUG == SPIF_DEBUG_FULL
+    dprintf("{\r\n0x%02X", Data[0]);
+    for (int i = 1; i < Size; i++)
+    {
+      if (i % 8 == 0)
+      {
+        dprintf("\r\n");
+      }
+      dprintf(", 0x%02X", Data[i]);
+    }
+    dprintf("\r\n}\r\n");
+#endif
+    retVal = true;
+
+  } while (0);
+
+  return retVal;
+}
+
+/************************************************************************************************************
+**************    Public Functions
+************************************************************************************************************/
+
+/**
+  * @brief  Initialize the SPIF.
+  * @note   Enable and configure the SPI and Set GPIO as output for CS pin on the CubeMX
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  *HSpi: Pointer to a SPI_HandleTypeDef structure
+  * @param  *Gpio: Pointer to a GPIO_TypeDef structure for CS
+  * @param  Pin: Pin of CS
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_Init(SPIF_HandleTypeDef *Handle, SPI_HandleTypeDef *HSpi, GPIO_TypeDef *Gpio, uint16_t Pin)
+{
+  bool retVal = false;
+  do
+  {
+    if ((Handle == NULL) || (HSpi == NULL) || (Gpio == NULL) || (Handle->Inited == 1))
+    {
+      dprintf("SPIF_Init() Error, Wrong Parameter\r\n");
+      break;
+    }
+    memset(Handle, 0, sizeof(SPIF_HandleTypeDef));
+    Handle->HSpi = HSpi;
+    Handle->Gpio = Gpio;
+    Handle->Pin = Pin;
+    SPIF_CsPin(Handle, 1);
+    /* wait for stable VCC */
+    while (HAL_GetTick() < 20)
+    {
+      SPIF_Delay(1);
+    }
+    if (SPIF_WriteDisable(Handle) == false)
+    {
+      break;
+    }
+    retVal = SPIF_FindChip(Handle);
+    if (retVal)
+    {
+      Handle->Inited = 1;
+      dprintf("SPIF_Init() Done\r\n");
+    }
+
+  } while (0);
+
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Full Erase chip.
+  * @note   Send the Full-Erase-chip command and wait for completion
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_EraseChip(SPIF_HandleTypeDef *Handle)
+{
+  SPIF_Lock(Handle);
+  bool retVal = false;
+  uint8_t tx[1] = {SPIF_CMD_CHIPERASE1};
+  do
+  {
+#if SPIF_DEBUG != SPIF_DEBUG_DISABLE
+    uint32_t dbgTime = HAL_GetTick();
+#endif
+    dprintf("SPIF_EraseChip() START\r\n");
+    if (SPIF_WriteEnable(Handle) == false)
+    {
+      break;
+    }
+    SPIF_CsPin(Handle, 0);
+    if (SPIF_Transmit(Handle, tx, 1, 100) == false)
+    {
+      SPIF_CsPin(Handle, 1);
+      break;
+    }
+    SPIF_CsPin(Handle, 1);
+    if (SPIF_WaitForWriting(Handle, Handle->BlockCnt * 1000))
+    {
+      dprintf("SPIF_EraseChip() DONE AFTER %ld ms\r\n", HAL_GetTick() - dbgTime);
+      retVal = true;
+    }
+
+  } while (0);
+
+  SPIF_WriteDisable(Handle);
+  SPIF_UnLock(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Erase Sector.
+  * @note   Send the Erase-Sector command and wait for completion
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  Sector: Selected Sector
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_EraseSector(SPIF_HandleTypeDef *Handle, uint32_t Sector)
+{
+  SPIF_Lock(Handle);
+  bool retVal = false;
+  uint32_t address = Sector * SPIF_SECTOR_SIZE;
+  uint8_t tx[5];
+  do
+  {
+#if SPIF_DEBUG != SPIF_DEBUG_DISABLE
+    uint32_t dbgTime = HAL_GetTick();
+#endif
+    dprintf("SPIF_EraseSector() START SECTOR %ld\r\n", Sector);
+    if (Sector >= Handle->SectorCnt)
+    {
+      dprintf("SPIF_EraseSector() ERROR Sector NUMBER\r\n");
+      break;
+    }
+    if (SPIF_WriteEnable(Handle) == false)
+    {
+      break;
+    }
+    SPIF_CsPin(Handle, 0);
+    if (Handle->BlockCnt >= 512)
+    {
+      tx[0] = SPIF_CMD_SECTORERASE4ADD;
+      tx[1] = (address & 0xFF000000) >> 24;
+      tx[2] = (address & 0x00FF0000) >> 16;
+      tx[3] = (address & 0x0000FF00) >> 8;
+      tx[4] = (address & 0x000000FF);
+      if (SPIF_Transmit(Handle, tx, 5, 100) == false)
+      {
+        SPIF_CsPin(Handle, 1);
+        break;
+      }
+    }
+    else
+    {
+      tx[0] = SPIF_CMD_SECTORERASE3ADD;
+      tx[1] = (address & 0x00FF0000) >> 16;
+      tx[2] = (address & 0x0000FF00) >> 8;
+      tx[3] = (address & 0x000000FF);
+      if (SPIF_Transmit(Handle, tx, 4, 100) == false)
+      {
+        SPIF_CsPin(Handle, 1);
+        break;
+      }
+    }
+    SPIF_CsPin(Handle, 1);
+    if (SPIF_WaitForWriting(Handle, 1000))
+    {
+      dprintf("SPIF_EraseSector() DONE AFTER %ld ms\r\n", HAL_GetTick() - dbgTime);
+      retVal = true;
+    }
+
+  } while (0);
+
+  SPIF_WriteDisable(Handle);
+  SPIF_UnLock(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Erase Block.
+  * @note   Send the Erase-Block command and wait for completion
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  Sector: Selected Block
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_EraseBlock(SPIF_HandleTypeDef *Handle, uint32_t Block)
+{
+  SPIF_Lock(Handle);
+  bool retVal = false;
+  uint32_t address = Block * SPIF_BLOCK_SIZE;
+  uint8_t tx[5];
+  do
+  {
+#if SPIF_DEBUG != SPIF_DEBUG_DISABLE
+    uint32_t dbgTime = HAL_GetTick();
+#endif
+    dprintf("SPIF_EraseBlock() START PAGE %ld\r\n", Block);
+    if (Block >= Handle->BlockCnt)
+    {
+      dprintf("SPIF_EraseBlock() ERROR Block NUMBER\r\n");
+      break;
+    }
+    if (SPIF_WriteEnable(Handle) == false)
+    {
+      break;
+    }
+    SPIF_CsPin(Handle, 0);
+    if (Handle->BlockCnt >= 512)
+    {
+      tx[0] = SPIF_CMD_BLOCKERASE4ADD;
+      tx[1] = (address & 0xFF000000) >> 24;
+      tx[2] = (address & 0x00FF0000) >> 16;
+      tx[3] = (address & 0x0000FF00) >> 8;
+      tx[4] = (address & 0x000000FF);
+      if (SPIF_Transmit(Handle, tx, 5, 100) == false)
+      {
+        SPIF_CsPin(Handle, 1);
+        break;
+      }
+    }
+    else
+    {
+      tx[0] = SPIF_CMD_BLOCKERASE3ADD;
+      tx[1] = (address & 0x00FF0000) >> 16;
+      tx[2] = (address & 0x0000FF00) >> 8;
+      tx[3] = (address & 0x000000FF);
+      if (SPIF_Transmit(Handle, tx, 4, 100) == false)
+      {
+        SPIF_CsPin(Handle, 1);
+        break;
+      }
+    }
+    SPIF_CsPin(Handle, 1);
+    if (SPIF_WaitForWriting(Handle, 3000))
+    {
+      dprintf("SPIF_EraseBlock() DONE AFTER %ld ms\r\n", HAL_GetTick() - dbgTime);
+      retVal = true;
+    }
+
+  } while (0);
+
+  SPIF_WriteDisable(Handle);
+  SPIF_UnLock(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Write data array to an Address
+  * @note   Write a data array with specified size.
+  * @note   All pages should be erased before write
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  Address: Start Address
+  * @param  *Data: Pointer to Data
+  * @param  Size: The length of data should be written. (in byte)
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_WriteAddress(SPIF_HandleTypeDef *Handle, uint32_t Address, uint8_t *Data, uint32_t Size)
+{
+  SPIF_Lock(Handle);
+  bool retVal = false;
+  uint32_t page, add, offset, remaining, length, maximum, index = 0;
+  add = Address;
+  remaining = Size;
+  do
+  {
+    page = SPIF_AddressToPage(add);
+    offset = add % SPIF_PAGE_SIZE;
+    maximum = SPIF_PAGE_SIZE - offset;
+    if (remaining <= maximum)
+    {
+      length = remaining;
+    }
+    else
+    {
+      length = maximum;
+    }
+    if (SPIF_WriteFn(Handle, page, &Data[index], length, offset) == false)
+    {
+      break;
+    }
+    add += length;
+    index += length;
+    remaining -= length;
+    if (remaining == 0)
+    {
+      retVal = true;
+      break;
+    }
+
+  } while (remaining > 0);
+
+  SPIF_UnLock(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Write data array to a Page
+  * @note   Write a data array with specified size.
+  * @note   The Page should be erased before write
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  PageNumber: Page Number
+  * @param  *Data: Pointer to Data
+  * @param  Size: The length of data should be written. (in byte)
+  * @param  Offset: The start point for writing data. (in byte)
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_WritePage(SPIF_HandleTypeDef *Handle, uint32_t PageNumber, uint8_t *Data, uint32_t Size, uint32_t Offset)
+{
+  SPIF_Lock(Handle);
+  bool retVal = false;
+  retVal = SPIF_WriteFn(Handle, PageNumber, Data, Size, Offset);
+  SPIF_UnLock(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Write data array to a Sector
+  * @note   Write a data array with specified size.
+  * @note   The Sector should be erased before write
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  SectorNumber: Sector Number
+  * @param  *Data: Pointer to Data
+  * @param  Size: The length of data should be written. (in byte)
+  * @param  Offset: The start point for writing data. (in byte)
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_WriteSector(SPIF_HandleTypeDef *Handle, uint32_t SectorNumber, uint8_t *Data, uint32_t Size, uint32_t Offset)
+{
+  SPIF_Lock(Handle);
+  bool retVal = true;
+  do
+  {
+    if (Offset >= SPIF_SECTOR_SIZE)
+    {
+      retVal = false;
+      break;
+    }
+    if (Size > (SPIF_SECTOR_SIZE - Offset))
+    {
+      Size = SPIF_SECTOR_SIZE - Offset;
+    }
+    uint32_t bytesWritten = 0;
+    uint32_t pageNumber = SectorNumber * (SPIF_SECTOR_SIZE / SPIF_PAGE_SIZE);
+    pageNumber += Offset / SPIF_PAGE_SIZE;
+    uint32_t remainingBytes = Size;
+    uint32_t pageOffset = Offset % SPIF_PAGE_SIZE;
+    while (remainingBytes > 0 && pageNumber < ((SectorNumber + 1) * (SPIF_SECTOR_SIZE / SPIF_PAGE_SIZE)))
+    {
+      uint32_t bytesToWrite = (remainingBytes > (SPIF_PAGE_SIZE - pageOffset)) ? (SPIF_PAGE_SIZE - pageOffset) : remainingBytes;
+      if (SPIF_WriteFn(Handle, pageNumber, Data + bytesWritten, bytesToWrite, pageOffset) == false)
+      {
+        retVal = false;
+        break;
+      }
+      bytesWritten += bytesToWrite;
+      remainingBytes -= bytesToWrite;
+      pageNumber++;
+      pageOffset = 0;
+    }
+  } while (0);
+  SPIF_UnLock(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Write data array to a Block
+  * @note   Write a data array with specified size.
+  * @note   The Block should be erased before write
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  SectorNumber: Block Number
+  * @param  *Data: Pointer to Data
+  * @param  Size: The length of data should be written. (in byte)
+  * @param  Offset: The start point for writing data. (in byte)
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_WriteBlock(SPIF_HandleTypeDef *Handle, uint32_t BlockNumber, uint8_t *Data, uint32_t Size, uint32_t Offset)
+{
+  SPIF_Lock(Handle);
+  bool retVal = true;
+  do
+  {
+    if (Offset >= SPIF_BLOCK_SIZE)
+    {
+      retVal = false;
+      break;
+    }
+    if (Size > (SPIF_BLOCK_SIZE - Offset))
+    {
+      Size = SPIF_BLOCK_SIZE - Offset;
+    }
+    uint32_t bytesWritten = 0;
+    uint32_t pageNumber = BlockNumber * (SPIF_BLOCK_SIZE / SPIF_PAGE_SIZE);
+    pageNumber += Offset / SPIF_PAGE_SIZE;
+    uint32_t remainingBytes = Size;
+    uint32_t pageOffset = Offset % SPIF_PAGE_SIZE;
+    while (remainingBytes > 0 && pageNumber < ((BlockNumber + 1) * (SPIF_BLOCK_SIZE / SPIF_PAGE_SIZE)))
+    {
+      uint32_t bytesToWrite = (remainingBytes > (SPIF_PAGE_SIZE - pageOffset)) ? (SPIF_PAGE_SIZE - pageOffset) : remainingBytes;
+      if (SPIF_WriteFn(Handle, pageNumber, Data + bytesWritten, bytesToWrite, pageOffset) == false)
+      {
+        retVal = false;
+        break;
+      }
+      bytesWritten += bytesToWrite;
+      remainingBytes -= bytesToWrite;
+      pageNumber++;
+      pageOffset = 0;
+    }
+
+  } while (0);
+
+  SPIF_UnLock(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Read From Address
+  * @note   Read data from memory and copy to array
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  Address: Start Address
+  * @param  *Data: Pointer to Data (output)
+  * @param  Size: The length of data should be written. (in byte)
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_ReadAddress(SPIF_HandleTypeDef *Handle, uint32_t Address, uint8_t *Data, uint32_t Size)
+{
+  SPIF_Lock(Handle);
+  bool retVal = false;
+  retVal = SPIF_ReadFn(Handle, Address, Data, Size);
+  SPIF_UnLock(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Read a Page
+  * @note   Read a page and copy to array
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  PageNumber: Page Number
+  * @param  *Data: Pointer to Data (output)
+  * @param  Size: The length of data should be read. (in byte)
+  * @param  Offset: The start point for Reading data. (in byte)
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_ReadPage(SPIF_HandleTypeDef *Handle, uint32_t PageNumber, uint8_t *Data, uint32_t Size, uint32_t Offset)
+{
+  SPIF_Lock(Handle);
+  bool retVal = false;
+  uint32_t address = SPIF_PageToAddress(PageNumber) + Offset;
+  uint32_t maximum = SPIF_PAGE_SIZE - Offset;
+  if (Size > maximum)
+  {
+    Size = maximum;
+  }
+  retVal = SPIF_ReadFn(Handle, address, Data, Size);
+  SPIF_UnLock(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Read a Sector
+  * @note   Read a Sector and copy to array
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  SectorNumber: Sector Number
+  * @param  *Data: Pointer to Data (output)
+  * @param  Size: The length of data should be read. (in byte)
+  * @param  Offset: The start point for Reading data. (in byte)
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_ReadSector(SPIF_HandleTypeDef *Handle, uint32_t SectorNumber, uint8_t *Data, uint32_t Size, uint32_t Offset)
+{
+  SPIF_Lock(Handle);
+  bool retVal = false;
+  uint32_t address = SPIF_SectorToAddress(SectorNumber) + Offset;
+  uint32_t maximum = SPIF_SECTOR_SIZE - Offset;
+  if (Size > maximum)
+  {
+    Size = maximum;
+  }
+  retVal = SPIF_ReadFn(Handle, address, Data, Size);
+  SPIF_UnLock(Handle);
+  return retVal;
+}
+
+/***********************************************************************************************************/
+
+/**
+  * @brief  Read a Block
+  * @note   Read a Block and copy to array
+  *
+  * @param  *Handle: Pointer to SPIF_HandleTypeDef structure
+  * @param  BlockNumber: Block Number
+  * @param  *Data: Pointer to Data (output)
+  * @param  Size: The length of data should be read. (in byte)
+  * @param  Offset: The start point for Reading data. (in byte)
+  *
+  * @retval bool: true or false
+  */
+bool SPIF_ReadBlock(SPIF_HandleTypeDef *Handle, uint32_t BlockNumber, uint8_t *Data, uint32_t Size, uint32_t Offset)
+{
+  SPIF_Lock(Handle);
+  bool retVal = false;
+  uint32_t address = SPIF_BlockToAddress(BlockNumber) + Offset;
+  uint32_t maximum = SPIF_BLOCK_SIZE - Offset;
+  if (Size > maximum)
+  {
+    Size = maximum;
+  }
+  retVal = SPIF_ReadFn(Handle, address, Data, Size);
+  SPIF_UnLock(Handle);
+  return retVal;
+}

+ 164 - 0
Middlewares/Third_Party/NimaLTD_Driver/SPIF/spif.h

@@ -0,0 +1,164 @@
+#ifndef _SPIF_H_
+#define _SPIF_H_
+
+/***********************************************************************************************************
+
+  Author:     Nima Askari
+  Github:     https://www.github.com/NimaLTD
+  LinkedIn:   https://www.linkedin.com/in/nimaltd
+  Youtube:    https://www.youtube.com/@nimaltd
+  Instagram:  https://instagram.com/github.NimaLTD
+
+  Version:    2.3.2
+
+  History:
+  
+              2.3.2
+              - Fixed SPIF_Read() offset. Fixed ISSI ID
+              
+			  2.3.1
+              - Fixed SPIF_WriteSector() and SPIF_WriteBlock()
+              
+              2.3.0
+              - Added ThreadX Configuration
+
+              2.2.2
+              - Compile error
+
+              2.2.1
+              - Updated SPIF_WriteAddress()
+  
+              2.2.0
+              - Added SPI_Trasmit and SPI_Receive again :)
+
+              2.1.0
+              - Added Support HAL-DMA
+              - Removed SPI_Trasmit function
+
+              2.0.1
+              - Removed SPI_Receive function
+
+              2.0.0
+              - Rewrite again
+              - Supported STM32CubeMx Packet installer
+
+***********************************************************************************************************/
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/************************************************************************************************************
+**************    Include Headers
+************************************************************************************************************/
+
+#include <stdbool.h>
+#include <string.h>
+#include "NimaLTD.I-CUBE-SPIF_conf.h"
+#include "spi.h"
+
+/************************************************************************************************************
+**************    Public Definitions
+************************************************************************************************************/
+
+#define SPIF_PAGE_SIZE                      0x100
+#define SPIF_SECTOR_SIZE                    0x1000
+#define SPIF_BLOCK_SIZE                     0x10000
+
+#define SPIF_PageToSector(PageNumber)      ((PageNumber * SPIF_PAGE_SIZE) / SPIF_SECTOR_SIZE)
+#define SPIF_PageToBlock(PageNumber)       ((PageNumber * SPIF_PAGE_SIZE) / SPIF_BLOCK_SIZE)
+#define SPIF_SectorToBlock(SectorNumber)   ((SectorNumber * SPIF_SECTOR_SIZE) / SPIF_BLOCK_SIZE)
+#define SPIF_SectorToPage(SectorNumber)    ((SectorNumber * SPIF_SECTOR_SIZE) / SPIF_PAGE_SIZE)
+#define SPIF_BlockToPage(BlockNumber)      ((BlockNumber * SPIF_BLOCK_SIZE) / SPIF_PAGE_SIZE)
+#define SPIF_PageToAddress(PageNumber)     (PageNumber * SPIF_PAGE_SIZE)
+#define SPIF_SectorToAddress(SectorNumber) (SectorNumber * SPIF_SECTOR_SIZE)
+#define SPIF_BlockToAddress(BlockNumber)   (BlockNumber * SPIF_BLOCK_SIZE)
+#define SPIF_AddressToPage(Address)        (Address / SPIF_PAGE_SIZE)
+#define SPIF_AddressToSector(Address)      (Address / SPIF_SECTOR_SIZE)
+#define SPIF_AddressToBlock(Address)       (Address / SPIF_BLOCK_SIZE)
+
+/************************************************************************************************************
+**************    Public struct/enum
+************************************************************************************************************/
+
+typedef enum
+{
+  SPIF_MANUFACTOR_ERROR = 0,
+  SPIF_MANUFACTOR_WINBOND = 0xEF,
+  SPIF_MANUFACTOR_ISSI = 0x9D,
+  SPIF_MANUFACTOR_MICRON = 0x20,
+  SPIF_MANUFACTOR_GIGADEVICE = 0xC8,
+  SPIF_MANUFACTOR_MACRONIX = 0xC2,
+  SPIF_MANUFACTOR_SPANSION = 0x01,
+  SPIF_MANUFACTOR_AMIC = 0x37,
+  SPIF_MANUFACTOR_SST = 0xBF,
+  SPIF_MANUFACTOR_HYUNDAI = 0xAD,
+  SPIF_MANUFACTOR_ATMEL = 0x1F,
+  SPIF_MANUFACTOR_FUDAN = 0xA1,
+  SPIF_MANUFACTOR_ESMT = 0x8C,
+  SPIF_MANUFACTOR_INTEL = 0x89,
+  SPIF_MANUFACTOR_SANYO = 0x62,
+  SPIF_MANUFACTOR_FUJITSU = 0x04,
+  SPIF_MANUFACTOR_EON = 0x1C,
+  SPIF_MANUFACTOR_PUYA = 0x85,
+
+} SPIF_ManufactorTypeDef;
+
+typedef enum
+{
+  SPIF_SIZE_ERROR = 0,
+  SPIF_SIZE_1MBIT = 0x11,
+  SPIF_SIZE_2MBIT = 0x12,
+  SPIF_SIZE_4MBIT = 0x13,
+  SPIF_SIZE_8MBIT = 0x14,
+  SPIF_SIZE_16MBIT = 0x15,
+  SPIF_SIZE_32MBIT = 0x16,
+  SPIF_SIZE_64MBIT = 0x17,
+  SPIF_SIZE_128MBIT = 0x18,
+  SPIF_SIZE_256MBIT = 0x19,
+  SPIF_SIZE_512MBIT = 0x20,
+
+} SPIF_SizeTypeDef;
+
+typedef struct
+{
+  SPI_HandleTypeDef      *HSpi;
+  GPIO_TypeDef           *Gpio;
+  SPIF_ManufactorTypeDef Manufactor;
+  SPIF_SizeTypeDef       Size;
+  uint8_t                Inited;
+  uint8_t                MemType;
+  uint8_t                Lock;
+  uint8_t                Reserved;
+  uint32_t               Pin;
+  uint32_t               PageCnt;
+  uint32_t               SectorCnt;
+  uint32_t               BlockCnt;
+
+} SPIF_HandleTypeDef;
+
+/************************************************************************************************************
+**************    Public Functions
+************************************************************************************************************/
+
+bool SPIF_Init(SPIF_HandleTypeDef *Handle, SPI_HandleTypeDef *HSpi, GPIO_TypeDef *Gpio, uint16_t Pin);
+
+bool SPIF_EraseChip(SPIF_HandleTypeDef *Handle);
+bool SPIF_EraseSector(SPIF_HandleTypeDef *Handle, uint32_t Sector);
+bool SPIF_EraseBlock(SPIF_HandleTypeDef *Handle, uint32_t Block);
+
+bool SPIF_WriteAddress(SPIF_HandleTypeDef *Handle, uint32_t Address, uint8_t *Data, uint32_t Size);
+bool SPIF_WritePage(SPIF_HandleTypeDef *Handle, uint32_t PageNumber, uint8_t *Data, uint32_t Size, uint32_t Offset);
+bool SPIF_WriteSector(SPIF_HandleTypeDef *Handle, uint32_t SectorNumber, uint8_t *Data, uint32_t Size, uint32_t Offset);
+bool SPIF_WriteBlock(SPIF_HandleTypeDef *Handle, uint32_t BlockNumber, uint8_t *Data, uint32_t Size, uint32_t Offset);
+
+bool SPIF_ReadAddress(SPIF_HandleTypeDef *Handle, uint32_t Address, uint8_t *Data, uint32_t Size);
+bool SPIF_ReadPage(SPIF_HandleTypeDef *Handle, uint32_t PageNumber, uint8_t *Data, uint32_t Size, uint32_t Offset);
+bool SPIF_ReadSector(SPIF_HandleTypeDef *Handle, uint32_t SectorNumber, uint8_t *Data, uint32_t Size, uint32_t Offset);
+bool SPIF_ReadBlock(SPIF_HandleTypeDef *Handle, uint32_t BlockNumber, uint8_t *Data, uint32_t Size, uint32_t Offset);
+
+#ifdef __cplusplus
+}
+#endif
+#endif

+ 20 - 5
cmake/stm32cubemx/CMakeLists.txt

@@ -11,15 +11,20 @@ set(MX_Defines_Syms
 # STM32CubeMX generated include paths
 set(MX_Include_Dirs
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Inc
-    ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Inc
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../I-CUBE-SPIF
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Inc/Legacy
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Inc
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/CMSIS/Device/ST/STM32F3xx/Include
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/CMSIS/Include
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Middlewares/Third_Party/NimaLTD_Driver/SPIF
 )
 
 # STM32CubeMX generated application sources
 set(MX_Application_Src
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/main.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/gpio.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/spi.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/usart.c
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/stm32f3xx_it.c
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/stm32f3xx_hal_msp.c
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/sysmem.c
@@ -30,8 +35,6 @@ set(MX_Application_Src
 # STM32 HAL/LL Drivers
 set(STM32_Drivers_Src
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Core/Src/system_stm32f3xx.c
-    ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_uart.c
-    ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_uart_ex.c
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal.c
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_rcc.c
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_rcc_ex.c
@@ -44,12 +47,19 @@ set(STM32_Drivers_Src
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_flash_ex.c
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_i2c.c
     ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_i2c_ex.c
-    ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_exti.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_exti.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_spi.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_spi_ex.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_uart.c
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Drivers/STM32F3xx_HAL_Driver/Src/stm32f3xx_hal_uart_ex.c
 )
 
 # Drivers Midllewares
 
 
+set(I-CUBE-SPIF_Src
+    ${CMAKE_CURRENT_SOURCE_DIR}/../../Middlewares/Third_Party/NimaLTD_Driver/SPIF/spif.c
+)
 
 # Link directories setup
 set(MX_LINK_DIRS
@@ -59,7 +69,7 @@ set(MX_LINK_DIRS
 set(MX_LINK_LIBS 
     STM32_Drivers
     ${TOOLCHAIN_LINK_LIBRARIES}
-    
+    I-CUBE-SPIF
	
 )
 # Interface library for includes and symbols
 add_library(stm32cubemx INTERFACE)
@@ -71,6 +81,11 @@ add_library(STM32_Drivers OBJECT)
 target_sources(STM32_Drivers PRIVATE ${STM32_Drivers_Src})
 target_link_libraries(STM32_Drivers PUBLIC stm32cubemx)
 
+
+# Create I-CUBE-SPIF static library
+add_library(I-CUBE-SPIF OBJECT)
+target_sources(I-CUBE-SPIF PRIVATE ${I-CUBE-SPIF_Src})
+target_link_libraries(I-CUBE-SPIF PUBLIC stm32cubemx)
 
 # Add STM32CubeMX generated application sources to the project
 target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${MX_Application_Src})

+ 77 - 65
embedded2.ioc

@@ -3,30 +3,35 @@ CAD.formats=
 CAD.pinconfig=
 CAD.provider=
 File.Version=6
+GPIO.groupedBy=Group By Peripherals
 KeepUserPlacement=false
 Mcu.CPN=STM32F302R8T6
 Mcu.Family=STM32F3
 Mcu.IP0=NVIC
 Mcu.IP1=RCC
-Mcu.IP2=SYS
-Mcu.IP3=USART2
-Mcu.IPNb=4
+Mcu.IP2=SPI2
+Mcu.IP3=SYS
+Mcu.IP4=USART2
+Mcu.IPNb=5
 Mcu.Name=STM32F302R(6-8)Tx
 Mcu.Package=LQFP64
-Mcu.Pin0=PC13
-Mcu.Pin1=PC14 - OSC32_IN
-Mcu.Pin10=PB3
-Mcu.Pin11=VP_SYS_VS_Systick
-Mcu.Pin2=PC15 - OSC32_OUT
-Mcu.Pin3=PF0-OSC_IN
-Mcu.Pin4=PF1-OSC_OUT
-Mcu.Pin5=PA2
-Mcu.Pin6=PA3
-Mcu.Pin7=PB13
-Mcu.Pin8=PA13
-Mcu.Pin9=PA14
-Mcu.PinsNb=12
-Mcu.ThirdPartyNb=0
+Mcu.Pin0=PC14 - OSC32_IN
+Mcu.Pin1=PC15 - OSC32_OUT
+Mcu.Pin10=PA14
+Mcu.Pin11=PB6
+Mcu.Pin12=VP_SYS_VS_Systick
+Mcu.Pin13=VP_NimaLTD.I-CUBE-SPIF_VS_DriverJjSPIF_1.0.0_2.3.2
+Mcu.Pin2=PF0-OSC_IN
+Mcu.Pin3=PF1-OSC_OUT
+Mcu.Pin4=PA2
+Mcu.Pin5=PA3
+Mcu.Pin6=PB13
+Mcu.Pin7=PB14
+Mcu.Pin8=PB15
+Mcu.Pin9=PA13
+Mcu.PinsNb=14
+Mcu.ThirdParty0=NimaLTD.I-CUBE-SPIF.2.3.2
+Mcu.ThirdPartyNb=1
 Mcu.UserConstants=
 Mcu.UserName=STM32F302R8Tx
 MxCube.Version=6.15.0
@@ -42,9 +47,11 @@ NVIC.PriorityGroup=NVIC_PRIORITYGROUP_0
 NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
 NVIC.SysTick_IRQn=true\:0\:0\:true\:false\:true\:true\:true\:false
 NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:true\:false\:false
-PA13.GPIOParameters=GPIO_Label
-PA13.GPIO_Label=TMS
-PA13.Locked=true
+NimaLTD.I-CUBE-SPIF.2.3.2.DriverJjSPIF=true
+NimaLTD.I-CUBE-SPIF.2.3.2.DriverJjSPIF_Checked=true
+NimaLTD.I-CUBE-SPIF.2.3.2.IPParameters=SPIF_DEBUG,DriverJjSPIF
+NimaLTD.I-CUBE-SPIF.2.3.2.SPIF_DEBUG=SPIF_DEBUG_FULL
+NimaLTD.I-CUBE-SPIF.2.3.2_SwParameter=DriverJjSPIF\:true;
 PA13.Mode=Serial_Wire
 PA13.Signal=SYS_JTMS-SWDIO
 PA14.GPIOParameters=GPIO_Label
@@ -68,20 +75,17 @@ PA3.GPIO_Speed=GPIO_SPEED_FREQ_LOW
 PA3.Locked=true
 PA3.Mode=Asynchronous
 PA3.Signal=USART2_RX
-PB13.GPIOParameters=GPIO_Label
-PB13.GPIO_Label=LD2 [Green Led]
-PB13.Locked=true
-PB13.Signal=GPIO_Output
-PB3.GPIOParameters=GPIO_Label
-PB3.GPIO_Label=SWO
-PB3.Locked=true
-PB3.Mode=Trace_Asynchronous_SW
-PB3.Signal=SYS_JTDO-TRACESWO
-PC13.GPIOParameters=GPIO_Label,GPIO_ModeDefaultEXTI
-PC13.GPIO_Label=B1 [Blue PushButton]
-PC13.GPIO_ModeDefaultEXTI=GPIO_MODE_IT_FALLING
-PC13.Locked=true
-PC13.Signal=GPXTI13
+PB13.Mode=Full_Duplex_Master
+PB13.Signal=SPI2_SCK
+PB14.Mode=Full_Duplex_Master
+PB14.Signal=SPI2_MISO
+PB15.Mode=Full_Duplex_Master
+PB15.Signal=SPI2_MOSI
+PB6.GPIOParameters=PinState,GPIO_Label
+PB6.GPIO_Label=SPI_CS
+PB6.Locked=true
+PB6.PinState=GPIO_PIN_SET
+PB6.Signal=GPIO_Output
 PC14\ -\ OSC32_IN.Locked=true
 PC14\ -\ OSC32_IN.Mode=LSE-External-Oscillator
 PC14\ -\ OSC32_IN.Signal=RCC_OSC32_IN
@@ -99,7 +103,7 @@ ProjectManager.BackupPrevious=false
 ProjectManager.CompilerLinker=GCC
 ProjectManager.CompilerOptimize=6
 ProjectManager.ComputerToolchain=false
-ProjectManager.CoupleFile=false
+ProjectManager.CoupleFile=true
 ProjectManager.CustomerFirmwarePackage=
 ProjectManager.DefaultFWLocation=true
 ProjectManager.DeletePrevious=true
@@ -125,55 +129,63 @@ ProjectManager.ToolChainLocation=
 ProjectManager.UAScriptAfterPath=
 ProjectManager.UAScriptBeforePath=
 ProjectManager.UnderRoot=false
-ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_USART2_UART_Init-USART2-false-HAL-true
-RCC.ADC12outputFreq_Value=64000000
-RCC.AHBFreq_Value=64000000
+ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_USART2_UART_Init-USART2-false-HAL-true,4-MX_SPI2_Init-SPI2-false-HAL-true,5-MX_USB_PCD_Init-USB-false-HAL-true
+RCC.ADC12outputFreq_Value=48000000
+RCC.AHBFreq_Value=48000000
 RCC.APB1CLKDivider=RCC_HCLK_DIV2
-RCC.APB1Freq_Value=32000000
-RCC.APB1TimFreq_Value=64000000
-RCC.APB2Freq_Value=64000000
-RCC.APB2TimFreq_Value=64000000
-RCC.CortexFreq_Value=64000000
-RCC.FCLKCortexFreq_Value=64000000
+RCC.APB1Freq_Value=24000000
+RCC.APB1TimFreq_Value=48000000
+RCC.APB2Freq_Value=48000000
+RCC.APB2TimFreq_Value=48000000
+RCC.CortexFreq_Value=48000000
+RCC.FCLKCortexFreq_Value=48000000
 RCC.FamilyName=M
-RCC.HCLKFreq_Value=64000000
-RCC.HSEPLLFreq_Value=4000000
+RCC.HCLKFreq_Value=48000000
+RCC.HSEPLLFreq_Value=8000000
 RCC.HSE_VALUE=8000000
 RCC.HSIPLLFreq_Value=4000000
 RCC.HSI_VALUE=8000000
 RCC.I2C1Freq_Value=8000000
 RCC.I2C2Freq_Value=8000000
 RCC.I2C3Freq_Value=8000000
-RCC.I2SClocksFreq_Value=64000000
-RCC.IPParameters=ADC12outputFreq_Value,AHBFreq_Value,APB1CLKDivider,APB1Freq_Value,APB1TimFreq_Value,APB2Freq_Value,APB2TimFreq_Value,CortexFreq_Value,FCLKCortexFreq_Value,FamilyName,HCLKFreq_Value,HSEPLLFreq_Value,HSE_VALUE,HSIPLLFreq_Value,HSI_VALUE,I2C1Freq_Value,I2C2Freq_Value,I2C3Freq_Value,I2SClocksFreq_Value,LSI_VALUE,MCOFreq_Value,PLLCLKFreq_Value,PLLM,PLLMCOFreq_Value,PLLMUL,PLLN,PLLP,PLLQ,RCC_PLLsource_Clock_Source_FROM_HSE,RTCFreq_Value,RTCHSEDivFreq_Value,SYSCLKFreq_VALUE,SYSCLKSourceVirtual,TIM15Freq_Value,TIM16Freq_Value,TIM17Freq_Value,TIM1Freq_Value,TIM2Freq_Value,USART1Freq_Value,USART2Freq_Value,USART3Freq_Value,USBFreq_Value,VCOOutput2Freq_Value
+RCC.I2SClocksFreq_Value=48000000
+RCC.IPParameters=ADC12outputFreq_Value,AHBFreq_Value,APB1CLKDivider,APB1Freq_Value,APB1TimFreq_Value,APB2Freq_Value,APB2TimFreq_Value,CortexFreq_Value,FCLKCortexFreq_Value,FamilyName,HCLKFreq_Value,HSEPLLFreq_Value,HSE_VALUE,HSIPLLFreq_Value,HSI_VALUE,I2C1Freq_Value,I2C2Freq_Value,I2C3Freq_Value,I2SClocksFreq_Value,LSI_VALUE,MCOFreq_Value,PLLCLKFreq_Value,PLLM,PLLMCOFreq_Value,PLLMUL,PLLN,PLLP,PLLQ,PLLSourceVirtual,RTCFreq_Value,RTCHSEDivFreq_Value,SYSCLKFreq_VALUE,SYSCLKSourceVirtual,TIM15Freq_Value,TIM16Freq_Value,TIM17Freq_Value,TIM1Freq_Value,TIM2Freq_Value,USART1Freq_Value,USART2Freq_Value,USART3Freq_Value,USBFreq_Value,VCOOutput2Freq_Value
 RCC.LSI_VALUE=40000
-RCC.MCOFreq_Value=64000000
-RCC.PLLCLKFreq_Value=64000000
+RCC.MCOFreq_Value=48000000
+RCC.PLLCLKFreq_Value=48000000
 RCC.PLLM=8
-RCC.PLLMCOFreq_Value=64000000
-RCC.PLLMUL=RCC_PLL_MUL16
+RCC.PLLMCOFreq_Value=48000000
+RCC.PLLMUL=RCC_PLL_MUL6
 RCC.PLLN=336
 RCC.PLLP=RCC_PLLP_DIV4
 RCC.PLLQ=7
-RCC.RCC_PLLsource_Clock_Source_FROM_HSE=RCC_HSE_PREDIV_DIV2
+RCC.PLLSourceVirtual=RCC_PLLSOURCE_HSE
 RCC.RTCFreq_Value=40000
 RCC.RTCHSEDivFreq_Value=250000
-RCC.SYSCLKFreq_VALUE=64000000
+RCC.SYSCLKFreq_VALUE=48000000
 RCC.SYSCLKSourceVirtual=RCC_SYSCLKSOURCE_PLLCLK
-RCC.TIM15Freq_Value=64000000
-RCC.TIM16Freq_Value=64000000
-RCC.TIM17Freq_Value=64000000
-RCC.TIM1Freq_Value=64000000
-RCC.TIM2Freq_Value=64000000
-RCC.USART1Freq_Value=32000000
+RCC.TIM15Freq_Value=48000000
+RCC.TIM16Freq_Value=48000000
+RCC.TIM17Freq_Value=48000000
+RCC.TIM1Freq_Value=48000000
+RCC.TIM2Freq_Value=48000000
+RCC.USART1Freq_Value=24000000
 RCC.USART2Freq_Value=32000000
 RCC.USART3Freq_Value=32000000
-RCC.USBFreq_Value=64000000
-RCC.VCOOutput2Freq_Value=4000000
-SH.GPXTI13.0=GPIO_EXTI13
-SH.GPXTI13.ConfNb=1
-USART2.IPParameters=VirtualMode-Asynchronous
+RCC.USBFreq_Value=48000000
+RCC.VCOOutput2Freq_Value=8000000
+SPI2.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_8
+SPI2.CalculateBaudRate=3.0 MBits/s
+SPI2.DataSize=SPI_DATASIZE_8BIT
+SPI2.Direction=SPI_DIRECTION_2LINES
+SPI2.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,DataSize,BaudRatePrescaler
+SPI2.Mode=SPI_MODE_MASTER
+SPI2.VirtualType=VM_MASTER
+USART2.BaudRate=115200
+USART2.IPParameters=VirtualMode-Asynchronous,BaudRate
 USART2.VirtualMode-Asynchronous=VM_ASYNC
+VP_NimaLTD.I-CUBE-SPIF_VS_DriverJjSPIF_1.0.0_2.3.2.Mode=DriverJjSPIF
+VP_NimaLTD.I-CUBE-SPIF_VS_DriverJjSPIF_1.0.0_2.3.2.Signal=NimaLTD.I-CUBE-SPIF_VS_DriverJjSPIF_1.0.0_2.3.2
 VP_SYS_VS_Systick.Mode=SysTick
 VP_SYS_VS_Systick.Signal=SYS_VS_Systick
 board=NUCLEO-F302R8

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio