├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── FAT_OPERATION.md ├── LICENSE.md ├── README.md ├── include ├── bootsel_button.h ├── mimic_fat.h ├── tusb_config.h └── unicode.h ├── littlefs_driver.c ├── main.c ├── mimic_fat.c ├── tests ├── CMakeLists.txt ├── main.c ├── test_create.c ├── test_delete.c ├── test_large_file.c ├── test_move.c ├── test_read.c ├── test_rename.c ├── test_update.c ├── tests.h └── util.c ├── unicode.c ├── usb_descriptors.c ├── usb_msc_driver.c └── vendor └── pico_sdk_import.cmake /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | *.swp 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/littlefs"] 2 | path = vendor/littlefs 3 | url = https://github.com/littlefs-project/littlefs.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13...3.27) 2 | 3 | include(vendor/pico_sdk_import.cmake) 4 | 5 | project(littlefs-usb C CXX ASM) 6 | set(FAMILY rp2040) 7 | set(CMAKE_C_STANDARD 11) 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | pico_sdk_init() 11 | 12 | add_executable(littlefs-usb 13 | main.c 14 | littlefs_driver.c 15 | usb_msc_driver.c 16 | usb_descriptors.c 17 | mimic_fat.c 18 | unicode.c 19 | ) 20 | target_link_libraries(littlefs-usb PRIVATE 21 | hardware_flash 22 | hardware_sync 23 | littlefs 24 | pico_stdlib 25 | tinyusb_additions 26 | tinyusb_board 27 | tinyusb_device 28 | ) 29 | 30 | target_include_directories(littlefs-usb 31 | PRIVATE 32 | vendor/littlefs 33 | ${CMAKE_CURRENT_LIST_DIR}/include 34 | ) 35 | target_compile_options(littlefs-usb PRIVATE -Werror -Wall -Wextra -Wnull-dereference) 36 | target_link_options(littlefs-usb PRIVATE -Wl,--print-memory-usage) 37 | 38 | 39 | add_library(littlefs INTERFACE) 40 | target_sources(littlefs INTERFACE 41 | vendor/littlefs/lfs.c 42 | vendor/littlefs/lfs_util.c 43 | ) 44 | target_include_directories(littlefs INTERFACE 45 | vendor/littlefs 46 | ) 47 | target_compile_options(littlefs INTERFACE -Wno-unused-function -Wno-null-dereference) 48 | target_link_libraries(littlefs INTERFACE hardware_flash) 49 | 50 | pico_enable_stdio_usb(littlefs-usb 1) 51 | pico_add_extra_outputs(littlefs-usb) 52 | 53 | target_compile_options(littlefs-usb PRIVATE -DENABLE_TRACE) 54 | 55 | 56 | find_program(OPENOCD openocd) 57 | if(OPENOCD) 58 | add_custom_target(flash 59 | COMMAND ${OPENOCD} -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "program ${CMAKE_PROJECT_NAME}.elf verify reset exit" 60 | DEPENDS ${CMAKE_PROJECT_NAME} 61 | ) 62 | add_custom_target(reset COMMAND ${OPENOCD} -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c init -c reset -c exit) 63 | 64 | add_subdirectory(tests EXCLUDE_FROM_ALL) 65 | endif() 66 | -------------------------------------------------------------------------------- /FAT_OPERATION.md: -------------------------------------------------------------------------------- 1 | # Overview of FAT file system update operations 2 | 3 | The FAT file system is operated by operations such as updating directory entries, updating the file allocation table and sending data to the cluster. Note that the procedure for these operations differs slightly between operating systems. 4 | 5 | ## Create 6 | 7 | For macos 8 | 9 | 1. write file blocks to unassigned clusters 10 | 2. update File allocation table 11 | 3. update dir entry. The clusters written in step 1 are specified 12 | 13 | For Windows 11 14 | 15 | 1. update dir entry. The cluster to which the file belongs is set to 0. 16 | 2. update dir entry. The cluster to be assigned is specified. Not yet allocated. 17 | 3. write the file block to the cluster specified in step 2. 18 | 4. update File allocation table 19 | 20 | ## Update 21 | 22 | For macos 23 | 24 | 1. write file blocks to unassigned clusters 25 | 2. update File allocation table 26 | 3. update dir entry. The cluster to which the file belongs is set to 0. 27 | 4. update dir entry. The clusters written in step 1 are specified. 28 | 29 | For Windows 11 30 | 31 | 1. update dir entry. The cluster to which the file belongs is set to 0. 32 | 2. update File allocation table 33 | 3. update dir entry. A new cluster is specified. 34 | 4. write the file block to the cluster specified in step 3 35 | 5. update File allocation table. 36 | 37 | ## Rename 38 | 39 | For macos 40 | 41 | 1. update dir entry. The old filename is flagged for deletion and a new file entry is added at the same time. Clusters have the same. 42 | 43 | For Windows 11 44 | 45 | 1. The file name of the directory entry is changed. 46 | 47 | ## Move 48 | 49 | 1. update the origin dir entry. Attach deletion flag to filename. 50 | 2. add files to the destination directory entry. Clusters have the same. 51 | 52 | ## Delete 53 | 54 | 1. update dir entry. Assign a delete flag to the filename 55 | 2. update File allocation table. 56 | 57 | ## About Directory Entries 58 | 59 | About directory entries Directory entries are data structures that hold information on files and directories held in the file system. Each directory is assigned a unique cluster number. A directory entry holds the names and cluster numbers of the files and directories belonging to the directory.The FAT file system can scan all directories starting from the root directory. Directory entries other than the root hold the cluster number of the parent directory. Note that only references to the root directory are specified with cluster number 0. 60 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2024, Hiroyuki OYAMA. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | - Redistributions in binary form must reproduce the above copyright notice, this 9 | list of conditions and the following disclaimer in the documentation and/or 10 | other materials provided with the distribution. 11 | - Neither the name of the copyright holder nor the names of its contributors may 12 | be used to endorse or promote products derived from this software without 13 | specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi Pico littlefs USB Flash Memory Interface 2 | 3 | This project demonstrates a method for mounting littlefs via USB to facilitate easy retrieval of sensor data and other information stored on microcontrollers from a standard PC. 4 | 5 | Littlefs is widely used as a reliable file system for microcontrollers. However, since this file system is not supported by ordinary host PCs, special software and procedures are required for file read and write operations. The core idea of this project is to add an intermediate conversion layer to the USB Mass Storage Class device driver of the microcontroller, mimicking littlefs as a FAT file system. This allows littlefs to be manipulated from the host PC as if it were a USB flash drive with a common FAT file system. 6 | 7 | ## Demo Overview 8 | 9 | The demo operates as follows: 10 | 11 | - Each time the BOOTSEL button is clicked, the number of clicks is added to the `SENSOR.TXT` file in the littlefs file system. 12 | - When the Pico is connected to the host PC via USB, it will be mounted as a USB flash drive with a FAT12 file system. 13 | - As a USB flash drive, you can create, read, update, and delete files in littlefs. However, moving directories is not supported. 14 | - Holding down the BOOTSEL button for 3 seconds will format the littlefs file system. 15 | 16 | ## Build and Installation 17 | 18 | The build requires updating the [littlefs](https://github.com/littlefs-project/littlefs) git submodule and instructing CMake how to retrieve the [pico-sdk](https://github.com/raspberrypi/pico-sdk). 19 | 20 | ```bash 21 | git submodule update --init 22 | 23 | mkdir build; cd build 24 | PICO_SDK_FETCH_FROM_GIT=1 cmake .. 25 | make 26 | ``` 27 | 28 | In this instruction, the `PICO_SDK_FETCH_FROM_GIT` environment variable is specified when CMake is run, instructing it to clone the pico-sdk from github. If you want to specify a pico-sdk that has already been deployed locally, specify it in the `PICO_SDK_PATH` environment variable. 29 | 30 | After successful compilation, `littlefs-usb.uf2` will be generated. Simply drag and drop it onto your Raspberry Pi Pico to install and run the application. 31 | 32 | ## Limitations 33 | 34 | The current implementation has several limitations: 35 | 36 | - Renaming a directory does not result in the expected behaviour: Renaming a directory does not move the directory and its contents, but creates a new directory. 37 | - Large files are slow: It can handle files up to the maximum size of FAT12, but is very slow to read. 38 | - Limited number of files on a directory: The number of files that can be stored in a single directory is limited to a maximum of 16. This is an implementation limitation that may be relaxed in the future. 39 | - No file update detection: The host PC cannot notice when the microcontroller updates a file. Remounting will reflect the update. 40 | - Unrefactored Source Code: The source code has not undergone refactoring. 41 | 42 | ## Mimicking Process 43 | 44 | FAT12 is a very simple file system and can be easily mimicked. Depending on the location of the block device requested by the USB host, the microcontroller assembles and returns the appropriate FAT12 block. 45 | 46 | - Block 0: Returns static FAT12 boot block. 47 | - Block 1: Returns the file allocation table (FAT). 48 | - Block 2: Returns the root directory's directory entry. 49 | - Block 3 and later: Returns littlefs file blocks or directory entries. 50 | 51 | Upon USB connection, all files in the littlefs file system are searched to build a cache of FAT directory entries. Read requests from the USB host determine the type (file or directory) of the requested object based on the cache. Requests for directories are sent directly from the cache, while requests for files open the corresponding file in littlefs and send its content. Write requests involve updating the cache and reflecting changes in littlefs. The cache is updated based on the differences in directory entries. 52 | 53 | See `FAT_OPERATION.md` for details on the sequence of disk operations. 54 | 55 | ## Testing 56 | 57 | The tests directory contains code to verify the API's behavior. After building and installing the test code on the Pico, the unit tests are executed directly on the device, and results are sent via UART. 58 | 59 | ```bash 60 | make tests 61 | ``` 62 | 63 | To run the tests, transfer the `tests/tests.uf2` file to your Pico. For a more comprehensive debugging experience, connect the [Raspberry Pi Debug Probe](https://www.raspberrypi.com/documentation/microcontrollers/debug-probe.html) to the SWD Debug and UART Serial interfaces of the Pico. Use the following command to install and run the tests: 64 | 65 | ```bash 66 | make run_tests 67 | ``` 68 | -------------------------------------------------------------------------------- /include/bootsel_button.h: -------------------------------------------------------------------------------- 1 | #ifndef PICO_LITTLEFS_USB_BOOTSEL_BUTTON_H_ 2 | #define PICO_LITTLEFS_USB_BOOTSEL_BUTTON_H_ 3 | 4 | #include 5 | #include 6 | 7 | 8 | bool __no_inline_not_in_flash_func(bb_get_bootsel_button)() { 9 | const uint CS_PIN_INDEX = 1; 10 | uint32_t flags = save_and_disable_interrupts(); 11 | hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl, 12 | GPIO_OVERRIDE_LOW << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB, 13 | IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS); 14 | for (volatile int i = 0; i < 1000; ++i); 15 | bool button_state = !(sio_hw->gpio_hi_in & (1u << CS_PIN_INDEX)); 16 | hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl, 17 | GPIO_OVERRIDE_NORMAL << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB, 18 | IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS); 19 | restore_interrupts(flags); 20 | 21 | return button_state; 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/mimic_fat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * mimic FAT file system functions 3 | * 4 | * Copyright 2024, Hiroyuki OYAMA. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #ifndef PICO_LITTLEFS_USB_MIMIC_FAT_H_ 8 | #define PICO_LITTLEFS_USB_MIMIC_FAT_H_ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "unicode.h" 15 | 16 | #define LITTLE_ENDIAN16(x) (x) 17 | #define LITTLE_ENDIAN32(x) (x) 18 | 19 | 20 | typedef struct { 21 | uint8_t DIR_Name[11]; 22 | uint8_t DIR_Attr; 23 | uint8_t DIR_NTRes; 24 | uint8_t DIR_CrtTimeTenth; 25 | uint16_t DIR_CrtTime; 26 | uint16_t DIR_CrtDate; 27 | uint16_t DIR_LstAccDate; 28 | uint16_t DIR_FstClusHI; 29 | uint16_t DIR_WrtTime; 30 | uint16_t DIR_WrtDate; 31 | uint16_t DIR_FstClusLO; 32 | uint32_t DIR_FileSize; 33 | } fat_dir_entry_t; 34 | 35 | typedef struct { 36 | uint8_t LDIR_Ord; 37 | uint8_t LDIR_Name1[10]; 38 | uint8_t LDIR_Attr; 39 | uint8_t LDIR_Type; 40 | uint8_t LDIR_Chksum; 41 | uint8_t LDIR_Name2[12]; 42 | uint8_t LDIR_FstClusLO[2]; 43 | uint8_t LDIR_Name3[4]; 44 | } fat_lfn_t; 45 | 46 | #define DISK_SECTOR_SIZE 512 47 | 48 | 49 | void mimic_fat_init(const struct lfs_config *c); 50 | size_t mimic_fat_total_sector_size(void); 51 | void mimic_fat_create_cache(void); 52 | void mimic_fat_cleanup_cache(void); 53 | void mimic_fat_read(uint8_t lun, uint32_t sector, void *buffer, uint32_t bufsize); 54 | void mimic_fat_write(uint8_t lun, uint32_t sector, void *buffer, uint32_t bufsize); 55 | bool mimic_fat_usb_device_is_enabled(void); 56 | void mimic_fat_update_usb_device_is_enabled(bool enable); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /include/tusb_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef PICO_LITTLEFS_USB_TUSB_CONFIG_H_ 27 | #define PICO_LITTLEFS_USB_TUSB_CONFIG_H_ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | //--------------------------------------------------------------------+ 34 | // Board Specific Configuration 35 | //--------------------------------------------------------------------+ 36 | 37 | // RHPort number used for device can be defined by board.mk, default to port 0 38 | #ifndef BOARD_TUD_RHPORT 39 | #define BOARD_TUD_RHPORT 0 40 | #endif 41 | 42 | // RHPort max operational speed can defined by board.mk 43 | #ifndef BOARD_TUD_MAX_SPEED 44 | #define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED 45 | #endif 46 | 47 | //-------------------------------------------------------------------- 48 | // Common Configuration 49 | //-------------------------------------------------------------------- 50 | 51 | // defined by compiler flags for flexibility 52 | #ifndef CFG_TUSB_MCU 53 | #error CFG_TUSB_MCU must be defined 54 | #endif 55 | 56 | #ifndef CFG_TUSB_OS 57 | #define CFG_TUSB_OS OPT_OS_NONE 58 | #endif 59 | 60 | #ifndef CFG_TUSB_DEBUG 61 | #define CFG_TUSB_DEBUG 0 62 | #endif 63 | 64 | // Enable Device stack 65 | #define CFG_TUD_ENABLED 1 66 | 67 | // Default is max speed that hardware controller could support with on-chip PHY 68 | #define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED 69 | 70 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 71 | * Tinyusb use follows macros to declare transferring memory so that they can be put 72 | * into those specific section. 73 | * e.g 74 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 75 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 76 | */ 77 | #ifndef CFG_TUSB_MEM_SECTION 78 | #define CFG_TUSB_MEM_SECTION 79 | #endif 80 | 81 | #ifndef CFG_TUSB_MEM_ALIGN 82 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 83 | #endif 84 | 85 | //-------------------------------------------------------------------- 86 | // DEVICE CONFIGURATION 87 | //-------------------------------------------------------------------- 88 | 89 | #ifndef CFG_TUD_ENDPOINT0_SIZE 90 | #define CFG_TUD_ENDPOINT0_SIZE 64 91 | #endif 92 | 93 | //------------- CLASS -------------// 94 | #define CFG_TUD_CDC 1 95 | #define CFG_TUD_MSC 1 96 | #define CFG_TUD_HID 0 97 | #define CFG_TUD_MIDI 0 98 | #define CFG_TUD_VENDOR 0 99 | 100 | // CDC FIFO size of TX and RX 101 | #define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 102 | #define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 103 | 104 | // CDC Endpoint transfer buffer size, more is faster 105 | #define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 106 | 107 | // MSC Buffer size of Device Mass storage 108 | #define CFG_TUD_MSC_EP_BUFSIZE 512 109 | 110 | #ifdef __cplusplus 111 | } 112 | #endif 113 | 114 | #endif /* PICO_LITTLEFS_USB_TUSB_CONFIG_H_ */ 115 | -------------------------------------------------------------------------------- /include/unicode.h: -------------------------------------------------------------------------------- 1 | #ifndef PICO_LITTLEFS_USB_UNICODE_H_ 2 | #define PICO_LITTLEFS_USB_UNICODE_H_ 3 | 4 | #include 5 | #include 6 | 7 | 8 | size_t strlen_utf8(const char *src); 9 | size_t ascii_to_utf16le(uint16_t *dist, size_t dist_size, const char *src, size_t src_size); 10 | size_t utf8_to_utf16le(uint16_t *dist, size_t dist_size, const char *src, size_t src_size); 11 | size_t utf16le_to_utf8(char *dist, size_t buffer_size, const uint16_t *src, size_t len); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /littlefs_driver.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Driver for Raspberry Pi Pico on-board flash with littlefs file system 3 | * 4 | * Copyright 2024, Hiroyuki OYAMA. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | #define FS_SIZE (1.8 * 1024 * 1024) 14 | 15 | 16 | static uint32_t fs_base(const struct lfs_config *c) { 17 | uint32_t storage_size = c->block_count * c->block_size; 18 | return PICO_FLASH_SIZE_BYTES - storage_size; 19 | } 20 | 21 | static int pico_read(const struct lfs_config* c, 22 | lfs_block_t block, 23 | lfs_off_t off, 24 | void* buffer, 25 | lfs_size_t size) 26 | { 27 | (void)c; 28 | 29 | uint8_t* p = (uint8_t*)(XIP_NOCACHE_NOALLOC_BASE + fs_base(c) + (block * FLASH_SECTOR_SIZE) + off); 30 | memcpy(buffer, p, size); 31 | return 0; 32 | } 33 | 34 | static int pico_prog(const struct lfs_config* c, 35 | lfs_block_t block, 36 | lfs_off_t off, 37 | const void* buffer, 38 | lfs_size_t size) 39 | { 40 | (void)c; 41 | uint32_t p = (block * FLASH_SECTOR_SIZE) + off; 42 | uint32_t ints = save_and_disable_interrupts(); 43 | flash_range_program(fs_base(c) + p, buffer, size); 44 | restore_interrupts(ints); 45 | return 0; 46 | } 47 | 48 | static int pico_erase(const struct lfs_config* c, lfs_block_t block) { 49 | (void)c; 50 | uint32_t off = block * FLASH_SECTOR_SIZE; 51 | uint32_t ints = save_and_disable_interrupts(); 52 | flash_range_erase(fs_base(c) + off, FLASH_SECTOR_SIZE); 53 | restore_interrupts(ints); 54 | return 0; 55 | } 56 | 57 | static int pico_sync(const struct lfs_config* c) { 58 | (void)c; 59 | return 0; 60 | } 61 | 62 | const struct lfs_config lfs_pico_flash_config = { 63 | .read = pico_read, 64 | .prog = pico_prog, 65 | .erase = pico_erase, 66 | .sync = pico_sync, 67 | .read_size = 1, 68 | .prog_size = FLASH_PAGE_SIZE, 69 | .block_size = FLASH_SECTOR_SIZE, 70 | .block_count = FS_SIZE / FLASH_SECTOR_SIZE, 71 | .cache_size = FLASH_SECTOR_SIZE, 72 | .lookahead_size = 16, 73 | .block_cycles = 500, 74 | }; 75 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Raspberry Pi Pico littlefs USB Flash Memory Interface 3 | * 4 | * Every time you press the BOOTSEL button on the Raspberry Pi Pico, 5 | * append a log to the littlefs `SENSOR.TXT`. The host PC can mount 6 | * the Pico like a USB Mass storage class flash memory device and 7 | * read `SENSOR.TXT`. 8 | * Hold the button down for 10 seconds to format Pico's flash memory. 9 | * 10 | * Copyright 2024, Hiroyuki OYAMA. All rights reserved. 11 | * SPDX-License-Identifier: BSD-3-Clause 12 | */ 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "bootsel_button.h" 20 | #include "mimic_fat.h" 21 | 22 | 23 | extern const struct lfs_config lfs_pico_flash_config; // littlefs_driver.c 24 | 25 | #define FILENAME "SENSOR.TXT" 26 | 27 | #define README_TXT \ 28 | "Raspberry Pi Pico littlefs USB Flash Memory Interface\n" \ 29 | "\n" \ 30 | "Every time you press the BOOTSEL button on the Raspberry Pi Pico,\n" \ 31 | "append a log to the littlefs `SENSOR.TXT`. The host PC can mount\n" \ 32 | "the Pico like a USB Mass storage class flash memory device and\n" \ 33 | "read `SENSOR.TXT`.\n" \ 34 | "Hold the button down for 10 seconds to format Pico's flash memory.\n" 35 | 36 | #define ANSI_RED "\e[31m" 37 | #define ANSI_CLEAR "\e[0m" 38 | 39 | 40 | lfs_t fs; 41 | 42 | /* 43 | * Format the file system if it does not exist 44 | */ 45 | static void test_filesystem_and_format_if_necessary(bool force_format) { 46 | if (force_format || (lfs_mount(&fs, &lfs_pico_flash_config) != 0)) { 47 | printf("Format the onboard flash memory with littlefs\n"); 48 | 49 | lfs_format(&fs, &lfs_pico_flash_config); 50 | lfs_mount(&fs, &lfs_pico_flash_config); 51 | 52 | lfs_file_t f; 53 | lfs_file_open(&fs, &f, "README.TXT", LFS_O_RDWR|LFS_O_CREAT); 54 | lfs_file_write(&fs, &f, README_TXT, strlen(README_TXT)); 55 | lfs_file_close(&fs, &f); 56 | 57 | if (mimic_fat_usb_device_is_enabled()) { 58 | mimic_fat_create_cache(); 59 | } 60 | } 61 | } 62 | 63 | /* 64 | * Log clicks on the BOOTSEL button to a log file. 65 | * Press and hold the button for 10 seconds to initialize the file system. 66 | */ 67 | static void sensor_logging_task(void) { 68 | static bool last_status = false; 69 | static int count = 0; 70 | bool button = bb_get_bootsel_button(); 71 | static uint64_t long_push = 0; 72 | 73 | if (last_status != button && button) { // Push BOOTSEL button 74 | count += 1; 75 | printf("Update %s\n", FILENAME); 76 | 77 | lfs_file_t f; 78 | lfs_file_open(&fs, &f, FILENAME, LFS_O_RDWR|LFS_O_APPEND|LFS_O_CREAT); 79 | uint8_t buffer[512]; 80 | snprintf((char *)buffer, sizeof(buffer), "click=%d\n", count); 81 | lfs_file_write(&fs, &f, buffer, strlen((char *)buffer)); 82 | printf((char *)buffer); 83 | lfs_file_close(&fs, &f); 84 | } 85 | last_status = button; 86 | 87 | if (button) { 88 | long_push++; 89 | } else { 90 | long_push = 0; 91 | } 92 | if (long_push > 35000) { // Long-push BOOTSEL button 93 | test_filesystem_and_format_if_necessary(true); 94 | count = 0; 95 | long_push = 0; 96 | } 97 | } 98 | 99 | int main(void) { 100 | //set_sys_clock_khz(250000, false); 101 | 102 | board_init(); 103 | tud_init(BOARD_TUD_RHPORT); 104 | stdio_init_all(); 105 | 106 | test_filesystem_and_format_if_necessary(false); 107 | while (true) { 108 | sensor_logging_task(); 109 | tud_task(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /mimic_fat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * mimic FAT file system functions 3 | * 4 | * Copyright 2024, Hiroyuki OYAMA. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #include "mimic_fat.h" 8 | 9 | 10 | #define ANSI_RED "\e[31m" 11 | #define ANSI_CLEAR "\e[0m" 12 | 13 | #ifdef ENABLE_TRACE 14 | #define TRACE(...) (printf(__VA_ARGS__)) 15 | #else 16 | #define TRACE(...) ((void)0) 17 | #endif 18 | 19 | 20 | static const struct lfs_config *littlefs_lfs_config = NULL; 21 | 22 | 23 | 24 | #define FAT_SHORT_NAME_MAX 11 25 | #define FAT_LONG_FILENAME_CHUNK_MAX 13 26 | 27 | 28 | static uint8_t fat_disk_image[1][DISK_SECTOR_SIZE] = { 29 | //------------- Block0: Boot Sector -------------// 30 | { 31 | 0xEB, 0x3C, 0x90, // BS_JmpBoot 32 | 'M', 'S', 'D', 'O', 'S', '5', '.', '0', // BS_OEMName 33 | 0x00, 0x02, // BPB_BytsPerSec 34 | 0x01, // BPB_SecPerClus 35 | 0x01, 0x00, // BPB_RsvdSecCnt 36 | 0x01, // BPB_NumFATs 37 | 0x10, 0x00, // BPB_RootEntCnt 38 | 0x00, 0x00, // BPB_TotSec16, to be set up later 39 | 0xF8, // BPB_Media 40 | 0x01, 0x00, // BPB_FATSz16 41 | 0x01, 0x00, // BPB_SecPerTrk 42 | 0x01, 0x00, // BPB_NumHeads 43 | 0x00, 0x00, 0x00, 0x00, // BPB_HiddSec 44 | 0x00, 0x00, 0x00, 0x00, // BPB_TotSec32 45 | 0x80, // BS_DrvNum 46 | 0x00, // BS_Reserved 47 | 0x29, // BS_BootSig 48 | 0x34, 0x12, 0x00, 0x00, // BS_VolID 49 | 'l' , 'i' , 't' , 't' , 'l' , 'e' , 'f' , 's' , 'U' , 'S' , 'B' , // BS_VolLab 50 | 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, // BS_FilSysType 51 | 0x00, 0x00, 52 | // Zero up to 2 last bytes of FAT magic code 53 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 56 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 57 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 59 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 61 | 62 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 63 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 64 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 69 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 71 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 76 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 78 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 79 | 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xAA 84 | } 85 | }; 86 | 87 | static lfs_t real_filesystem; 88 | static bool usb_device_is_enabled = false; 89 | 90 | static lfs_file_t fat_cache; 91 | 92 | void mimic_fat_init(const struct lfs_config *c) { 93 | littlefs_lfs_config = c; 94 | } 95 | 96 | bool mimic_fat_usb_device_is_enabled(void) { 97 | return usb_device_is_enabled; 98 | } 99 | 100 | void mimic_fat_update_usb_device_is_enabled(bool enable) { 101 | usb_device_is_enabled = enable; 102 | } 103 | 104 | 105 | static void print_block(uint8_t *buffer, size_t l) { 106 | size_t offset = 0; 107 | for (size_t i = 0; i < l; ++i) { 108 | if (i % 16 == 0) 109 | printf("0x%04u%s", offset, (i % 512) == 0 ? ">" : " "); 110 | if (isalnum(buffer[i])) { 111 | printf("'%c' ", buffer[i]); 112 | } else { 113 | printf("0x%02x", buffer[i]); 114 | } 115 | if (i % 16 == 15) { 116 | printf("\n"); 117 | offset += 16; 118 | } else { 119 | printf(", "); 120 | } 121 | } 122 | } 123 | 124 | static void print_hex(uint8_t *buffer, size_t l) { 125 | size_t offset = 0; 126 | for (size_t i = 0; i < l; ++i) { 127 | if (i % 16 == 0) 128 | printf("0x%04u%s ", offset, (i % 512) == 0 ? ">" : " "); 129 | printf("0x%02x,", buffer[i]); 130 | if (i % 16 == 15) { 131 | printf("\n"); 132 | offset += 16; 133 | } 134 | } 135 | } 136 | 137 | static void print_dir_entry(void *buffer) { 138 | uint8_t pbuffer[11+1]; 139 | fat_dir_entry_t *dir = (fat_dir_entry_t *)buffer; 140 | TRACE("--------\n"); 141 | for (size_t i = 0; i < DISK_SECTOR_SIZE / sizeof(fat_dir_entry_t); i++) { 142 | if (dir->DIR_Name[0] == '\0') { 143 | break; 144 | } 145 | if ((dir->DIR_Attr & 0x0F) != 0x0F) { 146 | memcpy(pbuffer, &dir->DIR_Name, 11); 147 | pbuffer[11] = '\0'; 148 | TRACE("name='%s' attr=0x%02X size=%lu cluster=%u\n", 149 | pbuffer, 150 | dir->DIR_Attr, 151 | dir->DIR_FileSize, 152 | dir->DIR_FstClusLO); 153 | } else { 154 | fat_lfn_t *lfn = (fat_lfn_t *)dir; 155 | uint16_t utf16le[13 + 1]; 156 | memcpy(utf16le, lfn->LDIR_Name1, 5*2); 157 | memcpy(utf16le + 5, lfn->LDIR_Name2, 6*2); 158 | memcpy(utf16le + 5 + 6, lfn->LDIR_Name3, 2*2); 159 | utf16le[13] = '\0'; 160 | char utf8[13 * 4 + 1]; 161 | utf16le_to_utf8(utf8, sizeof(utf8), utf16le, 13); 162 | TRACE("name='%s' attr=0x%02X ord=0x%02X checksum=%u cluster=%u\n", 163 | utf8, lfn->LDIR_Attr, lfn->LDIR_Ord, lfn->LDIR_Chksum, dir->DIR_FstClusLO); 164 | } 165 | dir++; 166 | } 167 | } 168 | 169 | static int is_fat_sfn_symbol(uint8_t c) { 170 | switch (c) { 171 | case '$': 172 | case '%': 173 | case '\'': 174 | case '-': 175 | case '_': 176 | case '@': 177 | case '~': 178 | case '`': 179 | case '!': 180 | case '(': 181 | case ')': 182 | case '{': 183 | case '}': 184 | case '^': 185 | case '#': 186 | case '&': 187 | return 1; 188 | break; 189 | default: 190 | return 0; 191 | } 192 | } 193 | 194 | static uint16_t read_fat(int cluster) { 195 | uint16_t offset = (uint16_t)floor((float)cluster + ((float)cluster / 2)); 196 | lfs_soff_t o = lfs_file_seek(&real_filesystem, &fat_cache, offset, LFS_SEEK_SET); 197 | if (o < 0) { 198 | printf("read_fat: lfs_file_seek error=%ld\n", o); 199 | return 0xFFF; 200 | } 201 | uint8_t current[2] = {0}; 202 | lfs_ssize_t s = lfs_file_read(&real_filesystem, &fat_cache, current, sizeof(current)); 203 | if (s < 0) { 204 | printf("read_fat: lfs_file_read error=%ld\n", s); 205 | } 206 | 207 | int16_t result = 0; 208 | if (cluster & 0x01) { 209 | result = (current[0] >> 4) | ((uint16_t)current[1] << 4); 210 | } else { 211 | result = current[0] | ((uint16_t)(current[1] & 0x0F) << 8); 212 | } 213 | return result; 214 | } 215 | 216 | static void update_fat(uint32_t cluster, uint16_t value) { 217 | size_t offset = (size_t)floor((float)cluster + ((float)cluster / 2)); 218 | 219 | uint8_t previous[2] = {0}; 220 | lfs_soff_t o = lfs_file_seek(&real_filesystem, &fat_cache, offset, LFS_SEEK_SET); 221 | if (o < 0) { 222 | printf("update_fat: lfs_file_seek error=%ld\n", o); 223 | return; 224 | } 225 | lfs_ssize_t s = lfs_file_read(&real_filesystem, &fat_cache, previous, sizeof(previous)); 226 | if (s < 0) { 227 | printf("update_fat: lfs_file_read error=%ld\n", s); 228 | return; 229 | } 230 | if (cluster & 0x01) { 231 | previous[0] = (previous[0] & 0x0F) | (value << 4); 232 | previous[1] = value >> 4; 233 | } else { 234 | previous[0] = value; 235 | previous[1] = (previous[1] & 0xF0) | ((value >> 8) & 0x0F); 236 | } 237 | 238 | o = lfs_file_seek(&real_filesystem, &fat_cache, offset, LFS_SEEK_SET); 239 | if (o < 0) { 240 | printf("update_fat: lfs_file_seek error=%ld\n", o); 241 | return; 242 | } 243 | s = lfs_file_write(&real_filesystem, &fat_cache, previous, sizeof(previous)); 244 | if (s != sizeof(previous)) { 245 | printf("update_fat: lfs_file_write error=%ld\n", s); 246 | } 247 | } 248 | 249 | #define BUFFER_SIZE 512 250 | #define END_OF_CLUSTER_CHAIN 0xFFF 251 | 252 | static size_t bulk_update_fat_buffer_read(size_t offset, void *buffer, size_t size) { 253 | lfs_soff_t o = lfs_file_seek(&real_filesystem, &fat_cache, offset, LFS_SEEK_SET); 254 | if (o < 0) { 255 | printf("bulk_update_fat_buffer_read: lfs_file_seek error=%ld\n", o); 256 | return 0; 257 | } 258 | lfs_ssize_t s = lfs_file_read(&real_filesystem, &fat_cache, buffer, size); 259 | if (s < 0) { 260 | printf("bulk_update_fat_buffer_read: lfs_file_read error=%ld\n", s); 261 | return 0; 262 | } 263 | return (size_t)s; 264 | } 265 | 266 | static size_t bulk_update_fat_buffer_write(size_t offset, void *buffer, size_t size) { 267 | lfs_soff_t o = lfs_file_seek(&real_filesystem, &fat_cache, offset, LFS_SEEK_SET); 268 | if (o < 0) { 269 | printf("bulk_update_fat_buffer_write: lfs_file_seek error=%ld\n", o); 270 | return 0; 271 | } 272 | lfs_ssize_t s = lfs_file_write(&real_filesystem, &fat_cache, buffer, size); 273 | if (s < 0) { 274 | printf("bulk_update_fat_buffer_write: lfs_file_read error=%ld\n", s); 275 | return 0; 276 | } 277 | return (size_t)s; 278 | } 279 | 280 | static size_t bulk_update_fat(uint32_t start_cluster, size_t size) { 281 | size_t num_clusters = ceil((double)size / 512); 282 | unsigned char buffer[BUFFER_SIZE]; 283 | unsigned int current_cluster = start_cluster; 284 | unsigned int next_cluster; 285 | int initial_offset = (3 * start_cluster) / 2; 286 | int initial_sector = initial_offset / BUFFER_SIZE; 287 | 288 | size_t read = bulk_update_fat_buffer_read(initial_sector * BUFFER_SIZE, buffer, sizeof(buffer)); 289 | if (read == 0) 290 | return 0; 291 | 292 | int sector_size = 512; 293 | 294 | for (unsigned int i = 0; i < num_clusters; i++) { 295 | int offset = floor((3 * current_cluster) / 2.0); 296 | int buffer_index = offset % sector_size; 297 | 298 | next_cluster = (i < num_clusters - 1) ? current_cluster + 1 : END_OF_CLUSTER_CHAIN; 299 | if (current_cluster & 0x01) { 300 | buffer[buffer_index] = (buffer[buffer_index] & 0x0F) | (next_cluster << 4); 301 | buffer[buffer_index + 1] = (next_cluster >> 4) & 0xFF; 302 | } else { 303 | buffer[buffer_index] = next_cluster & 0xFF; 304 | buffer[buffer_index + 1] = (buffer[buffer_index + 1] & 0xF0) | ((next_cluster >> 8) & 0x0F); 305 | } 306 | 307 | // FAT12 sector boundaries 308 | if (((current_cluster & 0x01) && buffer_index + 2 >= sector_size) || 309 | (buffer_index + 1 >= sector_size)) 310 | { 311 | 312 | size_t write = bulk_update_fat_buffer_write(initial_sector * BUFFER_SIZE, buffer, sizeof(buffer)); 313 | if (write == 0) 314 | return 0; 315 | 316 | initial_sector++; 317 | 318 | read = bulk_update_fat_buffer_read(initial_sector * BUFFER_SIZE, buffer, sizeof(buffer)); 319 | if (read == 0) 320 | return 0; 321 | 322 | buffer_index = 0; 323 | if (current_cluster & 0x01) { 324 | buffer[buffer_index] = (next_cluster >> 4) & 0xFF; 325 | } else { 326 | buffer[buffer_index] = (buffer[buffer_index] & 0xF0) | ((next_cluster >> 8) & 0x0F); 327 | } 328 | } 329 | current_cluster++; 330 | } 331 | 332 | size_t write = bulk_update_fat_buffer_write(initial_sector * BUFFER_SIZE, buffer, sizeof(buffer)); 333 | if (write == 0) 334 | return 0; 335 | return start_cluster + num_clusters + 1; 336 | } 337 | 338 | static void init_fat(void) { 339 | uint64_t storage_size = littlefs_lfs_config->block_count * littlefs_lfs_config->block_size; 340 | uint32_t cluster_size = storage_size / (DISK_SECTOR_SIZE * 1); 341 | 342 | struct lfs_info finfo; 343 | int err = lfs_stat(&real_filesystem, ".mimic", &finfo); 344 | if (err == LFS_ERR_NOENT) { 345 | err = lfs_mkdir(&real_filesystem, ".mimic"); 346 | if (err != LFS_ERR_OK) { 347 | printf("init_fat: can't create .mimic directory: err=%d\n", err); 348 | return; 349 | } 350 | } 351 | 352 | err = lfs_file_open(&real_filesystem, &fat_cache, ".mimic/FAT", LFS_O_RDWR|LFS_O_CREAT); 353 | assert(err == 0); 354 | 355 | uint8_t head[3] = {0xF8, 0xFF, 0xFF}; 356 | head[0] = 0xF8; 357 | head[1] = 0xFF; 358 | head[2] = 0xFF; 359 | lfs_ssize_t s = lfs_file_write(&real_filesystem, &fat_cache, head, sizeof(head)); 360 | if (s != sizeof(head)) { 361 | printf("init_fat: lfs_file_write error=%ld\n", s); 362 | return; 363 | } 364 | 365 | uint8_t pair[3] = {0x00, 0x00, 0x00}; 366 | for (size_t i = 0; i < (float)cluster_size / 2; i++) { 367 | s = lfs_file_write(&real_filesystem, &fat_cache, pair, sizeof(pair)); 368 | if (s != sizeof(pair)) { 369 | printf("init_fat: lfs_file_write error=%ld\n", s); 370 | break; 371 | } 372 | } 373 | } 374 | 375 | 376 | static void print_fat(size_t l) { 377 | TRACE("FAT table-------\n"); 378 | for (size_t i = 0; i < l; i++) { 379 | TRACE(" cluster=%d fat=%03x\n", i, read_fat(i)); 380 | } 381 | } 382 | 383 | 384 | static void set_fat_short_filename(uint8_t *short_filename, const char *filename) { 385 | uint8_t buffer[LFS_NAME_MAX + 1]; 386 | strncpy((char *)buffer, filename, sizeof(buffer)); 387 | 388 | char *basename = strtok((char *)buffer, "."); 389 | char *ext = strtok(NULL, "."); 390 | snprintf((char *)short_filename, sizeof(buffer), "%-8s%-3s", basename, ext); 391 | } 392 | 393 | static void restore_from_short_filename(char *filename, const char *short_filename) { 394 | char buffer[FAT_SHORT_NAME_MAX + 1]; 395 | strncpy(buffer, short_filename, sizeof(buffer) - 1); 396 | 397 | uint8_t basename[8 + 1]; 398 | uint8_t fileext[3 + 1]; 399 | for (int i = 0; i < 8; i++) { 400 | if (buffer[i] != ' ') { 401 | basename[i] = buffer[i]; 402 | } else { 403 | basename[i] = '\0'; 404 | break; 405 | } 406 | } 407 | basename[sizeof(basename)-1] = '\0'; 408 | for (int i = 0; i < 3; i++) { 409 | if (buffer[8+i] != ' ') { 410 | fileext[i] = buffer[8+i]; 411 | } else { 412 | fileext[i] = '\0'; 413 | break; 414 | } 415 | } 416 | fileext[sizeof(fileext)-1] = '\0'; 417 | 418 | sprintf((char *)filename, "%s.%s", basename, fileext); 419 | } 420 | 421 | static void restore_from_short_dirname(char *filename, const char *short_dirname) { 422 | strcpy(filename, short_dirname); 423 | 424 | size_t length = FAT_SHORT_NAME_MAX; 425 | while (length > 0 && filename[length - 1] == ' ') { 426 | --length; 427 | } 428 | filename[length] = '\0'; 429 | } 430 | 431 | static bool is_short_filename_file(uint8_t *filename) { 432 | char buffer[LFS_NAME_MAX + 1]; 433 | 434 | if (filename[0] == '.') 435 | return false; 436 | 437 | strncpy(buffer, (const char *)filename, sizeof(buffer)); 438 | unsigned char *name = (unsigned char *)strtok(buffer, "."); 439 | if (strlen((char *)name) > 8) { 440 | return false; 441 | } 442 | unsigned char *ext = (unsigned char *)strtok(NULL, "."); 443 | if (strlen((char *)ext) > 3) { 444 | return false; 445 | } 446 | 447 | for (int i = 0; i < 8; i++) { 448 | if (name[i] == '\0') { 449 | break; 450 | } 451 | if (isalnum(name[i]) == 0 && is_fat_sfn_symbol(name[i]) == 0) { 452 | return false; 453 | } 454 | if (isalpha(name[i]) > 0 && isupper(name[i]) == 0) { 455 | return false; 456 | } 457 | } 458 | for (int i = 0; i < 3; i++) { 459 | if (ext[i] == '\0') { 460 | break; 461 | } 462 | if (isalpha(ext[i]) > 0 && isupper(ext[i]) == 0) { 463 | return false; 464 | } 465 | if ((isalnum(ext[i]) == 0) && (is_fat_sfn_symbol(ext[i]) == 0)) { 466 | return false; 467 | } 468 | } 469 | return true; 470 | } 471 | 472 | static void to_uppercase(char *str) { 473 | if (str == NULL) 474 | return; 475 | 476 | while (*str) { 477 | *str = toupper((unsigned char) *str); 478 | str++; 479 | } 480 | } 481 | 482 | static bool is_short_filename_dir(uint8_t *filename) { 483 | uint8_t buffer[LFS_NAME_MAX + 1]; 484 | strncpy((char *)buffer, (const char *)filename, sizeof(buffer)); 485 | if (strlen((const char *)filename) > FAT_SHORT_NAME_MAX) { 486 | return false; 487 | } 488 | for (size_t i = 0; i < FAT_SHORT_NAME_MAX; i++) { 489 | if (filename[i] == '\0') { 490 | break; 491 | } 492 | if (isalnum(filename[i]) == 0 && is_fat_sfn_symbol(filename[i]) == 0) { 493 | return false; 494 | } 495 | if (isalpha(filename[i]) > 0 && isupper(filename[i]) == 0) { 496 | return false; 497 | } 498 | } 499 | return true; 500 | } 501 | 502 | static void create_shortened_short_filename(uint8_t *sfn, const char *long_filename) { 503 | uint8_t buffer[LFS_NAME_MAX + 1]; 504 | uint8_t filename[FAT_SHORT_NAME_MAX + 1]; 505 | 506 | strncpy((char *)buffer, long_filename, sizeof(buffer)); 507 | char *name = strtok((char *)buffer, "."); 508 | (void)name; 509 | char *ext = strtok(NULL, "."); 510 | ext[3] = '\0'; 511 | to_uppercase(ext); 512 | snprintf((char *)filename, sizeof(filename), "FIL~%04X%-3s", rand() % 0xFFFF, ext); 513 | memcpy(sfn, filename, FAT_SHORT_NAME_MAX); 514 | } 515 | 516 | static void create_shortened_short_filename_dir(uint8_t *sfn, const char *long_filename) { 517 | (void)long_filename; 518 | uint8_t filename[FAT_SHORT_NAME_MAX + 1]; 519 | 520 | snprintf((char *)filename, sizeof(filename), "DIR~%04X ", rand() % 0xFFFF); 521 | memcpy(sfn, filename, FAT_SHORT_NAME_MAX); 522 | } 523 | 524 | static uint8_t filename_check_sum(const uint8_t *filename) { 525 | uint8_t i, sum; 526 | 527 | for (i = sum = 0; i < FAT_SHORT_NAME_MAX; i++) { 528 | sum = (sum >> 1) + (sum << 7) + filename[i]; 529 | } 530 | return sum; 531 | } 532 | 533 | static void set_LFN_name123(fat_lfn_t *dir, const uint16_t *filename) { 534 | memcpy(&dir->LDIR_Name1, filename + 0, sizeof(uint16_t) * 5); 535 | memcpy(&dir->LDIR_Name2, filename + 5, sizeof(uint16_t) * 6); 536 | memcpy(&dir->LDIR_Name3, filename + 5+6, sizeof(uint16_t) * 2); 537 | } 538 | 539 | void set_volume_label_entry(fat_dir_entry_t *dir, const char *name) { 540 | uint8_t sfn_name[FAT_SHORT_NAME_MAX + 1]; 541 | snprintf((char *)sfn_name, sizeof(sfn_name), "%-11s", name); 542 | 543 | memcpy(dir->DIR_Name, sfn_name, FAT_SHORT_NAME_MAX); 544 | dir->DIR_Attr = 0x08; 545 | dir->DIR_NTRes = 0; 546 | dir->DIR_CrtTimeTenth = 0; 547 | dir->DIR_CrtTime = 0; 548 | dir->DIR_CrtDate = 0; 549 | dir->DIR_LstAccDate = 0; 550 | dir->DIR_FstClusHI = 0; 551 | dir->DIR_WrtTime = LITTLE_ENDIAN16(0x4F6D); 552 | dir->DIR_WrtDate = LITTLE_ENDIAN16(0x6543); 553 | dir->DIR_FstClusLO = 0; 554 | dir->DIR_FileSize = 0; 555 | } 556 | 557 | static void set_directory_entry(fat_dir_entry_t *dir, const char *name, int cluster) { 558 | uint8_t sfn_name[LFS_NAME_MAX + 1]; 559 | snprintf((char *)sfn_name, sizeof(sfn_name), "%-11s", name); 560 | memcpy(dir->DIR_Name, sfn_name, FAT_SHORT_NAME_MAX); 561 | dir->DIR_Attr = 0x10; // directory 562 | dir->DIR_NTRes = 0; 563 | dir->DIR_CrtTimeTenth = 0xC6; 564 | dir->DIR_CrtTime = LITTLE_ENDIAN16(0x526D); 565 | dir->DIR_CrtDate = LITTLE_ENDIAN16(0x6543); 566 | dir->DIR_LstAccDate = LITTLE_ENDIAN16(0x6543); 567 | dir->DIR_FstClusHI = 0; 568 | dir->DIR_WrtTime = LITTLE_ENDIAN16(0x526D); 569 | dir->DIR_WrtDate = LITTLE_ENDIAN16(0x6543); 570 | dir->DIR_FstClusLO = cluster; 571 | dir->DIR_FileSize = 0; 572 | } 573 | 574 | static void set_file_entry(fat_dir_entry_t *dir, struct lfs_info *info, int cluster) { 575 | set_fat_short_filename(dir->DIR_Name, info->name); 576 | dir->DIR_Attr = 0x20; 577 | dir->DIR_NTRes = 0; 578 | dir->DIR_CrtTimeTenth = 0xC6; 579 | dir->DIR_CrtTime = LITTLE_ENDIAN16(0x526D); 580 | dir->DIR_CrtDate = LITTLE_ENDIAN16(0x6543); 581 | dir->DIR_LstAccDate = LITTLE_ENDIAN16(0x6543); 582 | dir->DIR_FstClusHI = 0; 583 | dir->DIR_WrtTime = LITTLE_ENDIAN16(0x526D); 584 | dir->DIR_WrtDate = LITTLE_ENDIAN16(0x6543); 585 | dir->DIR_FstClusLO = info->size > 0 ? LITTLE_ENDIAN16(cluster) : 0; 586 | dir->DIR_FileSize = LITTLE_ENDIAN32(info->size); 587 | } 588 | 589 | static void set_long_file_entry(fat_dir_entry_t *dir, uint16_t *lfn_chunk, uint8_t lfn_order, uint8_t check_sum) { 590 | fat_lfn_t *long_dir_entry = (fat_lfn_t *)dir; 591 | set_LFN_name123(long_dir_entry, lfn_chunk); 592 | long_dir_entry->LDIR_Ord = lfn_order; 593 | long_dir_entry->LDIR_Attr = 0x0F; 594 | long_dir_entry->LDIR_Type = 0x00; 595 | long_dir_entry->LDIR_Chksum = check_sum; 596 | long_dir_entry->LDIR_FstClusLO[0] = 0x00; 597 | long_dir_entry->LDIR_FstClusLO[1] = 0x00; 598 | } 599 | 600 | 601 | /* 602 | * Save buffers sent by the host to LFS temporary files 603 | */ 604 | static bool save_temporary_file(uint32_t cluster, void *buffer) { 605 | TRACE("save_temporary_file: cluster=%lu\n", cluster); 606 | 607 | char filename[LFS_NAME_MAX + 1]; 608 | int tens = (cluster / 10) % 10; 609 | int hundreds = (cluster / 100) % 10; 610 | int thousands = (cluster / 1000) % 10; 611 | 612 | snprintf(filename, sizeof(filename), ".mimic/%d", thousands); 613 | struct lfs_info finfo; 614 | int err = lfs_stat(&real_filesystem, filename, &finfo); 615 | if (err == LFS_ERR_NOENT) { 616 | err = lfs_mkdir(&real_filesystem, filename); 617 | if (err != LFS_ERR_OK) { 618 | printf("save_temporary_file: can't create '%s' directory: err=%d\n", filename, err); 619 | return false; 620 | } 621 | } 622 | snprintf(filename, sizeof(filename), ".mimic/%d/%1d", thousands, hundreds); 623 | err = lfs_stat(&real_filesystem, filename, &finfo); 624 | if (err == LFS_ERR_NOENT) { 625 | err = lfs_mkdir(&real_filesystem, filename); 626 | if (err != LFS_ERR_OK) { 627 | printf("save_temporary_file: can't create '%s' directory: err=%d\n", filename, err); 628 | return false; 629 | } 630 | } 631 | snprintf(filename, sizeof(filename), ".mimic/%d/%1d/%1d", thousands, hundreds, tens); 632 | err = lfs_stat(&real_filesystem, filename, &finfo); 633 | if (err == LFS_ERR_NOENT) { 634 | err = lfs_mkdir(&real_filesystem, filename); 635 | if (err != LFS_ERR_OK) { 636 | printf("save_temporary_file: can't create '%s' directory: err=%d\n", filename, err); 637 | return false; 638 | } 639 | } 640 | 641 | snprintf(filename, sizeof(filename), ".mimic/%d/%1d/%1d/%04ld", thousands, hundreds,tens, cluster); 642 | lfs_file_t f; 643 | err = lfs_file_open(&real_filesystem, &f, filename, LFS_O_RDWR|LFS_O_CREAT); 644 | if (err != LFS_ERR_OK) { 645 | printf("save_temporary_file: can't lfs_file_open '%s' err=%d\n", filename, err); 646 | return false; 647 | } 648 | lfs_file_write(&real_filesystem, &f, buffer, 512); 649 | lfs_file_close(&real_filesystem, &f); 650 | return 1; 651 | } 652 | 653 | static int read_temporary_file(uint32_t cluster, void *buffer) { 654 | lfs_file_t f; 655 | char filename[LFS_NAME_MAX + 1]; 656 | 657 | int tens = (cluster/ 10) % 10; 658 | int hundreds = (cluster/ 100) % 10; 659 | int thousands = (cluster/ 1000) % 10; 660 | snprintf(filename, sizeof(filename), ".mimic/%d/%1d/%1d/%04ld", thousands, hundreds,tens, cluster); 661 | int err = lfs_file_open(&real_filesystem, &f, filename, LFS_O_RDONLY); 662 | if (err != LFS_ERR_OK) { 663 | if (err == LFS_ERR_NOENT) 664 | return err; 665 | printf("read_temporary_file: can't open '%s': err=%d\n", filename, err); 666 | return err; 667 | } 668 | 669 | lfs_ssize_t size = lfs_file_read(&real_filesystem, &f, buffer, 512); 670 | if (size != 512) { 671 | printf("read_temporary_file: can't read '%s': size=%lu\n", filename, size); 672 | lfs_file_close(&real_filesystem, &f); 673 | return err; 674 | } 675 | 676 | lfs_file_close(&real_filesystem, &f); 677 | return LFS_ERR_OK; 678 | } 679 | 680 | /* 681 | static bool delete_temporary_file(uint32_t cluster) { 682 | printf("delete_temporary_file: cluster=%lu\n", cluster); 683 | 684 | char filename[LFS_NAME_MAX + 1]; 685 | int tens = (file_id / 10) % 10; 686 | int hundreds = (file_id / 100) % 10; 687 | int thousands = (file_id / 1000) % 10; 688 | snprintf(filename, sizeof(filename), ".mimic/%d/%1d/%1d/%04ld", thousands, hundreds,tens, cluster); 689 | int err = lfs_remove(&real_filesystem, filename); 690 | if (err != LFS_ERR_OK) { 691 | printf("delete_temporary_file: can't lfs_remove '%s' error=%d\n", filename, err); 692 | return false; 693 | } 694 | return true; 695 | } 696 | */ 697 | 698 | static fat_dir_entry_t *append_dir_entry_volume_label(fat_dir_entry_t *entry, const char *volume_label) { 699 | uint8_t name[FAT_SHORT_NAME_MAX + 1]; 700 | 701 | snprintf((char *)name, sizeof(name), "%-11s", volume_label); 702 | memcpy(entry->DIR_Name, name, FAT_SHORT_NAME_MAX); 703 | entry->DIR_Attr = 0x08; 704 | entry->DIR_NTRes = 0; 705 | entry->DIR_CrtTimeTenth = 0; 706 | entry->DIR_CrtTime = 0; 707 | entry->DIR_CrtDate = 0; 708 | entry->DIR_LstAccDate = 0; 709 | entry->DIR_FstClusHI = 0; 710 | entry->DIR_WrtTime = LITTLE_ENDIAN16(0x4F6D); 711 | entry->DIR_WrtDate = LITTLE_ENDIAN16(0x6543); 712 | entry->DIR_FstClusLO = 0; 713 | entry->DIR_FileSize = 0; 714 | entry++; 715 | return entry; 716 | } 717 | 718 | static void long_filename_padding(uint16_t *filename, size_t length, size_t size) { 719 | for (size_t i = length; i < size; i++) { 720 | if (i == length) 721 | filename[i] = 0x0000; 722 | else 723 | filename[i] = 0xFFFF; 724 | } 725 | } 726 | 727 | static fat_dir_entry_t *append_dir_entry_directory(fat_dir_entry_t *entry, struct lfs_info *finfo, uint32_t cluster) { 728 | TRACE("append_dir_entry_directory '%s'\n", finfo->name); 729 | 730 | if (strcmp(finfo->name, ".") == 0 || strcmp(finfo->name, "..") == 0) { 731 | set_directory_entry(entry, finfo->name, cluster == 1 ? 0 : cluster); 732 | } 733 | else if (is_short_filename_dir((uint8_t *)finfo->name)) { 734 | set_directory_entry(entry, finfo->name, cluster); 735 | } else { 736 | fat_dir_entry_t short_dir_entry; 737 | 738 | set_directory_entry(&short_dir_entry, finfo->name, cluster); 739 | create_shortened_short_filename_dir(short_dir_entry.DIR_Name, finfo->name); 740 | uint8_t check_sum = filename_check_sum(short_dir_entry.DIR_Name); 741 | 742 | uint16_t filename[LFS_NAME_MAX + 1]; 743 | size_t len = utf8_to_utf16le(filename, sizeof(filename), finfo->name, strlen(finfo->name)); 744 | long_filename_padding(filename, len, LFS_NAME_MAX + 1); 745 | int long_filename_num = floor((len - 1) / FAT_LONG_FILENAME_CHUNK_MAX); 746 | 747 | for (int i = long_filename_num; i >= 0; i--) { 748 | uint8_t order = i + 1; 749 | uint16_t chunk[FAT_LONG_FILENAME_CHUNK_MAX]; 750 | uint16_t *head = (uint16_t *)&(filename[i * FAT_LONG_FILENAME_CHUNK_MAX]); 751 | memcpy(chunk, head, sizeof(chunk)); 752 | if (i == long_filename_num) 753 | order |= 0x40; 754 | set_long_file_entry(entry, chunk, order, check_sum); 755 | entry++; 756 | } 757 | memcpy(entry, &short_dir_entry, sizeof(short_dir_entry)); 758 | } 759 | entry++; 760 | return entry; 761 | } 762 | 763 | static fat_dir_entry_t *append_dir_entry_file(fat_dir_entry_t *entry, struct lfs_info *finfo, uint32_t cluster) { 764 | TRACE("append_dir_entry_file '%s' cluster=%lu\n", finfo->name, cluster); 765 | 766 | if (is_short_filename_file((uint8_t *)finfo->name)) { 767 | set_file_entry(entry, finfo, cluster); 768 | } else { 769 | fat_dir_entry_t short_dir_entry; 770 | set_file_entry(&short_dir_entry, finfo, cluster); 771 | create_shortened_short_filename(short_dir_entry.DIR_Name, finfo->name); 772 | 773 | uint8_t check_sum = filename_check_sum(short_dir_entry.DIR_Name); 774 | 775 | uint16_t filename[LFS_NAME_MAX + 1]; 776 | size_t len = utf8_to_utf16le(filename, sizeof(filename), finfo->name, strlen(finfo->name)); 777 | long_filename_padding(filename, len, LFS_NAME_MAX + 1); 778 | int long_filename_num = floor((len - 1) / FAT_LONG_FILENAME_CHUNK_MAX); 779 | 780 | for (int i = long_filename_num; i >= 0; i--) { 781 | uint8_t order = i + 1; 782 | uint16_t chunk[FAT_LONG_FILENAME_CHUNK_MAX]; 783 | uint16_t *head = (uint16_t *)&(filename[i * FAT_LONG_FILENAME_CHUNK_MAX]); 784 | memcpy(chunk, head, sizeof(chunk)); 785 | if (i == long_filename_num) 786 | order |= 0x40; 787 | set_long_file_entry(entry, chunk, order, check_sum); 788 | entry++; 789 | } 790 | memcpy(entry, &short_dir_entry, sizeof(short_dir_entry)); 791 | } 792 | 793 | entry++; 794 | return entry; 795 | } 796 | 797 | /* 798 | * Create a directory entry cache corresponding to the base file system 799 | * 800 | * Recursively traverse the specified base file system directory and update cache and allocation tables. 801 | */ 802 | static int create_dir_entry_cache(const char *path, uint32_t parent_cluster, uint32_t *allocated_cluster) { 803 | TRACE("create_dir_entry_cache('%s', %lu, %lu)\n", path, parent_cluster, *allocated_cluster); 804 | uint32_t current_cluster = *allocated_cluster; 805 | fat_dir_entry_t *entry; 806 | fat_dir_entry_t dir_entry[DISK_SECTOR_SIZE / sizeof(fat_dir_entry_t)] = {0}; 807 | lfs_dir_t dir; 808 | struct lfs_info finfo; 809 | char directory_path[LFS_NAME_MAX * 2 + 1 + 1]; // for sprintf "%s/%s" 810 | entry = dir_entry; 811 | 812 | if (parent_cluster == 0) { 813 | entry = append_dir_entry_volume_label(entry, "littlefsUSB"); 814 | current_cluster = 1; 815 | } 816 | update_fat(current_cluster, 0xFFF); 817 | 818 | int err = lfs_dir_open(&real_filesystem, &dir, path); 819 | if (err != LFS_ERR_OK) { 820 | printf("create_dir_entry_cache: lfs_dir_open('%s') error=%d\n", path, err); 821 | return err; 822 | } 823 | 824 | while (true) { 825 | err = lfs_dir_read(&real_filesystem, &dir, &finfo); 826 | if (err == 0) 827 | break; 828 | if (err < 0) { 829 | printf("create_dir_entry_cache: lfs_dir_read('%s') error=%d\n", path, err); 830 | break; 831 | } 832 | 833 | if (finfo.type == LFS_TYPE_DIR && parent_cluster == 0 && strcmp(finfo.name, ".mimic") == 0) { 834 | continue; 835 | } 836 | if (finfo.type == LFS_TYPE_DIR && parent_cluster == 0 837 | && (strcmp(finfo.name, ".") == 0 || strcmp(finfo.name, "..") == 0)) 838 | { 839 | continue; 840 | } 841 | if (finfo.type == LFS_TYPE_DIR && strcmp(finfo.name, ".") == 0) { 842 | entry = append_dir_entry_directory(entry, &finfo, current_cluster); 843 | continue; 844 | } 845 | if (finfo.type == LFS_TYPE_DIR && strcmp(finfo.name, "..") == 0) { 846 | if (parent_cluster == 0) 847 | entry = append_dir_entry_directory(entry, &finfo, 0); 848 | else 849 | entry = append_dir_entry_directory(entry, &finfo, parent_cluster); 850 | continue; 851 | } 852 | 853 | if (finfo.type == LFS_TYPE_DIR) { 854 | *allocated_cluster += 1; 855 | update_fat(*allocated_cluster, 0xFFF); 856 | entry = append_dir_entry_directory(entry, &finfo, *allocated_cluster); 857 | if (parent_cluster == 0) 858 | strncpy(directory_path, finfo.name, sizeof(directory_path)); 859 | else 860 | snprintf(directory_path, sizeof(directory_path), "%s/%s", path, finfo.name); 861 | directory_path[LFS_NAME_MAX] = '\0'; 862 | 863 | err = create_dir_entry_cache((const char *)directory_path, current_cluster, allocated_cluster); 864 | if (err < 0) { 865 | lfs_dir_close(&real_filesystem, &dir); 866 | return err; 867 | } 868 | 869 | } else if (finfo.type == LFS_TYPE_REG) { 870 | uint32_t file_cluster = *allocated_cluster + 1; 871 | if (finfo.size > 0) 872 | *allocated_cluster = bulk_update_fat(file_cluster, finfo.size); 873 | entry = append_dir_entry_file(entry, &finfo, file_cluster); 874 | } 875 | } 876 | lfs_dir_close(&real_filesystem, &dir); 877 | save_temporary_file(current_cluster, dir_entry); 878 | return 0; 879 | } 880 | 881 | /* 882 | * Rebuild the directory entry cache. 883 | * 884 | * Execute when USB is connected. 885 | */ 886 | void mimic_fat_create_cache(void) { 887 | TRACE(ANSI_RED "mimic_fat_create_cache()\n" ANSI_CLEAR); 888 | 889 | lfs_unmount(&real_filesystem); 890 | int err = lfs_mount(&real_filesystem, littlefs_lfs_config); 891 | if (err < 0) { 892 | printf("mimic_fat_create_cache: lfs_mount error=%d\n", err); 893 | return; 894 | } 895 | 896 | mimic_fat_cleanup_cache(); 897 | 898 | init_fat(); 899 | 900 | uint32_t allocated_cluster = 1; 901 | create_dir_entry_cache("", 0, &allocated_cluster); 902 | } 903 | 904 | static void delete_directory(const char *path) { 905 | uint8_t filename[LFS_NAME_MAX + 1 + 8]; 906 | lfs_dir_t dir; 907 | struct lfs_info finfo; 908 | 909 | int err = lfs_dir_open(&real_filesystem, &dir, path); 910 | if (err != LFS_ERR_OK) { 911 | return; 912 | } 913 | while (true) { 914 | err = lfs_dir_read(&real_filesystem, &dir, &finfo); 915 | if (err == 0) 916 | break; 917 | if (err < 0) { 918 | printf("delete_directory: lfs_dir_read('%s') error=%d\n", path, err); 919 | break; 920 | } 921 | if (strcmp(finfo.name, ".") == 0 || 922 | strcmp(finfo.name, "..") == 0) 923 | { 924 | continue; 925 | } 926 | 927 | snprintf((char *)filename, sizeof(filename), "%s/%s", path, finfo.name); 928 | if (finfo.type == LFS_TYPE_DIR) 929 | delete_directory((const char *)filename); 930 | err = lfs_remove(&real_filesystem, (const char *)filename); 931 | if (err != LFS_ERR_OK) { 932 | printf("delete_directory: lfs_remove('%s') error=%d\n", filename, err); 933 | continue; 934 | } 935 | } 936 | lfs_dir_close(&real_filesystem, &dir); 937 | 938 | } 939 | 940 | void mimic_fat_cleanup_cache(void) { 941 | uint8_t filename[LFS_NAME_MAX + 1 + 8]; 942 | lfs_dir_t dir; 943 | struct lfs_info finfo; 944 | 945 | int err = lfs_dir_open(&real_filesystem, &dir, ".mimic"); 946 | if (err != LFS_ERR_OK) { 947 | return; 948 | } 949 | while (true) { 950 | err = lfs_dir_read(&real_filesystem, &dir, &finfo); 951 | if (err == 0) 952 | break; 953 | if (err < 0) { 954 | printf("mimic_fat_cleanup_cache: lfs_dir_read('%s') error=%d\n", ".mimic", err); 955 | break; 956 | } 957 | if (strcmp(finfo.name, ".") == 0 || 958 | strcmp(finfo.name, "..") == 0) 959 | { 960 | continue; 961 | } 962 | 963 | snprintf((char *)filename, sizeof(filename), "%s/%s", ".mimic", finfo.name); 964 | delete_directory((const char *)filename); 965 | } 966 | lfs_dir_close(&real_filesystem, &dir); 967 | } 968 | 969 | static uint32_t cluster_size(void) { 970 | uint64_t storage_size = littlefs_lfs_config->block_count * littlefs_lfs_config->block_size; 971 | return storage_size / (DISK_SECTOR_SIZE * 1); 972 | } 973 | 974 | static size_t fat_sector_size(void) { 975 | return ceil((double)cluster_size() / DISK_SECTOR_SIZE); 976 | } 977 | 978 | static bool is_fat_sector(uint32_t sector) { 979 | return sector > 0 && fat_sector_size() >= sector; 980 | } 981 | 982 | size_t mimic_fat_total_sector_size(void) { 983 | uint64_t storage_size = littlefs_lfs_config->block_count * littlefs_lfs_config->block_size; 984 | return (double)storage_size / DISK_SECTOR_SIZE; 985 | } 986 | 987 | /* 988 | * Returns the boot sector of the FAT image when USB requests sector 0 989 | */ 990 | static void read_boot_sector(void *buffer, uint32_t bufsize) { 991 | TRACE("\e[36mRead read_boot_sector()\e[0m\n"); 992 | 993 | // BPB_TotSec16 994 | fat_disk_image[0][19] = (uint8_t)(mimic_fat_total_sector_size() & 0xFF); 995 | fat_disk_image[0][20] = (uint8_t)(mimic_fat_total_sector_size() >> 8); 996 | 997 | // BPB_FATSz16 998 | size_t fat_size = ceil((double)mimic_fat_total_sector_size() / DISK_SECTOR_SIZE); 999 | fat_disk_image[0][22] = fat_size & 0xFF; 1000 | fat_disk_image[0][23] = (fat_size & 0xFF00) >> 8; 1001 | 1002 | uint8_t const *addr = fat_disk_image[0]; 1003 | memcpy(buffer, addr, bufsize); 1004 | } 1005 | 1006 | /* 1007 | * Return the FAT table when USB requests sector 1. 1008 | * Build a FAT table based on littlefs files. 1009 | */ 1010 | static void read_fat_sector(uint32_t sector, void *buffer, uint32_t bufsize) { 1011 | (void)bufsize; 1012 | TRACE("\e[36mRead sector=%lu read_fat_sector()\e[0m\n", sector); 1013 | 1014 | lfs_soff_t offset = (sector - 1) * 512; 1015 | lfs_soff_t o = lfs_file_seek(&real_filesystem, &fat_cache, offset, LFS_SEEK_SET); 1016 | if (o < 0) { 1017 | printf("read_fat_sector: lfs_file_seek err=%ld\n", o); 1018 | return; 1019 | } 1020 | 1021 | lfs_ssize_t s = lfs_file_read(&real_filesystem, &fat_cache, buffer, bufsize); 1022 | if (s < 0) { 1023 | printf("read_fat_sector: lfs_file_read error=%ld\n", s); 1024 | } 1025 | } 1026 | 1027 | static void save_fat_sector(uint32_t request_block, void *buffer, size_t bufsize) { 1028 | size_t offset = (request_block - 1) * bufsize; 1029 | lfs_soff_t o = lfs_file_seek(&real_filesystem, &fat_cache, offset, LFS_SEEK_SET); 1030 | if (o < 0) { 1031 | printf("save_fat_sector: lfs_file_seek error=%ld\n", o); 1032 | return; 1033 | } 1034 | lfs_ssize_t s = lfs_file_write(&real_filesystem, &fat_cache, buffer, bufsize); 1035 | if (s != (lfs_ssize_t)bufsize) { 1036 | printf("save_fat_sector: lfs_file_read error=%ld\n", s); 1037 | } 1038 | } 1039 | 1040 | /* 1041 | * Restore the *result_filename of the file_cluster_id file belonging to directory_cluster_id. 1042 | */ 1043 | static void restore_file_from(char *result_filename, uint32_t directory_cluster_id, uint32_t file_cluster_id) { 1044 | TRACE("restore_file_from(directory_cluster_id=%lu, file_cluster_id=%lu)\n", directory_cluster_id, file_cluster_id); 1045 | assert(file_cluster_id >= 2); 1046 | 1047 | if (directory_cluster_id == 0) { 1048 | directory_cluster_id = 1; 1049 | } 1050 | 1051 | int cluster_id = directory_cluster_id; 1052 | int parent = 1; 1053 | int target = file_cluster_id; 1054 | 1055 | fat_dir_entry_t dir[16]; 1056 | uint8_t result[LFS_NAME_MAX * 2 + 1 + 1] = {0}; // for sprintf "%s/%s" 1057 | if (directory_cluster_id == 0 && file_cluster_id == 0) { 1058 | TRACE(" this is initial cluster\n"); 1059 | //return; 1060 | } 1061 | 1062 | uint32_t self = 0; 1063 | while (cluster_id >= 0) { 1064 | TRACE("restore_file_from: cluster_id=%u, parent=%u, target=%u\n", cluster_id, parent, target); 1065 | if ((cluster_id == 0 || cluster_id == 1) && read_temporary_file(1, &dir[0]) != 0) { 1066 | printf("temporary file '.mimic/%04d' not found\n", 1); 1067 | break; 1068 | } else if (read_temporary_file(cluster_id, &dir[0]) != 0) { 1069 | printf("temporary file '.mimic/%04d' not found\n", cluster_id); 1070 | break; 1071 | } 1072 | 1073 | //printf("restore_file_from---\n"); 1074 | //print_dir_entry(&dir); 1075 | //printf("--------------------\n"); 1076 | uint8_t child_filename[LFS_NAME_MAX + 1]; 1077 | char filename[LFS_NAME_MAX + 1]; 1078 | uint16_t long_filename[LFS_NAME_MAX + 1]; 1079 | bool is_long_filename = false; 1080 | for (int i = 0; i < 16; i++) { 1081 | if (dir[i].DIR_Attr == 0x08) { 1082 | parent = -1; 1083 | continue; 1084 | } 1085 | if (dir[i].DIR_Name[0] == '\0') { 1086 | break; 1087 | } 1088 | if (memcmp(dir[i].DIR_Name, ". ", 11) == 0) { 1089 | self = dir[i].DIR_FstClusLO; 1090 | continue; 1091 | } 1092 | if (memcmp(dir[i].DIR_Name, ".. ", 11) == 0) { 1093 | parent = dir[i].DIR_FstClusLO; 1094 | if (parent == 0) { 1095 | /* NOTE: According to the FAT specification, the reference to the root 1096 | * directory is `cluster==0`, but the actual state of the root directory 1097 | * is `cluster==1`, so it needs to be corrected. 1098 | */ 1099 | parent = 1; // Actual root directory 1100 | } 1101 | continue; 1102 | } 1103 | if (dir[i].DIR_Name[0] == 0xE5) 1104 | continue; 1105 | 1106 | if ((dir[i].DIR_Attr & 0x0F) == 0x0F) { 1107 | fat_lfn_t *long_file = (fat_lfn_t *)&dir[i]; 1108 | if (long_file->LDIR_Ord & 0x40) { 1109 | memset(long_filename, 0xFF, sizeof(long_filename)); 1110 | is_long_filename = true; 1111 | } 1112 | int offset = (long_file->LDIR_Ord & 0x0F) - 1; 1113 | memcpy(&long_filename[offset * 13 + 0], long_file->LDIR_Name1, sizeof(uint16_t) * 5); 1114 | memcpy(&long_filename[offset * 13 + 5], long_file->LDIR_Name2, sizeof(uint16_t) * 6); 1115 | memcpy(&long_filename[offset * 13 + 5 + 6], long_file->LDIR_Name3, sizeof(uint16_t) * 2); 1116 | continue; 1117 | } 1118 | 1119 | if (dir[i].DIR_Attr & 0x10) { // is directory 1120 | if (is_long_filename) { 1121 | utf16le_to_utf8(filename, sizeof(filename), long_filename, sizeof(long_filename)); 1122 | } else { 1123 | restore_from_short_dirname(filename, (const char *)dir[i].DIR_Name); 1124 | } 1125 | 1126 | if (dir[i].DIR_FstClusLO == target) { 1127 | strcpy((char *)child_filename, (const char *)result); 1128 | snprintf((char *)result, sizeof(result), "%s/%s", filename, child_filename); 1129 | result[LFS_NAME_MAX] = '\0'; 1130 | break; 1131 | } 1132 | 1133 | is_long_filename = false; 1134 | continue; 1135 | } else if (dir[i].DIR_Attr & 0x20 || dir[i].DIR_Attr == 0x00) { // is file 1136 | if (is_long_filename) { 1137 | utf16le_to_utf8(filename, sizeof(filename), long_filename, sizeof(long_filename)); 1138 | } else { 1139 | restore_from_short_filename(filename, (const char *)dir[i].DIR_Name); 1140 | } 1141 | 1142 | if (dir[i].DIR_FstClusLO == target) { 1143 | strcpy((char *)result, (const char *)filename); 1144 | target = cluster_id; 1145 | break; 1146 | 1147 | } 1148 | is_long_filename = false; 1149 | } else { 1150 | TRACE(" unknown DIR_Attr=0x%02X\n", dir[i].DIR_Attr); 1151 | } 1152 | } 1153 | 1154 | cluster_id = parent; 1155 | target = self; 1156 | } 1157 | 1158 | strncpy((char *)result_filename, (const char *)result, LFS_NAME_MAX); 1159 | result_filename[LFS_NAME_MAX] = '\0'; 1160 | } 1161 | 1162 | 1163 | /* 1164 | * Cache the results of the last search in case of reading large files. 1165 | */ 1166 | typedef struct { 1167 | uint32_t cluster; 1168 | uint32_t base_cluster; 1169 | size_t offset; 1170 | } find_base_cluster_and_offset_cache_t; 1171 | 1172 | static find_base_cluster_and_offset_cache_t base_cluster_cache = {0}; 1173 | /* 1174 | * Search for base cluster in the Allocation table 1175 | * 1176 | * Traverse the allocation table in reverse order and return the length of the allocation chain in offset. 1177 | */ 1178 | static uint32_t find_base_cluster_and_offset(uint32_t cluster, size_t *offset) { 1179 | uint32_t target = cluster; 1180 | uint32_t next_cluster; 1181 | bool is_exists = false; 1182 | uint32_t find_limit = cluster_size(); 1183 | 1184 | if (cluster > cluster_size()) { 1185 | return 0; 1186 | } 1187 | if (read_fat(cluster) == 0x00) { 1188 | return 0; 1189 | } 1190 | 1191 | // reset search cache 1192 | if (base_cluster_cache.cluster + 1 != cluster) { 1193 | base_cluster_cache.cluster = 0; 1194 | base_cluster_cache.base_cluster = 0; 1195 | base_cluster_cache.offset = 0; 1196 | } 1197 | 1198 | *offset = 0; 1199 | while (find_limit-- > 0) { 1200 | is_exists = false; 1201 | for (size_t i = 0; i < cluster_size(); i++) { 1202 | next_cluster = read_fat(i); 1203 | if (next_cluster >= 0xFF8 || next_cluster == 0x00) 1204 | continue; 1205 | if (next_cluster == cluster) { 1206 | cluster = i; // i is the cluster number cvhained to cluster 1207 | is_exists = true; 1208 | *offset += 1; 1209 | 1210 | if (base_cluster_cache.cluster == i) { 1211 | *offset += base_cluster_cache.offset; 1212 | base_cluster_cache.cluster = next_cluster; 1213 | base_cluster_cache.offset = *offset; 1214 | return base_cluster_cache.base_cluster; 1215 | } 1216 | 1217 | break; 1218 | } 1219 | } 1220 | if (!is_exists) 1221 | break; 1222 | } 1223 | base_cluster_cache.cluster = target; 1224 | base_cluster_cache.base_cluster = cluster; 1225 | base_cluster_cache.offset = *offset; 1226 | 1227 | return cluster; 1228 | } 1229 | 1230 | typedef struct { 1231 | bool is_found; 1232 | uint32_t directory_cluster; 1233 | bool is_directory; 1234 | char path[LFS_NAME_MAX + 1]; 1235 | size_t size; 1236 | } find_dir_entry_cache_result_t; 1237 | 1238 | /* 1239 | * Restore directory_cluster_id filename to *directory 1240 | */ 1241 | static void restore_directory_from(char *directory, uint32_t base_directory_cluster_id, uint32_t directory_cluster_id) { 1242 | int cluster_id = base_directory_cluster_id; 1243 | int parent = 0; 1244 | int target = directory_cluster_id; 1245 | 1246 | fat_dir_entry_t dir[16]; 1247 | uint8_t result[LFS_NAME_MAX * 2 + 1 + 1] = {0}; // for sprintf "%s/%s" 1248 | 1249 | while (cluster_id >= 0) { 1250 | if ((cluster_id == 0 || cluster_id == 1) && read_temporary_file(1, &dir[0]) != 0) { 1251 | TRACE("temporary file '.mimic/%04d' not found\n", 1); 1252 | break; 1253 | 1254 | } else if (read_temporary_file(cluster_id, &dir[0]) != 0) { 1255 | TRACE("temporary file '.mimic/%04d' not found\n", cluster_id); 1256 | break; 1257 | } 1258 | 1259 | uint8_t child_filename[LFS_NAME_MAX + 1]; 1260 | char filename[LFS_NAME_MAX + 1]; 1261 | uint16_t long_filename[LFS_NAME_MAX + 1]; 1262 | 1263 | bool is_long_filename = false; 1264 | for (int i = 0; i < 16; i++) { 1265 | if (dir[i].DIR_Attr == 0x08) { 1266 | parent = -1; 1267 | continue; 1268 | } 1269 | if (dir[i].DIR_Name[0] == '\0') { 1270 | break; 1271 | } 1272 | if (memcmp(dir[i].DIR_Name, ". ", 11) == 0) { 1273 | continue; 1274 | } 1275 | if (memcmp(dir[i].DIR_Name, ".. ", 11) == 0) { 1276 | /* NOTE: According to the FAT specification, the reference to the root 1277 | * directory is `cluster==0`, but the actual state of the root directory 1278 | * is `cluster==1`, so it needs to be corrected. 1279 | */ 1280 | parent = dir[i].DIR_FstClusLO != 0 ? dir[i].DIR_FstClusLO : 1; 1281 | continue; 1282 | } 1283 | if (dir[i].DIR_Name[0] == 0xE5) 1284 | continue; 1285 | 1286 | if ((dir[i].DIR_Attr & 0x0F) == 0x0F) { 1287 | fat_lfn_t *long_file = (fat_lfn_t *)&dir[i]; 1288 | if (long_file->LDIR_Ord & 0x40) { 1289 | memset(long_filename, 0xFF, sizeof(long_filename)); 1290 | is_long_filename = true; 1291 | } 1292 | int offset = (long_file->LDIR_Ord & 0x0F) - 1; 1293 | memcpy(&long_filename[offset * 13 + 0], long_file->LDIR_Name1, sizeof(uint16_t) * 5); 1294 | memcpy(&long_filename[offset * 13 + 5], long_file->LDIR_Name2, sizeof(uint16_t) * 6); 1295 | memcpy(&long_filename[offset * 13 + 5 + 6], long_file->LDIR_Name3, sizeof(uint16_t) * 2); 1296 | continue; 1297 | } 1298 | if (dir[i].DIR_Attr & 0x10) { // is directory 1299 | if (is_long_filename) { 1300 | utf16le_to_utf8(filename, sizeof(filename), long_filename, sizeof(long_filename)); 1301 | } else { 1302 | restore_from_short_dirname(filename, (const char *)dir[i].DIR_Name); 1303 | } 1304 | 1305 | if (dir[i].DIR_FstClusLO == target) { 1306 | strcpy((char *)child_filename, (const char *)result); 1307 | if (strlen((const char *)child_filename) == 0) 1308 | strncpy((char *)result, (const char *)filename, sizeof(result)); 1309 | else 1310 | snprintf((char *)result, sizeof(result), "%s/%s", filename, child_filename); 1311 | result[LFS_NAME_MAX] = '\0'; 1312 | 1313 | target = cluster_id; 1314 | break; 1315 | } 1316 | 1317 | is_long_filename = false; 1318 | continue; 1319 | } else { 1320 | is_long_filename = false; 1321 | } 1322 | } 1323 | 1324 | cluster_id = parent; 1325 | } 1326 | 1327 | strncpy((char *)directory, (const char *)result, LFS_NAME_MAX); 1328 | directory[LFS_NAME_MAX] = '\0'; 1329 | } 1330 | 1331 | typedef enum find_dir_entry_cache_return_t { 1332 | FIND_DIR_ENTRY_CACHE_RESULT_ERROR = -1, 1333 | FIND_DIR_ENTRY_CACHE_RESULT_NOT_FOUND = 0, 1334 | FIND_DIR_ENTRY_CACHE_RESULT_FOUND = 1, 1335 | } find_dir_entry_cache_return_t; 1336 | 1337 | static find_dir_entry_cache_return_t find_dir_entry_cache(find_dir_entry_cache_result_t *result, uint32_t base_cluster, uint32_t target_cluster) { 1338 | TRACE("find_dir_entry_cache(base=%lu, target=%lu)\n", base_cluster, target_cluster); 1339 | fat_dir_entry_t entry[16]; 1340 | 1341 | int err = read_temporary_file(base_cluster, entry); 1342 | if (err != LFS_ERR_OK) { 1343 | TRACE("find_dir_entry_cache: read_temporary_file(cluster=%lu) error=%d\n", base_cluster, err); 1344 | return FIND_DIR_ENTRY_CACHE_RESULT_ERROR; 1345 | } 1346 | 1347 | for (int i = (base_cluster == 1 ? 1 : 2); i < 16; i++) { 1348 | if (strncmp((const char *)entry[i].DIR_Name, ".. ", 11) == 0) 1349 | continue; 1350 | if (entry[i].DIR_Name[0] == 0xE5) 1351 | continue; 1352 | if (entry[i].DIR_Name[0] == 0) 1353 | break; 1354 | 1355 | if (entry[i].DIR_FstClusLO == target_cluster) { 1356 | result->is_found = true; 1357 | result->directory_cluster = base_cluster; 1358 | result->is_directory = (entry[i].DIR_Attr & 0x10) ? true : false; 1359 | result->size = entry[i].DIR_FileSize; 1360 | if (result->is_directory) 1361 | restore_directory_from(result->path, base_cluster, target_cluster); 1362 | else 1363 | restore_file_from(result->path, base_cluster, target_cluster); 1364 | return FIND_DIR_ENTRY_CACHE_RESULT_FOUND; 1365 | } 1366 | if ((entry[i].DIR_Attr & 0x10) == 0) 1367 | continue; 1368 | 1369 | find_dir_entry_cache_return_t r = find_dir_entry_cache(result, entry[i].DIR_FstClusLO, target_cluster); 1370 | if (r != FIND_DIR_ENTRY_CACHE_RESULT_NOT_FOUND) 1371 | return r; 1372 | } 1373 | return FIND_DIR_ENTRY_CACHE_RESULT_NOT_FOUND; 1374 | } 1375 | 1376 | static void create_blank_dir_entry_cache(uint32_t cluster, uint32_t parent_dir_cluster) { 1377 | fat_dir_entry_t entry[16] = {0}; 1378 | 1379 | set_directory_entry(&entry[0], ".", cluster); 1380 | set_directory_entry(&entry[1], "..", parent_dir_cluster == 1 ? 0 : parent_dir_cluster); 1381 | 1382 | save_temporary_file(cluster, entry); 1383 | } 1384 | 1385 | /* 1386 | */ 1387 | void mimic_fat_read(uint8_t lun, uint32_t sector, void *buffer, uint32_t bufsize) { 1388 | TRACE("\e[36mRead sector=%lu mimic_fat_read()\e[0m\n", sector); 1389 | (void)lun; 1390 | 1391 | if (sector == 0) { 1392 | read_boot_sector(buffer, bufsize); 1393 | return; 1394 | } else if (is_fat_sector(sector)) { 1395 | read_fat_sector(sector, buffer, bufsize); 1396 | return; 1397 | } 1398 | 1399 | uint32_t cluster = sector - fat_sector_size(); 1400 | size_t offset = 0; 1401 | find_dir_entry_cache_result_t result = {0}; 1402 | 1403 | if (cluster == 1) { 1404 | read_temporary_file(cluster, buffer); 1405 | return; 1406 | } 1407 | 1408 | uint32_t base_cluster = find_base_cluster_and_offset(cluster, &offset); 1409 | if (base_cluster == 0) { // is not allocated 1410 | return; 1411 | } 1412 | 1413 | find_dir_entry_cache_return_t r = find_dir_entry_cache(&result, 1, base_cluster); 1414 | if (r != FIND_DIR_ENTRY_CACHE_RESULT_FOUND) 1415 | return; 1416 | if (result.is_directory) { 1417 | read_temporary_file(cluster, buffer); 1418 | return; 1419 | } 1420 | 1421 | TRACE("mimic_fat_read: result.path='%s'\n", result.path); 1422 | 1423 | lfs_file_t f; 1424 | int err = lfs_file_open(&real_filesystem, &f, result.path, LFS_O_RDONLY); 1425 | if (err != LFS_ERR_OK) { 1426 | printf("mimic_fat_read_cluster: lfs_file_open('%s') error=%d\n", result.path, err); 1427 | return; 1428 | } 1429 | 1430 | lfs_soff_t seek = lfs_file_seek(&real_filesystem, &f, offset * DISK_SECTOR_SIZE, LFS_SEEK_SET); 1431 | if (seek < 0) { 1432 | printf("mimic_fat_read: lfs_file_seek(path='%s', offset=%u) error=%ld\n", result.path, offset * DISK_SECTOR_SIZE, seek); 1433 | } 1434 | lfs_ssize_t size = lfs_file_read(&real_filesystem, &f, buffer, bufsize); 1435 | if (size < 0) { 1436 | printf("mimic_fat_read: lfs_file_read(path='%s', offset=%u) error=%ld\n", result.path, offset, seek); 1437 | } 1438 | lfs_file_close(&real_filesystem, &f); 1439 | } 1440 | 1441 | static void difference_of_dir_entry(fat_dir_entry_t *orig, fat_dir_entry_t *new, 1442 | fat_dir_entry_t *update, 1443 | fat_dir_entry_t *delete) 1444 | { 1445 | bool is_found = false; 1446 | TRACE("difference_of_dir_entry-----\n"); 1447 | print_dir_entry(orig); 1448 | TRACE("----------------------------\n"); 1449 | print_dir_entry(new); 1450 | TRACE("----------------------------\n"); 1451 | 1452 | if (memcmp(orig, new, sizeof(fat_dir_entry_t) * 16) == 0) { 1453 | return; 1454 | } 1455 | 1456 | for (int i = 0; i < 16; i++) { 1457 | if (strncmp((const char *)new[i].DIR_Name, ". ", 11) == 0 1458 | || strncmp((const char *)new[i].DIR_Name, ".. ", 11) == 0 1459 | || (new[i].DIR_Attr & 0x0F) == 0x0F 1460 | || (new[i].DIR_Attr & 0x08) == 0x08) // volume label 1461 | { 1462 | continue; 1463 | } 1464 | 1465 | if (new[i].DIR_Name[0] == 0xE5) { 1466 | for (int j = 0; j < 16; j++) { 1467 | if ((orig[j].DIR_Attr & 0x08) == 0x08) // volume label 1468 | continue; 1469 | if (new[i].DIR_FstClusLO == orig[j].DIR_FstClusLO 1470 | && new[i].DIR_FileSize == orig[j].DIR_FileSize 1471 | && orig[j].DIR_Name[0] != 0xE5 1472 | && new[i].DIR_FileSize != 0) 1473 | { 1474 | // `delete` or `rename`. 1475 | memcpy(delete, &orig[j], sizeof(fat_dir_entry_t)); 1476 | delete++; 1477 | break; 1478 | } 1479 | 1480 | if (orig[j].DIR_Attr & 0x10 // directory 1481 | && new[i].DIR_FstClusLO == orig[j].DIR_FstClusLO 1482 | && new[i].DIR_FileSize == 0 1483 | && orig[j].DIR_Name[0] != 0xE5 1484 | && i == j) 1485 | { 1486 | // `delete` or `rename`. 1487 | memcpy(delete, &orig[j], sizeof(fat_dir_entry_t)); 1488 | delete++; 1489 | break; 1490 | } 1491 | } 1492 | continue; 1493 | } 1494 | 1495 | is_found = false; 1496 | for (int j = 0; j < 16; j++) { 1497 | if (new[i].DIR_Name[0] == 0xE5 1498 | || strncmp((const char *)orig[j].DIR_Name, ". ", 11) == 0 1499 | || strncmp((const char *)orig[j].DIR_Name, ".. ", 11) == 0 1500 | || (orig[j].DIR_Attr & 0x0F) == 0x0F 1501 | || (orig[j].DIR_Attr & 0x08) == 0x08) // volume label 1502 | { 1503 | continue; 1504 | } 1505 | 1506 | if (strncmp((const char *)new[i].DIR_Name, (const char *)orig[j].DIR_Name, 11) == 0 && 1507 | new[i].DIR_FstClusLO == orig[j].DIR_FstClusLO && 1508 | new[i].DIR_FileSize == orig[j].DIR_FileSize) 1509 | { 1510 | is_found = true; 1511 | break; 1512 | } 1513 | 1514 | // rename 1515 | if (i == j && 1516 | new[i].DIR_FstClusLO == orig[j].DIR_FstClusLO && 1517 | new[i].DIR_FileSize == orig[j].DIR_FileSize) 1518 | { 1519 | memcpy(delete, &orig[j], sizeof(fat_dir_entry_t)); 1520 | delete++; 1521 | break; 1522 | } 1523 | } 1524 | if (!is_found) { 1525 | memcpy(update, &new[i], sizeof(fat_dir_entry_t)); 1526 | update++; 1527 | } 1528 | } 1529 | } 1530 | 1531 | static int littlefs_mkdir(const char *filename) { 1532 | TRACE(ANSI_RED "littlefs_mkdir('%s')\n" ANSI_CLEAR, filename); 1533 | struct lfs_info finfo; 1534 | 1535 | int err = lfs_stat(&real_filesystem, filename, &finfo); 1536 | if (err == LFS_ERR_OK) { 1537 | return LFS_ERR_OK; 1538 | } 1539 | 1540 | err = lfs_mkdir(&real_filesystem, filename); 1541 | if (err != LFS_ERR_OK && err != LFS_ERR_EXIST) { 1542 | TRACE("littlefs_mkdir: lfs_mkdir err=%d\n", err); 1543 | return err; 1544 | } 1545 | 1546 | return LFS_ERR_OK; 1547 | } 1548 | 1549 | static int littlefs_write(const char *filename, uint32_t cluster, size_t size) { 1550 | TRACE(ANSI_RED "littlefs_write('%s', cluster=%lu, size=%u)\n" ANSI_CLEAR, filename, cluster, size); 1551 | 1552 | uint8_t buffer[512]; 1553 | 1554 | if (strlen(filename) == 0) { 1555 | printf(ANSI_RED "littlefs_write: filename not specified\n" ANSI_CLEAR); 1556 | return -1; 1557 | } 1558 | 1559 | lfs_file_t f; 1560 | int err = lfs_file_open(&real_filesystem, &f, filename, LFS_O_RDWR|LFS_O_CREAT); 1561 | if (err != LFS_ERR_OK) { 1562 | TRACE("littlefs_write: lfs_file_open error=%d\n", err); 1563 | return err; 1564 | } 1565 | 1566 | while (true) { 1567 | err = read_temporary_file(cluster, buffer); 1568 | if (err != LFS_ERR_OK) { 1569 | TRACE("littlefs_write: read_temporary_file error=%d\n", err); 1570 | lfs_file_close(&real_filesystem, &f); 1571 | return err; 1572 | } 1573 | size_t s = lfs_file_write(&real_filesystem, &f, buffer, sizeof(buffer)); 1574 | if (s != 512) { 1575 | TRACE("littlefs_write: lfs_file_write, %u < %u\n", s, 512); 1576 | lfs_file_close(&real_filesystem, &f); 1577 | return -1; 1578 | } 1579 | int next_cluster = read_fat(cluster); 1580 | if (next_cluster == 0x00) // not allocated 1581 | break; 1582 | if (next_cluster >= 0xFF8) // eof 1583 | break; 1584 | cluster = next_cluster; 1585 | } 1586 | err = lfs_file_truncate(&real_filesystem, &f, size); 1587 | if (err != LFS_ERR_OK) { 1588 | TRACE("littlefs_write: lfs_file_truncate err=%d\n", err); 1589 | lfs_file_close(&real_filesystem, &f); 1590 | return err; 1591 | } 1592 | lfs_file_close(&real_filesystem, &f); 1593 | return 0; 1594 | } 1595 | 1596 | static int littlefs_remove(const char *filename) { 1597 | TRACE(ANSI_RED "littlefs_remove('%s')\n" ANSI_CLEAR, filename); 1598 | 1599 | if (strlen(filename) == 0) { 1600 | TRACE("littlefs_remove: not allow brank filename\n"); 1601 | return LFS_ERR_INVAL; 1602 | } 1603 | int err = lfs_remove(&real_filesystem, filename); 1604 | if (err != LFS_ERR_OK) { 1605 | TRACE("littlefs_remove: lfs_remove: err=%d\n", err); 1606 | return err; 1607 | } 1608 | 1609 | return LFS_ERR_OK; 1610 | } 1611 | 1612 | /* 1613 | * Update a file or directory from the difference indicated by *src in dir_cluster_id 1614 | * 1615 | * *src is an array of differences created by diff_dir_entry() 1616 | */ 1617 | static void update_lfs_file_or_directory(fat_dir_entry_t *src, uint32_t dir_cluster_id) { 1618 | TRACE("update_lfs_file_or_directory(dir_cluster_id=%lu)\n", dir_cluster_id); 1619 | char filename[LFS_NAME_MAX + 1]; 1620 | char directory[LFS_NAME_MAX + 1]; 1621 | uint16_t long_filename[LFS_NAME_MAX + 1] = {0}; 1622 | 1623 | strcpy(directory, ""); 1624 | 1625 | bool is_long_filename = false; 1626 | for (int i = 0; i < 16; i++) { 1627 | fat_dir_entry_t *dir = &src[i]; 1628 | if (dir->DIR_Name[0] == '\0') 1629 | break; 1630 | if (dir->DIR_Name[0] == 0xE5 || dir->DIR_Name[0] == 0x05) 1631 | continue; 1632 | if (memcmp(dir->DIR_Name, ".. ", 11) == 0) 1633 | continue; 1634 | if (memcmp(dir->DIR_Name, ". ", 11) == 0) 1635 | continue; 1636 | 1637 | if ((dir->DIR_Attr & 0x0F) == 0x0F) { 1638 | fat_lfn_t *long_file = (fat_lfn_t *)dir; 1639 | if (long_file->LDIR_Ord & 0x40) { 1640 | memset(long_filename, 0xFF, sizeof(long_filename)); 1641 | is_long_filename = true; 1642 | } 1643 | int offset = (long_file->LDIR_Ord & 0x0F) - 1; 1644 | memcpy(&long_filename[offset * 13], long_file->LDIR_Name1, sizeof(uint16_t) * 5); 1645 | memcpy(&long_filename[offset * 13 + 5], long_file->LDIR_Name2, sizeof(uint16_t) * 6); 1646 | memcpy(&long_filename[offset * 13 + 5 + 6], long_file->LDIR_Name3, sizeof(uint16_t) * 2); 1647 | continue; 1648 | } 1649 | if (dir->DIR_Attr & 0x10) { // is directory 1650 | if (is_long_filename) { 1651 | utf16le_to_utf8(filename, sizeof(filename), long_filename, sizeof(long_filename)); 1652 | } else { 1653 | restore_from_short_dirname(filename, (const char *)dir->DIR_Name); 1654 | } 1655 | // FIXME: If there is a directory to be deleted with the same name, 1656 | // the files in the directory must be copied. 1657 | restore_directory_from(directory, dir_cluster_id, dir->DIR_FstClusLO); 1658 | littlefs_mkdir(directory); 1659 | create_blank_dir_entry_cache(dir->DIR_FstClusLO, dir_cluster_id); 1660 | 1661 | is_long_filename = false; 1662 | 1663 | continue; 1664 | } else if (dir->DIR_Attr & 0x20 || dir->DIR_Attr == 0x00) { // is file 1665 | if (is_long_filename) { 1666 | utf16le_to_utf8(filename, sizeof(filename), long_filename, sizeof(long_filename)); 1667 | } else { 1668 | restore_from_short_filename(filename, (const char *)dir->DIR_Name); 1669 | } 1670 | 1671 | if (dir->DIR_FstClusLO == 0) { 1672 | TRACE(" Files not yet assigned cluster=0\n"); 1673 | break; 1674 | } 1675 | 1676 | restore_file_from(filename, dir_cluster_id, dir->DIR_FstClusLO); 1677 | littlefs_write((const char *)filename, dir->DIR_FstClusLO, dir->DIR_FileSize); 1678 | is_long_filename = false; 1679 | continue; 1680 | } else { 1681 | TRACE(" unknown DIR_Attr = 0x%02X\n", dir->DIR_Attr); 1682 | } 1683 | is_long_filename = false; 1684 | } 1685 | } 1686 | 1687 | /* 1688 | * Save the contents of real file system filename in the cluster cache 1689 | */ 1690 | static void save_file_clusters(uint32_t cluster, const char *filename) { 1691 | TRACE("save_file_clusters(cluster=%lu, '%s')\n", cluster, filename); 1692 | 1693 | uint8_t buffer[DISK_SECTOR_SIZE] = {0}; 1694 | uint32_t next_cluster = cluster; 1695 | lfs_file_t f; 1696 | 1697 | int err = lfs_file_open(&real_filesystem, &f, filename, LFS_O_RDONLY); 1698 | if (err != LFS_ERR_OK) { 1699 | printf("save_file_clusters: lfs_file_open('%s') error=%d\n", filename, err); 1700 | return; 1701 | } 1702 | 1703 | lfs_soff_t seek_pos; 1704 | lfs_ssize_t read_bytes; 1705 | int offset = 0; 1706 | while (next_cluster < 0xFF8) { 1707 | next_cluster = read_fat(cluster); 1708 | 1709 | seek_pos = lfs_file_seek(&real_filesystem, &f, offset * DISK_SECTOR_SIZE, LFS_SEEK_SET); 1710 | if (seek_pos < 0) { 1711 | printf("save_file_clusters: lfs_file_seek(%u) failed: error=%ld\n", 1712 | offset * DISK_SECTOR_SIZE, seek_pos); 1713 | break; 1714 | } 1715 | read_bytes = lfs_file_read(&real_filesystem, &f, buffer, sizeof(buffer)); 1716 | if (read_bytes < 0) { 1717 | printf("save_file_clusters: lfs_file_read() error=%ld\n", read_bytes); 1718 | break; 1719 | } 1720 | save_temporary_file(cluster, buffer); 1721 | cluster = next_cluster; 1722 | offset++; 1723 | } 1724 | 1725 | lfs_file_close(&real_filesystem, &f); 1726 | } 1727 | 1728 | static void delete_dir_entry_cache(fat_dir_entry_t *src, uint32_t dir_cluster_id) { 1729 | char filename[LFS_NAME_MAX + 1]; 1730 | 1731 | for (int i = 0; i < 16; i++) { 1732 | fat_dir_entry_t *dir = &src[i]; 1733 | if (dir->DIR_Name[0] == '\0') 1734 | break; 1735 | 1736 | if (dir->DIR_Attr & 0x10) { 1737 | restore_directory_from(filename, dir_cluster_id, dir->DIR_FstClusLO); 1738 | } else { 1739 | restore_file_from(filename, dir_cluster_id, dir->DIR_FstClusLO); 1740 | save_file_clusters(dir->DIR_FstClusLO, filename); 1741 | } 1742 | littlefs_remove(filename); 1743 | 1744 | // Cluster cache is needed at the rename destination, so do not delete it. 1745 | /* 1746 | uint32_t next_cluster = dir->DIR_FstClusLO; 1747 | while (true) { 1748 | delete_temporary_file(next_cluster); 1749 | next_cluster = read_fat(next_cluster); 1750 | if (next_cluster >= 0xFF8) { 1751 | break; 1752 | } 1753 | } 1754 | */ 1755 | continue; 1756 | } 1757 | } 1758 | 1759 | static void update_dir_entry(uint32_t cluster, void *buffer) { 1760 | fat_dir_entry_t orig[16] = {0}; 1761 | fat_dir_entry_t *new = buffer; 1762 | fat_dir_entry_t dir_update[16] = {0}; 1763 | fat_dir_entry_t dir_delete[16] = {0}; 1764 | 1765 | if (read_temporary_file(cluster, orig) != 0) { 1766 | printf("update_dir_entry: entry not found cluster=%lu\n", cluster); 1767 | return; 1768 | } 1769 | 1770 | difference_of_dir_entry(orig, new, dir_update, dir_delete); 1771 | delete_dir_entry_cache(dir_delete, cluster); 1772 | 1773 | save_temporary_file(cluster, buffer); 1774 | update_lfs_file_or_directory(dir_update, cluster); 1775 | } 1776 | 1777 | /* 1778 | * Save request_blocks not associated with a resource in a temporary file 1779 | */ 1780 | static void update_file_entry(uint32_t cluster, void *buffer, uint32_t bufsize, 1781 | find_dir_entry_cache_result_t *result, size_t offset) 1782 | { 1783 | save_temporary_file(cluster, buffer); 1784 | if (!result->is_found) 1785 | return; 1786 | 1787 | TRACE(ANSI_RED "lfs_file_open('%s', cluster=%lu, offset=%u)\n" ANSI_CLEAR, result->path, cluster, offset); 1788 | lfs_file_t f; 1789 | int err; 1790 | if (offset == 0) { 1791 | err = lfs_file_open(&real_filesystem, &f, result->path, LFS_O_WRONLY|LFS_O_CREAT|LFS_O_TRUNC); 1792 | if (err != LFS_ERR_OK) { 1793 | printf("update_file_entry: lfs_file_open('%s') error=%d\n", result->path, err); 1794 | return; 1795 | } 1796 | } else { 1797 | err = lfs_file_open(&real_filesystem, &f, result->path, LFS_O_WRONLY); 1798 | if (err != LFS_ERR_OK) { 1799 | printf("update_file_entry: lfs_file_open('%s') error=%d\n", result->path, err); 1800 | return; 1801 | } 1802 | lfs_file_seek(&real_filesystem, &f, offset * DISK_SECTOR_SIZE, LFS_SEEK_SET); 1803 | } 1804 | 1805 | lfs_ssize_t size = lfs_file_write(&real_filesystem, &f, buffer, bufsize); 1806 | if (size < 0 || size != 512) { 1807 | printf("update_file_entry: lfs_file_write('%s') error=%ld\n", result->path, size); 1808 | lfs_file_close(&real_filesystem, &f); 1809 | return; 1810 | } 1811 | 1812 | if ((1 + offset) * 512 >= result->size) { 1813 | err = lfs_file_truncate(&real_filesystem, &f, result->size); 1814 | if (err != LFS_ERR_OK) { 1815 | printf("update_file_entry: lfs_file_truncate('%s') error=%d\n", result->path, err); 1816 | lfs_file_close(&real_filesystem, &f); 1817 | return; 1818 | } 1819 | } 1820 | err = lfs_file_close(&real_filesystem, &f); 1821 | if (err != LFS_ERR_OK) { 1822 | printf("update_file_entry: lfs_file_close('%s') error=%d\n", result->path, err); 1823 | return; 1824 | } 1825 | } 1826 | 1827 | void mimic_fat_write(uint8_t lun, uint32_t request_block, void *buffer, uint32_t bufsize) { 1828 | (void)lun; 1829 | find_dir_entry_cache_result_t result; 1830 | 1831 | if (request_block == 0) // master boot record 1832 | return; 1833 | 1834 | if (is_fat_sector(request_block)) { // FAT table 1835 | TRACE("\e[35mWrite FAT table\n" ANSI_CLEAR); 1836 | save_fat_sector(request_block, buffer, bufsize); 1837 | return; 1838 | } 1839 | 1840 | uint32_t cluster = request_block - fat_sector_size(); 1841 | TRACE("\e[35mWrite cluster=%lu\e[0m\n", cluster); 1842 | if (cluster == 1) { // root dir entry 1843 | TRACE("mimic_fat_write: update root dir_entry\n"); 1844 | 1845 | fat_dir_entry_t orig[16] = {0}; 1846 | fat_dir_entry_t dir_update[16] = {0}; 1847 | fat_dir_entry_t dir_delete[16] = {0}; 1848 | 1849 | read_temporary_file(cluster, orig); 1850 | difference_of_dir_entry(&orig[0], (fat_dir_entry_t *)buffer, dir_update, dir_delete); 1851 | 1852 | delete_dir_entry_cache(dir_delete, cluster); 1853 | 1854 | save_temporary_file(cluster, buffer); 1855 | save_temporary_file(0, buffer); // FIXME 1856 | 1857 | update_lfs_file_or_directory(dir_update, cluster); 1858 | } else { // data or directory entry 1859 | size_t offset = 0; 1860 | uint32_t base_cluster = find_base_cluster_and_offset(cluster, &offset); 1861 | 1862 | if (base_cluster == 0) { 1863 | TRACE("mimic_fat_write: not allocated cluster\n"); 1864 | save_temporary_file(cluster, buffer); 1865 | 1866 | // For hosts that write to unallocated space first 1867 | find_dir_entry_cache_return_t r = find_dir_entry_cache(&result, 1, cluster); 1868 | if (r != FIND_DIR_ENTRY_CACHE_RESULT_FOUND) // error or not found 1869 | return; 1870 | if (result.is_found && !result.is_directory) { 1871 | littlefs_write(result.path, cluster, result.size); 1872 | } 1873 | return; 1874 | } 1875 | 1876 | find_dir_entry_cache_return_t r = find_dir_entry_cache(&result, 1, base_cluster); 1877 | 1878 | if (r == FIND_DIR_ENTRY_CACHE_RESULT_ERROR) { 1879 | TRACE("mimic_fat_write: find_dir_entry_cache(1, base_cluster=%lu) error=%d\n", 1880 | base_cluster, r); 1881 | return; 1882 | } 1883 | if (r == FIND_DIR_ENTRY_CACHE_RESULT_NOT_FOUND) { 1884 | TRACE(ANSI_RED "find_dir_entry_cache not found cluster=%lu\n" ANSI_CLEAR, base_cluster); 1885 | save_temporary_file(cluster, buffer); 1886 | return; 1887 | } 1888 | 1889 | if (result.is_directory) 1890 | update_dir_entry(cluster, buffer); 1891 | else 1892 | update_file_entry(cluster, buffer, bufsize, &result, offset); 1893 | } 1894 | } 1895 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_BUILD_TYPE Debug) 2 | 3 | add_executable(tests 4 | ../mimic_fat.c 5 | ../littlefs_driver.c 6 | ../unicode.c 7 | ../usb_msc_driver.c 8 | ../usb_descriptors.c 9 | main.c 10 | util.c 11 | test_create.c 12 | test_read.c 13 | test_update.c 14 | test_rename.c 15 | test_move.c 16 | test_delete.c 17 | test_large_file.c 18 | ) 19 | 20 | target_link_libraries(tests PRIVATE 21 | hardware_flash 22 | hardware_sync 23 | littlefs 24 | pico_stdlib 25 | tinyusb_additions 26 | tinyusb_board 27 | tinyusb_device 28 | ) 29 | 30 | target_include_directories(tests 31 | PRIVATE 32 | ${CMAKE_CURRENT_LIST_DIR}/../include 33 | ) 34 | #target_compile_options(tests PRIVATE -Werror -Wall -Wextra -Wnull-dereference) 35 | #target_compile_options(tests PRIVATE -DENABLE_TRACE) 36 | 37 | pico_add_extra_outputs(tests) 38 | pico_enable_stdio_usb(tests 1) 39 | 40 | 41 | if(OPENOCD) 42 | add_custom_target(run_tests 43 | COMMAND ${OPENOCD} -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000" -c "program tests.elf verify reset exit" 44 | DEPENDS tests 45 | ) 46 | endif() 47 | -------------------------------------------------------------------------------- /tests/main.c: -------------------------------------------------------------------------------- 1 | #include "tests.h" 2 | #include 3 | #include 4 | 5 | 6 | #define ANSI_GREEN "\e[32m" 7 | #define ANSI_CLEAR "\e[0m" 8 | 9 | int main(void) { 10 | board_init(); 11 | tud_init(BOARD_TUD_RHPORT); 12 | stdio_init_all(); 13 | 14 | printf("Start all tests\n"); 15 | 16 | test_create(); 17 | test_read(); 18 | test_update(); 19 | test_rename(); 20 | test_move(); 21 | test_delete(); 22 | 23 | test_large_file(); 24 | 25 | printf(ANSI_GREEN "All tests are ok\n" ANSI_CLEAR); 26 | } 27 | -------------------------------------------------------------------------------- /tests/test_create.c: -------------------------------------------------------------------------------- 1 | #include "tests.h" 2 | 3 | 4 | extern const struct lfs_config lfs_pico_flash_config; // littlefs_driver.c 5 | extern int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); 6 | extern int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); 7 | 8 | static lfs_t fs; 9 | 10 | 11 | static void setup(void) { 12 | int err = lfs_format(&fs, &lfs_pico_flash_config); 13 | assert(err == 0); 14 | err = lfs_mount(&fs, &lfs_pico_flash_config); 15 | assert(err == 0); 16 | 17 | } 18 | 19 | static void reload(void) { 20 | lfs_unmount(&fs); 21 | int err = lfs_mount(&fs, &lfs_pico_flash_config); 22 | assert(err == 0); 23 | } 24 | 25 | static void cleanup(void) { 26 | lfs_unmount(&fs); 27 | } 28 | 29 | static void test_create_file(void) { 30 | uint8_t buffer[512]; 31 | const uint8_t message[] = "Hello World\n"; 32 | 33 | setup(); 34 | 35 | mimic_fat_init(&lfs_pico_flash_config); 36 | mimic_fat_create_cache(); 37 | 38 | // Create procedure from the USB layer 39 | 40 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 41 | uint32_t first_fat_sector = 1; 42 | uint32_t root_dir_sector = fat_sectors + 1; 43 | 44 | uint16_t cluster = 2; 45 | memset(buffer, 0, sizeof(buffer)); 46 | strncpy(buffer, message, sizeof(buffer)); 47 | tud_msc_write10_cb(0, fat_sectors + cluster, 0, buffer, sizeof(buffer)); // write to anonymous cache 48 | 49 | uint8_t fat[512] = {0xF8, 0xFF, 0xFF, 0x00, 0x00}; 50 | update_fat(fat, cluster, 0xFFF); 51 | tud_msc_write10_cb(0, first_fat_sector, 0, fat, sizeof(fat)); // update file allocated table 52 | 53 | fat_dir_entry_t root[16] = { 54 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 55 | {.DIR_Name = "CREATE TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = cluster, .DIR_FileSize = strlen(message)}, 56 | }; 57 | tud_msc_write10_cb(0, root_dir_sector, 0, root, sizeof(root)); // update directory entry 58 | 59 | reload(); 60 | 61 | // Test reflection on the littlefs layer 62 | lfs_file_t f; 63 | int err = lfs_file_open(&fs, &f, "CREATE.TXT", LFS_O_RDONLY); 64 | assert(err == LFS_ERR_OK); 65 | lfs_ssize_t size = lfs_file_read(&fs, &f, buffer, sizeof(buffer)); 66 | assert(size == strlen(message)); 67 | assert(strcmp(buffer, message) == 0); 68 | lfs_file_close(&fs, &f); 69 | 70 | cleanup(); 71 | } 72 | 73 | static void test_create_file_windows11(void) { 74 | uint8_t buffer[512]; 75 | const uint8_t message[] = "Hello World\n"; 76 | 77 | setup(); 78 | 79 | mimic_fat_init(&lfs_pico_flash_config); 80 | mimic_fat_create_cache(); 81 | 82 | 83 | // update dir entry. The cluster to which the file belongs is set to 0. 84 | fat_dir_entry_t root0[16] = { 85 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 86 | {.DIR_Name = "CREATE TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 87 | }; 88 | uint16_t fat_sectors = fat_sector_size(&lfs_pico_flash_config); 89 | uint32_t root_dir_sector = fat_sectors + 1; 90 | tud_msc_write10_cb(0, root_dir_sector, 0, root0, sizeof(root0)); 91 | 92 | // update dir entry. The cluster to be assigned is specified. Not yet allocated. 93 | uint16_t cluster = root_dir_sector + 1; 94 | fat_dir_entry_t root1[16] = { 95 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 96 | {.DIR_Name = "CREATE TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = cluster, .DIR_FileSize = strlen(message)}, 97 | }; 98 | tud_msc_write10_cb(0, root_dir_sector, 0, root1, sizeof(root1)); 99 | 100 | // write the file block to the cluster specified in step 2. 101 | memset(buffer, 0, sizeof(buffer)); 102 | strncpy(buffer, message, sizeof(buffer)); 103 | tud_msc_write10_cb(0, cluster + fat_sectors, 0, buffer, sizeof(buffer)); 104 | 105 | // update File allocation table 106 | uint8_t fat[512] = {0xF8, 0xFF, 0xFF, 0x00, 0x00}; 107 | update_fat(fat, cluster, 0xFFF); 108 | tud_msc_write10_cb(0, 1, 0, fat, sizeof(fat)); 109 | 110 | reload(); 111 | 112 | // Test reflection on the littlefs layer 113 | lfs_file_t f; 114 | int err = lfs_file_open(&fs, &f, "CREATE.TXT", LFS_O_RDONLY); 115 | assert(err == LFS_ERR_OK); 116 | lfs_ssize_t size = lfs_file_read(&fs, &f, buffer, sizeof(buffer)); 117 | assert(size == strlen(message)); 118 | assert(strcmp(buffer, message) == 0); 119 | lfs_file_close(&fs, &f); 120 | 121 | cleanup(); 122 | } 123 | 124 | static void test_create_accross_blocksize(void) { 125 | uint8_t buffer[512] = {0}; 126 | uint8_t read_buffer[1024] = {0}; 127 | const uint8_t message[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" \ 128 | "Hello World\n"; //512 byte + "Hello World\n" 129 | 130 | setup(); 131 | 132 | mimic_fat_init(&lfs_pico_flash_config); 133 | mimic_fat_create_cache(); 134 | 135 | uint16_t fat_sectors = fat_sector_size(&lfs_pico_flash_config); 136 | uint32_t first_fat_sector = 1; 137 | uint32_t root_dir_sector = fat_sectors + 1; 138 | 139 | // Update procedure from the USB layer 140 | uint16_t cluster = root_dir_sector + 1; 141 | memcpy(buffer, message, sizeof(buffer)); 142 | tud_msc_write10_cb(0, cluster + fat_sectors, 0, buffer, sizeof(buffer)); // write to first block 143 | memset(buffer, 0, sizeof(buffer)); 144 | strncpy(buffer, message + 512, sizeof(buffer)); 145 | tud_msc_write10_cb(0, cluster + fat_sectors + 1, 0, buffer, sizeof(buffer)); // write to 2nd block 146 | 147 | uint8_t fat[512] = {0xF8, 0xFF, 0xFF, 0x00, 0x00}; 148 | update_fat(fat, cluster, cluster + 1); // point next cluster 149 | update_fat(fat, cluster + 1, 0xFFF); // terminate allocate chain 150 | tud_msc_write10_cb(0, 1, 0, fat, sizeof(fat)); // update file allocated table 151 | 152 | fat_dir_entry_t root[16] = { 153 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 154 | {.DIR_Name = "CREATE TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = cluster, .DIR_FileSize = strlen(message)}, 155 | }; 156 | tud_msc_write10_cb(0, root_dir_sector, 0, root, sizeof(root)); // update directory entry 157 | 158 | reload(); 159 | 160 | // Test reflection on the littlefs layer 161 | lfs_file_t f; 162 | int err = lfs_file_open(&fs, &f, "CREATE.TXT", LFS_O_RDONLY); 163 | assert(err == LFS_ERR_OK); 164 | lfs_ssize_t size = lfs_file_read(&fs, &f, read_buffer, sizeof(read_buffer)); 165 | assert(size == strlen(message)); 166 | assert(strcmp(read_buffer, message) == 0); 167 | lfs_file_close(&fs, &f); 168 | 169 | cleanup(); 170 | } 171 | 172 | void test_create(void) { 173 | printf("create ................."); 174 | 175 | test_create_file(); 176 | test_create_file_windows11(); 177 | test_create_accross_blocksize(); 178 | 179 | printf("ok\n"); 180 | } 181 | -------------------------------------------------------------------------------- /tests/test_delete.c: -------------------------------------------------------------------------------- 1 | #include "tests.h" 2 | 3 | 4 | extern const struct lfs_config lfs_pico_flash_config; // littlefs_driver.c 5 | extern int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); 6 | 7 | static lfs_t fs; 8 | 9 | 10 | #define MESSAGE "Please delete me!\n" 11 | 12 | static void setup_file(void) { 13 | int err = lfs_format(&fs, &lfs_pico_flash_config); 14 | assert(err == 0); 15 | err = lfs_mount(&fs, &lfs_pico_flash_config); 16 | assert(err == 0); 17 | 18 | create_file(&fs, "DELETEME.TXT", MESSAGE); 19 | } 20 | 21 | static void setup_dir(void) { 22 | int err = lfs_format(&fs, &lfs_pico_flash_config); 23 | assert(err == 0); 24 | err = lfs_mount(&fs, &lfs_pico_flash_config); 25 | assert(err == 0); 26 | 27 | create_directory(&fs, "DIR_DEL"); 28 | } 29 | 30 | static void reload(void) { 31 | lfs_unmount(&fs); 32 | int err = lfs_mount(&fs, &lfs_pico_flash_config); 33 | assert(err == 0); 34 | } 35 | 36 | static void cleanup(void) { 37 | lfs_unmount(&fs); 38 | } 39 | 40 | static void test_delete_file(void) { 41 | setup_file(); 42 | 43 | mimic_fat_init(&lfs_pico_flash_config); 44 | mimic_fat_create_cache(); 45 | 46 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 47 | uint32_t first_fat_sector = 1; 48 | uint32_t root_dir_sector = fat_sectors + 1; 49 | 50 | // Update procedure from the USB layer 51 | uint16_t cluster = 2; 52 | uint8_t buffer[512]; 53 | 54 | // update dir entry. Assign a delete flag to the filename 55 | fat_dir_entry_t root[16] = { 56 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 57 | {.DIR_Name = "DELETEMETXT", .DIR_Attr = 0x20, .DIR_FstClusLO = cluster, .DIR_FileSize = strlen(MESSAGE)}, 58 | }; 59 | root[1].DIR_Name[0] = 0xE5; // delete flag 60 | tud_msc_write10_cb(0, root_dir_sector, 0, root, sizeof(root)); // update directory entry 61 | 62 | // update File allocation table 63 | uint8_t fat[512] = {0xF8, 0xFF, 0xFF, 0xFF, 0x0F}; // With cluster 2 allocated 64 | update_fat(fat, cluster, 0x000); 65 | tud_msc_write10_cb(0, 1, 0, fat, sizeof(fat)); // update file allocated table 66 | 67 | reload(); 68 | 69 | // Test reflection on the littlefs layer 70 | struct lfs_info finfo; 71 | int err = lfs_stat(&fs, "DELETEME.TXT", &finfo); 72 | assert(err == LFS_ERR_NOENT); 73 | 74 | cleanup(); 75 | } 76 | 77 | static void test_delete_dir(void) { 78 | setup_dir(); 79 | 80 | mimic_fat_init(&lfs_pico_flash_config); 81 | mimic_fat_create_cache(); 82 | 83 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 84 | uint32_t first_fat_sector = 1; 85 | uint32_t root_dir_sector = fat_sectors + 1; 86 | 87 | // Update procedure from the USB layer 88 | uint16_t cluster = 2; 89 | uint8_t buffer[512]; 90 | 91 | // update dir entry. Assign a delete flag to the filename 92 | fat_dir_entry_t root[16] = { 93 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 94 | {.DIR_Name = "DIR_DEL ", .DIR_Attr = 0x10, .DIR_FstClusLO = cluster, .DIR_FileSize = 0}, 95 | }; 96 | root[1].DIR_Name[0] = 0xE5; // delete flag 97 | tud_msc_write10_cb(0, root_dir_sector, 0, root, sizeof(root)); // update directory entry 98 | 99 | reload(); 100 | 101 | // Test reflection on the littlefs layer 102 | struct lfs_info finfo; 103 | int err = lfs_stat(&fs, "DIR_DEL", &finfo); 104 | assert(err == LFS_ERR_NOENT); 105 | 106 | cleanup(); 107 | } 108 | 109 | 110 | void test_delete(void) { 111 | printf("delete ................."); 112 | 113 | test_delete_file(); 114 | test_delete_dir(); 115 | 116 | printf("ok\n"); 117 | } 118 | -------------------------------------------------------------------------------- /tests/test_large_file.c: -------------------------------------------------------------------------------- 1 | #include "tests.h" 2 | #include 3 | #include 4 | #include 5 | 6 | extern const struct lfs_config lfs_pico_flash_config; // littlefs_driver.c 7 | extern int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); 8 | 9 | static lfs_t fs; 10 | static struct lfs_config test_config = { 11 | .read_size = 1, 12 | .prog_size = FLASH_PAGE_SIZE, 13 | .block_size = FLASH_SECTOR_SIZE, 14 | .block_count = (1.8 * 1024 * 1024) / FLASH_SECTOR_SIZE, 15 | .cache_size = FLASH_SECTOR_SIZE, 16 | .lookahead_size = 16, 17 | .block_cycles = 500, 18 | }; 19 | 20 | static void setup(void) { 21 | test_config.read = lfs_pico_flash_config.read; 22 | test_config.prog = lfs_pico_flash_config.prog; 23 | test_config.erase = lfs_pico_flash_config.erase; 24 | test_config.sync = lfs_pico_flash_config.sync; 25 | 26 | int err = lfs_format(&fs, (const struct lfs_config *)&lfs_pico_flash_config); 27 | assert(err == 0); 28 | err = lfs_mount(&fs, (const struct lfs_config *)&lfs_pico_flash_config); 29 | assert(err == 0); 30 | 31 | } 32 | 33 | static void reload(void) { 34 | lfs_unmount(&fs); 35 | int err = lfs_mount(&fs, &lfs_pico_flash_config); 36 | assert(err == 0); 37 | } 38 | 39 | 40 | static void cleanup(void) { 41 | lfs_unmount(&fs); 42 | } 43 | 44 | static uint32_t xor_rand(uint32_t *seed) { 45 | *seed ^= *seed << 13; 46 | *seed ^= *seed >> 17; 47 | *seed ^= *seed << 5; 48 | return *seed; 49 | } 50 | 51 | 52 | static void test_read_large_file(size_t file_size) { 53 | setup(); 54 | 55 | lfs_file_t f; 56 | int rc = lfs_file_open(&fs, &f, "LARGE.TXT", LFS_O_RDWR|LFS_O_CREAT); 57 | assert(rc == 0); 58 | uint8_t buffer[512]; 59 | 60 | uint32_t counter = file_size; 61 | xor_rand(&counter); 62 | 63 | size_t remind = file_size; 64 | size_t total_written = 0; 65 | size_t segment_size = file_size / 10; 66 | size_t next_segment_threshold = segment_size; 67 | while (remind > 0) { 68 | size_t chunk = remind % sizeof(buffer) ? remind % sizeof(buffer) : sizeof(buffer); 69 | uint32_t *b = (uint32_t *)buffer; 70 | for (size_t i = 0; i < (chunk / 4); i++) { 71 | b[i] = xor_rand(&counter); 72 | } 73 | lfs_ssize_t size = lfs_file_write(&fs, &f, buffer, chunk); 74 | assert(size == chunk); 75 | remind = remind - size; 76 | 77 | total_written += size; 78 | if (total_written >= next_segment_threshold || remind == 0) { 79 | printf("."); 80 | next_segment_threshold += segment_size; 81 | } 82 | } 83 | rc = lfs_file_close(&fs, &f); 84 | assert(rc == 0); 85 | 86 | reload(); 87 | 88 | mimic_fat_init(&lfs_pico_flash_config); 89 | mimic_fat_create_cache(); 90 | 91 | tud_msc_read10_cb(0, 0, 0, buffer, sizeof(buffer)); // Boot sector 92 | tud_msc_read10_cb(0, 1, 0, buffer, sizeof(buffer)); // Allocation table 93 | 94 | uint16_t fat_sectors = fat_sector_size(&lfs_pico_flash_config); 95 | uint32_t root_dir_sector = fat_sectors + 1; 96 | 97 | tud_msc_read10_cb(0, root_dir_sector, 0, buffer, sizeof(buffer)); // Root directory entry 98 | fat_dir_entry_t root[16] = { 99 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 100 | {.DIR_Name = "LARGE TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 2, .DIR_FileSize = file_size}, 101 | }; 102 | assert(dirent_cmp((fat_dir_entry_t *)buffer, root) == 0); 103 | 104 | counter = file_size; // reset random seed 105 | xor_rand(&counter); 106 | uint32_t file_sector = root_dir_sector + 1; 107 | remind = file_size; 108 | total_written = 0; 109 | segment_size = file_size / 10; 110 | next_segment_threshold = segment_size; 111 | while (remind > 0) { 112 | size_t chunk = remind % sizeof(buffer) ? remind % sizeof(buffer) : sizeof(buffer); 113 | char expected[512] = {0}; 114 | uint32_t *b = (uint32_t *)expected; 115 | for (size_t i = 0; i < (chunk / 4); i++) { 116 | b[i] = xor_rand(&counter); 117 | } 118 | tud_msc_read10_cb(0, file_sector, 0, buffer, sizeof(buffer)); 119 | file_sector++; 120 | remind = remind - chunk; 121 | assert(memcmp(expected, buffer, chunk) == 0); 122 | 123 | total_written += chunk; 124 | if (total_written >= next_segment_threshold || remind == 0) { 125 | printf("."); 126 | next_segment_threshold += segment_size; 127 | } 128 | } 129 | 130 | cleanup(); 131 | } 132 | 133 | 134 | void test_large_file(void) { 135 | printf("read 512 bytes file ."); 136 | test_read_large_file(512); 137 | printf(" ok\n"); 138 | 139 | printf("read 1024 bytes file "); 140 | test_read_large_file(1024); 141 | printf(" ok\n"); 142 | 143 | // NOTE: FAT12 tests whether sector boundaries are handled correctly because FAT is 12 bits wide. 144 | printf("read 174080 bytes file "); 145 | test_read_large_file(174080); 146 | printf(" ok\n"); 147 | 148 | printf("read 174592 bytes file "); 149 | test_read_large_file(174592); 150 | printf(" ok\n"); 151 | 152 | printf("read 348160 bytes file "); 153 | test_read_large_file(348160); 154 | printf(" ok\n"); 155 | 156 | printf("read 348672 bytes file "); 157 | test_read_large_file(348672); 158 | printf(" ok\n"); 159 | 160 | printf("read 522240 bytes file "); 161 | test_read_large_file(522240); 162 | printf(" ok\n"); 163 | 164 | printf("read 522752 bytes file "); 165 | test_read_large_file(522752); 166 | printf(" ok\n"); 167 | } 168 | -------------------------------------------------------------------------------- /tests/test_move.c: -------------------------------------------------------------------------------- 1 | #include "tests.h" 2 | 3 | 4 | extern const struct lfs_config lfs_pico_flash_config; // littlefs_driver.c 5 | extern int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); 6 | 7 | static lfs_t fs; 8 | 9 | #define MESSAGE "please move!\n" 10 | 11 | 12 | static void setup(void) { 13 | int err = lfs_format(&fs, &lfs_pico_flash_config); 14 | assert(err == 0); 15 | err = lfs_mount(&fs, &lfs_pico_flash_config); 16 | assert(err == 0); 17 | 18 | create_directory(&fs, "DIR_A"); 19 | create_file(&fs, "MOVEME.TXT", MESSAGE); 20 | } 21 | 22 | static void reload(void) { 23 | lfs_unmount(&fs); 24 | int err = lfs_mount(&fs, &lfs_pico_flash_config); 25 | assert(err == 0); 26 | } 27 | 28 | static void cleanup(void) { 29 | lfs_unmount(&fs); 30 | } 31 | 32 | static void test_move_file(void) { 33 | setup(); 34 | 35 | mimic_fat_init(&lfs_pico_flash_config); 36 | mimic_fat_create_cache(); 37 | 38 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 39 | uint32_t first_fat_sector = 1; 40 | uint32_t root_dir_sector = fat_sectors + 1; 41 | 42 | // Update procedure from the USB layer 43 | uint8_t buffer[512]; 44 | 45 | // update the origin dir entry. Attach deletion flag to filename. 46 | fat_dir_entry_t root0[16] = { 47 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 48 | {.DIR_Name = "DIR_A ", .DIR_Attr = 0x10, .DIR_FstClusLO = 2, .DIR_FileSize = 0}, 49 | {.DIR_Name = "MOVEME TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 3, .DIR_FileSize = strlen(MESSAGE)}, 50 | }; 51 | root0[2].DIR_Name[0] = 0xE5; // deletion flag 52 | tud_msc_write10_cb(0, root_dir_sector, 0, root0, sizeof(root0)); // update origin directory entry 53 | 54 | fat_dir_entry_t dir_a[16] = { 55 | {.DIR_Name = ". ", .DIR_Attr = 0x10, .DIR_FstClusLO = 2, .DIR_FileSize = 0}, 56 | {.DIR_Name = ".. ", .DIR_Attr = 0x10, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 57 | {.DIR_Name = "MOVEME TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 3, .DIR_FileSize = strlen(MESSAGE)}, 58 | }; 59 | tud_msc_write10_cb(0, fat_sectors + 2, 0, dir_a, sizeof(dir_a)); // update destination directory entry 60 | 61 | reload(); 62 | 63 | // Test reflection on the littlefs layer 64 | lfs_file_t f; 65 | int err = lfs_file_open(&fs, &f, "DIR_A/MOVEME.TXT", LFS_O_RDONLY); 66 | assert(err == LFS_ERR_OK); 67 | lfs_ssize_t size = lfs_file_read(&fs, &f, buffer, sizeof(buffer)); 68 | assert(size == strlen(MESSAGE)); 69 | assert(strcmp(buffer, MESSAGE) == 0); 70 | lfs_file_close(&fs, &f); 71 | 72 | struct lfs_info finfo; 73 | err = lfs_stat(&fs, "MOVEME.TXT", &finfo); 74 | assert(err == LFS_ERR_NOENT); 75 | 76 | cleanup(); 77 | } 78 | 79 | void test_move(void) { 80 | printf("move ................."); 81 | 82 | test_move_file(); 83 | 84 | printf("ok\n"); 85 | } 86 | -------------------------------------------------------------------------------- /tests/test_read.c: -------------------------------------------------------------------------------- 1 | #include "tests.h" 2 | 3 | 4 | extern const struct lfs_config lfs_pico_flash_config; // littlefs_driver.c 5 | extern int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); 6 | 7 | static lfs_t fs; 8 | 9 | 10 | static void setup(void) { 11 | int err = lfs_format(&fs, &lfs_pico_flash_config); 12 | assert(err == 0); 13 | err = lfs_mount(&fs, &lfs_pico_flash_config); 14 | assert(err == 0); 15 | } 16 | 17 | static void cleanup(void) { 18 | lfs_unmount(&fs); 19 | } 20 | 21 | static void test_read_file(void) { 22 | uint8_t buffer[512]; 23 | 24 | setup(); 25 | 26 | create_file(&fs, "READ.TXT", "Hello World!\n"); 27 | 28 | mimic_fat_init(&lfs_pico_flash_config); 29 | mimic_fat_create_cache(); 30 | 31 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 32 | uint32_t first_fat_sector = 1; 33 | uint32_t root_dir_sector = fat_sectors + 1; 34 | uint32_t cluster = 2; 35 | 36 | tud_msc_read10_cb(0, 0, 0, buffer, sizeof(buffer)); // Boot sector 37 | tud_msc_read10_cb(0, 1, 0, buffer, sizeof(buffer)); // Allocation table 38 | uint8_t expected_allocation_table[512] = {0xf8, 0xff, 0xff, 0xff, 0x0f, 0x00}; 39 | assert(memcmp(buffer, expected_allocation_table, 6) == 0); 40 | 41 | tud_msc_read10_cb(0, root_dir_sector, 0, buffer, sizeof(buffer)); // Root directory entry 42 | fat_dir_entry_t root[16] = { 43 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 44 | {.DIR_Name = "READ TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = cluster, .DIR_FileSize = strlen("Hello World!\n")}, 45 | }; 46 | assert(dirent_cmp((fat_dir_entry_t *)buffer, root) == 0); 47 | 48 | tud_msc_read10_cb(0, cluster + fat_sectors, 0, buffer, sizeof(buffer)); // TEST.TXT 49 | assert(strcmp(buffer, "Hello World!\n") == 0); 50 | 51 | cleanup(); 52 | } 53 | 54 | static void test_sub_directory(void) { 55 | uint8_t buffer[512]; 56 | 57 | setup(); 58 | 59 | create_directory(&fs, "DIR1"); 60 | create_file(&fs, "DIR1/SUB.TXT", "directory 1\n"); 61 | 62 | mimic_fat_init(&lfs_pico_flash_config); 63 | mimic_fat_create_cache(); 64 | 65 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 66 | uint32_t first_fat_sector = 1; 67 | uint32_t root_dir_sector = fat_sectors + 1; 68 | 69 | tud_msc_read10_cb(0, 0, 0, buffer, sizeof(buffer)); // Boot sector 70 | tud_msc_read10_cb(0, 1, 0, buffer, sizeof(buffer)); // Allocation table 71 | uint8_t expected_allocation_table[512] = {0xf8, 0xff, 0xff, 0xff, 0xff, 0xFF, 0x00}; 72 | assert(memcmp(buffer, expected_allocation_table, 7) == 0); 73 | 74 | tud_msc_read10_cb(0, root_dir_sector, 0, buffer, sizeof(buffer)); // Root directory entry 75 | fat_dir_entry_t root[16] = { 76 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 77 | {.DIR_Name = "DIR1 ", .DIR_Attr = 0x10, .DIR_FstClusLO = 2, .DIR_FileSize = 0}, 78 | }; 79 | assert(dirent_cmp((fat_dir_entry_t *)buffer, root) == 0); 80 | 81 | tud_msc_read10_cb(0, fat_sectors + 2, 0, buffer, sizeof(buffer)); // DIR1 directory entry 82 | fat_dir_entry_t dir1[16] = { 83 | {.DIR_Name = ". ", .DIR_Attr = 0x10, .DIR_FstClusLO = 2, .DIR_FileSize = 0}, 84 | {.DIR_Name = ".. ", .DIR_Attr = 0x10, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 85 | {.DIR_Name = "SUB TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 3, .DIR_FileSize = strlen("directory 1\n")}, 86 | }; 87 | assert(dirent_cmp((fat_dir_entry_t *)buffer, dir1) == 0); 88 | 89 | tud_msc_read10_cb(0, fat_sectors + 3, 0, buffer, sizeof(buffer)); // DIR1/SUB.TXT 90 | assert(strcmp(buffer, "directory 1\n") == 0); 91 | 92 | cleanup(); 93 | } 94 | 95 | static void test_long_filename(void) { 96 | uint8_t buffer[512]; 97 | 98 | setup(); 99 | 100 | create_file(&fs, "over 8x3 long filename.TXT", "long file name\n"); 101 | 102 | mimic_fat_init(&lfs_pico_flash_config); 103 | mimic_fat_create_cache(); 104 | 105 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 106 | uint32_t first_fat_sector = 1; 107 | uint32_t root_dir_sector = fat_sectors + 1; 108 | 109 | tud_msc_read10_cb(0, root_dir_sector, 0, buffer, sizeof(buffer)); // Root directory entry 110 | 111 | fat_dir_entry_t root[16] = { 112 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 113 | {}, 114 | {}, 115 | {.DIR_Name = "FIL~4000TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 2, .DIR_FileSize = strlen("long file name\n")}, 116 | }; 117 | set_long_filename_entry((fat_lfn_t *)&root[1], " filename.TXT", 0x42); 118 | set_long_filename_entry((fat_lfn_t *)&root[2], "over 8x3 long", 0x01); 119 | assert(dirent_cmp_lfn((fat_dir_entry_t *)buffer, root) == 0); 120 | 121 | cleanup(); 122 | } 123 | 124 | void test_read(void) { 125 | printf("read ..................."); 126 | 127 | test_read_file(); 128 | test_sub_directory(); 129 | test_long_filename(); 130 | 131 | printf("ok\n"); 132 | } 133 | -------------------------------------------------------------------------------- /tests/test_rename.c: -------------------------------------------------------------------------------- 1 | #include "tests.h" 2 | 3 | 4 | extern const struct lfs_config lfs_pico_flash_config; // littlefs_driver.c 5 | extern int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); 6 | extern int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); 7 | 8 | static lfs_t fs; 9 | 10 | #define MESSAGE "please rename!\n" 11 | 12 | 13 | static void setup(void) { 14 | int err = lfs_format(&fs, &lfs_pico_flash_config); 15 | assert(err == 0); 16 | err = lfs_mount(&fs, &lfs_pico_flash_config); 17 | assert(err == 0); 18 | 19 | create_file(&fs, "ORIGINAL.TXT", MESSAGE); 20 | } 21 | 22 | static void reload(void) { 23 | lfs_unmount(&fs); 24 | int err = lfs_mount(&fs, &lfs_pico_flash_config); 25 | assert(err == 0); 26 | } 27 | 28 | static void cleanup(void) { 29 | lfs_unmount(&fs); 30 | } 31 | 32 | static void test_rename_file(void) { 33 | setup(); 34 | 35 | mimic_fat_init(&lfs_pico_flash_config); 36 | mimic_fat_create_cache(); 37 | 38 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 39 | uint32_t first_fat_sector = 1; 40 | uint32_t root_dir_sector = fat_sectors + 1; 41 | 42 | // Update procedure from the USB layer 43 | uint8_t buffer[512]; 44 | uint16_t cluster = 2; 45 | 46 | // update dir entry. The old filename is flagged for deletion and a new file entry 47 | // is added at the same time. Clusters have the same. 48 | fat_dir_entry_t root0[16] = { 49 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 50 | {.DIR_Name = "ORIGINALTXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 2, .DIR_FileSize = strlen(MESSAGE)}, 51 | {.DIR_Name = "RENAMED TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 2, .DIR_FileSize = strlen(MESSAGE)}, 52 | }; 53 | root0[1].DIR_Name[0] = 0xE5; 54 | tud_msc_write10_cb(0, root_dir_sector, 0, root0, sizeof(root0)); // update directory entry 55 | 56 | reload(); 57 | 58 | // Test reflection on the littlefs layer 59 | lfs_file_t f; 60 | int err = lfs_file_open(&fs, &f, "RENAMED.TXT", LFS_O_RDONLY); 61 | assert(err == LFS_ERR_OK); 62 | lfs_ssize_t size = lfs_file_read(&fs, &f, buffer, sizeof(buffer)); 63 | assert(size == strlen(MESSAGE)); 64 | assert(strcmp(buffer, MESSAGE) == 0); 65 | lfs_file_close(&fs, &f); 66 | 67 | struct lfs_info finfo; 68 | err = lfs_stat(&fs, "ORIGINAL.TXT", &finfo); 69 | assert(err == LFS_ERR_NOENT); 70 | 71 | cleanup(); 72 | } 73 | 74 | static void test_rename_file_windows11(void) { 75 | setup(); 76 | 77 | mimic_fat_init(&lfs_pico_flash_config); 78 | mimic_fat_create_cache(); 79 | 80 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 81 | uint32_t first_fat_sector = 1; 82 | uint32_t root_dir_sector = fat_sectors + 1; 83 | 84 | // Update procedure from the USB layer 85 | uint8_t buffer[512]; 86 | uint16_t cluster = 2; 87 | 88 | // The file name of the directory entry is changed. 89 | fat_dir_entry_t root0[16] = { 90 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 91 | {.DIR_Name = "RENAMED TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 2, .DIR_FileSize = strlen(MESSAGE)}, 92 | }; 93 | tud_msc_write10_cb(0, root_dir_sector, 0, root0, sizeof(root0)); // update directory entry 94 | 95 | reload(); 96 | 97 | // Test reflection on the littlefs layer 98 | lfs_file_t f; 99 | int err = lfs_file_open(&fs, &f, "RENAMED.TXT", LFS_O_RDONLY); 100 | assert(err == LFS_ERR_OK); 101 | lfs_ssize_t size = lfs_file_read(&fs, &f, buffer, sizeof(buffer)); 102 | assert(size == strlen(MESSAGE)); 103 | assert(strcmp(buffer, MESSAGE) == 0); 104 | lfs_file_close(&fs, &f); 105 | 106 | struct lfs_info finfo; 107 | err = lfs_stat(&fs, "ORIGINAL.TXT", &finfo); 108 | assert(err == LFS_ERR_NOENT); 109 | 110 | cleanup(); 111 | } 112 | 113 | void test_rename(void) { 114 | printf("rename ................."); 115 | 116 | test_rename_file(); 117 | test_rename_file_windows11(); 118 | 119 | printf("ok\n"); 120 | } 121 | -------------------------------------------------------------------------------- /tests/test_update.c: -------------------------------------------------------------------------------- 1 | #include "tests.h" 2 | 3 | 4 | extern const struct lfs_config lfs_pico_flash_config; // littlefs_driver.c 5 | extern int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); 6 | 7 | static lfs_t fs; 8 | 9 | 10 | static void setup(void) { 11 | int err = lfs_format(&fs, &lfs_pico_flash_config); 12 | assert(err == 0); 13 | err = lfs_mount(&fs, &lfs_pico_flash_config); 14 | assert(err == 0); 15 | 16 | create_file(&fs, "UPDATE.TXT", "Please update!\n"); 17 | } 18 | 19 | static void reload(void) { 20 | lfs_unmount(&fs); 21 | int err = lfs_mount(&fs, &lfs_pico_flash_config); 22 | assert(err == 0); 23 | } 24 | 25 | static void cleanup(void) { 26 | lfs_unmount(&fs); 27 | } 28 | 29 | static void test_update_file(void) { 30 | setup(); 31 | 32 | mimic_fat_init(&lfs_pico_flash_config); 33 | mimic_fat_create_cache(); 34 | 35 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 36 | uint32_t first_fat_sector = 1; 37 | uint32_t root_dir_sector = fat_sectors + 1; 38 | 39 | // Update procedure from the USB layer 40 | uint16_t cluster = 3; // unassigned cluster 41 | uint8_t buffer[512]; 42 | const uint8_t message[] = "Hello World!\n"; 43 | // write file blocks to unassigned clusters 44 | memset(buffer, 0, sizeof(buffer)); 45 | strncpy(buffer, message, sizeof(buffer)); 46 | tud_msc_write10_cb(0, fat_sectors + cluster, 0, buffer, sizeof(buffer)); // write to anonymous cache 47 | 48 | // update File allocation table 49 | uint8_t fat[512] = {0xF8, 0xFF, 0xFF, 0xFF, 0x0F}; // With cluster 2 allocated 50 | update_fat(fat, cluster, 0xFFF); 51 | tud_msc_write10_cb(0, 1, 0, fat, sizeof(fat)); // update file allocated table 52 | 53 | // update dir entry. The cluster to which the file belongs is set to 0. 54 | fat_dir_entry_t root0[16] = { 55 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 56 | {.DIR_Name = "UPDATE TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 57 | }; 58 | tud_msc_write10_cb(0, root_dir_sector, 0, root0, sizeof(root0)); // update root directory entry 59 | 60 | // update dir entry. The clusters written in step 1 are specified. 61 | fat_dir_entry_t root1[16] = { 62 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 63 | {.DIR_Name = "UPDATE TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = cluster, .DIR_FileSize = strlen(message)}, 64 | }; 65 | tud_msc_write10_cb(0, root_dir_sector, 0, root1, sizeof(root1)); // update directory entry 66 | 67 | reload(); 68 | 69 | // Test reflection on the littlefs layer 70 | lfs_file_t f; 71 | int err = lfs_file_open(&fs, &f, "UPDATE.TXT", LFS_O_RDONLY); 72 | assert(err == LFS_ERR_OK); 73 | lfs_ssize_t size = lfs_file_read(&fs, &f, buffer, sizeof(buffer)); 74 | assert(size == strlen(message)); 75 | assert(strcmp(buffer, message) == 0); 76 | lfs_file_close(&fs, &f); 77 | 78 | cleanup(); 79 | } 80 | 81 | static void test_update_file_windows11(void) { 82 | setup(); 83 | 84 | mimic_fat_init(&lfs_pico_flash_config); 85 | mimic_fat_create_cache(); 86 | 87 | uint16_t fat_sectors = fat_sector_size((const struct lfs_config *)&lfs_pico_flash_config); 88 | uint32_t first_fat_sector = 1; 89 | uint32_t root_dir_sector = fat_sectors + 1; 90 | 91 | // Update procedure from the USB layer 92 | uint16_t cluster = 3; // unassigned cluster 93 | uint8_t buffer[512]; 94 | const uint8_t message[] = "Hello World!\n"; 95 | 96 | // update dir entry. The cluster to which the file belongs is set to 0. 97 | fat_dir_entry_t root0[16] = { 98 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 99 | {.DIR_Name = "UPDATE TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 100 | }; 101 | tud_msc_write10_cb(0, root_dir_sector, 0, root0, sizeof(root0)); // update directory entry 102 | 103 | // update File allocation table 104 | uint8_t fat[512] = {0xF8, 0xFF, 0xFF, 0xFF, 0x0F}; // With cluster 2 allocated 105 | update_fat(fat, cluster, 0xFFF); 106 | tud_msc_write10_cb(0, 1, 0, fat, sizeof(fat)); // update file allocated table 107 | 108 | // update dir entry. The clusters written in step 2 are specified. 109 | fat_dir_entry_t root1[16] = { 110 | {.DIR_Name = "littlefsUSB", .DIR_Attr = 0x08, .DIR_FstClusLO = 0, .DIR_FileSize = 0}, 111 | {.DIR_Name = "UPDATE TXT", .DIR_Attr = 0x20, .DIR_FstClusLO = cluster, .DIR_FileSize = strlen(message)}, 112 | }; 113 | tud_msc_write10_cb(0, root_dir_sector, 0, root1, sizeof(root1)); // update directory entry 114 | 115 | // write the file block to the cluster specified in step 3 116 | memset(buffer, 0, sizeof(buffer)); 117 | strncpy(buffer, message, sizeof(buffer)); 118 | tud_msc_write10_cb(0, fat_sectors + cluster, 0, buffer, sizeof(buffer)); 119 | 120 | // update File allocation table 121 | update_fat(fat, 2, 0x000); // Release old allocated areas 122 | tud_msc_write10_cb(0, 1, 0, fat, sizeof(fat)); // update file allocated table 123 | 124 | reload(); 125 | 126 | // Test reflection on the littlefs layer 127 | lfs_file_t f; 128 | int err = lfs_file_open(&fs, &f, "UPDATE.TXT", LFS_O_RDONLY); 129 | assert(err == LFS_ERR_OK); 130 | lfs_ssize_t size = lfs_file_read(&fs, &f, buffer, sizeof(buffer)); 131 | assert(size == strlen(message)); 132 | assert(strcmp(buffer, message) == 0); 133 | lfs_file_close(&fs, &f); 134 | 135 | cleanup(); 136 | } 137 | 138 | void test_update(void) { 139 | printf("update ................."); 140 | 141 | test_update_file(); 142 | test_update_file_windows11(); 143 | 144 | printf("ok\n"); 145 | } 146 | -------------------------------------------------------------------------------- /tests/tests.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024, Hiroyuki OYAMA. All rights reserved. 3 | * SPDX-License-Identifier: BSD-3-Clause 4 | */ 5 | #ifndef _TESTS_H_ 6 | #define _TESTS_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "mimic_fat.h" 15 | 16 | 17 | void test_create(void); 18 | void test_read(void); 19 | void test_update(void); 20 | void test_rename(void); 21 | void test_move(void); 22 | void test_delete(void); 23 | void test_large_file(); 24 | 25 | void print_block(uint8_t *buffer, size_t l); 26 | void print_dir_entry(void *buffer); 27 | 28 | void create_file(lfs_t *fs, const char *path, const char *content); 29 | void create_directory(lfs_t *fs, const char *path); 30 | void update_fat(uint8_t *buffer, uint16_t cluster, uint16_t value); 31 | uint16_t fat_sector_size(const struct lfs_config *c); 32 | 33 | int dirent_cmp(fat_dir_entry_t *a, fat_dir_entry_t *b); 34 | int dirent_cmp_lfn(fat_dir_entry_t *a, fat_dir_entry_t *b); 35 | void set_long_filename_entry(fat_lfn_t *ent, uint8_t *name, uint8_t order); 36 | 37 | #endif 38 | 39 | -------------------------------------------------------------------------------- /tests/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "tests.h" 3 | 4 | 5 | static const int FAT_SHORT_NAME_MAX = 11; 6 | static const int FAT_LONG_FILENAME_CHUNK_MAX = 13; 7 | 8 | 9 | void print_block(uint8_t *buffer, size_t l) { 10 | for (size_t i = 0; i < l; ++i) { 11 | if (isalnum(buffer[i])) { 12 | printf("'%c' ", buffer[i]); 13 | } else { 14 | printf("0x%02x", buffer[i]); 15 | } 16 | if (i % 16 == 15) 17 | printf("\n"); 18 | else 19 | printf(", "); 20 | } 21 | } 22 | 23 | void print_dir_entry(void *buffer) { 24 | uint8_t pbuffer[11+1]; 25 | fat_dir_entry_t *dir = (fat_dir_entry_t *)buffer; 26 | printf("--------\n"); 27 | for (size_t i = 0; i < DISK_SECTOR_SIZE / sizeof(fat_dir_entry_t); i++) { 28 | if (dir->DIR_Name[0] == '\0') { 29 | break; 30 | } 31 | if ((dir->DIR_Attr & 0x0F) != 0x0F) { 32 | memcpy(pbuffer, &dir->DIR_Name, 11); 33 | pbuffer[11] = '\0'; 34 | printf("name='%s' attr=0x%02X timeTenth=%u, CrtDate=%u,%u" 35 | " writeDateTime=%u,%u LstAccDate=%u Size=%lu cluster=%u\n", 36 | pbuffer, 37 | dir->DIR_Attr, 38 | dir->DIR_CrtTimeTenth, 39 | dir->DIR_CrtDate, dir->DIR_CrtTime, 40 | dir->DIR_WrtDate, dir->DIR_WrtTime, 41 | dir->DIR_LstAccDate, 42 | dir->DIR_FileSize, 43 | dir->DIR_FstClusLO); 44 | } else { 45 | fat_lfn_t *lfn = (fat_lfn_t *)dir; 46 | uint16_t utf16le[13 + 1]; 47 | memcpy(utf16le, lfn->LDIR_Name1, 5*2); 48 | memcpy(utf16le + 5, lfn->LDIR_Name2, 6*2); 49 | memcpy(utf16le + 5 + 6, lfn->LDIR_Name3, 2*2); 50 | utf16le[13] = '\0'; 51 | uint8_t utf8[13 * 4 + 1]; 52 | utf16le_to_utf8((char *)utf8, sizeof(utf8), utf16le, 13); 53 | printf("name='%s' attr=0x%02X ord=0x%02X cluster=%u\n", utf8, lfn->LDIR_Attr, lfn->LDIR_Ord, dir->DIR_FstClusLO); 54 | } 55 | dir++; 56 | } 57 | } 58 | 59 | void update_fat(uint8_t *buffer, uint16_t cluster, uint16_t value) { 60 | uint16_t offset = (uint16_t)floor((float)cluster + ((float)cluster / 2)) % DISK_SECTOR_SIZE; 61 | if (cluster & 0x01) { 62 | buffer[offset] = (buffer[offset] & 0x0F) | (value << 4); 63 | buffer[offset + 1] = value >> 4; 64 | } else { 65 | buffer[offset] = value; 66 | buffer[offset + 1] = (buffer[offset + 1] & 0xF0) | ((value >> 8) & 0x0F); 67 | } 68 | } 69 | 70 | uint16_t fat_sector_size(const struct lfs_config *c) { 71 | uint64_t storage_size = c->block_count * c->block_size; 72 | uint32_t cluster_size = storage_size / (DISK_SECTOR_SIZE * 1); 73 | return ceil((double)cluster_size / DISK_SECTOR_SIZE); 74 | } 75 | 76 | void create_file(lfs_t *fs, const char *path, const char *content) { 77 | lfs_file_t f; 78 | int err = lfs_file_open(fs, &f, path, LFS_O_RDWR|LFS_O_CREAT); 79 | assert(err == 0); 80 | size_t size = lfs_file_write(fs, &f, content, strlen(content)); 81 | assert(size == strlen(content)); 82 | lfs_file_close(fs, &f); 83 | } 84 | 85 | void create_directory(lfs_t *fs, const char *path) { 86 | int err = lfs_mkdir(fs, path); 87 | assert(err == 0); 88 | } 89 | 90 | void set_long_filename_entry(fat_lfn_t *ent, uint8_t *name, uint8_t order) { 91 | ent->LDIR_Ord = order; 92 | ent->LDIR_Attr = 0x0F; 93 | 94 | uint16_t chunk[13]; 95 | size_t l = utf8_to_utf16le(chunk, sizeof(chunk), (const char *)name, 13); 96 | memcpy(ent->LDIR_Name1, chunk + 0, sizeof(uint16_t) * 5); 97 | memcpy(ent->LDIR_Name2, chunk + 5, sizeof(uint16_t) * 6); 98 | memcpy(ent->LDIR_Name3, chunk + 5 + 6, sizeof(uint16_t) * 2); 99 | for (int i = 0; i < 5*2; i += 2) { 100 | if (ent->LDIR_Name1[i] == 0x00 && ent->LDIR_Name1[i+1] == 0x00) { 101 | ent->LDIR_Name1[i] = 0xFF; 102 | ent->LDIR_Name1[i+1] = 0xFF; 103 | } 104 | } 105 | for (int i = 0; i < 6*2; i += 2) { 106 | if (ent->LDIR_Name2[i] == 0x00 && ent->LDIR_Name2[i+1] == 0x00) { 107 | ent->LDIR_Name2[i] = 0xFF; 108 | ent->LDIR_Name2[i+1] = 0xFF; 109 | } 110 | } 111 | for (int i = 0; i < 2*2; i += 2) { 112 | if (ent->LDIR_Name3[i] == 0x00 && ent->LDIR_Name3[i+1] == 0x00) { 113 | ent->LDIR_Name3[i] = 0xFF; 114 | ent->LDIR_Name3[i+1] = 0xFF; 115 | } 116 | } 117 | if (l < 13 && l < 5) { 118 | ent->LDIR_Name1[l * 2] = 0x00; 119 | ent->LDIR_Name1[l * 2 + 1] = 0x00; 120 | } else if (l < FAT_LONG_FILENAME_CHUNK_MAX && l < FAT_SHORT_NAME_MAX) { 121 | ent->LDIR_Name2[(l-5) * 2] = 0x00; 122 | ent->LDIR_Name2[(l-5) * 2 + 1] = 0x00; 123 | } else if (l < FAT_LONG_FILENAME_CHUNK_MAX) { 124 | ent->LDIR_Name3[(l-5-6) * 2] = 0x00; 125 | ent->LDIR_Name3[(l-5-6) * 2 + 1] = 0x00; 126 | } 127 | } 128 | 129 | int dirent_cmp(fat_dir_entry_t *a, fat_dir_entry_t *b) { 130 | for (int i = 0; i < 16; i++) { 131 | if (memcmp((a + i)->DIR_Name, (b + i)->DIR_Name, 11) != 0) 132 | return -1; 133 | if ((a + i)->DIR_Attr != (b + i)->DIR_Attr) 134 | return -1; 135 | if ((a + i)->DIR_FstClusLO != (b + i)->DIR_FstClusLO) 136 | return -1; 137 | if ((a + i)->DIR_FileSize != (b + i)->DIR_FileSize) 138 | return -1; 139 | } 140 | return 0; 141 | } 142 | 143 | int dirent_cmp_lfn(fat_dir_entry_t *a, fat_dir_entry_t *b) { 144 | bool is_long = false; 145 | for (int i = 0; i < 16; i++) { 146 | if ((a + i)->DIR_Attr == 0x0F) { 147 | fat_lfn_t *al = (fat_lfn_t *)(a + i); 148 | fat_lfn_t *bl = (fat_lfn_t *)(b + i); 149 | if (memcmp(al->LDIR_Name1, bl->LDIR_Name1, sizeof(uint16_t) * 5) != 0) 150 | return -1; 151 | if (memcmp(al->LDIR_Name2, bl->LDIR_Name2, sizeof(uint16_t) * 6) != 0) 152 | return -1; 153 | if (memcmp(al->LDIR_Name3, bl->LDIR_Name3, sizeof(uint16_t) * 2) != 0) 154 | return -1; 155 | if (al->LDIR_Attr != bl->LDIR_Attr) 156 | return -1; 157 | if (al->LDIR_Ord != bl->LDIR_Ord) 158 | return -1; 159 | is_long = true; 160 | } else { 161 | if (is_long == false && memcmp((a + i)->DIR_Name, (b + i)->DIR_Name, 11) != 0) 162 | return -1; 163 | if ((a + i)->DIR_Attr != (b + i)->DIR_Attr) 164 | return -1; 165 | if ((a + i)->DIR_FstClusLO != (b + i)->DIR_FstClusLO) 166 | return -1; 167 | if ((a + i)->DIR_FileSize != (b + i)->DIR_FileSize) 168 | return -1; 169 | is_long = false; 170 | } 171 | } 172 | return 0; 173 | } 174 | -------------------------------------------------------------------------------- /unicode.c: -------------------------------------------------------------------------------- 1 | #include "unicode.h" 2 | 3 | 4 | size_t strlen_utf8(const char *src) { 5 | size_t count = 0; 6 | size_t i = 0; 7 | size_t src_size = strlen(src); 8 | 9 | while (i < src_size) { 10 | uint8_t byte = src[i]; 11 | 12 | if ((byte & 0x80) == 0) { // 1-byte UTF-8 13 | count++; 14 | } else if ((byte & 0xE0) == 0xC0) { // 2-byte UTF-8 15 | count++; 16 | i++; // Skip the continuation byte 17 | } else if ((byte & 0xF0) == 0xE0) { // 3-byte UTF-8 18 | count++; 19 | i += 2; // Skip the continuation bytes 20 | } else if ((byte & 0xF8) == 0xF0) { // 4-byte UTF-8 21 | count++; 22 | i += 3; // Skip the continuation bytes 23 | } else { 24 | return -1; // Invalid UTF-8 byte 25 | } 26 | 27 | i++; 28 | } 29 | return count; 30 | } 31 | 32 | size_t ascii_to_utf16le(uint16_t *dist, size_t dist_size, const char *src, size_t src_size) { 33 | size_t utf16le_pos = 0; 34 | 35 | for (size_t i = 0; i < src_size && src[i] != '\0'; ++i) { 36 | uint32_t codepoint = (uint32_t)src[i]; 37 | 38 | if (utf16le_pos + 1 <= dist_size) { 39 | dist[utf16le_pos++] = (uint16_t)codepoint; 40 | } else { 41 | break; 42 | } 43 | } 44 | 45 | if (utf16le_pos < dist_size) { 46 | dist[utf16le_pos] = '\0'; 47 | } 48 | return utf16le_pos; 49 | } 50 | 51 | // Convert UTF-8 to UTF-16LE and return the length of the converted string 52 | size_t utf8_to_utf16le(uint16_t* dist, size_t dist_size, const char *src, size_t src_size) { 53 | size_t dist_pos = 0; 54 | size_t src_pos = 0; 55 | 56 | while (src_pos < src_size && dist_pos < dist_size) { 57 | uint32_t codepoint = 0; 58 | size_t extra_bytes = 0; 59 | 60 | uint8_t byte = src[src_pos]; 61 | 62 | // Determine the number of bytes for the UTF-8 codepoint 63 | if ((byte & 0x80) == 0) { // 1-byte UTF-8 64 | codepoint = byte; 65 | } else if ((byte & 0xE0) == 0xC0) { // 2-byte UTF-8 66 | codepoint = byte & 0x1F; 67 | extra_bytes = 1; 68 | } else if ((byte & 0xF0) == 0xE0) { // 3-byte UTF-8 69 | codepoint = byte & 0x0F; 70 | extra_bytes = 2; 71 | } else if ((byte & 0xF8) == 0xF0) { // 4-byte UTF-8 72 | codepoint = byte & 0x07; 73 | extra_bytes = 3; 74 | } else { 75 | // Invalid UTF-8 byte 76 | return -1; // Return -1 to indicate an error 77 | } 78 | 79 | // Calculate the complete codepoint 80 | for (size_t j = 0; j < extra_bytes; ++j) { 81 | src_pos++; 82 | if (src_pos >= src_size) { 83 | return -1; // Incomplete UTF-8 sequence 84 | } 85 | 86 | byte = src[src_pos]; 87 | if ((byte & 0xC0) != 0x80) { 88 | return -1; // Invalid UTF-8 continuation byte 89 | } 90 | 91 | codepoint = (codepoint << 6) | (byte & 0x3F); 92 | } 93 | 94 | // Convert to UTF-16LE 95 | if (codepoint <= 0xFFFF) { // Basic Multilingual Plane 96 | if (dist_pos < dist_size) { 97 | dist[dist_pos++] = (uint16_t)codepoint; 98 | } 99 | } else { // Supplementary Planes (surrogates) 100 | codepoint -= 0x10000; 101 | if (dist_pos + 1 < dist_size) { 102 | dist[dist_pos++] = 0xD800 | ((codepoint >> 10) & 0x3FF); 103 | dist[dist_pos++] = 0xDC00 | (codepoint & 0x3FF); 104 | dist_pos += 2; 105 | } else { 106 | return -1; // Not enough space for surrogates 107 | } 108 | } 109 | 110 | src_pos++; 111 | } 112 | 113 | if (dist_pos < dist_size) { 114 | dist[dist_pos] = 0; // Null-terminate 115 | } 116 | 117 | return dist_pos; 118 | } 119 | 120 | size_t utf16le_to_utf8(char *dist, size_t buffer_size, const uint16_t *src, size_t len) { 121 | size_t dist_len = 0; 122 | 123 | for (size_t i = 0; i < len; ++i) { 124 | uint32_t codepoint = src[i]; 125 | if (codepoint == 0xFFFF) { 126 | break; 127 | } 128 | 129 | if (codepoint <= 0x7F) { 130 | if (dist_len + 1 <= buffer_size) { 131 | dist[dist_len++] = (uint8_t)codepoint; 132 | } else { 133 | break; 134 | } 135 | } else if (codepoint <= 0x7FF) { 136 | if (dist_len + 2 <= buffer_size) { 137 | dist[dist_len++] = (uint8_t)(0xC0 | (codepoint >> 6)); 138 | dist[dist_len++] = (uint8_t)(0x80 | (codepoint & 0x3F)); 139 | } else { 140 | break; 141 | } 142 | } else if (codepoint <= 0xFFFF) { 143 | if (dist_len + 3 <= buffer_size) { 144 | dist[dist_len++] = (uint8_t)(0xE0 | (codepoint >> 12)); 145 | dist[dist_len++] = (uint8_t)(0x80 | ((codepoint >> 6) & 0x3F)); 146 | dist[dist_len++] = (uint8_t)(0x80 | (codepoint & 0x3F)); 147 | } else { 148 | break; 149 | } 150 | } else { 151 | break; 152 | } 153 | } 154 | 155 | if (dist_len < buffer_size) { 156 | dist[dist_len] = '\0'; 157 | } 158 | return dist_len; 159 | } 160 | -------------------------------------------------------------------------------- /usb_descriptors.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "tusb.h" 27 | 28 | /* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. 29 | * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. 30 | * 31 | * Auto ProductID layout's Bitmap: 32 | * [MSB] HID | MSC | CDC [LSB] 33 | */ 34 | #define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) 35 | #define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ 36 | _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) 37 | 38 | #define USB_VID 0xCafe 39 | #define USB_BCD 0x0200 40 | 41 | //--------------------------------------------------------------------+ 42 | // Device Descriptors 43 | //--------------------------------------------------------------------+ 44 | tusb_desc_device_t const desc_device = 45 | { 46 | .bLength = sizeof(tusb_desc_device_t), 47 | .bDescriptorType = TUSB_DESC_DEVICE, 48 | .bcdUSB = USB_BCD, 49 | 50 | // Use Interface Association Descriptor (IAD) for CDC 51 | // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) 52 | .bDeviceClass = TUSB_CLASS_MISC, 53 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 54 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 55 | 56 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 57 | 58 | .idVendor = USB_VID, 59 | .idProduct = USB_PID, 60 | .bcdDevice = 0x0100, 61 | 62 | .iManufacturer = 0x01, 63 | .iProduct = 0x02, 64 | .iSerialNumber = 0x03, 65 | 66 | .bNumConfigurations = 0x01 67 | }; 68 | 69 | // Invoked when received GET DEVICE DESCRIPTOR 70 | // Application return pointer to descriptor 71 | uint8_t const * tud_descriptor_device_cb(void) 72 | { 73 | return (uint8_t const *) &desc_device; 74 | } 75 | 76 | //--------------------------------------------------------------------+ 77 | // Configuration Descriptor 78 | //--------------------------------------------------------------------+ 79 | 80 | enum 81 | { 82 | ITF_NUM_CDC = 0, 83 | ITF_NUM_CDC_DATA, 84 | ITF_NUM_MSC, 85 | ITF_NUM_TOTAL 86 | }; 87 | 88 | #if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX 89 | // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number 90 | // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ... 91 | #define EPNUM_CDC_NOTIF 0x81 92 | #define EPNUM_CDC_OUT 0x02 93 | #define EPNUM_CDC_IN 0x82 94 | 95 | #define EPNUM_MSC_OUT 0x05 96 | #define EPNUM_MSC_IN 0x85 97 | 98 | #elif CFG_TUSB_MCU == OPT_MCU_SAMG || CFG_TUSB_MCU == OPT_MCU_SAMX7X 99 | // SAMG & SAME70 don't support a same endpoint number with different direction IN and OUT 100 | // e.g EP1 OUT & EP1 IN cannot exist together 101 | #define EPNUM_CDC_NOTIF 0x81 102 | #define EPNUM_CDC_OUT 0x02 103 | #define EPNUM_CDC_IN 0x83 104 | 105 | #define EPNUM_MSC_OUT 0x04 106 | #define EPNUM_MSC_IN 0x85 107 | 108 | #elif CFG_TUSB_MCU == OPT_MCU_CXD56 109 | // CXD56 doesn't support a same endpoint number with different direction IN and OUT 110 | // e.g EP1 OUT & EP1 IN cannot exist together 111 | // CXD56 USB driver has fixed endpoint type (bulk/interrupt/iso) and direction (IN/OUT) by its number 112 | // 0 control (IN/OUT), 1 Bulk (IN), 2 Bulk (OUT), 3 In (IN), 4 Bulk (IN), 5 Bulk (OUT), 6 In (IN) 113 | #define EPNUM_CDC_NOTIF 0x83 114 | #define EPNUM_CDC_OUT 0x02 115 | #define EPNUM_CDC_IN 0x81 116 | 117 | #define EPNUM_MSC_OUT 0x05 118 | #define EPNUM_MSC_IN 0x84 119 | 120 | #elif CFG_TUSB_MCU == OPT_MCU_FT90X || CFG_TUSB_MCU == OPT_MCU_FT93X 121 | // FT9XX doesn't support a same endpoint number with different direction IN and OUT 122 | // e.g EP1 OUT & EP1 IN cannot exist together 123 | #define EPNUM_CDC_NOTIF 0x81 124 | #define EPNUM_CDC_OUT 0x02 125 | #define EPNUM_CDC_IN 0x83 126 | 127 | #define EPNUM_MSC_OUT 0x04 128 | #define EPNUM_MSC_IN 0x85 129 | 130 | #else 131 | #define EPNUM_CDC_NOTIF 0x81 132 | #define EPNUM_CDC_OUT 0x02 133 | #define EPNUM_CDC_IN 0x82 134 | 135 | #define EPNUM_MSC_OUT 0x03 136 | #define EPNUM_MSC_IN 0x83 137 | 138 | #endif 139 | 140 | #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_MSC_DESC_LEN) 141 | 142 | // full speed configuration 143 | uint8_t const desc_fs_configuration[] = 144 | { 145 | // Config number, interface count, string index, total length, attribute, power in mA 146 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), 147 | 148 | // Interface number, string index, EP notification address and size, EP data address (out, in) and size. 149 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64), 150 | 151 | // Interface number, string index, EP Out & EP In address, EP size 152 | TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64), 153 | }; 154 | 155 | #if TUD_OPT_HIGH_SPEED 156 | // Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration 157 | 158 | // high speed configuration 159 | uint8_t const desc_hs_configuration[] = 160 | { 161 | // Config number, interface count, string index, total length, attribute, power in mA 162 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), 163 | 164 | // Interface number, string index, EP notification address and size, EP data address (out, in) and size. 165 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 512), 166 | 167 | // Interface number, string index, EP Out & EP In address, EP size 168 | TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 512), 169 | }; 170 | 171 | // other speed configuration 172 | uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN]; 173 | 174 | // device qualifier is mostly similar to device descriptor since we don't change configuration based on speed 175 | tusb_desc_device_qualifier_t const desc_device_qualifier = 176 | { 177 | .bLength = sizeof(tusb_desc_device_qualifier_t), 178 | .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, 179 | .bcdUSB = USB_BCD, 180 | 181 | .bDeviceClass = TUSB_CLASS_MISC, 182 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 183 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 184 | 185 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 186 | .bNumConfigurations = 0x01, 187 | .bReserved = 0x00 188 | }; 189 | 190 | // Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request 191 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. 192 | // device_qualifier descriptor describes information about a high-speed capable device that would 193 | // change if the device were operating at the other speed. If not highspeed capable stall this request. 194 | uint8_t const* tud_descriptor_device_qualifier_cb(void) 195 | { 196 | return (uint8_t const*) &desc_device_qualifier; 197 | } 198 | 199 | // Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request 200 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete 201 | // Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa 202 | uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) 203 | { 204 | (void) index; // for multiple configurations 205 | 206 | // if link speed is high return fullspeed config, and vice versa 207 | // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG 208 | memcpy(desc_other_speed_config, 209 | (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, 210 | CONFIG_TOTAL_LEN); 211 | 212 | desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; 213 | 214 | return desc_other_speed_config; 215 | } 216 | 217 | #endif // highspeed 218 | 219 | 220 | // Invoked when received GET CONFIGURATION DESCRIPTOR 221 | // Application return pointer to descriptor 222 | // Descriptor contents must exist long enough for transfer to complete 223 | uint8_t const * tud_descriptor_configuration_cb(uint8_t index) 224 | { 225 | (void) index; // for multiple configurations 226 | 227 | #if TUD_OPT_HIGH_SPEED 228 | // Although we are highspeed, host may be fullspeed. 229 | return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration; 230 | #else 231 | return desc_fs_configuration; 232 | #endif 233 | } 234 | 235 | //--------------------------------------------------------------------+ 236 | // String Descriptors 237 | //--------------------------------------------------------------------+ 238 | 239 | // array of pointer to string descriptors 240 | char const* string_desc_arr [] = 241 | { 242 | (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) 243 | "TinyUSB", // 1: Manufacturer 244 | "TinyUSB Device", // 2: Product 245 | "123456789012", // 3: Serials, should use chip ID 246 | "TinyUSB CDC", // 4: CDC Interface 247 | "TinyUSB MSC", // 5: MSC Interface 248 | }; 249 | 250 | static uint16_t _desc_str[32]; 251 | 252 | // Invoked when received GET STRING DESCRIPTOR request 253 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete 254 | uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) 255 | { 256 | (void) langid; 257 | 258 | uint8_t chr_count; 259 | 260 | if ( index == 0) 261 | { 262 | memcpy(&_desc_str[1], string_desc_arr[0], 2); 263 | chr_count = 1; 264 | }else 265 | { 266 | // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. 267 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors 268 | 269 | if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; 270 | 271 | const char* str = string_desc_arr[index]; 272 | 273 | // Cap at max char 274 | chr_count = (uint8_t) strlen(str); 275 | if ( chr_count > 31 ) chr_count = 31; 276 | 277 | // Convert ASCII string into UTF-16 278 | for(uint8_t i=0; i 8 | #include "mimic_fat.h" 9 | 10 | 11 | extern const struct lfs_config lfs_pico_flash_config; 12 | static bool ejected = false; 13 | static bool is_initialized = false; 14 | 15 | 16 | void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { 17 | (void)lun; 18 | 19 | const char vid[] = "littlefs"; 20 | const char pid[] = "Mass Storage"; 21 | const char rev[] = "1.0"; 22 | 23 | memcpy(vendor_id , vid, strlen(vid)); 24 | memcpy(product_id , pid, strlen(pid)); 25 | memcpy(product_rev, rev, strlen(rev)); 26 | } 27 | 28 | bool tud_msc_test_unit_ready_cb(uint8_t lun) { 29 | (void)lun; 30 | return true; 31 | } 32 | 33 | void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) { 34 | (void)lun; 35 | 36 | *block_count = mimic_fat_total_sector_size(); 37 | *block_size = DISK_SECTOR_SIZE; 38 | } 39 | 40 | bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { 41 | (void)lun; 42 | (void)power_condition; 43 | 44 | if (load_eject) { 45 | if (start) { 46 | // load disk storage 47 | } else { 48 | // unload disk storage 49 | ejected = true; 50 | } 51 | } 52 | return true; 53 | } 54 | 55 | int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) { 56 | (void)lun; 57 | (void)offset; 58 | 59 | if (!is_initialized) { 60 | mimic_fat_init(&lfs_pico_flash_config); 61 | mimic_fat_update_usb_device_is_enabled(true); 62 | mimic_fat_create_cache(); 63 | is_initialized = true; 64 | } 65 | mimic_fat_read(lun, lba, buffer, bufsize); 66 | 67 | return (int32_t)bufsize; 68 | } 69 | 70 | bool tud_msc_is_writable_cb (uint8_t lun) { 71 | (void) lun; 72 | return true; 73 | } 74 | 75 | int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) { 76 | (void)offset; 77 | 78 | mimic_fat_write(lun, lba, buffer, bufsize); 79 | return bufsize; 80 | } 81 | 82 | int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize) { 83 | void const *response = NULL; 84 | int32_t resplen = 0; 85 | 86 | // most scsi handled is input 87 | bool in_xfer = true; 88 | 89 | switch (scsi_cmd[0]) { 90 | default: 91 | // Set Sense = Invalid Command Operation 92 | tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); 93 | // negative means error -> tinyusb could stall and/or response with failed status 94 | resplen = -1; 95 | break; 96 | } 97 | 98 | // return resplen must not larger than bufsize 99 | if (resplen > bufsize) resplen = bufsize; 100 | 101 | if (response && (resplen > 0)) { 102 | if(in_xfer) { 103 | memcpy(buffer, response, (size_t) resplen); 104 | } else { 105 | ; // SCSI output 106 | } 107 | } 108 | return (int32_t)resplen; 109 | } 110 | 111 | void tud_mount_cb(void) { 112 | printf("\e[45mmount\e[0m\n"); 113 | /* 114 | * NOTE: 115 | * This callback must be returned immediately. Time-consuming processing 116 | * here will cause TinyUSB to PANIC `ep 0 in was already available`. 117 | */ 118 | is_initialized = false; 119 | } 120 | 121 | void tud_suspend_cb(bool remote_wakeup_en) { 122 | (void)remote_wakeup_en; 123 | 124 | printf("\e[45msuspend\e[0m\n"); 125 | mimic_fat_cleanup_cache(); 126 | mimic_fat_update_usb_device_is_enabled(false); 127 | } 128 | -------------------------------------------------------------------------------- /vendor/pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | --------------------------------------------------------------------------------