├── .gitignore ├── go.mod ├── go.sum ├── version.go ├── examples ├── console │ ├── fatfs │ │ ├── sdcard │ │ │ ├── wioterminal.go │ │ │ ├── pygamer.go │ │ │ ├── m0.go │ │ │ ├── pyportal.go │ │ │ ├── thingplus-rp2040.go │ │ │ ├── p1am-100.go │ │ │ ├── grandcentral-m4.go │ │ │ ├── feather-m4.go │ │ │ └── main.go │ │ ├── machine │ │ │ └── main.go │ │ ├── spi │ │ │ └── main.go │ │ └── qspi │ │ │ └── main.go │ ├── littlefs │ │ ├── machine │ │ │ └── main.go │ │ ├── qspi │ │ │ └── main.go │ │ └── spi │ │ │ └── main.go │ └── example.go └── simple-fatfs │ ├── block_device_file.go │ └── main.go ├── fs.go ├── internal ├── gopointer │ ├── README.md │ ├── LICENSE │ └── pointer.go └── util │ ├── strings.go │ └── print.go ├── .github └── workflows │ └── build.yml ├── fatfs ├── go_fatfs.h ├── integer.h ├── go_fatfs.c ├── go_fatfs_callbacks.go ├── diskio.h ├── go_fatfs_test.go ├── ffsystem.c ├── ffconf.h ├── go_fatfs.go └── ff.h ├── tinygo_os.go ├── littlefs ├── lfs_util.c ├── go_lfs.c ├── docs │ ├── LICENSE.md │ └── README.md ├── go_lfs.h ├── go_lfs_callbacks.go ├── lfs_util.h ├── go_lfs_test.go ├── go_lfs.go └── lfs.h ├── Makefile ├── CHANGELOG.md ├── LICENSE ├── CONTRIBUTING.md ├── README.md └── tinyfs.go /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module tinygo.org/x/tinyfs 2 | 3 | go 1.22.1 4 | 5 | toolchain go1.24.0 6 | 7 | require tinygo.org/x/drivers v0.31.0 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | tinygo.org/x/drivers v0.31.0 h1:Q2RpvTRMtdmjHD2Xyn4e8WXsJZKpIny3Lg4hzG1dLu4= 2 | tinygo.org/x/drivers v0.31.0/go.mod h1:ZdErNrApSABdVXjA1RejD67R8SNRI6RKVfYgQDZtKtk= 3 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package tinyfs 2 | 3 | // Version returns a user-readable string showing the version of the package for support purposes. 4 | // Update this value before release of new version of software. 5 | const Version = "0.5.0" 6 | -------------------------------------------------------------------------------- /examples/console/fatfs/sdcard/wioterminal.go: -------------------------------------------------------------------------------- 1 | //go:build wioterminal 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | ) 8 | 9 | func init() { 10 | spi = machine.SPI2 11 | sckPin = machine.SCK2 12 | sdoPin = machine.SDO2 13 | sdiPin = machine.SDI2 14 | csPin = machine.SS2 15 | 16 | ledPin = machine.LED 17 | } 18 | -------------------------------------------------------------------------------- /examples/console/fatfs/sdcard/pygamer.go: -------------------------------------------------------------------------------- 1 | //go:build pygamer 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | ) 8 | 9 | func init() { 10 | spi = machine.SPI0 11 | sckPin = machine.SPI0_SCK_PIN 12 | sdoPin = machine.SPI0_SDO_PIN 13 | sdiPin = machine.SPI0_SDI_PIN 14 | csPin = machine.D4 15 | 16 | ledPin = machine.LED 17 | } 18 | -------------------------------------------------------------------------------- /examples/console/fatfs/sdcard/m0.go: -------------------------------------------------------------------------------- 1 | //go:build atsamd21 && !p1am_100 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | ) 8 | 9 | func init() { 10 | spi = machine.SPI0 11 | sckPin = machine.SPI0_SCK_PIN 12 | sdoPin = machine.SPI0_SDO_PIN 13 | sdiPin = machine.SPI0_SDI_PIN 14 | csPin = machine.D2 15 | 16 | ledPin = machine.LED 17 | } 18 | -------------------------------------------------------------------------------- /examples/console/fatfs/sdcard/pyportal.go: -------------------------------------------------------------------------------- 1 | //go:build pyportal 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | ) 8 | 9 | func init() { 10 | spi = machine.SPI0 11 | sckPin = machine.SPI0_SCK_PIN 12 | sdoPin = machine.SPI0_SDO_PIN 13 | sdiPin = machine.SPI0_SDI_PIN 14 | csPin = machine.D32 // SD_CS 15 | 16 | ledPin = machine.LED 17 | } 18 | -------------------------------------------------------------------------------- /examples/console/fatfs/sdcard/thingplus-rp2040.go: -------------------------------------------------------------------------------- 1 | //go:build thingplus_rp2040 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | ) 8 | 9 | func init() { 10 | spi = machine.SPI1 11 | sckPin = machine.SPI1_SCK_PIN 12 | sdoPin = machine.SPI1_SDO_PIN 13 | sdiPin = machine.SPI1_SDI_PIN 14 | csPin = machine.GPIO9 15 | 16 | ledPin = machine.LED 17 | } 18 | -------------------------------------------------------------------------------- /examples/console/fatfs/sdcard/p1am-100.go: -------------------------------------------------------------------------------- 1 | //go:build p1am_100 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | ) 8 | 9 | func init() { 10 | spi = machine.SDCARD_SPI 11 | sckPin = machine.SDCARD_SCK_PIN 12 | sdoPin = machine.SDCARD_SDO_PIN 13 | sdiPin = machine.SDCARD_SDI_PIN 14 | csPin = machine.SDCARD_SS_PIN 15 | 16 | ledPin = machine.LED 17 | } 18 | -------------------------------------------------------------------------------- /examples/console/fatfs/sdcard/grandcentral-m4.go: -------------------------------------------------------------------------------- 1 | //go:build grandcentral_m4 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | ) 8 | 9 | func init() { 10 | spi = machine.SPI1 11 | sckPin = machine.SDCARD_SCK_PIN 12 | sdoPin = machine.SDCARD_SDO_PIN 13 | sdiPin = machine.SDCARD_SDI_PIN 14 | csPin = machine.SDCARD_CS_PIN 15 | 16 | ledPin = machine.LED 17 | } 18 | -------------------------------------------------------------------------------- /examples/console/fatfs/sdcard/feather-m4.go: -------------------------------------------------------------------------------- 1 | //go:build feather_m4 || feather_m4_can || feather_nrf52840 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | ) 8 | 9 | func init() { 10 | spi = machine.SPI0 11 | sckPin = machine.SPI0_SCK_PIN 12 | sdoPin = machine.SPI0_SDO_PIN 13 | sdiPin = machine.SPI0_SDI_PIN 14 | csPin = machine.D10 15 | 16 | ledPin = machine.LED 17 | } 18 | -------------------------------------------------------------------------------- /fs.go: -------------------------------------------------------------------------------- 1 | package tinyfs 2 | 3 | import ( 4 | "io/fs" 5 | ) 6 | 7 | func NewTinyFS(filesystem Filesystem) fs.FS { 8 | return &fsWrapper{fs: filesystem} 9 | } 10 | 11 | type fsWrapper struct { 12 | fs Filesystem 13 | } 14 | 15 | func (w *fsWrapper) Open(name string) (f fs.File, err error) { 16 | return w.fs.Open(name) 17 | } 18 | 19 | // type assertion to ensure tinyfs.File satisfies fs.File 20 | var _ fs.File = (File)(nil) 21 | -------------------------------------------------------------------------------- /examples/console/fatfs/machine/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | // +build tinygo 3 | 4 | package main 5 | 6 | import ( 7 | "machine" 8 | 9 | "tinygo.org/x/tinyfs/examples/console" 10 | "tinygo.org/x/tinyfs/fatfs" 11 | ) 12 | 13 | var ( 14 | blockDevice = machine.Flash 15 | filesystem = fatfs.New(blockDevice) 16 | ) 17 | 18 | func main() { 19 | // Configure FATFS with sector size (must match value in ff.h - use 512) 20 | filesystem.Configure(&fatfs.Config{ 21 | SectorSize: 512, 22 | }) 23 | 24 | console.RunFor(blockDevice, filesystem) 25 | } 26 | -------------------------------------------------------------------------------- /internal/gopointer/README.md: -------------------------------------------------------------------------------- 1 | # go-pointer 2 | 3 | Utility for cgo 4 | 5 | ## Usage 6 | 7 | https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md 8 | 9 | In go 1.6, cgo argument can't be passed Go pointer. 10 | 11 | ``` 12 | var s string 13 | C.pass_pointer(pointer.Save(&s)) 14 | v := *(pointer.Restore(C.get_from_pointer()).(*string)) 15 | ``` 16 | 17 | ## Installation 18 | 19 | ``` 20 | go get github.com/mattn/go-pointer 21 | ``` 22 | 23 | ## License 24 | 25 | MIT 26 | 27 | ## Author 28 | 29 | Yasuhiro Matsumoto (a.k.a mattn) 30 | -------------------------------------------------------------------------------- /examples/console/littlefs/machine/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | // +build tinygo 3 | 4 | package main 5 | 6 | import ( 7 | "machine" 8 | 9 | "tinygo.org/x/tinyfs/examples/console" 10 | "tinygo.org/x/tinyfs/littlefs" 11 | ) 12 | 13 | var ( 14 | blockDevice = machine.Flash 15 | filesystem = littlefs.New(blockDevice) 16 | ) 17 | 18 | func main() { 19 | // Configure littlefs with parameters for caches and wear levelling 20 | filesystem.Configure(&littlefs.Config{ 21 | CacheSize: 512, 22 | LookaheadSize: 512, 23 | BlockCycles: 100, 24 | }) 25 | console.RunFor(blockDevice, filesystem) 26 | } 27 | -------------------------------------------------------------------------------- /internal/util/strings.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | // #include 4 | // #include 5 | import "C" 6 | 7 | import ( 8 | "unsafe" 9 | ) 10 | 11 | // would be nice to use C.CString instead, but TinyGo doesn't seem to support 12 | func CString(s string) unsafe.Pointer { 13 | ptr := C.malloc(C.size_t(len(s) + 1)) 14 | buf := (*[1 << 28]byte)(ptr)[: len(s)+1 : len(s)+1] 15 | copy(buf, s) 16 | buf[len(s)] = 0 17 | return ptr 18 | } 19 | 20 | // would be nice to use C.GoString instead, but TinyGo doesn't seem to support 21 | func GoString(s unsafe.Pointer) string { 22 | slen := int(C.strlen((*C.char)(s))) 23 | sbuf := make([]byte, slen) 24 | copy(sbuf, (*[1 << 28]byte)(unsafe.Pointer(s))[:slen:slen]) 25 | return string(sbuf) 26 | } 27 | -------------------------------------------------------------------------------- /internal/util/print.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | func Fprintxxd(w io.Writer, offset uint32, b []byte) { 11 | var l int 12 | var buf16 = make([]byte, 16) 13 | var padding = "" 14 | for i, c := 0, len(b); i < c; i += 16 { 15 | l = i + 16 16 | if l >= c { 17 | padding = strings.Repeat(" ", (l-c)*3) 18 | l = c 19 | } 20 | _, _ = fmt.Fprintf(w, "%08x: % x "+padding, offset+uint32(i), b[i:l]) 21 | for j, n := 0, l-i; j < 16; j++ { 22 | if j >= n { 23 | buf16[j] = ' ' 24 | } else if !strconv.IsPrint(rune(b[i+j])) { 25 | buf16[j] = '.' 26 | } else { 27 | buf16[j] = b[i+j] 28 | } 29 | } 30 | _, _ = w.Write(buf16) 31 | _, _ = fmt.Fprintln(w) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - dev 8 | - release 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | container: ghcr.io/tinygo-org/tinygo-dev 15 | steps: 16 | - name: Work around CVE-2022-24765 17 | # We're not on a multi-user machine, so this is safe. 18 | run: git config --global --add safe.directory "$GITHUB_WORKSPACE" 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | - name: TinyGo version check 22 | run: tinygo version 23 | - name: Enforce Go Formatted Code 24 | run: make fmt-check 25 | - name: Run build and smoke tests 26 | run: make smoke-test 27 | -------------------------------------------------------------------------------- /fatfs/go_fatfs.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "diskio.h" 3 | #include "ff.h" 4 | 5 | //DSTATUS go_fatfs_disk_status(void* ptr); 6 | //DSTATUS go_fatfs_disk_initialize(void* ptr); 7 | extern DRESULT go_fatfs_disk_read(void* drv, void* buff, DWORD sector, UINT count); 8 | extern DRESULT go_fatfs_disk_write(void* drv, void* buff, DWORD sector, UINT count); 9 | extern DRESULT go_fatfs_disk_ioctl(void* drv, BYTE cmd, DWORD* param); 10 | 11 | extern DWORD go_fatfs_get_fattime(); 12 | 13 | // Helper functions used to allocate new FatFs objects, needed because TinyGo 14 | // does not support sizeof() yet 15 | FATFS* go_fatfs_new_fatfs(void); 16 | FIL* go_fatfs_new_fil(void); 17 | FF_DIR* go_fatfs_new_ff_dir(void); 18 | 19 | //struct lfs_config* go_lfs_new_lfs_config(void); 20 | //lfs_dir_t* go_lfs_new_lfs_dir(void); 21 | -------------------------------------------------------------------------------- /tinygo_os.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo && osfilesystem 2 | // +build tinygo,osfilesystem 3 | 4 | package tinyfs 5 | 6 | import "os" 7 | 8 | type OSFilesystem struct { 9 | FS Filesystem 10 | } 11 | 12 | // OpenFile opens the named file. 13 | func (fs *OSFilesystem) OpenFile(name string, flag int, perm os.FileMode) (os.FileHandle, error) { 14 | return fs.FS.OpenFile(name, flag) 15 | } 16 | 17 | // Mkdir creates a new directory with the specified permission (before 18 | // umask). Some filesystems may not support directories or permissions. 19 | func (fs *OSFilesystem) Mkdir(name string, perm os.FileMode) error { 20 | return fs.FS.Mkdir(name, perm) 21 | } 22 | 23 | // Remove removes the named file or (empty) directory. 24 | func (fs *OSFilesystem) Remove(name string) error { 25 | return fs.FS.Remove(name) 26 | } 27 | 28 | var _ os.Filesystem = (*OSFilesystem)(nil) 29 | -------------------------------------------------------------------------------- /fatfs/integer.h: -------------------------------------------------------------------------------- 1 | /*-------------------------------------------*/ 2 | /* Integer type definitions for FatFs module */ 3 | /*-------------------------------------------*/ 4 | 5 | #ifndef FF_INTEGER 6 | #define FF_INTEGER 7 | 8 | #ifdef _WIN32 /* FatFs development platform */ 9 | 10 | #include 11 | #include 12 | typedef unsigned __int64 QWORD; 13 | 14 | 15 | #else /* Embedded platform */ 16 | 17 | /* These types MUST be 16-bit or 32-bit */ 18 | typedef int INT; 19 | typedef unsigned int UINT; 20 | 21 | /* This type MUST be 8-bit */ 22 | typedef unsigned char BYTE; 23 | 24 | /* These types MUST be 16-bit */ 25 | typedef short SHORT; 26 | typedef unsigned short WORD; 27 | typedef unsigned short WCHAR; 28 | 29 | /* These types MUST be 32-bit */ 30 | typedef long LONG; 31 | typedef unsigned long DWORD; 32 | 33 | /* This type MUST be 64-bit (Remove this for ANSI C (C89) compatibility) */ 34 | typedef unsigned long long QWORD; 35 | 36 | #endif 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /littlefs/lfs_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs util functions 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include "lfs_util.h" 9 | 10 | // Only compile if user does not provide custom config 11 | #ifndef LFS_CONFIG 12 | 13 | 14 | // Software CRC implementation with small lookup table 15 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { 16 | static const uint32_t rtable[16] = { 17 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 18 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 19 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 20 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, 21 | }; 22 | 23 | const uint8_t *data = buffer; 24 | 25 | for (size_t i = 0; i < size; i++) { 26 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; 27 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; 28 | } 29 | 30 | return crc; 31 | } 32 | 33 | 34 | #endif -------------------------------------------------------------------------------- /examples/console/fatfs/spi/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | "time" 8 | 9 | "tinygo.org/x/drivers/flash" 10 | "tinygo.org/x/tinyfs/examples/console" 11 | "tinygo.org/x/tinyfs/fatfs" 12 | ) 13 | 14 | var ( 15 | blockDevice = flash.NewSPI( 16 | machine.SPI1, 17 | machine.SPI1_SDO_PIN, 18 | machine.SPI1_SDI_PIN, 19 | machine.SPI1_SCK_PIN, 20 | machine.SPI1_CS_PIN, 21 | ) 22 | 23 | filesystem = fatfs.New(blockDevice) 24 | ) 25 | 26 | func main() { 27 | 28 | // Configure the flash device using the default auto-identifier function 29 | config := &flash.DeviceConfig{Identifier: flash.DefaultDeviceIdentifier} 30 | if err := blockDevice.Configure(config); err != nil { 31 | for { 32 | time.Sleep(5 * time.Second) 33 | println("Config was not valid: "+err.Error(), "\r") 34 | } 35 | } 36 | 37 | // Configure FATFS with sector size (must match value in ff.h - use 512) 38 | filesystem.Configure(&fatfs.Config{ 39 | SectorSize: 512, 40 | }) 41 | 42 | console.RunFor(blockDevice, filesystem) 43 | } 44 | -------------------------------------------------------------------------------- /examples/console/fatfs/sdcard/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "machine" 6 | "time" 7 | 8 | "tinygo.org/x/drivers/sdcard" 9 | "tinygo.org/x/tinyfs/examples/console" 10 | "tinygo.org/x/tinyfs/fatfs" 11 | ) 12 | 13 | var ( 14 | spi *machine.SPI 15 | sckPin machine.Pin 16 | sdoPin machine.Pin 17 | sdiPin machine.Pin 18 | csPin machine.Pin 19 | ledPin machine.Pin 20 | ) 21 | 22 | func main() { 23 | fmt.Printf("sdcard console\r\n") 24 | 25 | led := ledPin 26 | led.Configure(machine.PinConfig{Mode: machine.PinOutput}) 27 | 28 | sd := sdcard.New(spi, sckPin, sdoPin, sdiPin, csPin) 29 | err := sd.Configure() 30 | if err != nil { 31 | fmt.Printf("%s\r\n", err.Error()) 32 | for { 33 | time.Sleep(time.Hour) 34 | } 35 | } 36 | 37 | filesystem := fatfs.New(&sd) 38 | filesystem.Configure(&fatfs.Config{ 39 | SectorSize: 512, 40 | }) 41 | 42 | go console.RunFor(&sd, filesystem) 43 | 44 | for { 45 | led.High() 46 | time.Sleep(200 * time.Millisecond) 47 | led.Low() 48 | time.Sleep(200 * time.Millisecond) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/console/fatfs/qspi/main.go: -------------------------------------------------------------------------------- 1 | // +build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | "time" 8 | 9 | "tinygo.org/x/drivers/flash" 10 | "tinygo.org/x/tinyfs/examples/console" 11 | "tinygo.org/x/tinyfs/fatfs" 12 | ) 13 | 14 | var ( 15 | blockDevice = flash.NewQSPI( 16 | machine.QSPI_CS, 17 | machine.QSPI_SCK, 18 | machine.QSPI_DATA0, 19 | machine.QSPI_DATA1, 20 | machine.QSPI_DATA2, 21 | machine.QSPI_DATA3, 22 | ) 23 | 24 | filesystem = fatfs.New(blockDevice) 25 | ) 26 | 27 | func main() { 28 | 29 | // Configure the flash device using the default auto-identifier function 30 | config := &flash.DeviceConfig{Identifier: flash.DefaultDeviceIdentifier} 31 | if err := blockDevice.Configure(config); err != nil { 32 | for { 33 | time.Sleep(5 * time.Second) 34 | println("Config was not valid: "+err.Error(), "\r") 35 | } 36 | } 37 | 38 | // Configure FATFS with sector size (must match value in ff.h - use 512) 39 | filesystem.Configure(&fatfs.Config{ 40 | SectorSize: 512, 41 | }) 42 | 43 | console.RunFor(blockDevice, filesystem) 44 | } 45 | -------------------------------------------------------------------------------- /examples/console/littlefs/qspi/main.go: -------------------------------------------------------------------------------- 1 | // +build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | "time" 8 | 9 | "tinygo.org/x/tinyfs/examples/console" 10 | "tinygo.org/x/tinyfs/littlefs" 11 | "tinygo.org/x/drivers/flash" 12 | ) 13 | 14 | var ( 15 | blockDevice = flash.NewQSPI( 16 | machine.QSPI_CS, 17 | machine.QSPI_SCK, 18 | machine.QSPI_DATA0, 19 | machine.QSPI_DATA1, 20 | machine.QSPI_DATA2, 21 | machine.QSPI_DATA3, 22 | ) 23 | 24 | filesystem = littlefs.New(blockDevice) 25 | ) 26 | 27 | func main() { 28 | 29 | // Configure the flash device using the default auto-identifier function 30 | config := &flash.DeviceConfig{Identifier: flash.DefaultDeviceIdentifier} 31 | if err := blockDevice.Configure(config); err != nil { 32 | for { 33 | time.Sleep(5 * time.Second) 34 | println("Config was not valid: "+err.Error(), "\r") 35 | } 36 | } 37 | 38 | // Configure littlefs with parameters for caches and wear levelling 39 | filesystem.Configure(&littlefs.Config{ 40 | CacheSize: 512, 41 | LookaheadSize: 512, 42 | BlockCycles: 100, 43 | }) 44 | 45 | console.RunFor(blockDevice, filesystem) 46 | } 47 | -------------------------------------------------------------------------------- /examples/console/littlefs/spi/main.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | // +build tinygo 3 | 4 | package main 5 | 6 | import ( 7 | "machine" 8 | "time" 9 | 10 | "tinygo.org/x/drivers/flash" 11 | "tinygo.org/x/tinyfs/examples/console" 12 | "tinygo.org/x/tinyfs/littlefs" 13 | ) 14 | 15 | var ( 16 | blockDevice = flash.NewSPI( 17 | machine.SPI1, 18 | machine.SPI1_SDO_PIN, 19 | machine.SPI1_SDI_PIN, 20 | machine.SPI1_SCK_PIN, 21 | machine.SPI1_CS_PIN, 22 | ) 23 | 24 | filesystem = littlefs.New(blockDevice) 25 | ) 26 | 27 | func main() { 28 | 29 | // Configure the flash device using the default auto-identifier function 30 | config := &flash.DeviceConfig{Identifier: flash.DefaultDeviceIdentifier} 31 | if err := blockDevice.Configure(config); err != nil { 32 | for { 33 | time.Sleep(5 * time.Second) 34 | println("Config was not valid: "+err.Error(), "\r") 35 | } 36 | } 37 | 38 | // Configure littlefs with parameters for caches and wear levelling 39 | filesystem.Configure(&littlefs.Config{ 40 | CacheSize: 512, 41 | LookaheadSize: 512, 42 | BlockCycles: 100, 43 | }) 44 | 45 | console.RunFor(blockDevice, filesystem) 46 | } 47 | -------------------------------------------------------------------------------- /internal/gopointer/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Yasuhiro Matsumoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | clean: 3 | @rm -rf build 4 | 5 | FMT_PATHS = ./*.go ./examples/**/*.go ./fatfs/*.go ./littlefs/*.go 6 | 7 | fmt-check: 8 | @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 9 | 10 | smoke-test: 11 | go run ./examples/simple-fatfs 12 | @mkdir -p build 13 | tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/simple-fatfs/ 14 | @md5sum ./build/test.hex 15 | tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/console/fatfs/spi/ 16 | @md5sum ./build/test.hex 17 | tinygo build -size short -o ./build/test.hex -target=itsybitsy-m4 ./examples/console/fatfs/qspi/ 18 | @md5sum ./build/test.hex 19 | tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/console/fatfs/sdcard/ 20 | @md5sum ./build/test.hex 21 | tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/console/littlefs/spi/ 22 | @md5sum ./build/test.hex 23 | tinygo build -size short -o ./build/test.hex -target=itsybitsy-m4 ./examples/console/littlefs/qspi/ 24 | @md5sum ./build/test.hex 25 | 26 | test: clean fmt-check smoke-test 27 | -------------------------------------------------------------------------------- /internal/gopointer/pointer.go: -------------------------------------------------------------------------------- 1 | package gopointer 2 | 3 | // #include 4 | import "C" 5 | import ( 6 | "sync" 7 | "unsafe" 8 | ) 9 | 10 | var ( 11 | mutex sync.Mutex 12 | store = map[uintptr]interface{}{} 13 | ) 14 | 15 | func Save(v interface{}) unsafe.Pointer { 16 | if v == nil { 17 | return nil 18 | } 19 | 20 | // Generate real fake C pointer. 21 | // This pointer will not store any data, but will bi used for indexing purposes. 22 | // Since Go doest allow to cast dangling pointer to unsafe.Pointer, we do rally allocate one byte. 23 | // Why we need indexing, because Go doest allow C code to store pointers to Go data. 24 | var ptr unsafe.Pointer = C.malloc(C.size_t(1)) 25 | if ptr == nil { 26 | panic("can't allocate 'cgo-pointer hack index pointer': ptr == nil") 27 | } 28 | 29 | mutex.Lock() 30 | store[uintptr(ptr)] = v 31 | mutex.Unlock() 32 | 33 | return ptr 34 | } 35 | 36 | func Restore(ptr unsafe.Pointer) (v interface{}) { 37 | if ptr == nil { 38 | return nil 39 | } 40 | 41 | mutex.Lock() 42 | v = store[uintptr(ptr)] 43 | mutex.Unlock() 44 | return 45 | } 46 | 47 | func Unref(ptr unsafe.Pointer) { 48 | if ptr == nil { 49 | return 50 | } 51 | 52 | mutex.Lock() 53 | delete(store, uintptr(ptr)) 54 | mutex.Unlock() 55 | 56 | C.free(ptr) 57 | } 58 | -------------------------------------------------------------------------------- /littlefs/go_lfs.c: -------------------------------------------------------------------------------- 1 | #include "go_lfs.h" 2 | 3 | struct lfs* go_lfs_new_lfs() { 4 | return malloc(sizeof(struct lfs)); 5 | } 6 | 7 | struct lfs_config* go_lfs_new_lfs_config() { 8 | return malloc(sizeof(struct lfs_config)); 9 | } 10 | 11 | lfs_dir_t* go_lfs_new_lfs_dir() { 12 | return malloc(sizeof(lfs_dir_t)); 13 | } 14 | 15 | lfs_file_t* go_lfs_new_lfs_file() { 16 | return malloc(sizeof(lfs_file_t)); 17 | } 18 | 19 | struct lfs_config* go_lfs_set_callbacks(struct lfs_config *cfg) { 20 | cfg->read = go_lfs_c_cb_read; 21 | cfg->prog = go_lfs_c_cb_prog; 22 | cfg->erase = go_lfs_c_cb_erase; 23 | cfg->sync = go_lfs_c_cb_sync; 24 | return cfg; 25 | } 26 | 27 | int go_lfs_c_cb_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { 28 | return go_lfs_block_device_read(c->context, block, off, buffer, size); 29 | } 30 | 31 | int go_lfs_c_cb_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { 32 | return go_lfs_block_device_prog(c->context, block, off, buffer, size); 33 | } 34 | 35 | int go_lfs_c_cb_erase(const struct lfs_config *c, lfs_block_t block) { 36 | return go_lfs_block_device_erase(c->context, block); 37 | } 38 | 39 | int go_lfs_c_cb_sync(const struct lfs_config *c) { 40 | return go_lfs_block_device_sync(c->context); 41 | } 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.5.0 2 | --- 3 | - **all** 4 | - Adding io.Seeker interface to tinyfs.File 5 | - Adding Stat() method to tinyfs.File interface and the corresponding littlefs and fatfs implementations. Also adding NewTinyFS() method to create instances of the fs.FS interface. 6 | - **examples** 7 | - Update examples for latest driver changes 8 | - **modules** 9 | - update to latest version of tinygo drivers 0.31.0 10 | 11 | 0.4.0 12 | --- 13 | - **littlefs** 14 | - update to littlefs 2.8.1 15 | - **examples** 16 | - Adding sdcard example for fatfs 17 | - **modules** 18 | - update to latest version of tinygo drivers 0.27.0 19 | 20 | 0.3.0 21 | --- 22 | - **examples** 23 | - adding machine.Flash example 24 | - **build** 25 | - switch to ghcr.io for docker container 26 | - remove circleci 27 | - switch to GH actions 28 | - **docs** 29 | - switch CI badge in README to GH action 30 | - update LICENSE year to 2023 31 | - update README with useful info 32 | - **modules** 33 | - update to latest version of tinygo drivers 34 | 35 | 0.2.0 36 | --- 37 | - Compatability with TinyGo 0.23.0 38 | 39 | 40 | 0.1.0 41 | --- 42 | - **first release** 43 | - This is the first official release of TinyFS repo, coinciding with TinyGo 0.19.0. The following filesystems are supported: 44 | - LittleFS (https://github.com/littlefs-project/littlefs) 45 | - FAT (http://elm-chan.org/fsw/ff/00index_e.html) 46 | -------------------------------------------------------------------------------- /littlefs/docs/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Arm Limited. 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 ARM nor the names of its contributors may be used to 12 | endorse or promote products derived from this software without specific prior 13 | 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-2025 The TinyGo Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of the copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /fatfs/go_fatfs.c: -------------------------------------------------------------------------------- 1 | #include "go_fatfs.h" 2 | 3 | // implementation of disk interface layer defined in diskio.h 4 | 5 | DRESULT disk_read(void *drv, BYTE* buff, DWORD sector, UINT count) { 6 | return go_fatfs_disk_read(drv, buff, sector, count); 7 | } 8 | 9 | DRESULT disk_write(void *drv, const BYTE* buff, DWORD sector, UINT count) { 10 | return go_fatfs_disk_write(drv, (BYTE*)buff, sector, count); 11 | } 12 | 13 | DRESULT disk_ioctl(void *drv, BYTE cmd, void* buff) { 14 | return go_fatfs_disk_ioctl(drv, cmd, buff); 15 | } 16 | 17 | DWORD get_fattime() { 18 | return go_fatfs_get_fattime(); 19 | } 20 | 21 | // Helper functions for creating FatFs structs 22 | 23 | FATFS* go_fatfs_new_fatfs(void) { 24 | return malloc(sizeof(FATFS)); 25 | } 26 | 27 | FIL* go_fatfs_new_fil(void) { 28 | return malloc(sizeof(FIL)); 29 | } 30 | 31 | FF_DIR* go_fatfs_new_ff_dir(void) { 32 | return malloc(sizeof(FF_DIR)); 33 | } 34 | 35 | // if ffconf.h has FF_FS_READONLY set, certain functions aren't implemented, 36 | // which prevents the Go code from linking properly. 37 | #if FF_FS_READONLY == 1 38 | 39 | FRESULT f_mkfs (FATFS *fs, BYTE opt, DWORD au, void* work, UINT len) { 40 | return 99; 41 | } 42 | 43 | FRESULT f_mkdir (FATFS *fs, const TCHAR* path) { 44 | return 99; 45 | } 46 | 47 | FRESULT f_unlink (FATFS *fs, const TCHAR* path) { 48 | return 99; 49 | } 50 | 51 | FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw) { 52 | return 99; 53 | } 54 | 55 | FRESULT f_sync (FIL* fp) { 56 | return 99; 57 | } 58 | 59 | FRESULT f_rename (FATFS *fs, const TCHAR* path_old, const TCHAR* path_new) { 60 | return 99; 61 | } 62 | 63 | FRESULT f_getfree (FATFS *fs, DWORD* nclst) { 64 | return 99; 65 | } 66 | 67 | #endif -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Thank you for your interest in improving TinyFS. 4 | 5 | We would like your help to make this project better, so we appreciate any contributions. See if one of the following descriptions matches your situation: 6 | 7 | ### New to TinyGo 8 | 9 | We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6). 10 | 11 | ### Something is not working as you expect 12 | 13 | Please open a Github issue with your problem, and we will be happy to assist. 14 | 15 | ### Some specific feature does not appear to be in TinyFS 16 | 17 | We probably have not implemented it yet. Your contribution would be greatly appreciated. 18 | 19 | Please first open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else. 20 | 21 | ## How to use our Github repository 22 | 23 | The `release` branch of this repo will always have the latest released version of TinyFS. All of the active development work for the next release will take place in the `dev` branch. TinyFS will use semantic versioning and will create a tag/release for each release. 24 | 25 | Here is how to contribute back some code or documentation: 26 | 27 | - Fork repo 28 | - Create a feature branch off of the `dev` branch 29 | - Make some useful change 30 | - Make sure the tests still pass 31 | - Submit a pull request against the `dev` branch. 32 | - Be kind 33 | 34 | ## How to run tests 35 | 36 | To run the tests: 37 | 38 | ``` 39 | make test 40 | ``` 41 | -------------------------------------------------------------------------------- /littlefs/go_lfs.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lfs.h" 3 | 4 | // LittleFS uses function pointers to callback functions in order to allow for 5 | // a user-provided abstraction of a block device. Go does not allow for passing 6 | // Go function pointers to C, so instead we will use global Go functions with 7 | // exported symbols in our C callback functions. A pointer to the Go struct 8 | // representing each LittleFS instance is saved in the lfs_config.context field, 9 | // which is then used by those Go functions to dispatch callback invocations. 10 | extern int go_lfs_block_device_read(void*, lfs_block_t, lfs_off_t, void*, lfs_size_t); 11 | extern int go_lfs_block_device_prog(void*, lfs_block_t, lfs_off_t, const void*, lfs_size_t); 12 | extern int go_lfs_block_device_erase(void*, lfs_block_t); 13 | extern int go_lfs_block_device_sync(void*); 14 | 15 | // These are the global C callbacks. Pointers to these functions are passed to 16 | // the LittleFS library as the block device callbacks, and they in turn call 17 | // the associated global Go callbacks which handle dispatching the callback 18 | // invocations to the correct instance of the LFS struct in Go. 19 | int go_lfs_c_cb_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size); 20 | int go_lfs_c_cb_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size); 21 | int go_lfs_c_cb_erase(const struct lfs_config *c, lfs_block_t block); 22 | int go_lfs_c_cb_sync(const struct lfs_config *c); 23 | 24 | // Helper functions used to allocate new LFS objects, needed because TinyGo 25 | // does not support sizeof() yet 26 | struct lfs* go_lfs_new_lfs(void); 27 | struct lfs_config* go_lfs_new_lfs_config(void); 28 | lfs_dir_t* go_lfs_new_lfs_dir(void); 29 | lfs_file_t* go_lfs_new_lfs_file(void); 30 | 31 | // Helper function to set the function pointers to the global callbacks on a 32 | // provided LFS config struct 33 | struct lfs_config* go_lfs_set_callbacks(struct lfs_config *cfg); 34 | -------------------------------------------------------------------------------- /examples/simple-fatfs/block_device_file.go: -------------------------------------------------------------------------------- 1 | //go:build !tinygo 2 | // +build !tinygo 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "tinygo.org/x/tinyfs" 10 | "tinygo.org/x/tinyfs/fatfs" 11 | ) 12 | 13 | // FileBlockDevice is a block device implementation backed by a byte slice 14 | type FileBlockDevice struct { 15 | file *os.File 16 | blankBlock []byte 17 | pageSize uint32 18 | blockSize uint32 19 | blockCount uint32 20 | } 21 | 22 | var _ tinyfs.BlockDevice = (*FileBlockDevice)(nil) 23 | 24 | func NewFileDevice(file *os.File, pageSize, blockSize int, blockCount int) *FileBlockDevice { 25 | dev := &FileBlockDevice{ 26 | file: file, 27 | blankBlock: make([]byte, blockSize), 28 | pageSize: uint32(pageSize), 29 | blockSize: uint32(blockSize), 30 | blockCount: uint32(blockCount), 31 | } 32 | return dev 33 | } 34 | 35 | func (bd *FileBlockDevice) ReadAt(buf []byte, off int64) (n int, err error) { 36 | return bd.file.ReadAt(buf, off) 37 | //return copy(buf, bd.memory[off:]), nil 38 | } 39 | 40 | func (bd *FileBlockDevice) WriteAt(buf []byte, off int64) (n int, err error) { 41 | return bd.file.WriteAt(buf, off) 42 | } 43 | 44 | func (bd *FileBlockDevice) Size() int64 { 45 | return int64(bd.blockSize * bd.blockCount) 46 | } 47 | 48 | func (bd *FileBlockDevice) SectorSize() int { 49 | return fatfs.SectorSize 50 | } 51 | 52 | func (bd *FileBlockDevice) WriteBlockSize() int64 { 53 | return int64(bd.pageSize) 54 | } 55 | 56 | func (bd *FileBlockDevice) EraseBlockSize() int64 { 57 | return int64(bd.blockSize) 58 | } 59 | 60 | func (bd *FileBlockDevice) EraseBlocks(start int64, len int64) error { 61 | for i := int64(0); i < len; i++ { 62 | if err := bd.eraseBlock(uint32(start + i)); err != nil { 63 | return err 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | func (bd *FileBlockDevice) eraseBlock(block uint32) error { 70 | _, err := bd.file.WriteAt(bd.blankBlock, int64(bd.blockSize*block)) 71 | return err 72 | } 73 | 74 | func (bd *FileBlockDevice) Sync() error { 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /littlefs/go_lfs_callbacks.go: -------------------------------------------------------------------------------- 1 | package littlefs 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | 7 | "tinygo.org/x/tinyfs" 8 | "tinygo.org/x/tinyfs/internal/gopointer" 9 | ) 10 | 11 | import "C" 12 | 13 | const ( 14 | debug bool = false 15 | ) 16 | 17 | //export go_lfs_block_device_read 18 | func go_lfs_block_device_read(ctx unsafe.Pointer, block uint32, offset uint32, buf unsafe.Pointer, size int) int { 19 | if debug { 20 | fmt.Printf("go_lfs_block_device_read: %v, %v, %v, %v, %v\n", ctx, block, offset, buf, size) 21 | } 22 | fs := restore(ctx) 23 | addr := fs.blockSize()*block + offset 24 | buffer := (*[1 << 28]byte)(buf)[:size:size] 25 | _, err := fs.dev.ReadAt(buffer, int64(addr)) 26 | return go_lfs_block_errval("read", err) 27 | } 28 | 29 | //export go_lfs_block_device_prog 30 | func go_lfs_block_device_prog(ctx unsafe.Pointer, block uint32, offset uint32, buf unsafe.Pointer, size int) int { 31 | if debug { 32 | fmt.Printf("go_lfs_block_device_prog: %v, %v, %v, %v, %v\n", ctx, block, offset, buf, size) 33 | } 34 | fs := restore(ctx) 35 | addr := fs.blockSize()*block + offset 36 | buffer := (*[1 << 28]byte)(buf)[:size:size] 37 | _, err := fs.dev.WriteAt(buffer, int64(addr)) 38 | return go_lfs_block_errval("program", err) 39 | } 40 | 41 | //export go_lfs_block_device_erase 42 | func go_lfs_block_device_erase(ctx unsafe.Pointer, block uint32) int { 43 | if debug { 44 | fmt.Printf("go_lfs_block_device_erase: %v, %v\n", ctx, block) 45 | } 46 | return go_lfs_block_errval("erase", restore(ctx).dev.EraseBlocks(int64(block), 1)) 47 | } 48 | 49 | //export go_lfs_block_device_sync 50 | func go_lfs_block_device_sync(ctx unsafe.Pointer) int { 51 | if debug { 52 | fmt.Printf("go_lfs_block_device_sync: %v\n", ctx) 53 | } 54 | fs := restore(ctx) 55 | if syncer, ok := fs.dev.(tinyfs.Syncer); ok { 56 | return go_lfs_block_errval("sync", syncer.Sync()) 57 | } 58 | return errOK 59 | } 60 | 61 | func go_lfs_block_errval(op string, err error) int { 62 | if err != nil { 63 | if debug { 64 | println(op, "error:", err) 65 | } 66 | return int(errIO) 67 | } 68 | return errOK 69 | } 70 | 71 | func restore(ptr unsafe.Pointer) *LFS { 72 | return gopointer.Restore(ptr).(*LFS) 73 | } 74 | -------------------------------------------------------------------------------- /fatfs/go_fatfs_callbacks.go: -------------------------------------------------------------------------------- 1 | package fatfs 2 | 3 | // #include "diskio.h" 4 | // #include "ff.h" 5 | import "C" 6 | 7 | import ( 8 | "time" 9 | "unsafe" 10 | 11 | "tinygo.org/x/tinyfs" 12 | "tinygo.org/x/tinyfs/internal/gopointer" 13 | ) 14 | 15 | const ( 16 | debug = false 17 | ) 18 | 19 | //export go_fatfs_disk_read 20 | func go_fatfs_disk_read(drv unsafe.Pointer, bufptr unsafe.Pointer, sector uint32, count uint) int { 21 | if debug { 22 | println("disk_read:", sector, count) 23 | } 24 | bdev := restore(drv).dev 25 | sect := int(SectorSize) 26 | size := sect * int(count) 27 | addr := int64(sector) * int64(sect) 28 | buffer := (*[1 << 28]byte)(bufptr)[:size:size] 29 | if _, err := bdev.ReadAt(buffer, addr); err != nil { 30 | //println("disk_read error:", err) 31 | return C.RES_ERROR 32 | } 33 | return C.RES_OK 34 | } 35 | 36 | //export go_fatfs_disk_write 37 | func go_fatfs_disk_write(drv unsafe.Pointer, bufptr unsafe.Pointer, sector uint32, count uint) int { 38 | //println("disk_write:", sector, count) 39 | bdev := restore(drv).dev 40 | sect := int(SectorSize) 41 | size := sect * int(count) 42 | addr := int64(sector) * int64(sect) 43 | buffer := (*[1 << 28]byte)(bufptr)[:size:size] 44 | //xxdfprint(os.Stdout, 0, buffer) 45 | if _, err := bdev.WriteAt(buffer, addr); err != nil { 46 | //println("disk_write error:", err) 47 | return C.RES_ERROR 48 | } 49 | return C.RES_OK 50 | } 51 | 52 | //export go_fatfs_disk_ioctl 53 | func go_fatfs_disk_ioctl(drv unsafe.Pointer, cmd uint8, param unsafe.Pointer) int { 54 | //println("disk_ioctl:", cmd) 55 | bdev := restore(drv).dev 56 | switch cmd { 57 | case C.CTRL_SYNC: 58 | // Complete pending write process (needed at _FS_READONLY == 0) 59 | if syncer, ok := bdev.(tinyfs.Syncer); ok { 60 | if err := syncer.Sync(); err != nil { 61 | return C.RES_ERROR 62 | } 63 | } 64 | case C.GET_SECTOR_COUNT: 65 | // Get media size (needed at _USE_MKFS == 1) 66 | *((*C.DWORD)(param)) = C.DWORD(bdev.Size() / SectorSize) 67 | case C.GET_SECTOR_SIZE: 68 | // Get sector size (needed at _MAX_SS != _MIN_SS) 69 | *((*C.WORD)(param)) = C.WORD(SectorSize) 70 | case C.GET_BLOCK_SIZE: 71 | // Get erase block size (needed at _USE_MKFS == 1) 72 | // FIXME: not really sure why this doesn't work 73 | *((*C.DWORD)(param)) = C.DWORD(bdev.EraseBlockSize() / SectorSize) 74 | case C.IOCTL_INIT: 75 | // FIXME: not really sure what this would be used for 76 | *((*C.DSTATUS)(param)) = C.DSTATUS(0) 77 | case C.IOCTL_STATUS: 78 | // FIXME: not really sure what this would be used for 79 | *((*C.DSTATUS)(param)) = C.DSTATUS(0) 80 | } 81 | return C.RES_OK 82 | } 83 | 84 | //export go_fatfs_get_fattime 85 | func go_fatfs_get_fattime() (t uint32) { 86 | now := time.Now() 87 | year, month, day := now.Date() 88 | hour, minute, second := now.Hour(), now.Minute(), now.Second() 89 | t |= uint32(year-1980) << 24 90 | t |= (uint32(month) & 0xF) << 20 91 | t |= (uint32(day) & 0x1F) << 15 92 | t |= (uint32(hour) & 0x1F) << 10 93 | t |= (uint32(minute) & 0x3F) << 4 94 | t |= (uint32(second) / 2) & 0xF 95 | return t 96 | } 97 | 98 | func restore(ptr unsafe.Pointer) *FATFS { 99 | return gopointer.Restore(ptr).(*FATFS) 100 | } 101 | -------------------------------------------------------------------------------- /fatfs/diskio.h: -------------------------------------------------------------------------------- 1 | /* This file is part of ooFatFs, a customised version of FatFs 2 | * See https://github.com/micropython/oofatfs for details 3 | */ 4 | 5 | /*-----------------------------------------------------------------------/ 6 | / Low level disk interface modlue include file (C)ChaN, 2014 / 7 | /-----------------------------------------------------------------------*/ 8 | 9 | #ifndef _DISKIO_DEFINED 10 | #define _DISKIO_DEFINED 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | #include "integer.h" 17 | 18 | /* Status of Disk Functions */ 19 | typedef BYTE DSTATUS; 20 | 21 | /* Results of Disk Functions */ 22 | typedef enum { 23 | RES_OK = 0, /* 0: Successful */ 24 | RES_ERROR, /* 1: R/W Error */ 25 | RES_WRPRT, /* 2: Write Protected */ 26 | RES_NOTRDY, /* 3: Not Ready */ 27 | RES_PARERR /* 4: Invalid Parameter */ 28 | } DRESULT; 29 | 30 | 31 | /*---------------------------------------*/ 32 | /* Prototypes for disk control functions */ 33 | 34 | 35 | DRESULT disk_read (void *drv, BYTE* buff, DWORD sector, UINT count); 36 | DRESULT disk_write (void *drv, const BYTE* buff, DWORD sector, UINT count); 37 | DRESULT disk_ioctl (void *drv, BYTE cmd, void* buff); 38 | 39 | 40 | /* Disk Status Bits (DSTATUS) */ 41 | 42 | #define STA_NOINIT 0x01 /* Drive not initialized */ 43 | #define STA_NODISK 0x02 /* No medium in the drive */ 44 | #define STA_PROTECT 0x04 /* Write protected */ 45 | 46 | 47 | /* Command code for disk_ioctrl fucntion */ 48 | 49 | /* Generic command (Used by FatFs) */ 50 | #define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */ 51 | #define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */ 52 | #define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */ 53 | #define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */ 54 | #define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */ 55 | #define IOCTL_INIT 5 56 | #define IOCTL_STATUS 6 57 | 58 | /* Generic command (Not used by FatFs) */ 59 | #define CTRL_POWER 5 /* Get/Set power status */ 60 | #define CTRL_LOCK 6 /* Lock/Unlock media removal */ 61 | #define CTRL_EJECT 7 /* Eject media */ 62 | #define CTRL_FORMAT 8 /* Create physical format on the media */ 63 | 64 | /* MMC/SDC specific ioctl command */ 65 | #define MMC_GET_TYPE 10 /* Get card type */ 66 | #define MMC_GET_CSD 11 /* Get CSD */ 67 | #define MMC_GET_CID 12 /* Get CID */ 68 | #define MMC_GET_OCR 13 /* Get OCR */ 69 | #define MMC_GET_SDSTAT 14 /* Get SD status */ 70 | #define ISDIO_READ 55 /* Read data form SD iSDIO register */ 71 | #define ISDIO_WRITE 56 /* Write data to SD iSDIO register */ 72 | #define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */ 73 | 74 | /* ATA/CF specific ioctl command */ 75 | #define ATA_GET_REV 20 /* Get F/W revision */ 76 | #define ATA_GET_MODEL 21 /* Get model name */ 77 | #define ATA_GET_SN 22 /* Get serial number */ 78 | 79 | #ifdef __cplusplus 80 | } 81 | #endif 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /fatfs/go_fatfs_test.go: -------------------------------------------------------------------------------- 1 | //go:build !tinygo 2 | 3 | package fatfs 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | 9 | "tinygo.org/x/tinyfs" 10 | ) 11 | 12 | const ( 13 | testPageSize = 64 14 | testBlockSize = 256 15 | testBlockCount = 4096 16 | ) 17 | 18 | func TestType_String(t *testing.T) { 19 | expectString(t, "fatfs: (1) A hard error occurred in the low level disk I/O layer", FileResultErr.Error()) 20 | } 21 | 22 | func TestFormat(t *testing.T) { 23 | //dev2 := NewMemoryDevice(4096, 64) 24 | //fs2 := New(dev2) 25 | t.Run("BasicMounting", func(t *testing.T) { 26 | // file, err := os.Open("trinket_filesystem.img") 27 | // check(t, err) 28 | // dev := NewFileDevice(file, 512, 29) 29 | fs, _, umount := createTestFS(t) 30 | defer umount() 31 | n, err := fs.Free() 32 | check(t, err) 33 | println("free:", n) 34 | }) 35 | } 36 | 37 | func createTestFS(t *testing.T) (*FATFS, tinyfs.BlockDevice, func()) { 38 | // create/format/mount the filesystem 39 | dev := tinyfs.NewMemoryDevice(testPageSize, testBlockSize, testBlockCount) 40 | fs := New(dev) 41 | println("formatting") 42 | fs.Configure(&Config{SectorSize: 512}) 43 | //check(t, fs.Configure()) 44 | if err := fs.Format(); err != nil { 45 | t.Fatal(err) 46 | } 47 | /* 48 | buf := make([]byte, 512) 49 | dev.ReadAt(buf, 0) 50 | xxdfprint(os.Stdout, 0, buf) 51 | println("format successful; mounting") 52 | */ 53 | if err := fs.Mount(); err != nil { 54 | t.Error("Could not mount", err) 55 | } 56 | return fs, dev, func() { 57 | //if err := fs.Unmount(); err != nil { 58 | // t.Error("Could not ummount", err) 59 | //} 60 | } 61 | } 62 | 63 | func TestDirectories(t *testing.T) { 64 | 65 | const ( 66 | largeSize = 128 67 | ) 68 | t.Run("RootDirectory", func(t *testing.T) { 69 | fs, _, unmount := createTestFS(t) 70 | defer unmount() 71 | f, err := fs.Open("/") 72 | check(t, err) 73 | check(t, f.Close()) 74 | }) 75 | t.Run("DirectoryCreation", func(t *testing.T) { 76 | fs, _, unmount := createTestFS(t) 77 | defer unmount() 78 | check(t, fs.Mkdir("potato", 0777)) 79 | info, err := fs.Stat("potato") 80 | check(t, err) 81 | println("potato: ", info.Name(), info.IsDir(), info.Mode()) 82 | }) 83 | } 84 | 85 | func TestFiles(t *testing.T) { 86 | t.Run("BasicFile", func(t *testing.T) { 87 | fs, _, unmount := createTestFS(t) 88 | defer unmount() 89 | f, err := fs.OpenFile("hello_world.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC) 90 | check(t, err) 91 | n, err := f.Write([]byte("hello world")) 92 | check(t, err) 93 | check(t, f.Close()) 94 | if n != 11 { 95 | t.Fail() 96 | } 97 | f, err = fs.Open("hello_world.txt") 98 | check(t, err) 99 | info, err := f.Stat() 100 | check(t, err) 101 | if info.IsDir() { 102 | t.Fail() 103 | } 104 | if info.Name() != "hello_world.txt" { 105 | t.Fail() 106 | } 107 | if info.Size() != 11 { 108 | t.Fail() 109 | } 110 | }) 111 | } 112 | 113 | func expectString(t *testing.T, expected string, actual string) { 114 | if expected != actual { 115 | t.Fatalf("expected \"%s\", was actually \"%s\"", expected, actual) 116 | } 117 | } 118 | 119 | func check(t *testing.T, err error) { 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyFS 2 | 3 | [![Build](https://github.com/tinygo-org/tinyfs/actions/workflows/build.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinyfs/actions/workflows/build.yml) 4 | 5 | TinyFS contains Go implementations of embedded filesystems. The packages in 6 | this module require CGo support and are [TinyGo](https://tinygo.org/) compatible. 7 | 8 | ## Supported hardware 9 | 10 | You can use TinyFS with the following embedded hardware configurations: 11 | 12 | ### Onboard Flash memory 13 | 14 | You can use TinyFS on processors that have onboard Flash memory in the area above where the compiled TinyGo program is running. 15 | 16 | ### External Flash memory 17 | 18 | You can use TinyFS on an external Flash memory chip connected via SPI/QSPI hardware interface. 19 | 20 | See https://github.com/tinygo-org/drivers/tree/release/flash 21 | 22 | ### SD card connected via SPI/QPI 23 | 24 | You can use TinyFS on an SD card connected via SPI/QSPI hardware interface. 25 | 26 | See https://github.com/tinygo-org/drivers/tree/release/sdcard 27 | 28 | ## LittleFS 29 | 30 | The LittleFS file system is specifically designed for embedded applications. 31 | 32 | See https://github.com/littlefs-project/littlefs for more information. 33 | 34 | ### Example 35 | 36 | This example runs on the RP2040 using the on-board flash in the available memory above where the program code itself is running: 37 | 38 | ``` 39 | $ tinygo flash -target pico -monitor ./examples/console/littlefs/machine/ 40 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 41 | SPI Configured. Reading flash info 42 | ==> lsblk 43 | 44 | ------------------------------------- 45 | Device Information: 46 | ------------------------------------- 47 | flash data start: 10017000 48 | flash data end: 10200000 49 | ------------------------------------- 50 | 51 | ==> format 52 | Successfully formatted LittleFS filesystem. 53 | 54 | ==> mount 55 | Successfully mounted LittleFS filesystem. 56 | 57 | ==> ls 58 | ==> samples 59 | wrote 90 bytes to file0.txt 60 | wrote 90 bytes to file1.txt 61 | wrote 90 bytes to file2.txt 62 | wrote 90 bytes to file3.txt 63 | wrote 90 bytes to file4.txt 64 | ==> ls 65 | -rwxrwxrwx 90 file0.txt 66 | -rwxrwxrwx 90 file1.txt 67 | -rwxrwxrwx 90 file2.txt 68 | -rwxrwxrwx 90 file3.txt 69 | -rwxrwxrwx 90 file4.txt 70 | ==> cat file3.txt 71 | 00000000: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ 72 | 00000010: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? 73 | 00000020: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO 74 | 00000030: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_ 75 | 00000040: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno 76 | 00000050: 70 71 72 73 74 75 76 77 78 79 pqrstuvwxy 77 | ``` 78 | 79 | After unplugging and reconnecting the RP2040 device (a hard restart): 80 | 81 | ``` 82 | $ tinygo monitor 83 | Connected to /dev/ttyACM0. Press Ctrl-C to exit. 84 | SPI Configured. Reading flash info 85 | ==> mount 86 | Successfully mounted LittleFS filesystem. 87 | 88 | ==> ls 89 | -rwxrwxrwx 90 file0.txt 90 | -rwxrwxrwx 90 file1.txt 91 | -rwxrwxrwx 90 file2.txt 92 | -rwxrwxrwx 90 file3.txt 93 | -rwxrwxrwx 90 file4.txt 94 | ==> cat file3.txt 95 | 00000000: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ 96 | 00000010: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? 97 | 00000020: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO 98 | 00000030: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_ 99 | 00000040: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno 100 | 00000050: 70 71 72 73 74 75 76 77 78 79 pqrstuvwxy 101 | ``` 102 | 103 | ## FAT FS 104 | 105 | The FAT file system is not currently working, due to https://github.com/tinygo-org/tinygo/issues/3460. 106 | 107 | -------------------------------------------------------------------------------- /tinyfs.go: -------------------------------------------------------------------------------- 1 | package tinyfs 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | ) 8 | 9 | type Filesystem interface { 10 | Format() error 11 | Mkdir(path string, _ os.FileMode) error 12 | Mount() error 13 | Open(path string) (File, error) 14 | OpenFile(path string, flags int) (File, error) 15 | Remove(path string) error 16 | Rename(oldPath string, newPath string) error 17 | Stat(path string) (os.FileInfo, error) 18 | Unmount() error 19 | } 20 | 21 | // File specifies the common behavior of the file abstraction in TinyFS; this 22 | // interface may be changed or superseded by the TinyGo os.FileHandle interface 23 | // if/when that is merged and standardized. 24 | type File interface { 25 | FileHandle 26 | io.Seeker 27 | IsDir() bool 28 | Readdir(n int) (infos []os.FileInfo, err error) 29 | Stat() (info os.FileInfo, err error) 30 | } 31 | 32 | // FileHandle is a copy of the experimental os.FileHandle interface in TinyGo 33 | type FileHandle interface { 34 | // Read reads up to len(b) bytes from the file. 35 | Read(b []byte) (n int, err error) 36 | 37 | // Write writes up to len(b) bytes to the file. 38 | Write(b []byte) (n int, err error) 39 | 40 | // Close closes the file, making it unusable for further writes. 41 | Close() (err error) 42 | } 43 | 44 | // A BlockDevice is the raw device that is meant to store a filesystem. 45 | type BlockDevice interface { 46 | 47 | // ReadAt reads the given number of bytes from the block device. 48 | io.ReaderAt 49 | 50 | // WriteAt writes the given number of bytes to the block device. 51 | io.WriterAt 52 | 53 | // Size returns the number of bytes in this block device. 54 | Size() int64 55 | 56 | // WriteBlockSize returns the block size in which data can be written to 57 | // memory. It can be used by a client to optimize writes, non-aligned writes 58 | // should always work correctly. 59 | WriteBlockSize() int64 60 | 61 | // EraseBlockSize returns the smallest erasable area on this particular chip 62 | // in bytes. This is used for the block size in EraseBlocks. 63 | // It must be a power of two, and may be as small as 1. A typical size is 4096. 64 | EraseBlockSize() int64 65 | 66 | // EraseBlocks erases the given number of blocks. An implementation may 67 | // transparently coalesce ranges of blocks into larger bundles if the chip 68 | // supports this. The start and len parameters are in block numbers, use 69 | // EraseBlockSize to map addresses to blocks. 70 | EraseBlocks(start, len int64) error 71 | } 72 | 73 | type Syncer interface { 74 | // Sync triggers the devices to commit any pending or cached operations 75 | Sync() error 76 | } 77 | 78 | // MemBlockDevice is a block device implementation backed by a byte slice 79 | type MemBlockDevice struct { 80 | memory []byte 81 | blankBlock []byte 82 | blockCount uint32 83 | blockSize uint32 84 | pageSize uint32 85 | } 86 | 87 | var _ BlockDevice = (*MemBlockDevice)(nil) 88 | 89 | func NewMemoryDevice(pageSize, blockSize, blockCount int) *MemBlockDevice { 90 | dev := &MemBlockDevice{ 91 | memory: make([]byte, blockSize*blockCount), 92 | blankBlock: make([]byte, blockSize), 93 | pageSize: uint32(pageSize), 94 | blockSize: uint32(blockSize), 95 | blockCount: uint32(blockCount), 96 | } 97 | for i := range dev.blankBlock { 98 | dev.blankBlock[i] = 0xff 99 | } 100 | for i := uint32(0); i < uint32(blockCount); i++ { 101 | if err := dev.eraseBlock(i); err != nil { 102 | panic(fmt.Sprintf("could not initialize block %d: %s", i, err.Error())) 103 | } 104 | } 105 | return dev 106 | } 107 | 108 | func (bd *MemBlockDevice) ReadAt(buf []byte, off int64) (n int, err error) { 109 | return copy(buf, bd.memory[off:]), nil 110 | } 111 | 112 | func (bd *MemBlockDevice) WriteAt(buf []byte, off int64) (n int, err error) { 113 | return copy(bd.memory[off:], buf), nil 114 | } 115 | 116 | func (bd *MemBlockDevice) Size() int64 { 117 | return int64(bd.blockSize * bd.blockCount) 118 | } 119 | 120 | func (bd *MemBlockDevice) WriteBlockSize() int64 { 121 | return int64(bd.pageSize) 122 | } 123 | 124 | func (bd *MemBlockDevice) EraseBlockSize() int64 { 125 | return int64(bd.blockSize) 126 | } 127 | 128 | func (bd *MemBlockDevice) EraseBlocks(start int64, len int64) error { 129 | for i := int64(0); i < len; i++ { 130 | if err := bd.eraseBlock(uint32(start + i)); err != nil { 131 | return err 132 | } 133 | } 134 | return nil 135 | } 136 | 137 | func (bd *MemBlockDevice) eraseBlock(block uint32) error { 138 | copy(bd.memory[bd.blockSize*block:], bd.blankBlock) 139 | return nil 140 | } 141 | 142 | func (bd *MemBlockDevice) Sync() error { 143 | return nil 144 | } 145 | -------------------------------------------------------------------------------- /examples/simple-fatfs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/fs" 7 | "os" 8 | 9 | "tinygo.org/x/tinyfs" 10 | "tinygo.org/x/tinyfs/fatfs" 11 | ) 12 | 13 | func main() { 14 | 15 | // create/format/mount the filesystem 16 | fat := fatfs.New(tinyfs.NewMemoryDevice(64, 256, 4096)) 17 | fat.Configure(&fatfs.Config{SectorSize: 512}) 18 | 19 | if err := fat.Format(); err != nil { 20 | fmt.Println("Could not format", err) 21 | os.Exit(1) 22 | } 23 | if err := fat.Mount(); err != nil { 24 | fmt.Println("Could not mount", err) 25 | os.Exit(1) 26 | } 27 | defer func() { 28 | if err := fat.Unmount(); err != nil { 29 | fmt.Println("Could not ummount", err) 30 | os.Exit(1) 31 | } 32 | }() 33 | 34 | // test an invalid operation to make sure it returns an appropriate error 35 | if err := fat.Rename("test.txt", "test2.txt"); err == nil { 36 | fmt.Println("Could not rename file (as expected):", err) 37 | os.Exit(1) 38 | } 39 | 40 | // try out some filesystem operations 41 | 42 | path := "/tmp" 43 | fmt.Println("making directory", path) 44 | if err := fat.Mkdir(path, 0777); err != nil { 45 | fmt.Println("Could not create "+path+" dir", err) 46 | os.Exit(1) 47 | } 48 | 49 | filepath := path + "/test.txt" 50 | fmt.Println("opening file", filepath) 51 | f, err := fat.OpenFile(filepath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY) 52 | if err != nil { 53 | fmt.Println("Could not open file", err) 54 | os.Exit(1) 55 | } 56 | 57 | size, err := fat.Free() 58 | if err != nil { 59 | fmt.Println("Could not get filesystem free:", err.Error()) 60 | } else { 61 | fmt.Println("Filesystem free:", size) 62 | } 63 | 64 | /* 65 | fmt.Println("truncating file") 66 | if err := f.Truncate(256); err != nil { 67 | fmt.Println("Could not trucate file", err) 68 | os.Exit(1) 69 | } 70 | */ 71 | 72 | for i := 0; i < 20; i++ { 73 | if _, err := f.Write([]byte("01234567890abcdef")); err != nil { 74 | fmt.Println("Could not write: ", err.Error()) 75 | os.Exit(1) 76 | } 77 | } 78 | 79 | fmt.Println("closing file") 80 | if err := f.Close(); err != nil { 81 | fmt.Println("Could not close file", err) 82 | os.Exit(1) 83 | } 84 | 85 | if stat, err := fat.Stat(path); err != nil { 86 | fmt.Println("Could not stat dir", err) 87 | os.Exit(1) 88 | } else { 89 | fmt.Printf( 90 | "dir stat: name=%s size=%d dir=%t\n", 91 | stat.Name(), stat.Size(), stat.IsDir()) 92 | } 93 | 94 | if stat, err := fat.Stat(filepath); err != nil { 95 | fmt.Println("Could not stat file", err) 96 | os.Exit(1) 97 | } else { 98 | fmt.Printf( 99 | "file stat: name=%s size=%d dir=%t\n", 100 | stat.Name(), stat.Size(), stat.IsDir()) 101 | } 102 | 103 | fmt.Println("opening file read only") 104 | f, err = fat.OpenFile(filepath, os.O_RDONLY) 105 | if err != nil { 106 | fmt.Println("Could not open file", err) 107 | os.Exit(1) 108 | } 109 | defer f.Close() 110 | 111 | fmt.Println("calling File.Stat()") 112 | info, err := f.Stat() 113 | if err != nil { 114 | fmt.Println("Could not stat file", err) 115 | os.Exit(1) 116 | } else { 117 | fmt.Printf( 118 | "file info: name=%s size=%d dir=%t\n", 119 | info.Name(), info.Size(), info.IsDir()) 120 | } 121 | /* 122 | if size, err := f.Size(); err != nil { 123 | fmt.Printf("Failed getting file size: %v\n", err) 124 | } else { 125 | fmt.Printf("file size: %d\n", size) 126 | } 127 | */ 128 | 129 | buf := make([]byte, 57) 130 | for n := 0; n < 50; n++ { 131 | /* 132 | offset, err := f.Tell() 133 | if err != nil { 134 | fmt.Printf("Could not read offset with Tell: %s\n", err.Error()) 135 | } else { 136 | fmt.Printf("reading from offset: %d\n", offset) 137 | } 138 | */ 139 | n, err := f.Read(buf) 140 | if err != nil { 141 | if err != io.EOF { 142 | fmt.Printf("f.Read() error: %v\n", err.Error()) 143 | } 144 | break 145 | } 146 | fmt.Printf("read %d bytes from file: `%s`\n", n, string(buf[:n])) 147 | } 148 | 149 | offset, err := f.Seek(8, io.SeekStart) 150 | if err != nil { 151 | fmt.Println("Could not Seek(): ", err) 152 | } else { 153 | fmt.Println("new offset:", offset) 154 | n, err := f.Read(buf) 155 | if err != nil { 156 | if err != io.EOF { 157 | fmt.Printf("f.Read() error: %v\n", err.Error()) 158 | } 159 | } 160 | fmt.Printf("read %d bytes from file: `%s`\n", n, string(buf[:n])) 161 | } 162 | 163 | size, err = fat.Free() 164 | if err != nil { 165 | fmt.Println("Could not get filesystem free:", err.Error()) 166 | } else { 167 | fmt.Println("Filesystem free:", size) 168 | } 169 | 170 | dir, err := fat.Open("tmp") 171 | if err != nil { 172 | fmt.Printf("Could not open directory %s: %v\n", path, err) 173 | os.Exit(1) 174 | } 175 | defer dir.Close() 176 | infos, err := dir.Readdir(0) 177 | _ = infos 178 | if err != nil { 179 | fmt.Printf("Could not read directory %s: %v\n", path, err) 180 | os.Exit(1) 181 | } 182 | for _, info := range infos { 183 | fmt.Printf(" directory entry: %s %d %t\n", info.Name(), info.Size(), info.IsDir()) 184 | } 185 | 186 | // exercise fs.FS interfaces: 187 | { 188 | var fsfs fs.FS = tinyfs.NewTinyFS(fat) 189 | f, err := fsfs.Open(filepath) 190 | if err != nil { 191 | fmt.Printf("Could not open %s via io/fs package: %v\n", filepath, err) 192 | os.Exit(1) 193 | } 194 | info, err := f.Stat() 195 | if err != nil { 196 | fmt.Println("Could not stat file", err) 197 | os.Exit(1) 198 | } else { 199 | fmt.Printf( 200 | "fsfs info: name=%s size=%d dir=%t\n", 201 | info.Name(), info.Size(), info.IsDir()) 202 | } 203 | } 204 | 205 | fmt.Println("done") 206 | return 207 | } 208 | -------------------------------------------------------------------------------- /fatfs/ffsystem.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------*/ 2 | /* Sample Code of OS Dependent Functions for FatFs */ 3 | /* (C)ChaN, 2018 */ 4 | /*------------------------------------------------------------------------*/ 5 | 6 | 7 | #include "ff.h" 8 | 9 | 10 | #if FF_USE_LFN == 3 /* Dynamic memory allocation */ 11 | 12 | /*------------------------------------------------------------------------*/ 13 | /* Allocate a memory block */ 14 | /*------------------------------------------------------------------------*/ 15 | 16 | void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */ 17 | UINT msize /* Number of bytes to allocate */ 18 | ) 19 | { 20 | return malloc(msize); /* Allocate a new memory block with POSIX API */ 21 | } 22 | 23 | 24 | /*------------------------------------------------------------------------*/ 25 | /* Free a memory block */ 26 | /*------------------------------------------------------------------------*/ 27 | 28 | void ff_memfree ( 29 | void* mblock /* Pointer to the memory block to free (nothing to do if null) */ 30 | ) 31 | { 32 | free(mblock); /* Free the memory block with POSIX API */ 33 | } 34 | 35 | #endif 36 | 37 | 38 | 39 | #if FF_FS_REENTRANT /* Mutal exclusion */ 40 | 41 | /*------------------------------------------------------------------------*/ 42 | /* Create a Synchronization Object */ 43 | /*------------------------------------------------------------------------*/ 44 | /* This function is called in f_mount() function to create a new 45 | / synchronization object for the volume, such as semaphore and mutex. 46 | / When a 0 is returned, the f_mount() function fails with FR_INT_ERR. 47 | */ 48 | 49 | //const osMutexDef_t Mutex[FF_VOLUMES]; /* Table of CMSIS-RTOS mutex */ 50 | 51 | 52 | int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object */ 53 | BYTE vol, /* Corresponding volume (logical drive number) */ 54 | FF_SYNC_t* sobj /* Pointer to return the created sync object */ 55 | ) 56 | { 57 | /* Win32 */ 58 | *sobj = CreateMutex(NULL, FALSE, NULL); 59 | return (int)(*sobj != INVALID_HANDLE_VALUE); 60 | 61 | /* uITRON */ 62 | // T_CSEM csem = {TA_TPRI,1,1}; 63 | // *sobj = acre_sem(&csem); 64 | // return (int)(*sobj > 0); 65 | 66 | /* uC/OS-II */ 67 | // OS_ERR err; 68 | // *sobj = OSMutexCreate(0, &err); 69 | // return (int)(err == OS_NO_ERR); 70 | 71 | /* FreeRTOS */ 72 | // *sobj = xSemaphoreCreateMutex(); 73 | // return (int)(*sobj != NULL); 74 | 75 | /* CMSIS-RTOS */ 76 | // *sobj = osMutexCreate(&Mutex[vol]); 77 | // return (int)(*sobj != NULL); 78 | } 79 | 80 | 81 | /*------------------------------------------------------------------------*/ 82 | /* Delete a Synchronization Object */ 83 | /*------------------------------------------------------------------------*/ 84 | /* This function is called in f_mount() function to delete a synchronization 85 | / object that created with ff_cre_syncobj() function. When a 0 is returned, 86 | / the f_mount() function fails with FR_INT_ERR. 87 | */ 88 | 89 | int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to an error */ 90 | FF_SYNC_t sobj /* Sync object tied to the logical drive to be deleted */ 91 | ) 92 | { 93 | /* Win32 */ 94 | return (int)CloseHandle(sobj); 95 | 96 | /* uITRON */ 97 | // return (int)(del_sem(sobj) == E_OK); 98 | 99 | /* uC/OS-II */ 100 | // OS_ERR err; 101 | // OSMutexDel(sobj, OS_DEL_ALWAYS, &err); 102 | // return (int)(err == OS_NO_ERR); 103 | 104 | /* FreeRTOS */ 105 | // vSemaphoreDelete(sobj); 106 | // return 1; 107 | 108 | /* CMSIS-RTOS */ 109 | // return (int)(osMutexDelete(sobj) == osOK); 110 | } 111 | 112 | 113 | /*------------------------------------------------------------------------*/ 114 | /* Request Grant to Access the Volume */ 115 | /*------------------------------------------------------------------------*/ 116 | /* This function is called on entering file functions to lock the volume. 117 | / When a 0 is returned, the file function fails with FR_TIMEOUT. 118 | */ 119 | 120 | int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a grant */ 121 | FF_SYNC_t sobj /* Sync object to wait */ 122 | ) 123 | { 124 | /* Win32 */ 125 | return (int)(WaitForSingleObject(sobj, FF_FS_TIMEOUT) == WAIT_OBJECT_0); 126 | 127 | /* uITRON */ 128 | // return (int)(wai_sem(sobj) == E_OK); 129 | 130 | /* uC/OS-II */ 131 | // OS_ERR err; 132 | // OSMutexPend(sobj, FF_FS_TIMEOUT, &err)); 133 | // return (int)(err == OS_NO_ERR); 134 | 135 | /* FreeRTOS */ 136 | // return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE); 137 | 138 | /* CMSIS-RTOS */ 139 | // return (int)(osMutexWait(sobj, FF_FS_TIMEOUT) == osOK); 140 | } 141 | 142 | 143 | /*------------------------------------------------------------------------*/ 144 | /* Release Grant to Access the Volume */ 145 | /*------------------------------------------------------------------------*/ 146 | /* This function is called on leaving file functions to unlock the volume. 147 | */ 148 | 149 | void ff_rel_grant ( 150 | FF_SYNC_t sobj /* Sync object to be signaled */ 151 | ) 152 | { 153 | /* Win32 */ 154 | ReleaseMutex(sobj); 155 | 156 | /* uITRON */ 157 | // sig_sem(sobj); 158 | 159 | /* uC/OS-II */ 160 | // OSMutexPost(sobj); 161 | 162 | /* FreeRTOS */ 163 | // xSemaphoreGive(sobj); 164 | 165 | /* CMSIS-RTOS */ 166 | // osMutexRelease(sobj); 167 | } 168 | 169 | #endif 170 | 171 | -------------------------------------------------------------------------------- /littlefs/lfs_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs utility functions 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #ifndef LFS_UTIL_H 9 | #define LFS_UTIL_H 10 | 11 | // For some reason, the TinyGo compiler needs this to be defined here: 12 | #define LFS_NO_DEBUG 1 13 | #define LFS_NO_ASSERT 1 14 | #define LFS_NO_ERROR 1 15 | #define LFS_NO_WARN 1 16 | 17 | // Users can override lfs_util.h with their own configuration by defining 18 | // LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). 19 | // 20 | // If LFS_CONFIG is used, none of the default utils will be emitted and must be 21 | // provided by the config file. To start, I would suggest copying lfs_util.h 22 | // and modifying as needed. 23 | #ifdef LFS_CONFIG 24 | #define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) 25 | #define LFS_STRINGIZE2(x) #x 26 | #include LFS_STRINGIZE(LFS_CONFIG) 27 | #else 28 | 29 | // System includes 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #ifndef LFS_NO_MALLOC 36 | #include 37 | #endif 38 | #ifndef LFS_NO_ASSERT 39 | #include 40 | #endif 41 | #if !defined(LFS_NO_DEBUG) || \ 42 | !defined(LFS_NO_WARN) || \ 43 | !defined(LFS_NO_ERROR) || \ 44 | defined(LFS_YES_TRACE) 45 | #include 46 | #endif 47 | 48 | #ifdef __cplusplus 49 | extern "C" 50 | { 51 | #endif 52 | 53 | 54 | // Macros, may be replaced by system specific wrappers. Arguments to these 55 | // macros must not have side-effects as the macros can be removed for a smaller 56 | // code footprint 57 | 58 | // Logging functions 59 | #ifndef LFS_TRACE 60 | #ifdef LFS_YES_TRACE 61 | #define LFS_TRACE_(fmt, ...) \ 62 | printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 63 | #define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") 64 | #else 65 | #define LFS_TRACE(...) 66 | #endif 67 | #endif 68 | 69 | #ifndef LFS_DEBUG 70 | #ifndef LFS_NO_DEBUG 71 | #define LFS_DEBUG_(fmt, ...) \ 72 | printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 73 | #define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") 74 | #else 75 | #define LFS_DEBUG(...) 76 | #endif 77 | #endif 78 | 79 | #ifndef LFS_WARN 80 | #ifndef LFS_NO_WARN 81 | #define LFS_WARN_(fmt, ...) \ 82 | printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 83 | #define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") 84 | #else 85 | #define LFS_WARN(...) 86 | #endif 87 | #endif 88 | 89 | #ifndef LFS_ERROR 90 | #ifndef LFS_NO_ERROR 91 | #define LFS_ERROR_(fmt, ...) \ 92 | printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 93 | #define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") 94 | #else 95 | #define LFS_ERROR(...) 96 | #endif 97 | #endif 98 | 99 | // Runtime assertions 100 | #ifndef LFS_ASSERT 101 | #ifndef LFS_NO_ASSERT 102 | #define LFS_ASSERT(test) assert(test) 103 | #else 104 | #define LFS_ASSERT(test) 105 | #endif 106 | #endif 107 | 108 | 109 | // Builtin functions, these may be replaced by more efficient 110 | // toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more 111 | // expensive basic C implementation for debugging purposes 112 | 113 | // Min/max functions for unsigned 32-bit numbers 114 | static inline uint32_t lfs_max(uint32_t a, uint32_t b) { 115 | return (a > b) ? a : b; 116 | } 117 | 118 | static inline uint32_t lfs_min(uint32_t a, uint32_t b) { 119 | return (a < b) ? a : b; 120 | } 121 | 122 | // Align to nearest multiple of a size 123 | static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { 124 | return a - (a % alignment); 125 | } 126 | 127 | static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { 128 | return lfs_aligndown(a + alignment-1, alignment); 129 | } 130 | 131 | // Find the smallest power of 2 greater than or equal to a 132 | static inline uint32_t lfs_npw2(uint32_t a) { 133 | #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) 134 | return 32 - __builtin_clz(a-1); 135 | #else 136 | uint32_t r = 0; 137 | uint32_t s; 138 | a -= 1; 139 | s = (a > 0xffff) << 4; a >>= s; r |= s; 140 | s = (a > 0xff ) << 3; a >>= s; r |= s; 141 | s = (a > 0xf ) << 2; a >>= s; r |= s; 142 | s = (a > 0x3 ) << 1; a >>= s; r |= s; 143 | return (r | (a >> 1)) + 1; 144 | #endif 145 | } 146 | 147 | // Count the number of trailing binary zeros in a 148 | // lfs_ctz(0) may be undefined 149 | static inline uint32_t lfs_ctz(uint32_t a) { 150 | #if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) 151 | return __builtin_ctz(a); 152 | #else 153 | return lfs_npw2((a & -a) + 1) - 1; 154 | #endif 155 | } 156 | 157 | // Count the number of binary ones in a 158 | static inline uint32_t lfs_popc(uint32_t a) { 159 | #if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) 160 | return __builtin_popcount(a); 161 | #else 162 | a = a - ((a >> 1) & 0x55555555); 163 | a = (a & 0x33333333) + ((a >> 2) & 0x33333333); 164 | return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; 165 | #endif 166 | } 167 | 168 | // Find the sequence comparison of a and b, this is the distance 169 | // between a and b ignoring overflow 170 | static inline int lfs_scmp(uint32_t a, uint32_t b) { 171 | return (int)(unsigned)(a - b); 172 | } 173 | 174 | // Convert between 32-bit little-endian and native order 175 | static inline uint32_t lfs_fromle32(uint32_t a) { 176 | #if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 177 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 178 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) 179 | return a; 180 | #elif !defined(LFS_NO_INTRINSICS) && ( \ 181 | (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 182 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 183 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) 184 | return __builtin_bswap32(a); 185 | #else 186 | return (((uint8_t*)&a)[0] << 0) | 187 | (((uint8_t*)&a)[1] << 8) | 188 | (((uint8_t*)&a)[2] << 16) | 189 | (((uint8_t*)&a)[3] << 24); 190 | #endif 191 | } 192 | 193 | static inline uint32_t lfs_tole32(uint32_t a) { 194 | return lfs_fromle32(a); 195 | } 196 | 197 | // Convert between 32-bit big-endian and native order 198 | static inline uint32_t lfs_frombe32(uint32_t a) { 199 | #if !defined(LFS_NO_INTRINSICS) && ( \ 200 | (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ 201 | (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ 202 | (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) 203 | return __builtin_bswap32(a); 204 | #elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ 205 | (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ 206 | (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) 207 | return a; 208 | #else 209 | return (((uint8_t*)&a)[0] << 24) | 210 | (((uint8_t*)&a)[1] << 16) | 211 | (((uint8_t*)&a)[2] << 8) | 212 | (((uint8_t*)&a)[3] << 0); 213 | #endif 214 | } 215 | 216 | static inline uint32_t lfs_tobe32(uint32_t a) { 217 | return lfs_frombe32(a); 218 | } 219 | 220 | // Calculate CRC-32 with polynomial = 0x04c11db7 221 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); 222 | 223 | // Allocate memory, only used if buffers are not provided to littlefs 224 | // Note, memory must be 64-bit aligned 225 | static inline void *lfs_malloc(size_t size) { 226 | #ifndef LFS_NO_MALLOC 227 | return malloc(size); 228 | #else 229 | (void)size; 230 | return NULL; 231 | #endif 232 | } 233 | 234 | // Deallocate memory, only used if buffers are not provided to littlefs 235 | static inline void lfs_free(void *p) { 236 | #ifndef LFS_NO_MALLOC 237 | free(p); 238 | #else 239 | (void)p; 240 | #endif 241 | } 242 | 243 | 244 | #ifdef __cplusplus 245 | } /* extern "C" */ 246 | #endif 247 | 248 | #endif 249 | #endif -------------------------------------------------------------------------------- /littlefs/go_lfs_test.go: -------------------------------------------------------------------------------- 1 | //go:build !tinygo 2 | 3 | package littlefs 4 | 5 | import ( 6 | "io" 7 | "math/rand" 8 | "os" 9 | "testing" 10 | 11 | "tinygo.org/x/tinyfs" 12 | ) 13 | 14 | const ( 15 | testPageSize = 64 16 | testBlockSize = 256 17 | testBlockCount = 2048 18 | ) 19 | 20 | var defaultConfig = &Config{ 21 | // ReadSize: 16, 22 | // ProgSize: 16, 23 | // BlockSize: 512, 24 | // BlockCount: 1024, 25 | CacheSize: 128, 26 | LookaheadSize: 128, 27 | BlockCycles: 500, 28 | } 29 | 30 | var zeroBlock = make([]byte, testBlockSize) 31 | 32 | func TestFormat(t *testing.T) { 33 | dev := tinyfs.NewMemoryDevice(testPageSize, testBlockSize, testBlockCount) 34 | fs := New(dev) 35 | fs.Configure(defaultConfig) 36 | 37 | t.Run("BasicFormatting", func(t *testing.T) { 38 | if err := fs.Format(); err != nil { 39 | t.Fatal(err) 40 | } 41 | }) 42 | 43 | t.Run("BasicMounting", func(t *testing.T) { 44 | if err := fs.Mount(); err != nil { 45 | t.Fatal(err) 46 | } 47 | }) 48 | 49 | t.Run("InvalidSuperblocks", func(t *testing.T) { 50 | if err := fs.Unmount(); err != nil { 51 | t.Fatal(err) 52 | } 53 | if _, err := dev.WriteAt(zeroBlock, 0); err != nil { 54 | t.Fatal(err) 55 | } 56 | if _, err := dev.WriteAt(zeroBlock, testBlockSize); err != nil { 57 | t.Fatal(err) 58 | } 59 | if err := fs.Format(); err == nil { 60 | t.Log("expected error when formatting") 61 | if err := fs.Mount(); err == nil { 62 | t.Log("expected error when mounting") 63 | //t.Fail() 64 | } 65 | } else if err != errNoSpace { 66 | t.Logf("expected error to be ErrNoSpace; was %s", err) 67 | t.Fail() 68 | } 69 | }) 70 | 71 | t.Run("InvalidMount", func(t *testing.T) { 72 | dev := tinyfs.NewMemoryDevice(testPageSize, testBlockSize, testBlockCount) 73 | fs := New(dev) 74 | fs.Configure(defaultConfig) 75 | if err := fs.Mount(); err == nil { 76 | t.Log("expected error when mounting") 77 | t.Fail() 78 | } else if err != errCorrupt { 79 | t.Logf("expected error to be ErrCorrupt; was %s", err) 80 | t.Fail() 81 | } 82 | }) 83 | 84 | t.Run("ExpandingSuperblock", func(t *testing.T) { 85 | if err := fs.Format(); err != nil { 86 | t.Fatal(err) 87 | } 88 | if err := fs.Mount(); err != nil { 89 | t.Fatal(err) 90 | } 91 | for i := 0; i < 100; i++ { 92 | if err := fs.Mkdir("dummy", 0777); err != nil { 93 | t.Fatalf("failing mkdir on iteration %d: %s", i, err) 94 | } 95 | if err := fs.Remove("dummy"); err != nil { 96 | t.Fatalf("failing remove on iteration %d: %s", i, err) 97 | } 98 | } 99 | if err := fs.Unmount(); err != nil { 100 | t.Fatal(err) 101 | } 102 | if err := fs.Mount(); err != nil { 103 | t.Fatalf("failed to re-mount: %s", err) 104 | } 105 | if err := fs.Mkdir("dummy", 0777); err != nil { 106 | t.Fatalf("fail to mkdir: %s", err) 107 | } 108 | }) 109 | } 110 | 111 | func TestFiles(t *testing.T) { 112 | fs, _, unmount := createTestFS(t, defaultConfig) 113 | defer unmount() 114 | 115 | const ( 116 | smallSize = 32 117 | mediumSize = 8192 118 | largeSize = 262144 119 | ) 120 | 121 | t.Run("SimpleFileTest", func(t *testing.T) { 122 | f, err := fs.OpenFile("hello", os.O_WRONLY|os.O_CREATE) 123 | if err != nil { 124 | t.Error(err) 125 | } 126 | s := "Hello World!" 127 | if _, err := f.Write([]byte(s)); err != nil { 128 | t.Error(err) 129 | } 130 | if err := f.Close(); err != nil { 131 | t.Error(err) 132 | } 133 | f, err = fs.Open("hello") 134 | if err != nil { 135 | t.Error(err) 136 | } 137 | buf := make([]byte, 24) 138 | n, err := f.Read(buf) 139 | if err != nil { 140 | t.Error(err) 141 | } 142 | s2 := string(buf[:n]) 143 | if n != len(s) { 144 | t.Fail() 145 | } 146 | if s != s2 { 147 | t.Fail() 148 | } 149 | if err := f.Close(); err != nil { 150 | t.Error(err) 151 | } 152 | }) 153 | 154 | t.Run("SmallFile", func(t *testing.T) { 155 | writeFileTest(t, fs, smallSize, "smallavocado") 156 | readFileTest(t, fs, smallSize, "smallavocado") 157 | }) 158 | 159 | t.Run("MediumFile", func(t *testing.T) { 160 | writeFileTest(t, fs, mediumSize, "mediumavocado") 161 | readFileTest(t, fs, mediumSize, "mediumavocado") 162 | }) 163 | 164 | t.Run("LargeFile", func(t *testing.T) { 165 | writeFileTest(t, fs, largeSize, "largeavocado") 166 | readFileTest(t, fs, largeSize, "largeavocado") 167 | }) 168 | 169 | t.Run("ZeroFile", func(t *testing.T) { 170 | writeFileTest(t, fs, 0, "noavocado") 171 | readFileTest(t, fs, 0, "noavocado") 172 | }) 173 | } 174 | 175 | func TestDirectories(t *testing.T) { 176 | 177 | fs, _, unmount := createTestFS(t, defaultConfig) 178 | defer unmount() 179 | const ( 180 | largeSize = 128 181 | ) 182 | 183 | t.Run("RootDirectory", func(t *testing.T) { 184 | f, err := fs.Open("/") 185 | check(t, err) 186 | check(t, f.Close()) 187 | }) 188 | 189 | t.Run("DirectoryCreation", func(t *testing.T) { 190 | check(t, fs.Mkdir("potato", 0777)) 191 | }) 192 | 193 | t.Run("FileCreation", func(t *testing.T) { 194 | f, err := fs.OpenFile("burrito", os.O_CREATE|os.O_WRONLY) 195 | check(t, err) 196 | check(t, f.Close()) 197 | }) 198 | /* 199 | t.Run("DirectoryIteration", func(t *testing.T) { 200 | dir, err := fs.Open("/") 201 | check(t, err) 202 | defer check(t, dir.Close()) 203 | infos, err := dir.Readdir(0) 204 | if err != nil { 205 | 206 | } 207 | }) 208 | */ 209 | t.Run("DirectoryFailures", func(t *testing.T) { 210 | 211 | }) 212 | 213 | t.Run("NestedDirectories", func(t *testing.T) { 214 | 215 | }) 216 | 217 | t.Run("MultiBlockDirectory", func(t *testing.T) { 218 | 219 | }) 220 | 221 | t.Run("DirectoryRemove", func(t *testing.T) { 222 | 223 | }) 224 | 225 | t.Run("DirectoryRename", func(t *testing.T) { 226 | 227 | }) 228 | 229 | t.Run("RecursiveRemove", func(t *testing.T) { 230 | 231 | }) 232 | 233 | t.Run("MultiBlockRename", func(t *testing.T) { 234 | 235 | }) 236 | 237 | t.Run("MultiBlockRemove", func(t *testing.T) { 238 | 239 | }) 240 | 241 | t.Run("MultiBlockDirectoryWithFiles", func(t *testing.T) { 242 | 243 | }) 244 | 245 | t.Run("MultiBlockRenameWithFiles", func(t *testing.T) { 246 | 247 | }) 248 | 249 | t.Run("MultiBlockRemoveWithFiles", func(t *testing.T) { 250 | 251 | }) 252 | } 253 | 254 | func createTestFS(t *testing.T, config *Config) (*LFS, tinyfs.BlockDevice, func()) { 255 | // create/format/mount the filesystem 256 | bd := tinyfs.NewMemoryDevice(testPageSize, testBlockSize, testBlockCount) 257 | fs := New(bd).Configure(config) 258 | if err := fs.Format(); err != nil { 259 | t.Error("Could not format", err) 260 | } 261 | if err := fs.Mount(); err != nil { 262 | t.Error("Could not mount", err) 263 | } 264 | return fs, bd, func() { 265 | if err := fs.Unmount(); err != nil { 266 | t.Error("Could not ummount", err) 267 | } 268 | } 269 | } 270 | 271 | func writeFileTest(t *testing.T, lfs *LFS, size int, name string) { 272 | buf := make([]byte, 32) 273 | f, err := lfs.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC) 274 | if err != nil { 275 | t.Fatal(err) 276 | } 277 | defer f.Close() 278 | var totalBytes int 279 | for i := 0; i < size; i += len(buf) { 280 | b := buf[:] 281 | if len(b) > size-i { 282 | b = b[:size-i] 283 | } 284 | if _, err = rand.Read(b); err != nil { 285 | t.Fatal(err) 286 | } 287 | if n, err := f.Write(b); err != nil { 288 | t.Fatal(err) 289 | } else { 290 | totalBytes += n 291 | } 292 | } 293 | if totalBytes != size { 294 | t.Fatalf("expected to read %d bytes, was actually %d", size, totalBytes) 295 | } 296 | } 297 | 298 | func readFileTest(t *testing.T, lfs *LFS, size int, name string) { 299 | info, err := lfs.Stat(name) 300 | if err != nil { 301 | t.Fatal(err) 302 | } 303 | if info.Size() != int64(size) { 304 | t.Fatalf("expected size %d but was actually %d", size, info.Size()) 305 | } 306 | if info.IsDir() { 307 | t.Fatalf("expected file, but was a directory") 308 | } 309 | buf := make([]byte, 32) 310 | f, err := lfs.OpenFile(name, os.O_RDONLY) 311 | if err != nil { 312 | t.Fatal(err) 313 | } 314 | defer f.Close() 315 | var totalRead int 316 | for i := 0; i < size; i += len(buf) { 317 | b := buf[:] 318 | if len(b) > size-i { 319 | b = b[:size-i] 320 | } 321 | if n, err := f.Read(b); err != nil { 322 | t.Fatalf("error reading file after %d bytes: %v", n, err) 323 | } else { 324 | totalRead += n 325 | } 326 | } 327 | if totalRead != size { 328 | t.Fatalf("expected to read %d bytes, was actually %d", size, totalRead) 329 | } 330 | if _, err := f.Read(buf); err != io.EOF { 331 | t.Fatalf("expected io.EOF after %d bytes, was actually: %v", totalRead, err) 332 | } 333 | } 334 | 335 | func check(t *testing.T, err error) { 336 | if err != nil { 337 | t.Fatal(err) 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /littlefs/docs/README.md: -------------------------------------------------------------------------------- 1 | ## littlefs 2 | 3 | A little fail-safe filesystem designed for microcontrollers. 4 | 5 | ``` 6 | | | | .---._____ 7 | .-----. | | 8 | --|o |---| littlefs | 9 | --| |---| | 10 | '-----' '----------' 11 | | | | 12 | ``` 13 | 14 | **Power-loss resilience** - littlefs is designed to handle random power 15 | failures. All file operations have strong copy-on-write guarantees and if 16 | power is lost the filesystem will fall back to the last known good state. 17 | 18 | **Dynamic wear leveling** - littlefs is designed with flash in mind, and 19 | provides wear leveling over dynamic blocks. Additionally, littlefs can 20 | detect bad blocks and work around them. 21 | 22 | **Bounded RAM/ROM** - littlefs is designed to work with a small amount of 23 | memory. RAM usage is strictly bounded, which means RAM consumption does not 24 | change as the filesystem grows. The filesystem contains no unbounded 25 | recursion and dynamic memory is limited to configurable buffers that can be 26 | provided statically. 27 | 28 | ## Example 29 | 30 | Here's a simple example that updates a file named `boot_count` every time 31 | main runs. The program can be interrupted at any time without losing track 32 | of how many times it has been booted and without corrupting the filesystem: 33 | 34 | ``` c 35 | #include "lfs.h" 36 | 37 | // variables used by the filesystem 38 | lfs_t lfs; 39 | lfs_file_t file; 40 | 41 | // configuration of the filesystem is provided by this struct 42 | const struct lfs_config cfg = { 43 | // block device operations 44 | .read = user_provided_block_device_read, 45 | .prog = user_provided_block_device_prog, 46 | .erase = user_provided_block_device_erase, 47 | .sync = user_provided_block_device_sync, 48 | 49 | // block device configuration 50 | .read_size = 16, 51 | .prog_size = 16, 52 | .block_size = 4096, 53 | .block_count = 128, 54 | .cache_size = 16, 55 | .lookahead_size = 16, 56 | .block_cycles = 500, 57 | }; 58 | 59 | // entry point 60 | int main(void) { 61 | // mount the filesystem 62 | int err = lfs_mount(&lfs, &cfg); 63 | 64 | // reformat if we can't mount the filesystem 65 | // this should only happen on the first boot 66 | if (err) { 67 | lfs_format(&lfs, &cfg); 68 | lfs_mount(&lfs, &cfg); 69 | } 70 | 71 | // read current count 72 | uint32_t boot_count = 0; 73 | lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); 74 | lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count)); 75 | 76 | // update boot count 77 | boot_count += 1; 78 | lfs_file_rewind(&lfs, &file); 79 | lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count)); 80 | 81 | // remember the storage is not updated until the file is closed successfully 82 | lfs_file_close(&lfs, &file); 83 | 84 | // release any resources we were using 85 | lfs_unmount(&lfs); 86 | 87 | // print the boot count 88 | printf("boot_count: %d\n", boot_count); 89 | } 90 | ``` 91 | 92 | ## Usage 93 | 94 | Detailed documentation (or at least as much detail as is currently available) 95 | can be found in the comments in [lfs.h](lfs.h). 96 | 97 | littlefs takes in a configuration structure that defines how the filesystem 98 | operates. The configuration struct provides the filesystem with the block 99 | device operations and dimensions, tweakable parameters that tradeoff memory 100 | usage for performance, and optional static buffers if the user wants to avoid 101 | dynamic memory. 102 | 103 | The state of the littlefs is stored in the `lfs_t` type which is left up 104 | to the user to allocate, allowing multiple filesystems to be in use 105 | simultaneously. With the `lfs_t` and configuration struct, a user can 106 | format a block device or mount the filesystem. 107 | 108 | Once mounted, the littlefs provides a full set of POSIX-like file and 109 | directory functions, with the deviation that the allocation of filesystem 110 | structures must be provided by the user. 111 | 112 | All POSIX operations, such as remove and rename, are atomic, even in event 113 | of power-loss. Additionally, file updates are not actually committed to 114 | the filesystem until sync or close is called on the file. 115 | 116 | ## Other notes 117 | 118 | All littlefs calls have the potential to return a negative error code. The 119 | errors can be either one of those found in the `enum lfs_error` in 120 | [lfs.h](lfs.h), or an error returned by the user's block device operations. 121 | 122 | In the configuration struct, the `prog` and `erase` function provided by the 123 | user may return a `LFS_ERR_CORRUPT` error if the implementation already can 124 | detect corrupt blocks. However, the wear leveling does not depend on the return 125 | code of these functions, instead all data is read back and checked for 126 | integrity. 127 | 128 | If your storage caches writes, make sure that the provided `sync` function 129 | flushes all the data to memory and ensures that the next read fetches the data 130 | from memory, otherwise data integrity can not be guaranteed. If the `write` 131 | function does not perform caching, and therefore each `read` or `write` call 132 | hits the memory, the `sync` function can simply return 0. 133 | 134 | ## Design 135 | 136 | At a high level, littlefs is a block based filesystem that uses small logs to 137 | store metadata and larger copy-on-write (COW) structures to store file data. 138 | 139 | In littlefs, these ingredients form a sort of two-layered cake, with the small 140 | logs (called metadata pairs) providing fast updates to metadata anywhere on 141 | storage, while the COW structures store file data compactly and without any 142 | wear amplification cost. 143 | 144 | Both of these data structures are built out of blocks, which are fed by a 145 | common block allocator. By limiting the number of erases allowed on a block 146 | per allocation, the allocator provides dynamic wear leveling over the entire 147 | filesystem. 148 | 149 | ``` 150 | root 151 | .--------.--------. 152 | | A'| B'| | 153 | | | |-> | 154 | | | | | 155 | '--------'--------' 156 | .----' '--------------. 157 | A v B v 158 | .--------.--------. .--------.--------. 159 | | C'| D'| | | E'|new| | 160 | | | |-> | | | E'|-> | 161 | | | | | | | | | 162 | '--------'--------' '--------'--------' 163 | .-' '--. | '------------------. 164 | v v .-' v 165 | .--------. .--------. v .--------. 166 | | C | | D | .--------. write | new E | 167 | | | | | | E | ==> | | 168 | | | | | | | | | 169 | '--------' '--------' | | '--------' 170 | '--------' .-' | 171 | .-' '-. .-------------|------' 172 | v v v v 173 | .--------. .--------. .--------. 174 | | F | | G | | new F | 175 | | | | | | | 176 | | | | | | | 177 | '--------' '--------' '--------' 178 | ``` 179 | 180 | More details on how littlefs works can be found in [DESIGN.md](DESIGN.md) and 181 | [SPEC.md](SPEC.md). 182 | 183 | - [DESIGN.md](DESIGN.md) - A fully detailed dive into how littlefs works. 184 | I would suggest reading it as the tradeoffs at work are quite interesting. 185 | 186 | - [SPEC.md](SPEC.md) - The on-disk specification of littlefs with all the 187 | nitty-gritty details. May be useful for tooling development. 188 | 189 | ## Testing 190 | 191 | The littlefs comes with a test suite designed to run on a PC using the 192 | [emulated block device](emubd/lfs_emubd.h) found in the emubd directory. 193 | The tests assume a Linux environment and can be started with make: 194 | 195 | ``` bash 196 | make test 197 | ``` 198 | 199 | ## License 200 | 201 | The littlefs is provided under the [BSD-3-Clause] license. See 202 | [LICENSE.md](LICENSE.md) for more information. Contributions to this project 203 | are accepted under the same license. 204 | 205 | Individual files contain the following tag instead of the full license text. 206 | 207 | SPDX-License-Identifier: BSD-3-Clause 208 | 209 | This enables machine processing of license information based on the SPDX 210 | License Identifiers that are here available: http://spdx.org/licenses/ 211 | 212 | ## Related projects 213 | 214 | - [littlefs-fuse] - A [FUSE] wrapper for littlefs. The project allows you to 215 | mount littlefs directly on a Linux machine. Can be useful for debugging 216 | littlefs if you have an SD card handy. 217 | 218 | - [littlefs-js] - A javascript wrapper for littlefs. I'm not sure why you would 219 | want this, but it is handy for demos. You can see it in action 220 | [here][littlefs-js-demo]. 221 | 222 | - [mklfs] - A command line tool built by the [Lua RTOS] guys for making 223 | littlefs images from a host PC. Supports Windows, Mac OS, and Linux. 224 | 225 | - [Mbed OS] - The easiest way to get started with littlefs is to jump into Mbed 226 | which already has block device drivers for most forms of embedded storage. 227 | littlefs is available in Mbed OS as the [LittleFileSystem] class. 228 | 229 | - [SPIFFS] - Another excellent embedded filesystem for NOR flash. As a more 230 | traditional logging filesystem with full static wear-leveling, SPIFFS will 231 | likely outperform littlefs on small memories such as the internal flash on 232 | microcontrollers. 233 | 234 | - [Dhara] - An interesting NAND flash translation layer designed for small 235 | MCUs. It offers static wear-leveling and power-resilience with only a fixed 236 | _O(|address|)_ pointer structure stored on each block and in RAM. 237 | 238 | 239 | [BSD-3-Clause]: https://spdx.org/licenses/BSD-3-Clause.html 240 | [littlefs-fuse]: https://github.com/geky/littlefs-fuse 241 | [FUSE]: https://github.com/libfuse/libfuse 242 | [littlefs-js]: https://github.com/geky/littlefs-js 243 | [littlefs-js-demo]:http://littlefs.geky.net/demo.html 244 | [mklfs]: https://github.com/whitecatboard/Lua-RTOS-ESP32/tree/master/components/mklfs/src 245 | [Lua RTOS]: https://github.com/whitecatboard/Lua-RTOS-ESP32 246 | [Mbed OS]: https://github.com/armmbed/mbed-os 247 | [LittleFileSystem]: https://os.mbed.com/docs/mbed-os/v5.12/apis/littlefilesystem.html 248 | [SPIFFS]: https://github.com/pellepl/spiffs 249 | [Dhara]: https://github.com/dlbeer/dhara 250 | -------------------------------------------------------------------------------- /littlefs/go_lfs.go: -------------------------------------------------------------------------------- 1 | package littlefs 2 | 3 | // #include 4 | // #include 5 | // #include "./go_lfs.h" 6 | import "C" 7 | 8 | import ( 9 | "errors" 10 | "io" 11 | "os" 12 | "time" 13 | "unsafe" 14 | 15 | "tinygo.org/x/tinyfs" 16 | "tinygo.org/x/tinyfs/internal/gopointer" 17 | "tinygo.org/x/tinyfs/internal/util" 18 | ) 19 | 20 | const ( 21 | errOK = C.LFS_ERR_OK // No error 22 | errIO Error = C.LFS_ERR_IO // Error during device operation 23 | errCorrupt Error = C.LFS_ERR_CORRUPT // Corrupted 24 | errNoEntry Error = C.LFS_ERR_NOENT // No directory entry 25 | errEntryExists Error = C.LFS_ERR_EXIST // Entry already exists 26 | errNotDir Error = C.LFS_ERR_NOTDIR // Entry is not a dir 27 | errIsDir Error = C.LFS_ERR_ISDIR // Entry is a dir 28 | errDirNotEmpty Error = C.LFS_ERR_NOTEMPTY // Dir is not empty 29 | errBadFileNum Error = C.LFS_ERR_BADF // Bad file number 30 | errFileTooLarge Error = C.LFS_ERR_FBIG // File too large 31 | errInvalidParam Error = C.LFS_ERR_INVAL // Invalid parameter 32 | errNoSpace Error = C.LFS_ERR_NOSPC // No space left on device 33 | errNoMemory Error = C.LFS_ERR_NOMEM // No more memory available 34 | errNoAttr Error = C.LFS_ERR_NOATTR // No data/attr available 35 | errNameTooLong Error = C.LFS_ERR_NAMETOOLONG // File name too long 36 | 37 | fileTypeReg fileType = C.LFS_TYPE_REG 38 | fileTypeDir fileType = C.LFS_TYPE_DIR 39 | ) 40 | 41 | func translateFlags(osFlags int) C.int { 42 | var result C.int 43 | if osFlags&os.O_RDONLY > 0 { 44 | result |= C.LFS_O_RDONLY 45 | } 46 | if osFlags&os.O_WRONLY > 0 { 47 | result |= C.LFS_O_WRONLY 48 | } 49 | if osFlags&os.O_RDWR > 0 { 50 | result |= C.LFS_O_RDWR 51 | } 52 | if osFlags&os.O_CREATE > 0 { 53 | result |= C.LFS_O_CREAT 54 | } 55 | if osFlags&os.O_EXCL > 0 { 56 | result |= C.LFS_O_EXCL 57 | } 58 | if osFlags&os.O_TRUNC > 0 { 59 | result |= C.LFS_O_TRUNC 60 | } 61 | if osFlags&os.O_APPEND > 0 { 62 | result |= C.LFS_O_APPEND 63 | } 64 | return result 65 | } 66 | 67 | type fileType uint 68 | 69 | type Error int 70 | 71 | func (err Error) Error() string { 72 | switch err { 73 | case errIO: 74 | return "littlefs: Error during device operation" 75 | case errCorrupt: 76 | return "littlefs: Corrupted" 77 | case errNoEntry: 78 | return "littlefs: No directory entry" 79 | case errEntryExists: 80 | return "littlefs: Entry already exists" 81 | case errNotDir: 82 | return "littlefs: Entry is not a dir" 83 | case errIsDir: 84 | return "littlefs: Entry is a dir" 85 | case errDirNotEmpty: 86 | return "littlefs: Dir is not empty" 87 | case errBadFileNum: 88 | return "littlefs: Bad file number" 89 | case errFileTooLarge: 90 | return "littlefs: File too large" 91 | case errInvalidParam: 92 | return "littlefs: Invalid parameter" 93 | case errNoSpace: 94 | return "littlefs: No space left on device" 95 | case errNoMemory: 96 | return "littlefs: No more memory available" 97 | case errNoAttr: 98 | return "littlefs: No data/attr available" 99 | case errNameTooLong: 100 | return "littlefs: File name too long" 101 | default: 102 | return "littlefs: Unknown error" 103 | } 104 | } 105 | 106 | type Config struct { 107 | //ReadSize uint32 108 | //ProgSize uint32 109 | //BlockSize uint32 110 | //BlockCount uint32 111 | CacheSize uint32 112 | LookaheadSize uint32 113 | BlockCycles int32 114 | } 115 | 116 | type Info struct { 117 | ftyp fileType 118 | size uint32 119 | name string 120 | } 121 | 122 | func (info Info) Name() string { 123 | return info.name 124 | } 125 | 126 | func (info Info) Size() int64 { 127 | return int64(info.size) 128 | } 129 | 130 | func (info Info) IsDir() bool { 131 | return info.ftyp == fileTypeDir 132 | } 133 | 134 | func (info Info) Sys() interface{} { 135 | return nil 136 | } 137 | 138 | func (info Info) Mode() os.FileMode { 139 | v := os.FileMode(0777) 140 | if info.IsDir() { 141 | v |= os.ModeDir 142 | } 143 | return v 144 | } 145 | 146 | func (info Info) ModTime() time.Time { 147 | return time.Time{} 148 | } 149 | 150 | type LFS struct { 151 | dev tinyfs.BlockDevice 152 | ptr unsafe.Pointer 153 | lfs *C.struct_lfs 154 | cfg *C.struct_lfs_config 155 | } 156 | 157 | func New(blockdev tinyfs.BlockDevice) *LFS { 158 | return &LFS{ 159 | dev: blockdev, 160 | } 161 | } 162 | 163 | func (l *LFS) Configure(config *Config) *LFS { 164 | l.lfs = C.go_lfs_new_lfs() 165 | l.cfg = C.go_lfs_new_lfs_config() 166 | *l.cfg = C.struct_lfs_config{ 167 | context: gopointer.Save(l), 168 | read_size: C.lfs_size_t(l.dev.WriteBlockSize()), 169 | prog_size: C.lfs_size_t(l.dev.WriteBlockSize()), 170 | block_size: C.lfs_size_t(l.dev.EraseBlockSize()), 171 | block_count: C.lfs_size_t(l.dev.Size() / l.dev.EraseBlockSize()), 172 | cache_size: C.lfs_size_t(config.CacheSize), 173 | lookahead_size: C.lfs_size_t(config.LookaheadSize), 174 | block_cycles: C.int32_t(config.BlockCycles), 175 | } 176 | C.go_lfs_set_callbacks(l.cfg) 177 | return l 178 | } 179 | 180 | func (l *LFS) blockSize() uint32 { 181 | return uint32(l.cfg.block_size) 182 | } 183 | 184 | func (l *LFS) Mount() error { 185 | return errval(C.lfs_mount(l.lfs, l.cfg)) 186 | } 187 | 188 | func (l *LFS) Format() error { 189 | return errval(C.lfs_format(l.lfs, l.cfg)) 190 | } 191 | 192 | func (l *LFS) Unmount() error { 193 | return errval(C.lfs_unmount(l.lfs)) 194 | } 195 | 196 | func (l *LFS) Remove(path string) error { 197 | cs := cstring(path) 198 | defer C.free(unsafe.Pointer(cs)) 199 | return errval(C.lfs_remove(l.lfs, cs)) 200 | } 201 | 202 | func (l *LFS) Rename(oldPath string, newPath string) error { 203 | cs1, cs2 := cstring(oldPath), cstring(newPath) 204 | defer C.free(unsafe.Pointer(cs1)) 205 | defer C.free(unsafe.Pointer(cs2)) 206 | return errval(C.lfs_rename(l.lfs, cs1, cs2)) 207 | } 208 | 209 | func (l *LFS) Stat(path string) (os.FileInfo, error) { 210 | cs := cstring(path) 211 | defer C.free(unsafe.Pointer(cs)) 212 | info := C.struct_lfs_info{} 213 | if err := errval(C.lfs_stat(l.lfs, cs, &info)); err != nil { 214 | return nil, err 215 | } 216 | return &Info{ 217 | ftyp: fileType(info._type), 218 | size: uint32(info.size), 219 | name: gostring(&info.name[0]), 220 | }, nil 221 | } 222 | 223 | func (l *LFS) Mkdir(path string, _ os.FileMode) error { 224 | cs := (*C.char)(cstring(path)) 225 | defer C.free(unsafe.Pointer(cs)) 226 | return errval(C.lfs_mkdir(l.lfs, cs)) 227 | } 228 | 229 | func (l *LFS) Open(path string) (tinyfs.File, error) { 230 | return l.OpenFile(path, os.O_RDONLY) 231 | } 232 | 233 | func (l *LFS) OpenFile(path string, flags int) (tinyfs.File, error) { 234 | 235 | cs := (*C.char)(cstring(path)) 236 | defer C.free(unsafe.Pointer(cs)) 237 | file := &File{lfs: l, info: Info{name: path}} 238 | 239 | info := C.struct_lfs_info{} 240 | if err := errval(C.lfs_stat(l.lfs, cs, &info)); err == nil { 241 | file.info.ftyp = fileType(info._type) 242 | file.info.size = uint32(info.size) 243 | } 244 | 245 | var errno C.int 246 | if file.info.ftyp == fileTypeDir { 247 | file.typ = fileTypeDir 248 | file.hndl = unsafe.Pointer(C.go_lfs_new_lfs_dir()) 249 | errno = C.lfs_dir_open(l.lfs, file.dirptr(), cs) 250 | } else { 251 | file.typ = fileTypeReg 252 | file.hndl = unsafe.Pointer(C.go_lfs_new_lfs_file()) 253 | errno = C.lfs_file_open(l.lfs, file.fileptr(), cs, C.int(translateFlags(flags))) 254 | } 255 | 256 | if err := errval(errno); err != nil { 257 | if file.hndl != nil { 258 | C.free(file.hndl) 259 | file.hndl = nil 260 | } 261 | return nil, err 262 | } 263 | 264 | return file, nil 265 | } 266 | 267 | // Size finds the current size of the filesystem 268 | // 269 | // Note: Result is best effort. If files share COW structures, the returned 270 | // size may be larger than the filesystem actually is. 271 | // 272 | // Returns the number of allocated blocks, or a negative error code on failure. 273 | func (l *LFS) Size() (n int, err error) { 274 | errno := C.int(C.lfs_fs_size(l.lfs)) 275 | if errno < 0 { 276 | return 0, errval(errno) 277 | } 278 | return int(errno), nil 279 | } 280 | 281 | type File struct { 282 | lfs *LFS 283 | typ fileType 284 | hndl unsafe.Pointer 285 | info Info 286 | } 287 | 288 | func (f *File) dirptr() *C.struct_lfs_dir { 289 | return (*C.struct_lfs_dir)(f.hndl) 290 | } 291 | 292 | func (f *File) fileptr() *C.struct_lfs_file { 293 | return (*C.struct_lfs_file)(f.hndl) 294 | } 295 | 296 | // Name returns the name of the file as presented to OpenFile 297 | func (f *File) Name() string { 298 | return f.info.name 299 | } 300 | 301 | // Close the file; any pending writes are written out to storage 302 | func (f *File) Close() error { 303 | if f.hndl != nil { 304 | defer func() { 305 | C.free(f.hndl) 306 | f.hndl = nil 307 | }() 308 | switch f.typ { 309 | case fileTypeReg: 310 | return errval(C.lfs_file_close(f.lfs.lfs, f.fileptr())) 311 | case fileTypeDir: 312 | return errval(C.lfs_dir_close(f.lfs.lfs, f.dirptr())) 313 | default: 314 | panic("lfs: unknown typ for file handle") 315 | } 316 | } 317 | return nil 318 | } 319 | 320 | func (f *File) Read(buf []byte) (n int, err error) { 321 | if f.IsDir() { 322 | return 0, errIsDir 323 | } 324 | bufptr := unsafe.Pointer(&buf[0]) 325 | buflen := C.lfs_size_t(len(buf)) 326 | errno := C.int(C.lfs_file_read(f.lfs.lfs, f.fileptr(), bufptr, buflen)) 327 | if errno > 0 { 328 | return int(errno), nil 329 | } else if errno == 0 { 330 | // TODO: any extra checks needed here? 331 | return 0, io.EOF 332 | } else { 333 | return 0, errval(errno) 334 | } 335 | } 336 | 337 | // Seek changes the position of the file 338 | func (f *File) Seek(offset int64, whence int) (ret int64, err error) { 339 | errno := C.int(C.lfs_file_seek(f.lfs.lfs, f.fileptr(), C.lfs_soff_t(offset), C.int(whence))) 340 | if errno < 0 { 341 | return -1, errval(errno) 342 | } 343 | return int64(errno), nil 344 | } 345 | 346 | // Tell returns the position of the file 347 | func (f *File) Tell() (ret int64, err error) { 348 | errno := C.int(C.lfs_file_tell(f.lfs.lfs, f.fileptr())) 349 | if errno < 0 { 350 | return -1, errval(errno) 351 | } 352 | return int64(errno), nil 353 | } 354 | 355 | // Rewind changes the position of the file to the beginning of the file 356 | func (f *File) Rewind() (err error) { 357 | return errval(C.lfs_file_rewind(f.lfs.lfs, f.fileptr())) 358 | } 359 | 360 | // Size returns the size of the file 361 | func (f *File) Size() (int64, error) { 362 | errno := C.int(C.lfs_file_size(f.lfs.lfs, f.fileptr())) 363 | if errno < 0 { 364 | return -1, errval(errno) 365 | } 366 | return int64(errno), nil 367 | } 368 | 369 | // Stat satisfies the `fs.File` interface 370 | func (f *File) Stat() (os.FileInfo, error) { 371 | return f.info, nil 372 | } 373 | 374 | // Sync synchronizes to storage so that any pending writes are written out. 375 | func (f *File) Sync() error { 376 | return errval(C.lfs_file_sync(f.lfs.lfs, f.fileptr())) 377 | } 378 | 379 | // Truncate the size of the file to the specified size 380 | func (f *File) Truncate(size uint32) error { 381 | return errval(C.lfs_file_truncate(f.lfs.lfs, f.fileptr(), C.lfs_off_t(size))) 382 | } 383 | 384 | func (f *File) Write(buf []byte) (n int, err error) { 385 | bufptr := unsafe.Pointer(&buf[0]) 386 | buflen := C.lfs_size_t(len(buf)) 387 | errno := C.lfs_file_write(f.lfs.lfs, f.fileptr(), bufptr, buflen) 388 | if errno > 0 { 389 | return int(errno), nil 390 | } else { 391 | return 0, errval(C.int(errno)) 392 | } 393 | } 394 | 395 | func (f *File) IsDir() bool { 396 | return f.typ == fileTypeDir 397 | } 398 | 399 | func (f *File) Readdir(n int) (infos []os.FileInfo, err error) { 400 | if n > 0 { 401 | return nil, errors.New("n > 0 is not supported yet") 402 | } 403 | if !f.IsDir() { 404 | return nil, errNotDir 405 | } 406 | for { 407 | var info C.struct_lfs_info 408 | i := C.lfs_dir_read(f.lfs.lfs, f.dirptr(), &info) 409 | if i == 0 { 410 | return 411 | } 412 | if i < 0 { 413 | err = errval(C.int(i)) 414 | return 415 | } 416 | name := gostring(&info.name[0]) 417 | if name == "." || name == ".." { 418 | continue // littlefs returns . and .., but Readdir() in Go does not 419 | } 420 | infos = append(infos, Info{ 421 | ftyp: fileType(info._type), 422 | size: uint32(info.size), 423 | name: name, 424 | }) 425 | } 426 | } 427 | 428 | func errval(errno C.int) error { 429 | if errno < errOK { 430 | return Error(errno) 431 | } 432 | return nil 433 | } 434 | 435 | func cstring(s string) *C.char { 436 | return (*C.char)(util.CString(s)) 437 | } 438 | 439 | func gostring(s *C.char) string { 440 | return util.GoString(unsafe.Pointer(s)) 441 | } 442 | -------------------------------------------------------------------------------- /fatfs/ffconf.h: -------------------------------------------------------------------------------- 1 | /* This file is part of ooFatFs, a customised version of FatFs 2 | * See https://github.com/micropython/oofatfs for details 3 | */ 4 | 5 | /*---------------------------------------------------------------------------/ 6 | / FatFs Functional Configurations 7 | /---------------------------------------------------------------------------*/ 8 | 9 | #define FFCONF_DEF 86604 /* Revision ID */ 10 | 11 | /*---------------------------------------------------------------------------/ 12 | / Function Configurations 13 | /---------------------------------------------------------------------------*/ 14 | 15 | #define FF_FS_READONLY 0 16 | /* This option switches read-only configuration. (0:Read/Write or 1:Read-only) 17 | / Read-only configuration removes writing API functions, f_write(), f_sync(), 18 | / f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree() 19 | / and optional writing functions as well. */ 20 | 21 | 22 | #define FF_FS_MINIMIZE 0 23 | /* This option defines minimization level to remove some basic API functions. 24 | / 25 | / 0: Basic functions are fully enabled. 26 | / 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename() 27 | / are removed. 28 | / 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1. 29 | / 3: f_lseek() function is removed in addition to 2. */ 30 | 31 | 32 | #define FF_USE_STRFUNC 0 33 | /* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf(). 34 | / 35 | / 0: Disable string functions. 36 | / 1: Enable without LF-CRLF conversion. 37 | / 2: Enable with LF-CRLF conversion. */ 38 | 39 | 40 | #define FF_USE_FIND 0 41 | /* This option switches filtered directory read functions, f_findfirst() and 42 | / f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */ 43 | 44 | 45 | #define FF_USE_MKFS 1 46 | /* This option switches f_mkfs() function. (0:Disable or 1:Enable) */ 47 | 48 | 49 | #define FF_USE_FASTSEEK 0 50 | /* This option switches fast seek function. (0:Disable or 1:Enable) */ 51 | 52 | 53 | #define FF_USE_EXPAND 0 54 | /* This option switches f_expand function. (0:Disable or 1:Enable) */ 55 | 56 | 57 | #define FF_USE_CHMOD 0 58 | /* This option switches attribute manipulation functions, f_chmod() and f_utime(). 59 | / (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */ 60 | 61 | 62 | #define FF_USE_LABEL 0 63 | /* This option switches volume label functions, f_getlabel() and f_setlabel(). 64 | / (0:Disable or 1:Enable) */ 65 | 66 | 67 | #define FF_USE_FORWARD 0 68 | /* This option switches f_forward() function. (0:Disable or 1:Enable) */ 69 | 70 | 71 | #define FF_USE_REPAIR 0 72 | /* This option switches f_repair() function. (0:Disable or 1:Enable) */ 73 | 74 | 75 | /*---------------------------------------------------------------------------/ 76 | / Locale and Namespace Configurations 77 | /---------------------------------------------------------------------------*/ 78 | 79 | #define FF_CODE_PAGE 932 80 | /* This option specifies the OEM code page to be used on the target system. 81 | / Incorrect code page setting can cause a file open failure. 82 | / 83 | / 437 - U.S. 84 | / 720 - Arabic 85 | / 737 - Greek 86 | / 771 - KBL 87 | / 775 - Baltic 88 | / 850 - Latin 1 89 | / 852 - Latin 2 90 | / 855 - Cyrillic 91 | / 857 - Turkish 92 | / 860 - Portuguese 93 | / 861 - Icelandic 94 | / 862 - Hebrew 95 | / 863 - Canadian French 96 | / 864 - Arabic 97 | / 865 - Nordic 98 | / 866 - Russian 99 | / 869 - Greek 2 100 | / 932 - Japanese (DBCS) 101 | / 936 - Simplified Chinese (DBCS) 102 | / 949 - Korean (DBCS) 103 | / 950 - Traditional Chinese (DBCS) 104 | / 0 - Include all code pages above and configured by f_setcp() 105 | */ 106 | 107 | 108 | #define FF_USE_LFN 2 109 | #define FF_MAX_LFN 255 110 | /* The FF_USE_LFN switches the support for LFN (long file name). 111 | / 112 | / 0: Disable LFN. FF_MAX_LFN has no effect. 113 | / 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe. 114 | / 2: Enable LFN with dynamic working buffer on the STACK. 115 | / 3: Enable LFN with dynamic working buffer on the HEAP. 116 | / 117 | / To enable the LFN, ffunicode.c needs to be added to the project. The LFN function 118 | / requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and 119 | / additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled. 120 | / The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can 121 | / be in range of 12 to 255. It is recommended to be set 255 to fully support LFN 122 | / specification. 123 | / When use stack for the working buffer, take care on stack overflow. When use heap 124 | / memory for the working buffer, memory management functions, ff_memalloc() and 125 | / ff_memfree() in ffsystem.c, need to be added to the project. */ 126 | 127 | 128 | #define FF_LFN_UNICODE 0 129 | /* This option switches the character encoding on the API when LFN is enabled. 130 | / 131 | / 0: ANSI/OEM in current CP (TCHAR = char) 132 | / 1: Unicode in UTF-16 (TCHAR = WCHAR) 133 | / 2: Unicode in UTF-8 (TCHAR = char) 134 | / 3: Unicode in UTF-32 (TCHAR = DWORD) 135 | / 136 | / Also behavior of string I/O functions will be affected by this option. 137 | / When LFN is not enabled, this option has no effect. */ 138 | 139 | 140 | #define FF_LFN_BUF 255 141 | #define FF_SFN_BUF 12 142 | /* This set of options defines size of file name members in the FILINFO structure 143 | / which is used to read out directory items. These values should be suffcient for 144 | / the file names to read. The maximum possible length of the read file name depends 145 | / on character encoding. When LFN is not enabled, these options have no effect. */ 146 | 147 | 148 | #define FF_STRF_ENCODE 3 149 | /* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(), 150 | / f_putc(), f_puts and f_printf() convert the character encoding in it. 151 | / This option selects assumption of character encoding ON THE FILE to be 152 | / read/written via those functions. 153 | / 154 | / 0: ANSI/OEM in current CP 155 | / 1: Unicode in UTF-16LE 156 | / 2: Unicode in UTF-16BE 157 | / 3: Unicode in UTF-8 158 | */ 159 | 160 | 161 | #define FF_FS_RPATH 0 162 | /* This option configures support for relative path. 163 | / 164 | / 0: Disable relative path and remove related functions. 165 | / 1: Enable relative path. f_chdir() and f_chdrive() are available. 166 | / 2: f_getcwd() function is available in addition to 1. 167 | */ 168 | 169 | 170 | /*---------------------------------------------------------------------------/ 171 | / Drive/Volume Configurations 172 | /---------------------------------------------------------------------------*/ 173 | 174 | #define FF_VOLUMES 1 175 | /* Number of volumes (logical drives) to be used. (1-10) */ 176 | 177 | 178 | #define FF_STR_VOLUME_ID 0 179 | #define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3" 180 | /* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings. 181 | / When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive 182 | / number in the path name. FF_VOLUME_STRS defines the volume ID strings for each 183 | / logical drives. Number of items must not be less than FF_VOLUMES. Valid 184 | / characters for the volume ID strings are A-Z, a-z and 0-9, however, they are 185 | / compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is 186 | / not defined, a user defined volume string table needs to be defined as: 187 | / 188 | / const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",... 189 | */ 190 | 191 | 192 | #define FF_MULTI_PARTITION 0 193 | /* This option switches support for multiple volumes on the physical drive. 194 | / By default (0), each logical drive number is bound to the same physical drive 195 | / number and only an FAT volume found on the physical drive will be mounted. 196 | / When this function is enabled (1), each logical drive number can be bound to 197 | / arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() 198 | / funciton will be available. */ 199 | 200 | 201 | #define FF_MIN_SS 512 202 | #define FF_MAX_SS 512 203 | /* This set of options configures the range of sector size to be supported. (512, 204 | / 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and 205 | / harddisk. But a larger value may be required for on-board flash memory and some 206 | / type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured 207 | / for variable sector size mode and disk_ioctl() function needs to implement 208 | / GET_SECTOR_SIZE command. */ 209 | 210 | 211 | #define FF_USE_TRIM 0 212 | /* This option switches support for ATA-TRIM. (0:Disable or 1:Enable) 213 | / To enable Trim function, also CTRL_TRIM command should be implemented to the 214 | / disk_ioctl() function. */ 215 | 216 | 217 | #define FF_FS_NOFSINFO 0 218 | /* If you need to know correct free space on the FAT32 volume, set bit 0 of this 219 | / option, and f_getfree() function at first time after volume mount will force 220 | / a full FAT scan. Bit 1 controls the use of last allocated cluster number. 221 | / 222 | / bit0=0: Use free cluster count in the FSINFO if available. 223 | / bit0=1: Do not trust free cluster count in the FSINFO. 224 | / bit1=0: Use last allocated cluster number in the FSINFO if available. 225 | / bit1=1: Do not trust last allocated cluster number in the FSINFO. 226 | */ 227 | 228 | 229 | 230 | /*---------------------------------------------------------------------------/ 231 | / System Configurations 232 | /---------------------------------------------------------------------------*/ 233 | 234 | #define FF_FS_TINY 0 235 | /* This option switches tiny buffer configuration. (0:Normal or 1:Tiny) 236 | / At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes. 237 | / Instead of private sector buffer eliminated from the file object, common sector 238 | / buffer in the filesystem object (FATFS) is used for the file data transfer. */ 239 | 240 | 241 | #define FF_FS_EXFAT 0 242 | /* This option switches support for exFAT filesystem. (0:Disable or 1:Enable) 243 | / To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1) 244 | / Note that enabling exFAT discards ANSI C (C89) compatibility. */ 245 | 246 | 247 | #define FF_FS_NORTC 0 248 | #define FF_NORTC_MON 1 249 | #define FF_NORTC_MDAY 1 250 | #define FF_NORTC_YEAR 2018 251 | /* The option FF_FS_NORTC switches timestamp functiton. If the system does not have 252 | / any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable 253 | / the timestamp function. Every object modified by FatFs will have a fixed timestamp 254 | / defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time. 255 | / To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be 256 | / added to the project to read current time form real-time clock. FF_NORTC_MON, 257 | / FF_NORTC_MDAY and FF_NORTC_YEAR have no effect. 258 | / These options have no effect at read-only configuration (FF_FS_READONLY = 1). */ 259 | 260 | 261 | #define FF_FS_LOCK 0 262 | /* The option FF_FS_LOCK switches file lock function to control duplicated file open 263 | / and illegal operation to open objects. This option must be 0 when FF_FS_READONLY 264 | / is 1. 265 | / 266 | / 0: Disable file lock function. To avoid volume corruption, application program 267 | / should avoid illegal open, remove and rename to the open objects. 268 | / >0: Enable file lock function. The value defines how many files/sub-directories 269 | / can be opened simultaneously under file lock control. Note that the file 270 | / lock control is independent of re-entrancy. */ 271 | 272 | 273 | /* #include // O/S definitions */ 274 | #define FF_FS_REENTRANT 0 275 | #define FF_FS_TIMEOUT 1000 276 | #define FF_SYNC_t HANDLE 277 | /* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs 278 | / module itself. Note that regardless of this option, file access to different 279 | / volume is always re-entrant and volume control functions, f_mount(), f_mkfs() 280 | / and f_fdisk() function, are always not re-entrant. Only file/directory access 281 | / to the same volume is under control of this function. 282 | / 283 | / 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect. 284 | / 1: Enable re-entrancy. Also user provided synchronization handlers, 285 | / ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj() 286 | / function, must be added to the project. Samples are available in 287 | / option/syscall.c. 288 | / 289 | / The FF_FS_TIMEOUT defines timeout period in unit of time tick. 290 | / The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*, 291 | / SemaphoreHandle_t and etc. A header file for O/S definitions needs to be 292 | / included somewhere in the scope of ff.h. */ 293 | 294 | 295 | 296 | /*--- End of configuration options ---*/ 297 | -------------------------------------------------------------------------------- /examples/console/example.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | // +build tinygo 3 | 4 | package console 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "machine" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "time" 14 | 15 | "tinygo.org/x/drivers/flash" 16 | "tinygo.org/x/tinyfs" 17 | ) 18 | 19 | const consoleBufLen = 64 20 | 21 | const startBlock = 0 22 | const blockCount = 0 23 | 24 | var ( 25 | debug = false 26 | 27 | input [consoleBufLen]byte 28 | 29 | console = machine.Serial 30 | readyLED = machine.LED 31 | 32 | // flashdev *flash.Device 33 | blockdev tinyfs.BlockDevice 34 | fs tinyfs.Filesystem 35 | 36 | currdir = "/" 37 | 38 | commands = map[string]cmdfunc{ 39 | "": noop, 40 | "dbg": dbg, 41 | "lsblk": lsblk, 42 | "mount": mount, 43 | "umount": umount, 44 | "format": format, 45 | "xxd": xxd, 46 | "ls": ls, 47 | "samples": samples, 48 | "mkdir": mkdir, 49 | "cat": cat, 50 | "create": create, 51 | "write": write, 52 | "rm": rm, 53 | } 54 | ) 55 | 56 | type cmdfunc func(argv []string) 57 | 58 | const ( 59 | StateInput = iota 60 | StateEscape 61 | StateEscBrc 62 | StateCSI 63 | ) 64 | 65 | func RunFor(dev tinyfs.BlockDevice, filesys tinyfs.Filesystem) { 66 | time.Sleep(3 * time.Second) 67 | blockdev = dev 68 | 69 | fs = filesys 70 | 71 | readyLED.Configure(machine.PinConfig{Mode: machine.PinOutput}) 72 | readyLED.High() 73 | 74 | readyLED.Low() 75 | println("SPI Configured. Reading flash info") 76 | 77 | /* 78 | lfsConfig := flashlfs.NewConfig() 79 | if blockCount == 0 { 80 | lfsConfig.BlockCount = (flashdev.Attrs().TotalSize / lfsConfig.BlockSize) - startBlock 81 | } else { 82 | lfsConfig.BlockCount = blockCount 83 | } 84 | println("Start block:", startBlock) 85 | println("Block count:", lfsConfig.BlockCount) 86 | 87 | blockdev = flashlfs.NewBlockDevice(flashdev, startBlock, lfsConfig.BlockSize) 88 | */ 89 | 90 | prompt() 91 | 92 | var state = StateInput 93 | 94 | for i := 0; ; { 95 | if console.Buffered() > 0 { 96 | data, _ := console.ReadByte() 97 | if debug { 98 | fmt.Printf("\rdata: %x\r\n\r", data) 99 | prompt() 100 | console.Write(input[:i]) 101 | } 102 | switch state { 103 | case StateInput: 104 | switch data { 105 | case 0x8: 106 | fallthrough 107 | case 0x7f: // this is probably wrong... works on my machine tho :) 108 | // backspace 109 | if i > 0 { 110 | i -= 1 111 | console.Write([]byte{0x8, 0x20, 0x8}) 112 | } 113 | case 13: 114 | // return key 115 | if console.Buffered() > 0 { 116 | data, _ := console.ReadByte() 117 | if data != 10 { 118 | println("\r\nunexpected: \r", int(data)) 119 | } 120 | } 121 | console.Write([]byte("\r\n")) 122 | runCommand(string(input[:i])) 123 | prompt() 124 | 125 | i = 0 126 | continue 127 | case 27: 128 | // escape 129 | state = StateEscape 130 | default: 131 | // anything else, just echo the character if it is printable 132 | if strconv.IsPrint(rune(data)) { 133 | if i < (consoleBufLen - 1) { 134 | console.WriteByte(data) 135 | input[i] = data 136 | i++ 137 | } 138 | } 139 | } 140 | case StateEscape: 141 | switch data { 142 | case 0x5b: 143 | state = StateEscBrc 144 | default: 145 | state = StateInput 146 | } 147 | default: 148 | // TODO: handle escape sequences 149 | state = StateInput 150 | } 151 | } 152 | } 153 | } 154 | 155 | func runCommand(line string) { 156 | argv := strings.SplitN(strings.TrimSpace(line), " ", -1) 157 | cmd := argv[0] 158 | cmdfn, ok := commands[cmd] 159 | if !ok { 160 | println("unknown command: " + line) 161 | return 162 | } 163 | cmdfn(argv) 164 | } 165 | 166 | func noop(argv []string) {} 167 | 168 | func dbg(argv []string) { 169 | if debug { 170 | debug = false 171 | println("Console debugging off") 172 | } else { 173 | debug = true 174 | println("Console debugging on") 175 | } 176 | } 177 | 178 | func lsblk(argv []string) { 179 | if flashdev, ok := blockdev.(*flash.Device); ok { 180 | lsblk_flash(flashdev) 181 | return 182 | } 183 | if blockdev == machine.Flash { 184 | lsblk_machine() 185 | return 186 | } 187 | println("Unknown device") 188 | } 189 | 190 | func lsblk_flash(flashdev *flash.Device) { 191 | attrs := flashdev.Attrs() 192 | status1, _ := flashdev.ReadStatus() 193 | status2, _ := flashdev.ReadStatus2() 194 | serialNumber1, _ := flashdev.ReadSerialNumber() 195 | fmt.Printf( 196 | "\n-------------------------------------\r\n"+ 197 | " Device Information: \r\n"+ 198 | "-------------------------------------\r\n"+ 199 | " JEDEC ID: %6X\r\n"+ 200 | " Serial: %08X\r\n"+ 201 | " Status 1: %02x\r\n"+ 202 | " Status 2: %02x\r\n"+ 203 | " \r\n"+ 204 | " Max clock speed (MHz): %d\r\n"+ 205 | " Has Sector Protection: %t\r\n"+ 206 | " Supports Fast Reads: %t\r\n"+ 207 | " Supports QSPI Reads: %t\r\n"+ 208 | " Supports QSPI Write: %t\r\n"+ 209 | " Write Status Split: %t\r\n"+ 210 | " Single Status Byte: %t\r\n"+ 211 | "-------------------------------------\r\n\r\n", 212 | attrs.JedecID.Uint32(), 213 | serialNumber1, 214 | status1, 215 | status2, 216 | attrs.MaxClockSpeedMHz, 217 | attrs.HasSectorProtection, 218 | attrs.SupportsFastRead, 219 | attrs.SupportsQSPI, 220 | attrs.SupportsQSPIWrites, 221 | attrs.WriteStatusSplit, 222 | attrs.SingleStatusByte, 223 | ) 224 | } 225 | 226 | func lsblk_machine() { 227 | fmt.Printf( 228 | "\n-------------------------------------\r\n"+ 229 | " Device Information: \r\n"+ 230 | "-------------------------------------\r\n"+ 231 | " flash data start: %08X\r\n"+ 232 | " flash data end: %08X\r\n"+ 233 | "-------------------------------------\r\n\r\n", 234 | machine.FlashDataStart(), 235 | machine.FlashDataEnd(), 236 | ) 237 | } 238 | 239 | func mount(argv []string) { 240 | if err := fs.Mount(); err != nil { 241 | println("Could not mount LittleFS filesystem: " + err.Error() + "\r\n") 242 | } else { 243 | println("Successfully mounted LittleFS filesystem.\r\n") 244 | } 245 | } 246 | 247 | func format(argv []string) { 248 | if err := fs.Format(); err != nil { 249 | println("Could not format LittleFS filesystem: " + err.Error() + "\r\n") 250 | } else { 251 | println("Successfully formatted LittleFS filesystem.\r\n") 252 | } 253 | } 254 | 255 | func umount(argv []string) { 256 | if err := fs.Unmount(); err != nil { 257 | println("Could not unmount LittleFS filesystem: " + err.Error() + "\r\n") 258 | } else { 259 | println("Successfully unmounted LittleFS filesystem.\r\n") 260 | } 261 | } 262 | 263 | /* 264 | var err error 265 | if fatfs == nil { 266 | fatfs, err = fat.New(fatdisk) 267 | if err != nil { 268 | fatfs = nil 269 | println("could not load FAT filesystem: " + err.Error() + "\r\n") 270 | } 271 | fmt.Printf("loaded fs\r\n") 272 | } 273 | if rootdir == nil { 274 | rootdir, err = fatfs.RootDir() 275 | if err != nil { 276 | rootdir = nil 277 | println("could not load rootdir: " + err.Error() + "\r\n") 278 | } 279 | fmt.Printf("loaded rootdir\r\n") 280 | } 281 | if currdir == nil { 282 | currdir = rootdir 283 | } 284 | */ 285 | 286 | func ls(argv []string) { 287 | path := "/" 288 | if len(argv) > 1 { 289 | path = strings.TrimSpace(argv[1]) 290 | } 291 | dir, err := fs.Open(path) 292 | if err != nil { 293 | fmt.Printf("Could not open directory %s: %v\n", path, err) 294 | return 295 | } 296 | defer dir.Close() 297 | infos, err := dir.Readdir(0) 298 | _ = infos 299 | if err != nil { 300 | fmt.Printf("Could not read directory %s: %v\n", path, err) 301 | return 302 | } 303 | for _, info := range infos { 304 | s := "-rwxrwxrwx" 305 | if info.IsDir() { 306 | s = "drwxrwxrwx" 307 | } 308 | fmt.Printf("%s %5d %s\n", s, info.Size(), info.Name()) 309 | } 310 | } 311 | 312 | func mkdir(argv []string) { 313 | tgt := "" 314 | if len(argv) == 2 { 315 | tgt = strings.TrimSpace(argv[1]) 316 | } 317 | if debug { 318 | println("Trying mkdir to " + tgt) 319 | } 320 | if tgt == "" { 321 | println("Usage: mkdir ") 322 | return 323 | } 324 | err := fs.Mkdir(tgt, 0777) 325 | if err != nil { 326 | println("Could not mkdir " + tgt + ": " + err.Error()) 327 | } 328 | } 329 | 330 | func rm(argv []string) { 331 | tgt := "" 332 | if len(argv) == 2 { 333 | tgt = strings.TrimSpace(argv[1]) 334 | } 335 | if debug { 336 | println("Trying rm to " + tgt) 337 | } 338 | if tgt == "" { 339 | println("Usage: rm ") 340 | return 341 | } 342 | err := fs.Remove(tgt) 343 | if err != nil { 344 | println("Could not rm " + tgt + ": " + err.Error()) 345 | } 346 | } 347 | 348 | func samples(argv []string) { 349 | buf := make([]byte, 90) 350 | for i := 0; i < 5; i++ { 351 | name := fmt.Sprintf("file%d.txt", i) 352 | if bytes, err := createSampleFile(name, buf); err != nil { 353 | fmt.Printf("%s\r\n", err) 354 | return 355 | } else { 356 | fmt.Printf("wrote %d bytes to %s\r\n", bytes, name) 357 | } 358 | } 359 | } 360 | 361 | func create(argv []string) { 362 | tgt := "" 363 | if len(argv) == 2 { 364 | tgt = strings.TrimSpace(argv[1]) 365 | } 366 | if debug { 367 | println("Trying create to " + tgt) 368 | } 369 | buf := make([]byte, 90) 370 | if bytes, err := createSampleFile(tgt, buf); err != nil { 371 | fmt.Printf("%s\r\n", err) 372 | return 373 | } else { 374 | fmt.Printf("wrote %d bytes to %s\r\n", bytes, tgt) 375 | } 376 | } 377 | 378 | func write(argv []string) { 379 | tgt := "" 380 | if len(argv) == 2 { 381 | tgt = strings.TrimSpace(argv[1]) 382 | } 383 | if debug { 384 | println("Trying receive to " + tgt) 385 | } 386 | buf := make([]byte, 1) 387 | f, err := fs.OpenFile(tgt, os.O_CREATE|os.O_WRONLY|os.O_TRUNC) 388 | if err != nil { 389 | fmt.Printf("error opening %s: %s\r\n", tgt, err.Error()) 390 | return 391 | } 392 | defer f.Close() 393 | var n int 394 | for { 395 | if console.Buffered() > 0 { 396 | data, _ := console.ReadByte() 397 | switch data { 398 | case 0x04: 399 | fmt.Printf("wrote %d bytes to %s\r\n", n, tgt) 400 | return 401 | default: 402 | // anything else, just echo the character if it is printable 403 | if strconv.IsPrint(rune(data)) { 404 | console.WriteByte(data) 405 | } 406 | buf[0] = data 407 | if _, err := f.Write(buf); err != nil { 408 | fmt.Printf("\nerror writing: %s\r\n", err) 409 | return 410 | } 411 | n++ 412 | } 413 | } 414 | } 415 | } 416 | 417 | func createSampleFile(name string, buf []byte) (int, error) { 418 | for j := uint8(0); j < uint8(len(buf)); j++ { 419 | buf[j] = 0x20 + j 420 | } 421 | f, err := fs.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC) 422 | if err != nil { 423 | return 0, fmt.Errorf("error opening %s: %s", name, err.Error()) 424 | } 425 | defer f.Close() 426 | bytes, err := f.Write(buf) 427 | if err != nil { 428 | return 0, fmt.Errorf("error writing %s: %s", name, err.Error()) 429 | } 430 | return bytes, nil 431 | } 432 | 433 | /* 434 | func cd(argv []string) { 435 | 436 | if fatfs == nil || rootdir == nil { 437 | mnt(nil) 438 | } 439 | if len(argv) == 1 { 440 | currdir = rootdir 441 | return 442 | } 443 | tgt := "" 444 | if len(argv) == 2 { 445 | tgt = strings.TrimSpace(argv[1]) 446 | } 447 | if debug { 448 | println("Trying to cd to " + tgt) 449 | } 450 | if tgt == "" { 451 | println("Usage: cd ") 452 | return 453 | } 454 | if debug { 455 | println("Getting entry") 456 | } 457 | entry := currdir.Entry(tgt) 458 | if entry == nil { 459 | println("File not found: " + tgt) 460 | return 461 | } 462 | if !entry.IsDir() { 463 | println("Not a directory: " + tgt) 464 | return 465 | } 466 | if debug { 467 | println("Getting dir") 468 | } 469 | cd, err := entry.Dir() 470 | if err != nil { 471 | println("Could not cd to " + tgt + ": " + err.Error()) 472 | } 473 | currdir = cd 474 | } 475 | */ 476 | 477 | func cat(argv []string) { 478 | tgt := "" 479 | if len(argv) == 2 { 480 | tgt = strings.TrimSpace(argv[1]) 481 | } 482 | if debug { 483 | println("Trying to cat to " + tgt) 484 | } 485 | if tgt == "" { 486 | println("Usage: cat ") 487 | return 488 | } 489 | if debug { 490 | println("Getting entry") 491 | } 492 | f, err := fs.Open(tgt) 493 | if err != nil { 494 | println("Could not open: " + err.Error()) 495 | return 496 | } 497 | defer f.Close() 498 | if f.IsDir() { 499 | println("Not a file: " + tgt) 500 | return 501 | } 502 | off := 0x0 503 | buf := make([]byte, 64) 504 | for { 505 | n, err := f.Read(buf) 506 | if err != nil { 507 | if err == io.EOF { 508 | break 509 | } 510 | println("Error reading " + tgt + ": " + err.Error()) 511 | } 512 | xxdfprint(os.Stdout, uint32(off), buf[:n]) 513 | off += n 514 | } 515 | } 516 | 517 | func xxd(argv []string) { 518 | var err error 519 | var addr uint64 = 0x0 520 | var size int = 64 521 | switch len(argv) { 522 | case 3: 523 | if size, err = strconv.Atoi(argv[2]); err != nil { 524 | println("Invalid size argument: " + err.Error() + "\r\n") 525 | return 526 | } 527 | if size > 512 || size < 1 { 528 | fmt.Printf("Size of hexdump must be greater than 0 and less than %d\r\n", 512) 529 | return 530 | } 531 | fallthrough 532 | case 2: 533 | /* 534 | if argv[1][:2] != "0x" { 535 | println("Invalid hex address (should start with 0x)") 536 | return 537 | } 538 | */ 539 | if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil { 540 | println("Invalid address: " + err.Error() + "\r\n") 541 | return 542 | } 543 | fallthrough 544 | case 1: 545 | // no args supplied, so nothing to do here, just use the defaults 546 | default: 547 | println("usage: xxd \r\n") 548 | return 549 | } 550 | buf := make([]byte, size) 551 | //bsz := uint64(flash.SectorSize) 552 | //blockdev.ReadBlock(uint32(addr/bsz), uint32(addr%bsz), buf) 553 | blockdev.ReadAt(buf, int64(addr)) 554 | xxdfprint(os.Stdout, uint32(addr), buf) 555 | } 556 | 557 | func xxdfprint(w io.Writer, offset uint32, b []byte) { 558 | var l int 559 | var buf16 = make([]byte, 16) 560 | var padding = "" 561 | for i, c := 0, len(b); i < c; i += 16 { 562 | l = i + 16 563 | if l >= c { 564 | padding = strings.Repeat(" ", (l-c)*3) 565 | l = c 566 | } 567 | fmt.Fprintf(w, "%08x: % x "+padding, offset+uint32(i), b[i:l]) 568 | for j, n := 0, l-i; j < 16; j++ { 569 | if j >= n { 570 | buf16[j] = ' ' 571 | } else if !strconv.IsPrint(rune(b[i+j])) { 572 | buf16[j] = '.' 573 | } else { 574 | buf16[j] = b[i+j] 575 | } 576 | } 577 | console.Write(buf16) 578 | println() 579 | } 580 | } 581 | 582 | func prompt() { 583 | print("==> ") 584 | } 585 | -------------------------------------------------------------------------------- /fatfs/go_fatfs.go: -------------------------------------------------------------------------------- 1 | package fatfs 2 | 3 | // #include 4 | // #include 5 | // #include "./go_fatfs.h" 6 | import "C" 7 | import ( 8 | "errors" 9 | "io" 10 | "os" 11 | "time" 12 | "unsafe" 13 | 14 | "tinygo.org/x/tinyfs" 15 | "tinygo.org/x/tinyfs/internal/gopointer" 16 | "tinygo.org/x/tinyfs/internal/util" 17 | ) 18 | 19 | const ( 20 | FileResultOK = C.FR_OK /* (0) Succeeded */ 21 | FileResultErr FileResult = C.FR_DISK_ERR 22 | FileResultIntErr FileResult = C.FR_INT_ERR 23 | FileResultNotReady FileResult = C.FR_NOT_READY 24 | FileResultNoFile FileResult = C.FR_NO_FILE 25 | FileResultNoPath FileResult = C.FR_NO_PATH 26 | FileResultInvalidName FileResult = C.FR_INVALID_NAME 27 | FileResultDenied FileResult = C.FR_DENIED 28 | FileResultExist FileResult = C.FR_EXIST 29 | FileResultInvalidObject FileResult = C.FR_INVALID_OBJECT 30 | FileResultWriteProtected FileResult = C.FR_WRITE_PROTECTED 31 | FileResultInvalidDrive FileResult = C.FR_INVALID_DRIVE 32 | FileResultNotEnabled FileResult = C.FR_NOT_ENABLED 33 | FileResultNoFilesystem FileResult = C.FR_NO_FILESYSTEM 34 | FileResultMkfsAborted FileResult = C.FR_MKFS_ABORTED 35 | FileResultTimeout FileResult = C.FR_TIMEOUT 36 | FileResultLocked FileResult = C.FR_LOCKED 37 | FileResultNotEnoughCore FileResult = C.FR_NOT_ENOUGH_CORE 38 | FileResultTooManyOpenFiles FileResult = C.FR_TOO_MANY_OPEN_FILES 39 | FileResultInvalidParameter FileResult = C.FR_INVALID_PARAMETER 40 | FileResultReadOnly FileResult = 99 41 | FileResultNotImplemented FileResult = 0xe0 // tinyfs custom error 42 | 43 | TypeFAT12 Type = C.FS_FAT12 44 | TypeFAT16 Type = C.FS_FAT16 45 | TypeFAT32 Type = C.FS_FAT32 46 | TypeEXFAT Type = C.FS_EXFAT 47 | 48 | AttrReadOnly FileAttr = C.AM_RDO 49 | AttrHidden FileAttr = C.AM_HID 50 | AttrSystem FileAttr = C.AM_SYS 51 | AttrDirectory FileAttr = C.AM_DIR 52 | AttrArchive FileAttr = C.AM_ARC 53 | 54 | SectorSize = 512 55 | 56 | FileAccessRead OpenFlag = C.FA_READ 57 | FileAccessWrite OpenFlag = C.FA_WRITE 58 | FileAccessOpenExisting OpenFlag = C.FA_OPEN_EXISTING 59 | FileAccessCreateNew OpenFlag = C.FA_CREATE_NEW 60 | FileAccessCreateAlways OpenFlag = C.FA_CREATE_ALWAYS 61 | FileAccessOpenAlways OpenFlag = C.FA_OPEN_ALWAYS 62 | FileAccessOpenAppend OpenFlag = C.FA_OPEN_APPEND 63 | ) 64 | 65 | type OpenFlag uint 66 | 67 | type Type uint 68 | 69 | func (t Type) String() string { 70 | switch t { 71 | case TypeFAT12: 72 | return "FAT12" 73 | case TypeFAT16: 74 | return "FAT16" 75 | case TypeFAT32: 76 | return "FAT32" 77 | case TypeEXFAT: 78 | return "EXFAT" 79 | default: 80 | return "invalid/unknown" 81 | } 82 | } 83 | 84 | type FileResult uint 85 | 86 | func (r FileResult) Error() string { 87 | var msg string 88 | switch r { 89 | case FileResultErr: 90 | msg = "(1) A hard error occurred in the low level disk I/O layer" 91 | case FileResultIntErr: 92 | msg = "(2) Assertion failed" 93 | case FileResultNotReady: 94 | msg = "(3) The physical drive cannot work" 95 | case FileResultNoFile: 96 | msg = "(4) Could not find the file" 97 | case FileResultNoPath: 98 | msg = "(5) Could not find the path" 99 | case FileResultInvalidName: 100 | msg = "(6) The path name format is invalid" 101 | case FileResultDenied: 102 | msg = "(7) Access denied due to prohibited access or directory full" 103 | case FileResultExist: 104 | msg = "(8) Access denied due to prohibited access" 105 | case FileResultInvalidObject: 106 | msg = "(9) The file/directory object is invalid" 107 | case FileResultWriteProtected: 108 | msg = "(10) The physical drive is write protected" 109 | case FileResultInvalidDrive: 110 | msg = "(11) The logical drive number is invalid" 111 | case FileResultNotEnabled: 112 | msg = "(12) The volume has no work area" 113 | case FileResultNoFilesystem: 114 | msg = "(13) There is no valid FAT volume" 115 | case FileResultMkfsAborted: 116 | msg = "(14) The f_mkfs() aborted due to any problem" 117 | case FileResultTimeout: 118 | msg = "(15) Could not get a grant to access the volume within defined period" 119 | case FileResultLocked: 120 | msg = "(16) The operation is rejected according to the file sharing policy" 121 | case FileResultNotEnoughCore: 122 | msg = "(17) LFN working buffer could not be allocated" 123 | case FileResultTooManyOpenFiles: 124 | msg = "(18) Number of open files > FF_FS_LOCK" 125 | case FileResultInvalidParameter: 126 | msg = "(19) Given parameter is invalid" 127 | case FileResultReadOnly: 128 | msg = "(99) Read-only filesystem" 129 | case FileResultNotImplemented: 130 | msg = "(e0) Feature Not Implemented" 131 | default: 132 | msg = "unknown file result error" 133 | } 134 | return "fatfs: " + msg 135 | } 136 | 137 | type FileAttr byte 138 | 139 | type Info struct { 140 | size int64 141 | name string 142 | attr FileAttr 143 | } 144 | 145 | var _ os.FileInfo = (*Info)(nil) 146 | 147 | func (info Info) Name() string { 148 | return info.name 149 | } 150 | 151 | func (info Info) Size() int64 { 152 | return info.size 153 | } 154 | 155 | func (info Info) IsDir() bool { 156 | return (info.attr & AttrDirectory) > 0 157 | } 158 | 159 | func (info Info) Sys() interface{} { 160 | return nil 161 | } 162 | 163 | func (info Info) Mode() os.FileMode { 164 | v := os.FileMode(0777) 165 | if info.IsDir() { 166 | v |= os.ModeDir 167 | } 168 | return v 169 | } 170 | 171 | func (info Info) ModTime() time.Time { 172 | return time.Time{} 173 | } 174 | 175 | type FATFS struct { 176 | dev tinyfs.BlockDevice 177 | fs *C.FATFS 178 | } 179 | 180 | type Config struct { 181 | SectorSize int 182 | } 183 | 184 | func New(blockdev tinyfs.BlockDevice) *FATFS { 185 | return &FATFS{ 186 | dev: blockdev, 187 | } 188 | } 189 | 190 | func (l *FATFS) Configure(config *Config) *FATFS { 191 | l.fs = C.go_fatfs_new_fatfs() 192 | l.fs.drv = gopointer.Save(l) 193 | return l 194 | } 195 | 196 | func (l *FATFS) Mount() error { 197 | return errval(C.f_mount(l.fs)) 198 | } 199 | 200 | func (l *FATFS) Format() error { 201 | work := make([]byte, SectorSize) 202 | return errval(C.f_mkfs(l.fs, C.FM_FAT, 0, unsafe.Pointer(&work[0]), C.UINT(len(work)))) 203 | } 204 | 205 | func (l *FATFS) Free() (int64, error) { 206 | var clust C.DWORD 207 | res := C.f_getfree(l.fs, &clust) 208 | if err := errval(res); err != nil { 209 | return 0, err 210 | } 211 | return int64(clust * SectorSize), nil 212 | } 213 | 214 | func (l *FATFS) Unmount() error { 215 | return nil 216 | } 217 | 218 | func (l *FATFS) Remove(path string) error { 219 | cs := cstring(path) 220 | defer C.free(unsafe.Pointer(cs)) 221 | return errval(C.f_unlink(l.fs, cs)) 222 | } 223 | 224 | func (l *FATFS) Rename(oldPath string, newPath string) error { 225 | cs1, cs2 := cstring(oldPath), cstring(newPath) 226 | defer C.free(unsafe.Pointer(cs1)) 227 | defer C.free(unsafe.Pointer(cs2)) 228 | return errval(C.f_rename(l.fs, cs1, cs2)) 229 | } 230 | 231 | func (l *FATFS) Stat(path string) (os.FileInfo, error) { 232 | cs := cstring(path) 233 | defer C.free(unsafe.Pointer(cs)) 234 | info := C.FILINFO{} 235 | if err := errval(C.f_stat(l.fs, cs, &info)); err != nil { 236 | return nil, err 237 | } 238 | return &Info{ 239 | size: int64(info.fsize), 240 | name: gostring(&info.fname[0]), 241 | attr: FileAttr(info.fattrib), 242 | }, nil 243 | } 244 | 245 | func (l *FATFS) Mkdir(path string, _ os.FileMode) error { 246 | cs := cstring(path) 247 | defer C.free(unsafe.Pointer(cs)) 248 | return errval(C.f_mkdir(l.fs, cs)) 249 | } 250 | 251 | func (l *FATFS) Open(path string) (tinyfs.File, error) { 252 | return l.OpenFile(path, os.O_RDONLY) 253 | } 254 | 255 | func (l *FATFS) OpenFile(path string, flags int) (tinyfs.File, error) { 256 | 257 | // create a C string with the file path 258 | cs := cstring(path) 259 | defer C.free(unsafe.Pointer(cs)) 260 | 261 | // stat the file path to see if it exists and if it is a file/dir 262 | info := &C.FILINFO{} 263 | if err := errval(C.f_stat(l.fs, cs, info)); err != nil && err != FileResultNoFile && err != FileResultInvalidName { 264 | //println("warning:", err) 265 | return nil, err 266 | } 267 | 268 | // use f_open or f_opendir to obtain a handle to the object 269 | var file = &File{fs: l, info: Info{ 270 | name: path, 271 | size: int64(info.fsize), 272 | attr: FileAttr(info.fattrib), 273 | }} 274 | var errno C.FRESULT 275 | if path == "/" || info.fattrib&C.AM_DIR > 0 { 276 | // directory 277 | file.typ = uint8(C.AM_DIR) 278 | file.hndl = unsafe.Pointer(C.go_fatfs_new_ff_dir()) 279 | errno = C.f_opendir(l.fs, (*C.FF_DIR)(file.hndl), cs) 280 | } else { 281 | // file 282 | file.typ = 0 283 | file.hndl = unsafe.Pointer(C.go_fatfs_new_fil()) 284 | errno = C.f_open(l.fs, (*C.FIL)(file.hndl), cs, translateFlags(flags)) 285 | } 286 | 287 | // check to make sure f_open/f_opendir didn't produce an error 288 | if err := errval(errno); err != nil { 289 | if file.hndl != nil { 290 | C.free(file.hndl) 291 | file.hndl = nil 292 | } 293 | return nil, err 294 | } 295 | 296 | // file handle was initialized successfully 297 | return file, nil 298 | } 299 | 300 | // translateFlags translates osFlags such as os.O_RDONLY into fatfs flags. 301 | // http://elm-chan.org/fsw/ff/doc/open.html 302 | func translateFlags(osFlags int) C.BYTE { 303 | var result C.BYTE 304 | result = C.FA_READ 305 | switch osFlags { 306 | case os.O_RDONLY: 307 | // r 308 | result = C.FA_READ 309 | case os.O_WRONLY | os.O_CREATE | os.O_TRUNC: 310 | // w 311 | result = C.FA_CREATE_ALWAYS | C.FA_WRITE 312 | case os.O_WRONLY | os.O_CREATE | os.O_APPEND: 313 | // a 314 | result = C.FA_OPEN_APPEND | C.FA_WRITE 315 | case os.O_RDWR: 316 | // r+ 317 | result = C.FA_READ | C.FA_WRITE 318 | case os.O_RDWR | os.O_CREATE | os.O_TRUNC: 319 | // w+ 320 | result = C.FA_CREATE_ALWAYS | C.FA_WRITE | C.FA_READ 321 | case os.O_RDWR | os.O_CREATE | os.O_APPEND: 322 | // a+ 323 | result = C.FA_OPEN_APPEND | C.FA_WRITE | C.FA_READ 324 | default: 325 | } 326 | return result 327 | } 328 | 329 | type File struct { 330 | fs *FATFS 331 | typ uint8 332 | hndl unsafe.Pointer 333 | info Info 334 | } 335 | 336 | func (f *File) dirptr() *C.FF_DIR { 337 | return (*C.FF_DIR)(f.hndl) 338 | } 339 | 340 | func (f *File) fileptr() *C.FIL { 341 | return (*C.FIL)(f.hndl) 342 | } 343 | 344 | // Name returns the name of the file as presented to OpenFile 345 | func (f *File) Name() string { 346 | return f.info.name 347 | } 348 | 349 | func (f *File) Close() error { 350 | var errno C.FRESULT 351 | if f.hndl != nil { 352 | defer func() { 353 | C.free(f.hndl) 354 | f.hndl = nil 355 | }() 356 | if f.IsDir() { 357 | errno = C.f_closedir(f.dirptr()) 358 | } else { 359 | errno = C.f_close(f.fileptr()) 360 | } 361 | } 362 | return errval(errno) 363 | } 364 | 365 | func (f *File) Read(buf []byte) (n int, err error) { 366 | if f.IsDir() { 367 | return 0, FileResultInvalidObject 368 | } 369 | bufptr := unsafe.Pointer(&buf[0]) 370 | var br, btr C.UINT = 0, C.UINT(len(buf)) 371 | errno := C.f_read(f.fileptr(), bufptr, btr, &br) 372 | if err := errval(errno); err != nil { 373 | return int(br), err 374 | } 375 | if br == 0 && btr > 0 { 376 | return 0, io.EOF 377 | } 378 | return int(br), nil 379 | } 380 | 381 | // Seek changes the position of the file 382 | func (f *File) Seek(offset int64, whence int) (ret int64, err error) { 383 | // FRESULT f_lseek ( 384 | // FIL* fp, /* Pointer to the file object */ 385 | // FSIZE_t ofs /* File pointer from top of file */ 386 | // ) 387 | switch whence { 388 | case io.SeekStart: 389 | case io.SeekCurrent: 390 | return -1, FileResultNotImplemented // FIXME: support these options 391 | case io.SeekEnd: 392 | return -1, FileResultNotImplemented // FIXME: support these options 393 | default: 394 | return -1, FileResultInvalidParameter 395 | } 396 | errno := C.f_lseek(f.fileptr(), C.FSIZE_t(offset)) 397 | if err := errval(errno); err != nil { 398 | return -1, err 399 | } 400 | return offset, nil 401 | } 402 | 403 | /* 404 | // Tell returns the position of the file 405 | func (f *File) Tell() (ret int64, err error) { 406 | errno := C.int(C.f_tell(f.fileptr(), &f.fptr)) 407 | if errno < 0 { 408 | return -1, errval(errno) 409 | } 410 | return int64(errno), nil 411 | } 412 | 413 | // Rewind changes the position of the file to the beginning of the file 414 | func (f *File) Rewind() (err error) { 415 | return errval(C.lfs_file_rewind(f.lfs.lfs, &f.fptr)) 416 | } 417 | */ 418 | 419 | // Size returns the size of the file 420 | func (f *File) Size() (int64, error) { 421 | if f.IsDir() { 422 | ptr := f.dirptr() 423 | return int64(ptr.obj.objsize), nil 424 | } else { 425 | ptr := f.fileptr() 426 | return int64(ptr.obj.objsize), nil 427 | } 428 | } 429 | 430 | func (f *File) Stat() (os.FileInfo, error) { 431 | return f.info, nil 432 | } 433 | 434 | // Synchronize a file on storage 435 | // 436 | // Any pending writes are written out to storage. 437 | // Returns a negative error code on failure. 438 | func (f *File) Sync() error { 439 | return errval(C.f_sync(f.fileptr())) 440 | } 441 | 442 | /* 443 | // Truncates the size of the file to the specified size 444 | // 445 | // Returns a negative error code on failure. 446 | func (f *File) Truncate(size uint32) error { 447 | return errval(C.lfs_file_truncate(f.lfs.lfs, &f.fptr, C.lfs_off_t(size))) 448 | } 449 | */ 450 | 451 | func (f *File) Write(buf []byte) (n int, err error) { 452 | if f.IsDir() { 453 | return 0, FileResultInvalidObject 454 | } 455 | bufptr := unsafe.Pointer(&buf[0]) 456 | var bw, btw C.UINT = 0, C.UINT(len(buf)) 457 | errno := C.f_write(f.fileptr(), bufptr, btw, &bw) 458 | if err := errval(errno); err != nil { 459 | return int(bw), err 460 | } 461 | if bw < btw { 462 | return int(bw), errors.New("volume is full") 463 | } 464 | return int(bw), nil 465 | } 466 | 467 | func (f *File) IsDir() bool { 468 | return f.typ == C.AM_DIR 469 | } 470 | 471 | func (f *File) Readdir(n int) (infos []os.FileInfo, err error) { 472 | if !f.IsDir() { 473 | return nil, FileResultInvalidObject 474 | } 475 | if n == 0 { 476 | // passing nil pointer to f_readdir resets the read index 477 | if err := errval(C.f_readdir(f.dirptr(), nil)); err != nil { 478 | return nil, err 479 | } 480 | } 481 | for { 482 | info := C.FILINFO{} 483 | if err := errval(C.f_readdir(f.dirptr(), &info)); err != nil { 484 | return nil, err 485 | } 486 | if fname := gostring(&info.fname[0]); fname == "" { 487 | return infos, nil 488 | } else { 489 | infos = append(infos, &Info{ 490 | size: int64(info.fsize), 491 | name: fname, 492 | attr: FileAttr(info.fattrib), 493 | }) 494 | } 495 | } 496 | } 497 | 498 | func cstring(s string) *C.char { 499 | return (*C.char)(util.CString(s)) 500 | } 501 | 502 | func gostring(s *C.char) string { 503 | return util.GoString(unsafe.Pointer(s)) 504 | } 505 | 506 | func errval(errno C.FRESULT) error { 507 | if errno > FileResultOK { 508 | return FileResult(errno) 509 | } 510 | return nil 511 | } 512 | -------------------------------------------------------------------------------- /fatfs/ff.h: -------------------------------------------------------------------------------- 1 | /* This file is part of ooFatFs, a customised version of FatFs 2 | * See https://github.com/micropython/oofatfs for details 3 | */ 4 | 5 | /*----------------------------------------------------------------------------/ 6 | / FatFs - Generic FAT Filesystem module R0.13c / 7 | /-----------------------------------------------------------------------------/ 8 | / 9 | / Copyright (C) 2018, ChaN, all right reserved. 10 | / 11 | / FatFs module is an open source software. Redistribution and use of FatFs in 12 | / source and binary forms, with or without modification, are permitted provided 13 | / that the following condition is met: 14 | 15 | / 1. Redistributions of source code must retain the above copyright notice, 16 | / this condition and the following disclaimer. 17 | / 18 | / This software is provided by the copyright holder and contributors "AS IS" 19 | / and any warranties related to this software are DISCLAIMED. 20 | / The copyright owner or contributors be NOT LIABLE for any damages caused 21 | / by use of this software. 22 | / 23 | /----------------------------------------------------------------------------*/ 24 | 25 | 26 | #ifndef FF_DEFINED 27 | #define FF_DEFINED 86604 /* Revision ID */ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | #include "ffconf.h" //FFCONF_H /* FatFs configuration options */ 34 | #include "integer.h" 35 | 36 | #if FF_DEFINED != FFCONF_DEF 37 | #error Wrong configuration file (ffconf.h). 38 | #endif 39 | 40 | 41 | /* Integer types used for FatFs API */ 42 | // 43 | //#if defined(_WIN32) /* Main development platform */ 44 | //#define FF_INTDEF 2 45 | //#include 46 | //typedef unsigned __int64 QWORD; 47 | //#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */ 48 | //#define FF_INTDEF 2 49 | //#include 50 | //typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ 51 | //typedef unsigned char BYTE; /* char must be 8-bit */ 52 | //typedef uint16_t WORD; /* 16-bit unsigned integer */ 53 | //typedef uint16_t WCHAR; /* 16-bit unsigned integer */ 54 | //typedef uint32_t DWORD; /* 32-bit unsigned integer */ 55 | //typedef uint64_t QWORD; /* 64-bit unsigned integer */ 56 | //#else /* Earlier than C99 */ 57 | //#define FF_INTDEF 1 58 | //typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ 59 | //typedef unsigned char BYTE; /* char must be 8-bit */ 60 | //typedef unsigned short WORD; /* 16-bit unsigned integer */ 61 | //typedef unsigned short WCHAR; /* 16-bit unsigned integer */ 62 | //typedef unsigned long DWORD; /* 32-bit unsigned integer */ 63 | //#endif 64 | 65 | 66 | /* Definitions of volume management */ 67 | 68 | #if FF_STR_VOLUME_ID 69 | #ifndef FF_VOLUME_STRS 70 | extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */ 71 | #endif 72 | #endif 73 | 74 | 75 | 76 | /* Type of path name strings on FatFs API */ 77 | 78 | #ifndef _INC_TCHAR 79 | #define _INC_TCHAR 80 | 81 | #if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */ 82 | typedef WCHAR TCHAR; 83 | #define _T(x) L ## x 84 | #define _TEXT(x) L ## x 85 | #elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */ 86 | typedef char TCHAR; 87 | #define _T(x) u8 ## x 88 | #define _TEXT(x) u8 ## x 89 | #elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */ 90 | typedef DWORD TCHAR; 91 | #define _T(x) U ## x 92 | #define _TEXT(x) U ## x 93 | #elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3) 94 | #error Wrong FF_LFN_UNICODE setting 95 | #else /* ANSI/OEM code in SBCS/DBCS */ 96 | typedef char TCHAR; 97 | #define _T(x) x 98 | #define _TEXT(x) x 99 | #endif 100 | 101 | #endif 102 | 103 | 104 | 105 | /* Type of file size variables */ 106 | 107 | #if FF_FS_EXFAT 108 | #if FF_INTDEF != 2 109 | #error exFAT feature wants C99 or later 110 | #endif 111 | typedef QWORD FSIZE_t; 112 | #else 113 | typedef DWORD FSIZE_t; 114 | #endif 115 | 116 | 117 | 118 | /* Filesystem object structure (FATFS) */ 119 | 120 | typedef struct { 121 | void *drv; // block device underlying this filesystem 122 | #if FF_MULTI_PARTITION /* Multiple partition configuration */ 123 | BYTE part; // Partition: 0:Auto detect, 1-4:Forced partition 124 | #endif 125 | BYTE fs_type; /* Filesystem type (0:not mounted) */ 126 | BYTE n_fats; /* Number of FATs (1 or 2) */ 127 | BYTE wflag; /* win[] flag (b0:dirty) */ 128 | BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */ 129 | WORD id; /* Volume mount ID */ 130 | WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ 131 | WORD csize; /* Cluster size [sectors] */ 132 | #if FF_MAX_SS != FF_MIN_SS 133 | WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */ 134 | #endif 135 | #if FF_USE_LFN 136 | WCHAR* lfnbuf; /* LFN working buffer */ 137 | #endif 138 | #if FF_FS_EXFAT 139 | BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */ 140 | #endif 141 | #if FF_FS_REENTRANT 142 | FF_SYNC_t sobj; /* Identifier of sync object */ 143 | #endif 144 | #if !FF_FS_READONLY 145 | DWORD last_clst; /* Last allocated cluster */ 146 | DWORD free_clst; /* Number of free clusters */ 147 | #endif 148 | #if FF_FS_RPATH 149 | DWORD cdir; /* Current directory start cluster (0:root) */ 150 | #if FF_FS_EXFAT 151 | DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */ 152 | DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */ 153 | DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */ 154 | #endif 155 | #endif 156 | DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */ 157 | DWORD fsize; /* Size of an FAT [sectors] */ 158 | DWORD volbase; /* Volume base sector */ 159 | DWORD fatbase; /* FAT base sector */ 160 | DWORD dirbase; /* Root directory base sector/cluster */ 161 | DWORD database; /* Data base sector */ 162 | #if FF_FS_EXFAT 163 | DWORD bitbase; /* Allocation bitmap base sector */ 164 | #endif 165 | DWORD winsect; /* Current sector appearing in the win[] */ 166 | BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ 167 | } FATFS; 168 | 169 | 170 | 171 | /* Object ID and allocation information (FFOBJID) */ 172 | 173 | typedef struct { 174 | FATFS* fs; /* Pointer to the hosting volume of this object */ 175 | WORD id; /* Hosting volume mount ID */ 176 | BYTE attr; /* Object attribute */ 177 | BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */ 178 | DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */ 179 | FSIZE_t objsize; /* Object size (valid when sclust != 0) */ 180 | #if FF_FS_EXFAT 181 | DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */ 182 | DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */ 183 | DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */ 184 | DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */ 185 | DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */ 186 | #endif 187 | #if FF_FS_LOCK 188 | UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */ 189 | #endif 190 | } FFOBJID; 191 | 192 | 193 | 194 | /* File object structure (FIL) */ 195 | 196 | typedef struct { 197 | FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */ 198 | BYTE flag; /* File status flags */ 199 | BYTE err; /* Abort flag (error code) */ 200 | FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */ 201 | DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */ 202 | DWORD sect; /* Sector number appearing in buf[] (0:invalid) */ 203 | #if !FF_FS_READONLY 204 | DWORD dir_sect; /* Sector number containing the directory entry (not used at exFAT) */ 205 | BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */ 206 | #endif 207 | #if FF_USE_FASTSEEK 208 | DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */ 209 | #endif 210 | #if !FF_FS_TINY 211 | BYTE buf[FF_MAX_SS]; /* File private data read/write window */ 212 | #endif 213 | } FIL; 214 | 215 | 216 | 217 | /* Directory object structure (FF_DIR) */ 218 | 219 | typedef struct { 220 | FFOBJID obj; /* Object identifier */ 221 | DWORD dptr; /* Current read/write offset */ 222 | DWORD clust; /* Current cluster */ 223 | DWORD sect; /* Current sector (0:Read operation has terminated) */ 224 | BYTE* dir; /* Pointer to the directory item in the win[] */ 225 | BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */ 226 | #if FF_USE_LFN 227 | DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */ 228 | #endif 229 | #if FF_USE_FIND 230 | const TCHAR* pat; /* Pointer to the name matching pattern */ 231 | #endif 232 | } FF_DIR; 233 | 234 | 235 | 236 | /* File information structure (FILINFO) */ 237 | 238 | typedef struct { 239 | FSIZE_t fsize; /* File size */ 240 | WORD fdate; /* Modified date */ 241 | WORD ftime; /* Modified time */ 242 | BYTE fattrib; /* File attribute */ 243 | #if FF_USE_LFN 244 | TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */ 245 | TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ 246 | #else 247 | TCHAR fname[12 + 1]; /* File name */ 248 | #endif 249 | } FILINFO; 250 | 251 | 252 | 253 | /* File function return code (FRESULT) */ 254 | 255 | typedef enum { 256 | FR_OK = 0, /* (0) Succeeded */ 257 | FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */ 258 | FR_INT_ERR, /* (2) Assertion failed */ 259 | FR_NOT_READY, /* (3) The physical drive cannot work */ 260 | FR_NO_FILE, /* (4) Could not find the file */ 261 | FR_NO_PATH, /* (5) Could not find the path */ 262 | FR_INVALID_NAME, /* (6) The path name format is invalid */ 263 | FR_DENIED, /* (7) Access denied due to prohibited access or directory full */ 264 | FR_EXIST, /* (8) Access denied due to prohibited access */ 265 | FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */ 266 | FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */ 267 | FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */ 268 | FR_NOT_ENABLED, /* (12) The volume has no work area */ 269 | FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ 270 | FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */ 271 | FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ 272 | FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ 273 | FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ 274 | FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */ 275 | FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ 276 | } FRESULT; 277 | 278 | 279 | 280 | /*--------------------------------------------------------------*/ 281 | /* FatFs module application interface */ 282 | 283 | FRESULT f_open (FATFS *fs, FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */ 284 | FRESULT f_close (FIL* fp); /* Close an open file object */ 285 | FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */ 286 | FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */ 287 | FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ 288 | FRESULT f_truncate (FIL* fp); /* Truncate the file */ 289 | FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */ 290 | FRESULT f_opendir (FATFS *fs, FF_DIR* dp, const TCHAR* path); /* Open a directory */ 291 | FRESULT f_closedir (FF_DIR* dp); /* Close an open directory */ 292 | FRESULT f_readdir (FF_DIR* dp, FILINFO* fno); /* Read a directory item */ 293 | FRESULT f_findfirst (FF_DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ 294 | FRESULT f_findnext (FF_DIR* dp, FILINFO* fno); /* Find next file */ 295 | FRESULT f_mkdir (FATFS *fs, const TCHAR* path); /* Create a sub directory */ 296 | FRESULT f_unlink (FATFS *fs, const TCHAR* path); /* Delete an existing file or directory */ 297 | FRESULT f_rename (FATFS *fs, const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ 298 | FRESULT f_stat (FATFS *fs, const TCHAR* path, FILINFO* fno); /* Get file status */ 299 | FRESULT f_chmod (FATFS *fs, const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ 300 | FRESULT f_utime (FATFS *fs, const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */ 301 | FRESULT f_chdir (FATFS *fs, const TCHAR* path); /* Change current directory */ 302 | FRESULT f_getcwd (FATFS *fs, TCHAR* buff, UINT len); /* Get current directory */ 303 | FRESULT f_getfree (FATFS *fs, DWORD* nclst); /* Get number of free clusters on the drive */ 304 | FRESULT f_getlabel (FATFS *fs, TCHAR* label, DWORD* vsn); /* Get volume label */ 305 | FRESULT f_setlabel (FATFS *fs, const TCHAR* label); /* Set volume label */ 306 | FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */ 307 | FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */ 308 | FRESULT f_mount (FATFS* fs); /* Mount/Unmount a logical drive */ 309 | FRESULT f_umount (FATFS* fs); /* Unmount a logical drive */ 310 | FRESULT f_mkfs (FATFS *fs, BYTE opt, DWORD au, void* work, UINT len); /* Create a FAT volume */ 311 | FRESULT f_fdisk (void *pdrv, const DWORD* szt, void* work); /* Divide a physical drive into some partitions */ 312 | FRESULT f_setcp (WORD cp); /* Set current code page */ 313 | FRESULT f_repair (FATFS* fs, void* work, UINT len); /* Free unreferenced clusters from the FAT */ 314 | 315 | #define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) 316 | #define f_error(fp) ((fp)->err) 317 | #define f_tell(fp) ((fp)->fptr) 318 | #define f_size(fp) ((fp)->obj.objsize) 319 | #define f_rewind(fp) f_lseek((fp), 0) 320 | #define f_rewinddir(dp) f_readdir((dp), 0) 321 | #define f_rmdir(path) f_unlink(path) 322 | #define f_unmount(path) f_mount(0, path, 0) 323 | 324 | #ifndef EOF 325 | #define EOF (-1) 326 | #endif 327 | 328 | 329 | 330 | 331 | /*--------------------------------------------------------------*/ 332 | /* Additional user defined functions */ 333 | 334 | /* RTC function */ 335 | #if !FF_FS_READONLY && !FF_FS_NORTC 336 | DWORD get_fattime (void); 337 | #endif 338 | 339 | /* LFN support functions */ 340 | #if FF_USE_LFN >= 1 /* Code conversion (defined in unicode.c) */ 341 | WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */ 342 | WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */ 343 | DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */ 344 | #endif 345 | #if FF_USE_LFN == 3 /* Dynamic memory allocation */ 346 | void* ff_memalloc (UINT msize); /* Allocate memory block */ 347 | void ff_memfree (void* mblock); /* Free memory block */ 348 | #endif 349 | 350 | /* Sync functions */ 351 | #if FF_FS_REENTRANT 352 | int ff_cre_syncobj (FATFS *fatfs, FF_SYNC_t* sobj); /* Create a sync object */ 353 | int ff_req_grant (FF_SYNC_t sobj); /* Lock sync object */ 354 | void ff_rel_grant (FF_SYNC_t sobj); /* Unlock sync object */ 355 | int ff_del_syncobj (FF_SYNC_t sobj); /* Delete a sync object */ 356 | #endif 357 | 358 | 359 | 360 | 361 | /*--------------------------------------------------------------*/ 362 | /* Flags and offset address */ 363 | 364 | 365 | /* File access mode and open method flags (3rd argument of f_open) */ 366 | #define FA_READ 0x01 367 | #define FA_WRITE 0x02 368 | #define FA_OPEN_EXISTING 0x00 369 | #define FA_CREATE_NEW 0x04 370 | #define FA_CREATE_ALWAYS 0x08 371 | #define FA_OPEN_ALWAYS 0x10 372 | #define FA_OPEN_APPEND 0x30 373 | 374 | /* Fast seek controls (2nd argument of f_lseek) */ 375 | #define CREATE_LINKMAP ((FSIZE_t)0 - 1) 376 | 377 | /* Format options (2nd argument of f_mkfs) */ 378 | #define FM_FAT 0x01 379 | #define FM_FAT32 0x02 380 | #define FM_EXFAT 0x04 381 | #define FM_ANY 0x07 382 | #define FM_SFD 0x08 383 | 384 | /* Filesystem type (FATFS.fs_type) */ 385 | #define FS_FAT12 1 386 | #define FS_FAT16 2 387 | #define FS_FAT32 3 388 | #define FS_EXFAT 4 389 | 390 | /* File attribute bits for directory entry (FILINFO.fattrib) */ 391 | #define AM_RDO 0x01 /* Read only */ 392 | #define AM_HID 0x02 /* Hidden */ 393 | #define AM_SYS 0x04 /* System */ 394 | #define AM_DIR 0x10 /* Directory */ 395 | #define AM_ARC 0x20 /* Archive */ 396 | 397 | 398 | #ifdef __cplusplus 399 | } 400 | #endif 401 | 402 | #endif /* FF_DEFINED */ 403 | -------------------------------------------------------------------------------- /littlefs/lfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The little filesystem 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #ifndef LFS_H 9 | #define LFS_H 10 | 11 | #include "lfs_util.h" 12 | 13 | #ifdef __cplusplus 14 | extern "C" 15 | { 16 | #endif 17 | 18 | 19 | /// Version info /// 20 | 21 | // Software library version 22 | // Major (top-nibble), incremented on backwards incompatible changes 23 | // Minor (bottom-nibble), incremented on feature additions 24 | #define LFS_VERSION 0x00020008 25 | #define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) 26 | #define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) 27 | 28 | // Version of On-disk data structures 29 | // Major (top-nibble), incremented on backwards incompatible changes 30 | // Minor (bottom-nibble), incremented on feature additions 31 | #define LFS_DISK_VERSION 0x00020001 32 | #define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) 33 | #define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) 34 | 35 | 36 | /// Definitions /// 37 | 38 | // Type definitions 39 | typedef uint32_t lfs_size_t; 40 | typedef uint32_t lfs_off_t; 41 | 42 | typedef int32_t lfs_ssize_t; 43 | typedef int32_t lfs_soff_t; 44 | 45 | typedef uint32_t lfs_block_t; 46 | 47 | // Maximum name size in bytes, may be redefined to reduce the size of the 48 | // info struct. Limited to <= 1022. Stored in superblock and must be 49 | // respected by other littlefs drivers. 50 | #ifndef LFS_NAME_MAX 51 | #define LFS_NAME_MAX 255 52 | #endif 53 | 54 | // Maximum size of a file in bytes, may be redefined to limit to support other 55 | // drivers. Limited on disk to <= 4294967296. However, above 2147483647 the 56 | // functions lfs_file_seek, lfs_file_size, and lfs_file_tell will return 57 | // incorrect values due to using signed integers. Stored in superblock and 58 | // must be respected by other littlefs drivers. 59 | #ifndef LFS_FILE_MAX 60 | #define LFS_FILE_MAX 2147483647 61 | #endif 62 | 63 | // Maximum size of custom attributes in bytes, may be redefined, but there is 64 | // no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. 65 | #ifndef LFS_ATTR_MAX 66 | #define LFS_ATTR_MAX 1022 67 | #endif 68 | 69 | // Possible error codes, these are negative to allow 70 | // valid positive return values 71 | enum lfs_error { 72 | LFS_ERR_OK = 0, // No error 73 | LFS_ERR_IO = -5, // Error during device operation 74 | LFS_ERR_CORRUPT = -84, // Corrupted 75 | LFS_ERR_NOENT = -2, // No directory entry 76 | LFS_ERR_EXIST = -17, // Entry already exists 77 | LFS_ERR_NOTDIR = -20, // Entry is not a dir 78 | LFS_ERR_ISDIR = -21, // Entry is a dir 79 | LFS_ERR_NOTEMPTY = -39, // Dir is not empty 80 | LFS_ERR_BADF = -9, // Bad file number 81 | LFS_ERR_FBIG = -27, // File too large 82 | LFS_ERR_INVAL = -22, // Invalid parameter 83 | LFS_ERR_NOSPC = -28, // No space left on device 84 | LFS_ERR_NOMEM = -12, // No more memory available 85 | LFS_ERR_NOATTR = -61, // No data/attr available 86 | LFS_ERR_NAMETOOLONG = -36, // File name too long 87 | }; 88 | 89 | // File types 90 | enum lfs_type { 91 | // file types 92 | LFS_TYPE_REG = 0x001, 93 | LFS_TYPE_DIR = 0x002, 94 | 95 | // internally used types 96 | LFS_TYPE_SPLICE = 0x400, 97 | LFS_TYPE_NAME = 0x000, 98 | LFS_TYPE_STRUCT = 0x200, 99 | LFS_TYPE_USERATTR = 0x300, 100 | LFS_TYPE_FROM = 0x100, 101 | LFS_TYPE_TAIL = 0x600, 102 | LFS_TYPE_GLOBALS = 0x700, 103 | LFS_TYPE_CRC = 0x500, 104 | 105 | // internally used type specializations 106 | LFS_TYPE_CREATE = 0x401, 107 | LFS_TYPE_DELETE = 0x4ff, 108 | LFS_TYPE_SUPERBLOCK = 0x0ff, 109 | LFS_TYPE_DIRSTRUCT = 0x200, 110 | LFS_TYPE_CTZSTRUCT = 0x202, 111 | LFS_TYPE_INLINESTRUCT = 0x201, 112 | LFS_TYPE_SOFTTAIL = 0x600, 113 | LFS_TYPE_HARDTAIL = 0x601, 114 | LFS_TYPE_MOVESTATE = 0x7ff, 115 | LFS_TYPE_CCRC = 0x500, 116 | LFS_TYPE_FCRC = 0x5ff, 117 | 118 | // internal chip sources 119 | LFS_FROM_NOOP = 0x000, 120 | LFS_FROM_MOVE = 0x101, 121 | LFS_FROM_USERATTRS = 0x102, 122 | }; 123 | 124 | // File open flags 125 | enum lfs_open_flags { 126 | // open flags 127 | LFS_O_RDONLY = 1, // Open a file as read only 128 | #ifndef LFS_READONLY 129 | LFS_O_WRONLY = 2, // Open a file as write only 130 | LFS_O_RDWR = 3, // Open a file as read and write 131 | LFS_O_CREAT = 0x0100, // Create a file if it does not exist 132 | LFS_O_EXCL = 0x0200, // Fail if a file already exists 133 | LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size 134 | LFS_O_APPEND = 0x0800, // Move to end of file on every write 135 | #endif 136 | 137 | // internally used flags 138 | #ifndef LFS_READONLY 139 | LFS_F_DIRTY = 0x010000, // File does not match storage 140 | LFS_F_WRITING = 0x020000, // File has been written since last flush 141 | #endif 142 | LFS_F_READING = 0x040000, // File has been read since last flush 143 | #ifndef LFS_READONLY 144 | LFS_F_ERRED = 0x080000, // An error occurred during write 145 | #endif 146 | LFS_F_INLINE = 0x100000, // Currently inlined in directory entry 147 | }; 148 | 149 | // File seek flags 150 | enum lfs_whence_flags { 151 | LFS_SEEK_SET = 0, // Seek relative to an absolute position 152 | LFS_SEEK_CUR = 1, // Seek relative to the current file position 153 | LFS_SEEK_END = 2, // Seek relative to the end of the file 154 | }; 155 | 156 | 157 | // Configuration provided during initialization of the littlefs 158 | struct lfs_config { 159 | // Opaque user provided context that can be used to pass 160 | // information to the block device operations 161 | void *context; 162 | 163 | // Read a region in a block. Negative error codes are propagated 164 | // to the user. 165 | int (*read)(const struct lfs_config *c, lfs_block_t block, 166 | lfs_off_t off, void *buffer, lfs_size_t size); 167 | 168 | // Program a region in a block. The block must have previously 169 | // been erased. Negative error codes are propagated to the user. 170 | // May return LFS_ERR_CORRUPT if the block should be considered bad. 171 | int (*prog)(const struct lfs_config *c, lfs_block_t block, 172 | lfs_off_t off, const void *buffer, lfs_size_t size); 173 | 174 | // Erase a block. A block must be erased before being programmed. 175 | // The state of an erased block is undefined. Negative error codes 176 | // are propagated to the user. 177 | // May return LFS_ERR_CORRUPT if the block should be considered bad. 178 | int (*erase)(const struct lfs_config *c, lfs_block_t block); 179 | 180 | // Sync the state of the underlying block device. Negative error codes 181 | // are propagated to the user. 182 | int (*sync)(const struct lfs_config *c); 183 | 184 | #ifdef LFS_THREADSAFE 185 | // Lock the underlying block device. Negative error codes 186 | // are propagated to the user. 187 | int (*lock)(const struct lfs_config *c); 188 | 189 | // Unlock the underlying block device. Negative error codes 190 | // are propagated to the user. 191 | int (*unlock)(const struct lfs_config *c); 192 | #endif 193 | 194 | // Minimum size of a block read in bytes. All read operations will be a 195 | // multiple of this value. 196 | lfs_size_t read_size; 197 | 198 | // Minimum size of a block program in bytes. All program operations will be 199 | // a multiple of this value. 200 | lfs_size_t prog_size; 201 | 202 | // Size of an erasable block in bytes. This does not impact ram consumption 203 | // and may be larger than the physical erase size. However, non-inlined 204 | // files take up at minimum one block. Must be a multiple of the read and 205 | // program sizes. 206 | lfs_size_t block_size; 207 | 208 | // Number of erasable blocks on the device. 209 | lfs_size_t block_count; 210 | 211 | // Number of erase cycles before littlefs evicts metadata logs and moves 212 | // the metadata to another block. Suggested values are in the 213 | // range 100-1000, with large values having better performance at the cost 214 | // of less consistent wear distribution. 215 | // 216 | // Set to -1 to disable block-level wear-leveling. 217 | int32_t block_cycles; 218 | 219 | // Size of block caches in bytes. Each cache buffers a portion of a block in 220 | // RAM. The littlefs needs a read cache, a program cache, and one additional 221 | // cache per file. Larger caches can improve performance by storing more 222 | // data and reducing the number of disk accesses. Must be a multiple of the 223 | // read and program sizes, and a factor of the block size. 224 | lfs_size_t cache_size; 225 | 226 | // Size of the lookahead buffer in bytes. A larger lookahead buffer 227 | // increases the number of blocks found during an allocation pass. The 228 | // lookahead buffer is stored as a compact bitmap, so each byte of RAM 229 | // can track 8 blocks. Must be a multiple of 8. 230 | lfs_size_t lookahead_size; 231 | 232 | // Optional statically allocated read buffer. Must be cache_size. 233 | // By default lfs_malloc is used to allocate this buffer. 234 | void *read_buffer; 235 | 236 | // Optional statically allocated program buffer. Must be cache_size. 237 | // By default lfs_malloc is used to allocate this buffer. 238 | void *prog_buffer; 239 | 240 | // Optional statically allocated lookahead buffer. Must be lookahead_size 241 | // and aligned to a 32-bit boundary. By default lfs_malloc is used to 242 | // allocate this buffer. 243 | void *lookahead_buffer; 244 | 245 | // Optional upper limit on length of file names in bytes. No downside for 246 | // larger names except the size of the info struct which is controlled by 247 | // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in 248 | // superblock and must be respected by other littlefs drivers. 249 | lfs_size_t name_max; 250 | 251 | // Optional upper limit on files in bytes. No downside for larger files 252 | // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX when zero. Stored 253 | // in superblock and must be respected by other littlefs drivers. 254 | lfs_size_t file_max; 255 | 256 | // Optional upper limit on custom attributes in bytes. No downside for 257 | // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to 258 | // LFS_ATTR_MAX when zero. 259 | lfs_size_t attr_max; 260 | 261 | // Optional upper limit on total space given to metadata pairs in bytes. On 262 | // devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB) 263 | // can help bound the metadata compaction time. Must be <= block_size. 264 | // Defaults to block_size when zero. 265 | lfs_size_t metadata_max; 266 | 267 | #ifdef LFS_MULTIVERSION 268 | // On-disk version to use when writing in the form of 16-bit major version 269 | // + 16-bit minor version. This limiting metadata to what is supported by 270 | // older minor versions. Note that some features will be lost. Defaults to 271 | // to the most recent minor version when zero. 272 | uint32_t disk_version; 273 | #endif 274 | }; 275 | 276 | // File info structure 277 | struct lfs_info { 278 | // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR 279 | uint8_t type; 280 | 281 | // Size of the file, only valid for REG files. Limited to 32-bits. 282 | lfs_size_t size; 283 | 284 | // Name of the file stored as a null-terminated string. Limited to 285 | // LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to 286 | // reduce RAM. LFS_NAME_MAX is stored in superblock and must be 287 | // respected by other littlefs drivers. 288 | char name[LFS_NAME_MAX+1]; 289 | }; 290 | 291 | // Filesystem info structure 292 | struct lfs_fsinfo { 293 | // On-disk version. 294 | uint32_t disk_version; 295 | 296 | // Size of a logical block in bytes. 297 | lfs_size_t block_size; 298 | 299 | // Number of logical blocks in filesystem. 300 | lfs_size_t block_count; 301 | 302 | // Upper limit on the length of file names in bytes. 303 | lfs_size_t name_max; 304 | 305 | // Upper limit on the size of files in bytes. 306 | lfs_size_t file_max; 307 | 308 | // Upper limit on the size of custom attributes in bytes. 309 | lfs_size_t attr_max; 310 | }; 311 | 312 | // Custom attribute structure, used to describe custom attributes 313 | // committed atomically during file writes. 314 | struct lfs_attr { 315 | // 8-bit type of attribute, provided by user and used to 316 | // identify the attribute 317 | uint8_t type; 318 | 319 | // Pointer to buffer containing the attribute 320 | void *buffer; 321 | 322 | // Size of attribute in bytes, limited to LFS_ATTR_MAX 323 | lfs_size_t size; 324 | }; 325 | 326 | // Optional configuration provided during lfs_file_opencfg 327 | struct lfs_file_config { 328 | // Optional statically allocated file buffer. Must be cache_size. 329 | // By default lfs_malloc is used to allocate this buffer. 330 | void *buffer; 331 | 332 | // Optional list of custom attributes related to the file. If the file 333 | // is opened with read access, these attributes will be read from disk 334 | // during the open call. If the file is opened with write access, the 335 | // attributes will be written to disk every file sync or close. This 336 | // write occurs atomically with update to the file's contents. 337 | // 338 | // Custom attributes are uniquely identified by an 8-bit type and limited 339 | // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller 340 | // than the buffer, it will be padded with zeros. If the stored attribute 341 | // is larger, then it will be silently truncated. If the attribute is not 342 | // found, it will be created implicitly. 343 | struct lfs_attr *attrs; 344 | 345 | // Number of custom attributes in the list 346 | lfs_size_t attr_count; 347 | }; 348 | 349 | 350 | /// internal littlefs data structures /// 351 | typedef struct lfs_cache { 352 | lfs_block_t block; 353 | lfs_off_t off; 354 | lfs_size_t size; 355 | uint8_t *buffer; 356 | } lfs_cache_t; 357 | 358 | typedef struct lfs_mdir { 359 | lfs_block_t pair[2]; 360 | uint32_t rev; 361 | lfs_off_t off; 362 | uint32_t etag; 363 | uint16_t count; 364 | bool erased; 365 | bool split; 366 | lfs_block_t tail[2]; 367 | } lfs_mdir_t; 368 | 369 | // littlefs directory type 370 | typedef struct lfs_dir { 371 | struct lfs_dir *next; 372 | uint16_t id; 373 | uint8_t type; 374 | lfs_mdir_t m; 375 | 376 | lfs_off_t pos; 377 | lfs_block_t head[2]; 378 | } lfs_dir_t; 379 | 380 | // littlefs file type 381 | typedef struct lfs_file { 382 | struct lfs_file *next; 383 | uint16_t id; 384 | uint8_t type; 385 | lfs_mdir_t m; 386 | 387 | struct lfs_ctz { 388 | lfs_block_t head; 389 | lfs_size_t size; 390 | } ctz; 391 | 392 | uint32_t flags; 393 | lfs_off_t pos; 394 | lfs_block_t block; 395 | lfs_off_t off; 396 | lfs_cache_t cache; 397 | 398 | const struct lfs_file_config *cfg; 399 | } lfs_file_t; 400 | 401 | typedef struct lfs_superblock { 402 | uint32_t version; 403 | lfs_size_t block_size; 404 | lfs_size_t block_count; 405 | lfs_size_t name_max; 406 | lfs_size_t file_max; 407 | lfs_size_t attr_max; 408 | } lfs_superblock_t; 409 | 410 | typedef struct lfs_gstate { 411 | uint32_t tag; 412 | lfs_block_t pair[2]; 413 | } lfs_gstate_t; 414 | 415 | // The littlefs filesystem type 416 | typedef struct lfs { 417 | lfs_cache_t rcache; 418 | lfs_cache_t pcache; 419 | 420 | lfs_block_t root[2]; 421 | struct lfs_mlist { 422 | struct lfs_mlist *next; 423 | uint16_t id; 424 | uint8_t type; 425 | lfs_mdir_t m; 426 | } *mlist; 427 | uint32_t seed; 428 | 429 | lfs_gstate_t gstate; 430 | lfs_gstate_t gdisk; 431 | lfs_gstate_t gdelta; 432 | 433 | struct lfs_free { 434 | lfs_block_t off; 435 | lfs_block_t size; 436 | lfs_block_t i; 437 | lfs_block_t ack; 438 | uint32_t *buffer; 439 | } free; 440 | 441 | const struct lfs_config *cfg; 442 | lfs_size_t block_count; 443 | lfs_size_t name_max; 444 | lfs_size_t file_max; 445 | lfs_size_t attr_max; 446 | 447 | #ifdef LFS_MIGRATE 448 | struct lfs1 *lfs1; 449 | #endif 450 | } lfs_t; 451 | 452 | 453 | /// Filesystem functions /// 454 | 455 | #ifndef LFS_READONLY 456 | // Format a block device with the littlefs 457 | // 458 | // Requires a littlefs object and config struct. This clobbers the littlefs 459 | // object, and does not leave the filesystem mounted. The config struct must 460 | // be zeroed for defaults and backwards compatibility. 461 | // 462 | // Returns a negative error code on failure. 463 | int lfs_format(lfs_t *lfs, const struct lfs_config *config); 464 | #endif 465 | 466 | // Mounts a littlefs 467 | // 468 | // Requires a littlefs object and config struct. Multiple filesystems 469 | // may be mounted simultaneously with multiple littlefs objects. Both 470 | // lfs and config must be allocated while mounted. The config struct must 471 | // be zeroed for defaults and backwards compatibility. 472 | // 473 | // Returns a negative error code on failure. 474 | int lfs_mount(lfs_t *lfs, const struct lfs_config *config); 475 | 476 | // Unmounts a littlefs 477 | // 478 | // Does nothing besides releasing any allocated resources. 479 | // Returns a negative error code on failure. 480 | int lfs_unmount(lfs_t *lfs); 481 | 482 | /// General operations /// 483 | 484 | #ifndef LFS_READONLY 485 | // Removes a file or directory 486 | // 487 | // If removing a directory, the directory must be empty. 488 | // Returns a negative error code on failure. 489 | int lfs_remove(lfs_t *lfs, const char *path); 490 | #endif 491 | 492 | #ifndef LFS_READONLY 493 | // Rename or move a file or directory 494 | // 495 | // If the destination exists, it must match the source in type. 496 | // If the destination is a directory, the directory must be empty. 497 | // 498 | // Returns a negative error code on failure. 499 | int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); 500 | #endif 501 | 502 | // Find info about a file or directory 503 | // 504 | // Fills out the info structure, based on the specified file or directory. 505 | // Returns a negative error code on failure. 506 | int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); 507 | 508 | // Get a custom attribute 509 | // 510 | // Custom attributes are uniquely identified by an 8-bit type and limited 511 | // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than 512 | // the buffer, it will be padded with zeros. If the stored attribute is larger, 513 | // then it will be silently truncated. If no attribute is found, the error 514 | // LFS_ERR_NOATTR is returned and the buffer is filled with zeros. 515 | // 516 | // Returns the size of the attribute, or a negative error code on failure. 517 | // Note, the returned size is the size of the attribute on disk, irrespective 518 | // of the size of the buffer. This can be used to dynamically allocate a buffer 519 | // or check for existence. 520 | lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, 521 | uint8_t type, void *buffer, lfs_size_t size); 522 | 523 | #ifndef LFS_READONLY 524 | // Set custom attributes 525 | // 526 | // Custom attributes are uniquely identified by an 8-bit type and limited 527 | // to LFS_ATTR_MAX bytes. If an attribute is not found, it will be 528 | // implicitly created. 529 | // 530 | // Returns a negative error code on failure. 531 | int lfs_setattr(lfs_t *lfs, const char *path, 532 | uint8_t type, const void *buffer, lfs_size_t size); 533 | #endif 534 | 535 | #ifndef LFS_READONLY 536 | // Removes a custom attribute 537 | // 538 | // If an attribute is not found, nothing happens. 539 | // 540 | // Returns a negative error code on failure. 541 | int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); 542 | #endif 543 | 544 | 545 | /// File operations /// 546 | 547 | #ifndef LFS_NO_MALLOC 548 | // Open a file 549 | // 550 | // The mode that the file is opened in is determined by the flags, which 551 | // are values from the enum lfs_open_flags that are bitwise-ored together. 552 | // 553 | // Returns a negative error code on failure. 554 | int lfs_file_open(lfs_t *lfs, lfs_file_t *file, 555 | const char *path, int flags); 556 | 557 | // if LFS_NO_MALLOC is defined, lfs_file_open() will fail with LFS_ERR_NOMEM 558 | // thus use lfs_file_opencfg() with config.buffer set. 559 | #endif 560 | 561 | // Open a file with extra configuration 562 | // 563 | // The mode that the file is opened in is determined by the flags, which 564 | // are values from the enum lfs_open_flags that are bitwise-ored together. 565 | // 566 | // The config struct provides additional config options per file as described 567 | // above. The config struct must remain allocated while the file is open, and 568 | // the config struct must be zeroed for defaults and backwards compatibility. 569 | // 570 | // Returns a negative error code on failure. 571 | int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, 572 | const char *path, int flags, 573 | const struct lfs_file_config *config); 574 | 575 | // Close a file 576 | // 577 | // Any pending writes are written out to storage as though 578 | // sync had been called and releases any allocated resources. 579 | // 580 | // Returns a negative error code on failure. 581 | int lfs_file_close(lfs_t *lfs, lfs_file_t *file); 582 | 583 | // Synchronize a file on storage 584 | // 585 | // Any pending writes are written out to storage. 586 | // Returns a negative error code on failure. 587 | int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); 588 | 589 | // Read data from file 590 | // 591 | // Takes a buffer and size indicating where to store the read data. 592 | // Returns the number of bytes read, or a negative error code on failure. 593 | lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, 594 | void *buffer, lfs_size_t size); 595 | 596 | #ifndef LFS_READONLY 597 | // Write data to file 598 | // 599 | // Takes a buffer and size indicating the data to write. The file will not 600 | // actually be updated on the storage until either sync or close is called. 601 | // 602 | // Returns the number of bytes written, or a negative error code on failure. 603 | lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, 604 | const void *buffer, lfs_size_t size); 605 | #endif 606 | 607 | // Change the position of the file 608 | // 609 | // The change in position is determined by the offset and whence flag. 610 | // Returns the new position of the file, or a negative error code on failure. 611 | lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, 612 | lfs_soff_t off, int whence); 613 | 614 | #ifndef LFS_READONLY 615 | // Truncates the size of the file to the specified size 616 | // 617 | // Returns a negative error code on failure. 618 | int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); 619 | #endif 620 | 621 | // Return the position of the file 622 | // 623 | // Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) 624 | // Returns the position of the file, or a negative error code on failure. 625 | lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); 626 | 627 | // Change the position of the file to the beginning of the file 628 | // 629 | // Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET) 630 | // Returns a negative error code on failure. 631 | int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); 632 | 633 | // Return the size of the file 634 | // 635 | // Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) 636 | // Returns the size of the file, or a negative error code on failure. 637 | lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); 638 | 639 | 640 | /// Directory operations /// 641 | 642 | #ifndef LFS_READONLY 643 | // Create a directory 644 | // 645 | // Returns a negative error code on failure. 646 | int lfs_mkdir(lfs_t *lfs, const char *path); 647 | #endif 648 | 649 | // Open a directory 650 | // 651 | // Once open a directory can be used with read to iterate over files. 652 | // Returns a negative error code on failure. 653 | int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); 654 | 655 | // Close a directory 656 | // 657 | // Releases any allocated resources. 658 | // Returns a negative error code on failure. 659 | int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); 660 | 661 | // Read an entry in the directory 662 | // 663 | // Fills out the info structure, based on the specified file or directory. 664 | // Returns a positive value on success, 0 at the end of directory, 665 | // or a negative error code on failure. 666 | int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); 667 | 668 | // Change the position of the directory 669 | // 670 | // The new off must be a value previous returned from tell and specifies 671 | // an absolute offset in the directory seek. 672 | // 673 | // Returns a negative error code on failure. 674 | int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); 675 | 676 | // Return the position of the directory 677 | // 678 | // The returned offset is only meant to be consumed by seek and may not make 679 | // sense, but does indicate the current position in the directory iteration. 680 | // 681 | // Returns the position of the directory, or a negative error code on failure. 682 | lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); 683 | 684 | // Change the position of the directory to the beginning of the directory 685 | // 686 | // Returns a negative error code on failure. 687 | int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); 688 | 689 | 690 | /// Filesystem-level filesystem operations 691 | 692 | // Find on-disk info about the filesystem 693 | // 694 | // Fills out the fsinfo structure based on the filesystem found on-disk. 695 | // Returns a negative error code on failure. 696 | int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo); 697 | 698 | // Finds the current size of the filesystem 699 | // 700 | // Note: Result is best effort. If files share COW structures, the returned 701 | // size may be larger than the filesystem actually is. 702 | // 703 | // Returns the number of allocated blocks, or a negative error code on failure. 704 | lfs_ssize_t lfs_fs_size(lfs_t *lfs); 705 | 706 | // Traverse through all blocks in use by the filesystem 707 | // 708 | // The provided callback will be called with each block address that is 709 | // currently in use by the filesystem. This can be used to determine which 710 | // blocks are in use or how much of the storage is available. 711 | // 712 | // Returns a negative error code on failure. 713 | int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); 714 | 715 | // Attempt to proactively find free blocks 716 | // 717 | // Calling this function is not required, but may allowing the offloading of 718 | // the expensive block allocation scan to a less time-critical code path. 719 | // 720 | // Note: littlefs currently does not persist any found free blocks to disk. 721 | // This may change in the future. 722 | // 723 | // Returns a negative error code on failure. Finding no free blocks is 724 | // not an error. 725 | int lfs_fs_gc(lfs_t *lfs); 726 | 727 | #ifndef LFS_READONLY 728 | // Attempt to make the filesystem consistent and ready for writing 729 | // 730 | // Calling this function is not required, consistency will be implicitly 731 | // enforced on the first operation that writes to the filesystem, but this 732 | // function allows the work to be performed earlier and without other 733 | // filesystem changes. 734 | // 735 | // Returns a negative error code on failure. 736 | int lfs_fs_mkconsistent(lfs_t *lfs); 737 | #endif 738 | 739 | #ifndef LFS_READONLY 740 | // Grows the filesystem to a new size, updating the superblock with the new 741 | // block count. 742 | // 743 | // Note: This is irreversible. 744 | // 745 | // Returns a negative error code on failure. 746 | int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count); 747 | #endif 748 | 749 | #ifndef LFS_READONLY 750 | #ifdef LFS_MIGRATE 751 | // Attempts to migrate a previous version of littlefs 752 | // 753 | // Behaves similarly to the lfs_format function. Attempts to mount 754 | // the previous version of littlefs and update the filesystem so it can be 755 | // mounted with the current version of littlefs. 756 | // 757 | // Requires a littlefs object and config struct. This clobbers the littlefs 758 | // object, and does not leave the filesystem mounted. The config struct must 759 | // be zeroed for defaults and backwards compatibility. 760 | // 761 | // Returns a negative error code on failure. 762 | int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg); 763 | #endif 764 | #endif 765 | 766 | 767 | #ifdef __cplusplus 768 | } /* extern "C" */ 769 | #endif 770 | 771 | #endif --------------------------------------------------------------------------------