From 31b4edc67b75658ce5e2d41f2fc87331f4b26d49 Mon Sep 17 00:00:00 2001 From: bhgv Date: Sun, 10 May 2020 02:59:23 +0300 Subject: a try to add support of FreeRTOS riscV-64 (k210 cpu). first step --- FreeRTOS/riscv64/include/emu.h | 41 + FreeRTOS/riscv64/include/fpuctl.h | 0 FreeRTOS/riscv64/include/lib9.h | 512 +++++++ bld-lin-fb-k210.sh | 25 + bld-lin.sh | 2 + emu/FreeRTOS/arm-tas-v5.S | 18 + emu/FreeRTOS/arm-tas-v7.S | 30 + emu/FreeRTOS/asm-386.S | 51 + emu/FreeRTOS/asm-arm.S | 62 + emu/FreeRTOS/asm-mips.S | 28 + emu/FreeRTOS/asm-power.S | 61 + emu/FreeRTOS/asm-spim.S | 29 + emu/FreeRTOS/audio-oss.c | 441 ++++++ emu/FreeRTOS/cmd.c | 213 +++ emu/FreeRTOS/deveia.c | 44 + emu/FreeRTOS/devfs-posix.c | 1096 ++++++++++++++ emu/FreeRTOS/devfs.c | 26 + emu/FreeRTOS/emu | 112 ++ emu/FreeRTOS/emu-g | 101 ++ emu/FreeRTOS/emu-wrt | 111 ++ emu/FreeRTOS/kproc-xthreads.c | 264 ++++ emu/FreeRTOS/mk-wrt | 8 + emu/FreeRTOS/mkfile | 62 + emu/FreeRTOS/mkfile-386 | 0 emu/FreeRTOS/mkfile-arm | 2 + emu/FreeRTOS/mkfile-mips | 0 emu/FreeRTOS/mkfile-power | 0 emu/FreeRTOS/mkfile-riscv64 | 0 emu/FreeRTOS/os.c | 332 +++++ emu/FreeRTOS/segflush-386.c | 11 + emu/FreeRTOS/segflush-arm.c | 14 + emu/FreeRTOS/segflush-mips.S | 16 + emu/FreeRTOS/segflush-power.c | 34 + emu/FreeRTOS/segflush-riscv64.c | 11 + emu/FreeRTOS/segflush-spim.S | 16 + emu/FreeRTOS/win-fb.c | 1881 ++++++++++++++++++++++++ include/png/lodepng.h | 4 +- lib9/getcallerpc-FreeRTOS-riscv64.c | 12 + lib9/setfcr-FreeRTOS-riscv64.c | 64 + libdynld/dynld-riscv64.c | 42 + libinterp/comp-riscv64.c | 1998 +++++++++++++++++++++++++ libinterp/das-riscv64.c | 9 + libiot/freertos/adds.c | 317 ++++ libiot/freertos/adds.h | 198 +++ libiot/freertos/mkfile | 3 + libiot/hal/mkfile | 3 + libiot/hal/w25qxx.c | 288 ++++ libiot/hal/w25qxx.h | 88 ++ libiot/include/esp_err.h | 148 ++ libiot/lfs/lfs.c | 2592 +++++++++++++++++++++++++++++++++ libiot/lfs/lfs.h | 495 +++++++ libiot/lfs/lfs_util.c | 31 + libiot/lfs/lfs_util.h | 186 +++ libiot/linenoise/linenoise.c | 637 ++++++++ libiot/linenoise/linenoise.h | 56 + libiot/mkfile | 26 + libiot/pthread/_pthread.c | 743 ++++++++++ libiot/pthread/_pthread.h | 241 +++ libiot/pthread/attr.c | 244 ++++ libiot/pthread/cond.c | 204 +++ libiot/pthread/create.c | 55 + libiot/pthread/join.c | 70 + libiot/pthread/key.c | 163 +++ libiot/pthread/kill.c | 60 + libiot/pthread/mkfile | 10 + libiot/pthread/mutex.c | 241 +++ libiot/pthread/once.c | 67 + libiot/pthread/self.c | 53 + libiot/pthread/test/pthread-cleanup.c | 200 +++ libiot/pthread/test/pthread-cond.c | 107 ++ libiot/pthread/test/pthread-join.c | 85 ++ libiot/pthread/test/pthread-once.c | 68 + libiot/ramfs/ramfs.c | 1088 ++++++++++++++ libiot/ramfs/ramfs.h | 274 ++++ libiot/spiffs/k210_spiffs.c | 190 +++ libiot/spiffs/k210_spiffs.h | 59 + libiot/spiffs/mkfile | 9 + libiot/spiffs/spiffs.h | 816 +++++++++++ libiot/spiffs/spiffs_cache.c | 314 ++++ libiot/spiffs/spiffs_check.c | 995 +++++++++++++ libiot/spiffs/spiffs_config.h | 360 +++++ libiot/spiffs/spiffs_gc.c | 606 ++++++++ libiot/spiffs/spiffs_hydrogen.c | 1405 ++++++++++++++++++ libiot/spiffs/spiffs_nucleus.c | 2327 +++++++++++++++++++++++++++++ libiot/spiffs/spiffs_nucleus.h | 797 ++++++++++ libiot/sys/console.c | 211 +++ libiot/sys/console.h | 61 + libiot/sys/history.c | 199 +++ libiot/sys/history.h | 57 + libiot/sys/list.c | 429 ++++++ libiot/sys/list.h | 85 ++ libiot/sys/mkfile | 5 + libiot/sys/mutex.c | 155 ++ libiot/sys/mutex.h | 69 + libiot/sys/panic.c | 52 + libiot/sys/panic.h | 51 + libiot/vfs/fat.c | 453 ++++++ libiot/vfs/include/esp_vfs.h | 424 ++++++ libiot/vfs/include/sys/dirent.h | 56 + libiot/vfs/lfs.c | 949 ++++++++++++ libiot/vfs/mkfile | 7 + libiot/vfs/ramfs.c | 634 ++++++++ libiot/vfs/spiffs.c | 1423 ++++++++++++++++++ libiot/vfs/vfs.h | 128 ++ libmath/FPcontrol-FreeRTOS.c | 77 + mkconfig.FreeRTOS-riscv64 | 35 + mkconfig.Linux-386 | 35 + mkfiles/mkfile-FreeRTOS-riscv64 | 37 + 108 files changed, 30333 insertions(+), 1 deletion(-) create mode 100644 FreeRTOS/riscv64/include/emu.h create mode 100644 FreeRTOS/riscv64/include/fpuctl.h create mode 100644 FreeRTOS/riscv64/include/lib9.h create mode 100755 bld-lin-fb-k210.sh create mode 100644 emu/FreeRTOS/arm-tas-v5.S create mode 100644 emu/FreeRTOS/arm-tas-v7.S create mode 100644 emu/FreeRTOS/asm-386.S create mode 100644 emu/FreeRTOS/asm-arm.S create mode 100644 emu/FreeRTOS/asm-mips.S create mode 100644 emu/FreeRTOS/asm-power.S create mode 100644 emu/FreeRTOS/asm-spim.S create mode 100644 emu/FreeRTOS/audio-oss.c create mode 100644 emu/FreeRTOS/cmd.c create mode 100644 emu/FreeRTOS/deveia.c create mode 100644 emu/FreeRTOS/devfs-posix.c create mode 100644 emu/FreeRTOS/devfs.c create mode 100644 emu/FreeRTOS/emu create mode 100644 emu/FreeRTOS/emu-g create mode 100644 emu/FreeRTOS/emu-wrt create mode 100644 emu/FreeRTOS/kproc-xthreads.c create mode 100644 emu/FreeRTOS/mk-wrt create mode 100644 emu/FreeRTOS/mkfile create mode 100644 emu/FreeRTOS/mkfile-386 create mode 100644 emu/FreeRTOS/mkfile-arm create mode 100644 emu/FreeRTOS/mkfile-mips create mode 100644 emu/FreeRTOS/mkfile-power create mode 100644 emu/FreeRTOS/mkfile-riscv64 create mode 100644 emu/FreeRTOS/os.c create mode 100644 emu/FreeRTOS/segflush-386.c create mode 100644 emu/FreeRTOS/segflush-arm.c create mode 100644 emu/FreeRTOS/segflush-mips.S create mode 100644 emu/FreeRTOS/segflush-power.c create mode 100644 emu/FreeRTOS/segflush-riscv64.c create mode 100644 emu/FreeRTOS/segflush-spim.S create mode 100644 emu/FreeRTOS/win-fb.c create mode 100644 lib9/getcallerpc-FreeRTOS-riscv64.c create mode 100644 lib9/setfcr-FreeRTOS-riscv64.c create mode 100644 libdynld/dynld-riscv64.c create mode 100644 libinterp/comp-riscv64.c create mode 100644 libinterp/das-riscv64.c create mode 100644 libiot/freertos/adds.c create mode 100644 libiot/freertos/adds.h create mode 100644 libiot/freertos/mkfile create mode 100644 libiot/hal/mkfile create mode 100644 libiot/hal/w25qxx.c create mode 100644 libiot/hal/w25qxx.h create mode 100644 libiot/include/esp_err.h create mode 100644 libiot/lfs/lfs.c create mode 100644 libiot/lfs/lfs.h create mode 100644 libiot/lfs/lfs_util.c create mode 100644 libiot/lfs/lfs_util.h create mode 100644 libiot/linenoise/linenoise.c create mode 100644 libiot/linenoise/linenoise.h create mode 100644 libiot/mkfile create mode 100644 libiot/pthread/_pthread.c create mode 100644 libiot/pthread/_pthread.h create mode 100644 libiot/pthread/attr.c create mode 100644 libiot/pthread/cond.c create mode 100644 libiot/pthread/create.c create mode 100644 libiot/pthread/join.c create mode 100644 libiot/pthread/key.c create mode 100644 libiot/pthread/kill.c create mode 100644 libiot/pthread/mkfile create mode 100644 libiot/pthread/mutex.c create mode 100644 libiot/pthread/once.c create mode 100644 libiot/pthread/self.c create mode 100644 libiot/pthread/test/pthread-cleanup.c create mode 100644 libiot/pthread/test/pthread-cond.c create mode 100644 libiot/pthread/test/pthread-join.c create mode 100644 libiot/pthread/test/pthread-once.c create mode 100644 libiot/ramfs/ramfs.c create mode 100644 libiot/ramfs/ramfs.h create mode 100644 libiot/spiffs/k210_spiffs.c create mode 100644 libiot/spiffs/k210_spiffs.h create mode 100644 libiot/spiffs/mkfile create mode 100644 libiot/spiffs/spiffs.h create mode 100644 libiot/spiffs/spiffs_cache.c create mode 100644 libiot/spiffs/spiffs_check.c create mode 100644 libiot/spiffs/spiffs_config.h create mode 100644 libiot/spiffs/spiffs_gc.c create mode 100644 libiot/spiffs/spiffs_hydrogen.c create mode 100644 libiot/spiffs/spiffs_nucleus.c create mode 100644 libiot/spiffs/spiffs_nucleus.h create mode 100644 libiot/sys/console.c create mode 100644 libiot/sys/console.h create mode 100644 libiot/sys/history.c create mode 100644 libiot/sys/history.h create mode 100644 libiot/sys/list.c create mode 100644 libiot/sys/list.h create mode 100644 libiot/sys/mkfile create mode 100644 libiot/sys/mutex.c create mode 100644 libiot/sys/mutex.h create mode 100644 libiot/sys/panic.c create mode 100644 libiot/sys/panic.h create mode 100644 libiot/vfs/fat.c create mode 100644 libiot/vfs/include/esp_vfs.h create mode 100644 libiot/vfs/include/sys/dirent.h create mode 100644 libiot/vfs/lfs.c create mode 100644 libiot/vfs/mkfile create mode 100644 libiot/vfs/ramfs.c create mode 100644 libiot/vfs/spiffs.c create mode 100644 libiot/vfs/vfs.h create mode 100644 libmath/FPcontrol-FreeRTOS.c create mode 100644 mkconfig.FreeRTOS-riscv64 create mode 100644 mkconfig.Linux-386 create mode 100644 mkfiles/mkfile-FreeRTOS-riscv64 diff --git a/FreeRTOS/riscv64/include/emu.h b/FreeRTOS/riscv64/include/emu.h new file mode 100644 index 0000000..060b350 --- /dev/null +++ b/FreeRTOS/riscv64/include/emu.h @@ -0,0 +1,41 @@ +/* + * system- and machine-specific declarations for emu: + * floating-point save and restore, signal handling primitive, and + * implementation of the current-process variable `up'. + */ + +#ifndef _EMU_H_ +#define _EMU_H_ + +/* + * This structure must agree with FPsave and FPrestore asm routines + */ +typedef struct FPU FPU; +struct FPU +{ + uchar env[28]; +}; + + +#ifndef USE_PTHREADS +#define KSTACK (16 * 1024) /* must be power of two */ +static __inline Proc *getup(void) { + Proc *p; + __asm__( "mov %0, %%sp;" + : "=r" (p) + ); + return *(Proc **)((uintptr)p & ~(KSTACK - 1)); +} +#else +#define KSTACK (32 * 1024) /* need not be power of two */ +extern Proc* getup(void); +#endif + +#define up (getup()) + +//typedef sigjmp_buf osjmpbuf; +//#define ossetjmp(buf) sigsetjmp(buf, 1) +typedef jmp_buf osjmpbuf; +#define ossetjmp(buf) setjmp(buf) + +#endif //_EMU_H_ \ No newline at end of file diff --git a/FreeRTOS/riscv64/include/fpuctl.h b/FreeRTOS/riscv64/include/fpuctl.h new file mode 100644 index 0000000..e69de29 diff --git a/FreeRTOS/riscv64/include/lib9.h b/FreeRTOS/riscv64/include/lib9.h new file mode 100644 index 0000000..c1196b0 --- /dev/null +++ b/FreeRTOS/riscv64/include/lib9.h @@ -0,0 +1,512 @@ +#ifndef _LIB9_H +#define _LIB9_H + + +/* define _BSD_SOURCE to use ISO C, POSIX, and 4.3BSD things. */ +#define USE_PTHREADS +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif +#ifndef _BSD_SOURCE +#define _BSD_SOURCE +#endif +#define _XOPEN_SOURCE 500 +#define _LARGEFILE_SOURCE 1 +#define _LARGEFILE64_SOURCE 1 +#define _FILE_OFFSET_BITS 64 +#ifdef USE_PTHREADS +#define _REENTRANT 1 +#endif +//#include +#include +#include +#include +#define sync __os_sync +#include +#undef sync +#include +#define __NO_STRING_INLINES +#include +#include "math.h" +#include +#include +#include +//#include + +#define getwd infgetwd + +#ifndef EMU +typedef struct Proc Proc; +#endif + +/* + * math module dtoa + * #define __LITTLE_ENDIAN /usr/include/endian.h under linux + */ + +#define nil ((void*)0) + +typedef unsigned char uchar; +typedef signed char schar; +typedef unsigned int Rune; +typedef long long int vlong; +typedef unsigned long long int uvlong; +typedef unsigned int u32int; +typedef uvlong u64int; + +typedef unsigned int mpdigit; /* for /sys/include/mp.h */ +typedef unsigned short u16int; +typedef unsigned char u8int; +typedef unsigned long uintptr; + +#define USED(x) if(x){}else{} +#define SET(x) + +#undef nelem +#define nelem(x) (sizeof(x)/sizeof((x)[0])) +#undef offsetof +#define offsetof(s, m) (ulong)(&(((s*)0)->m)) +#undef assert +#define assert(x) if(x){}else _assert("x") + +/* + * most mem and string routines are declared by ANSI/POSIX files above + */ + +extern char* strecpy(char*, char*, char*); +extern char* strdup(const char*); +extern int cistrncmp(char*, char*, int); +extern int cistrcmp(char*, char*); +extern char* cistrstr(char*, char*); +extern int tokenize(char*, char**, int); + +enum +{ + UTFmax = 4, /* maximum bytes per rune */ + Runesync = 0x80, /* cannot represent part of a UTF sequence (<) */ + Runeself = 0x80, /* rune and UTF sequences are the same (<) */ + Runeerror = 0xFFFD, /* decoding error in UTF */ + Runemax = 0x10FFFF, /* 21-bit rune */ + Runemask = 0x1FFFFF, /* bits used by runes (see grep) */ +}; + +/* + * rune routines + */ +extern int runetochar(char*, Rune*); +extern int chartorune(Rune*, char*); +extern int runelen(long); +extern int runenlen(Rune*, int); +extern int fullrune(char*, int); +extern int utflen(char*); +extern int utfnlen(char*, long); +extern char* utfrune(char*, long); +extern char* utfrrune(char*, long); +extern char* utfutf(char*, char*); +extern char* utfecpy(char*, char*, char*); + +extern Rune* runestrcat(Rune*, Rune*); +extern Rune* runestrchr(Rune*, Rune); +extern int runestrcmp(Rune*, Rune*); +extern Rune* runestrcpy(Rune*, Rune*); +extern Rune* runestrncpy(Rune*, Rune*, long); +extern Rune* runestrecpy(Rune*, Rune*, Rune*); +extern Rune* runestrdup(Rune*); +extern Rune* runestrncat(Rune*, Rune*, long); +extern int runestrncmp(Rune*, Rune*, long); +extern Rune* runestrrchr(Rune*, Rune); +extern long runestrlen(Rune*); +extern Rune* runestrstr(Rune*, Rune*); + +extern Rune tolowerrune(Rune); +extern Rune totitlerune(Rune); +extern Rune toupperrune(Rune); +extern int isalpharune(Rune); +extern int islowerrune(Rune); +extern int isspacerune(Rune); +extern int istitlerune(Rune); +extern int isupperrune(Rune); + +/* + * malloc + */ +extern void* malloc(size_t); +extern void* mallocz(ulong, int); +extern void free(void*); +extern ulong msize(void*); +extern void* calloc(size_t, size_t); +extern void* realloc(void*, size_t); +extern void setmalloctag(void*, ulong); +extern void setrealloctag(void*, ulong); +extern ulong getmalloctag(void*); +extern ulong getrealloctag(void*); +extern void* malloctopoolblock(void*); + +/* + * print routines + */ +typedef struct Fmt Fmt; +struct Fmt{ + uchar runes; /* output buffer is runes or chars? */ + void *start; /* of buffer */ + void *to; /* current place in the buffer */ + void *stop; /* end of the buffer; overwritten if flush fails */ + int (*flush)(Fmt *); /* called when to == stop */ + void *farg; /* to make flush a closure */ + int nfmt; /* num chars formatted so far */ + va_list args; /* args passed to dofmt */ + int r; /* % format Rune */ + int width; + int prec; + ulong flags; +}; + +enum{ + FmtWidth = 1, + FmtLeft = FmtWidth << 1, + FmtPrec = FmtLeft << 1, + FmtSharp = FmtPrec << 1, + FmtSpace = FmtSharp << 1, + FmtSign = FmtSpace << 1, + FmtZero = FmtSign << 1, + FmtUnsigned = FmtZero << 1, + FmtShort = FmtUnsigned << 1, + FmtLong = FmtShort << 1, + FmtVLong = FmtLong << 1, + FmtComma = FmtVLong << 1, + FmtByte = FmtComma << 1, + + FmtFlag = FmtByte << 1 +}; + +extern int print(char*, ...); +extern char* seprint(char*, char*, char*, ...); +extern char* vseprint(char*, char*, char*, va_list); +extern int snprint(char*, int, char*, ...); +extern int vsnprint(char*, int, char*, va_list); +extern char* smprint(char*, ...); +extern char* vsmprint(char*, va_list); +extern int sprint(char*, char*, ...); +extern int fprint(int, char*, ...); +extern int vfprint(int, char*, va_list); + +extern int runesprint(Rune*, char*, ...); +extern int runesnprint(Rune*, int, char*, ...); +extern int runevsnprint(Rune*, int, char*, va_list); +extern Rune* runeseprint(Rune*, Rune*, char*, ...); +extern Rune* runevseprint(Rune*, Rune*, char*, va_list); +extern Rune* runesmprint(char*, ...); +extern Rune* runevsmprint(char*, va_list); + +extern int fmtfdinit(Fmt*, int, char*, int); +extern int fmtfdflush(Fmt*); +extern int fmtstrinit(Fmt*); +extern char* fmtstrflush(Fmt*); +extern int runefmtstrinit(Fmt*); +extern Rune* runefmtstrflush(Fmt*); + +extern int fmtinstall(int, int (*)(Fmt*)); +extern int dofmt(Fmt*, char*); +extern int dorfmt(Fmt*, Rune*); +extern int fmtprint(Fmt*, char*, ...); +extern int fmtvprint(Fmt*, char*, va_list); +extern int fmtrune(Fmt*, int); +extern int fmtstrcpy(Fmt*, char*); +extern int fmtrunestrcpy(Fmt*, Rune*); +/* + * error string for %r + * supplied on per os basis, not part of fmt library + */ +extern int errfmt(Fmt *f); + +/* + * quoted strings + */ +extern char *unquotestrdup(char*); +extern Rune *unquoterunestrdup(Rune*); +extern char *quotestrdup(char*); +extern Rune *quoterunestrdup(Rune*); +extern int quotestrfmt(Fmt*); +extern int quoterunestrfmt(Fmt*); +extern void quotefmtinstall(void); +extern int (*doquote)(int); + +/* + * random number + */ +extern ulong truerand(void); +extern ulong ntruerand(ulong); + +/* + * math + */ +extern int isNaN(double); +extern double NaN(void); +extern int isInf(double, int); + +/* + * Time-of-day + */ + +typedef struct Tm Tm; +struct Tm { + int sec; + int min; + int hour; + int mday; + int mon; + int year; + int wday; + int yday; + char zone[4]; + int tzoff; +}; +extern vlong osnsec(void); +#define nsec osnsec + +/* + * one-of-a-kind + */ +extern void _assert(char*); +extern double charstod(int(*)(void*), void*); +extern char* cleanname(char*); +//extern uintptr getcallerpc(void*); +extern int getfields(char*, char**, int, int, char*); +extern char* getuser(void); +extern char* getwd(char*, int); +extern double ipow10(int); +#define pow10 infpow10 +extern double pow10(int); +extern vlong strtoll(const char*, char**, int); +#define qsort infqsort +extern void qsort(void*, long, long, int (*)(void*, void*)); +extern uvlong strtoull(const char*, char**, int); +extern void sysfatal(char*, ...); +extern int dec64(uchar*, int, char*, int); +extern int enc64(char*, int, uchar*, int); +extern int dec32(uchar*, int, char*, int); +extern int enc32(char*, int, uchar*, int); +extern int dec16(uchar*, int, char*, int); +extern int enc16(char*, int, uchar*, int); +extern int encodefmt(Fmt*); + +/* + * synchronization + */ +typedef +struct Lock { + int val; + int pid; +} Lock; + +extern int _tas(int*); + +extern void lock(Lock*); +extern void unlock(Lock*); +extern int canlock(Lock*); + +typedef struct QLock QLock; +struct QLock +{ + Lock use; /* to access Qlock structure */ + Proc *head; /* next process waiting for object */ + Proc *tail; /* last process waiting for object */ + int locked; /* flag */ +}; + +extern void qlock(QLock*); +extern void qunlock(QLock*); +extern int canqlock(QLock*); +extern void _qlockinit(ulong (*)(ulong, ulong)); /* called only by the thread library */ + +typedef +struct RWLock +{ + Lock l; /* Lock modify lock */ + QLock x; /* Mutual exclusion lock */ + QLock k; /* Lock for waiting writers */ + int readers; /* Count of readers in lock */ +} RWLock; + +extern int canrlock(RWLock*); +extern int canwlock(RWLock*); +extern void rlock(RWLock*); +extern void runlock(RWLock*); +extern void wlock(RWLock*); +extern void wunlock(RWLock*); + +/* + * network dialing + */ +#define NETPATHLEN 40 + +/* + * system calls + * + */ +#define STATMAX 65535U /* max length of machine-independent stat structure */ +#define DIRMAX (sizeof(Dir)+STATMAX) /* max length of Dir structure */ +#define ERRMAX 128 /* max length of error string */ + +#define MORDER 0x0003 /* mask for bits defining order of mounting */ +#define MREPL 0x0000 /* mount replaces object */ +#define MBEFORE 0x0001 /* mount goes before others in union directory */ +#define MAFTER 0x0002 /* mount goes after others in union directory */ +#define MCREATE 0x0004 /* permit creation in mounted directory */ +#define MCACHE 0x0010 /* cache some data */ +#define MMASK 0x0017 /* all bits on */ + +#define OREAD 0 /* open for read */ +#define OWRITE 1 /* write */ +#define ORDWR 2 /* read and write */ +#define OEXEC 3 /* execute, == read but check execute permission */ +#define OTRUNC 16 /* or'ed in (except for exec), truncate file first */ +#define OCEXEC 32 /* or'ed in, close on exec */ +#define ORCLOSE 64 /* or'ed in, remove on close */ +#define OEXCL 0x1000 /* or'ed in, exclusive use (create only) */ + +#define AEXIST 0 /* accessible: exists */ +#define AEXEC 1 /* execute access */ +#define AWRITE 2 /* write access */ +#define AREAD 4 /* read access */ + +/* bits in Qid.type */ +#define QTDIR 0x80 /* type bit for directories */ +#define QTAPPEND 0x40 /* type bit for append only files */ +#define QTEXCL 0x20 /* type bit for exclusive use files */ +#define QTMOUNT 0x10 /* type bit for mounted channel */ +#define QTAUTH 0x08 /* type bit for authentication file */ +#define QTFILE 0x00 /* plain file */ + +/* bits in Dir.mode */ +#define DMDIR 0x80000000 /* mode bit for directories */ +#define DMAPPEND 0x40000000 /* mode bit for append only files */ +#define DMEXCL 0x20000000 /* mode bit for exclusive use files */ +#define DMMOUNT 0x10000000 /* mode bit for mounted channel */ +#define DMAUTH 0x08000000 /* mode bit for authentication file */ +#define DMREAD 0x4 /* mode bit for read permission */ +#define DMWRITE 0x2 /* mode bit for write permission */ +#define DMEXEC 0x1 /* mode bit for execute permission */ + +typedef +struct Qid +{ + uvlong path; + ulong vers; + uchar type; +} Qid; + +typedef +struct Dir { + /* system-modified data */ + ushort type; /* server type */ + uint dev; /* server subtype */ + /* file data */ + Qid qid; /* unique id from server */ + ulong mode; /* permissions */ + ulong atime; /* last read time */ + ulong mtime; /* last write time */ + vlong length; /* file length */ + char *name; /* last element of path */ + char *uid; /* owner name */ + char *gid; /* group name */ + char *muid; /* last modifier name */ +} Dir; + +extern Dir* dirstat(char*); +extern Dir* dirfstat(int); +extern int dirwstat(char*, Dir*); +extern int dirfwstat(int, Dir*); +extern long dirread(int, Dir**); +extern void nulldir(Dir*); +extern long dirreadall(int, Dir**); + +typedef +struct Waitmsg +{ + int pid; /* of loved one */ + ulong time[3]; /* of loved one & descendants */ + char *msg; +} Waitmsg; + +extern void _exits(char*); + +extern void exits(char*); +extern int create(char*, int, int); +extern int errstr(char*, uint); + +extern void perror(const char*); +extern long readn(int, void*, long); +extern int remove(const char*); +extern void rerrstr(char*, uint); +extern vlong seek(int, vlong, int); +extern int segflush(void*, ulong); +extern void werrstr(char*, ...); + +extern char *argv0; +#define ARGBEGIN for((argv0||(argv0=*argv)),argv++,argc--;\ + argv[0] && argv[0][0]=='-' && argv[0][1];\ + argc--, argv++) {\ + char *_args, *_argt;\ + Rune _argc;\ + _args = &argv[0][1];\ + if(_args[0]=='-' && _args[1]==0){\ + argc--; argv++; break;\ + }\ + _argc = 0;\ + while(*_args && (_args += chartorune(&_argc, _args)))\ + switch(_argc) +#define ARGEND SET(_argt);USED(_argt);USED(_argc); USED(_args);}USED(argv); USED(argc); +#define ARGF() (_argt=_args, _args="",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): 0)) +#define EARGF(x) (_argt=_args, _args="",\ + (*_argt? _argt: argv[1]? (argc--, *++argv): ((x), abort(), (char*)0))) + +#define ARGC() _argc + +/* + * Extensions for Inferno to basic libc.h + */ + +#define setbinmode() + +/* need the inline because the link register is not saved in a known location */ +#if 0 +static __inline uintptr getcallerpc(void* dummy) { + ulong lr; + __asm__( "mov %0, %%lr;" + : "=r" (lr) + ); + return lr; +} +#endif + +extern void setfcr(ulong); +extern void setfsr(ulong); +extern ulong getfcr(void); +extern ulong getfsr(void); + +/* FCR */ +#define FPINEX (1<<5) +#define FPUNFL ((1<<4)|(1<<1)) +#define FPOVFL (1<<3) +#define FPZDIV (1<<2) +#define FPINVAL (1<<0) +#define FPRNR (0<<10) +#define FPRZ (3<<10) +#define FPRPINF (2<<10) +#define FPRNINF (1<<10) +#define FPRMASK (3<<10) +#define FPPEXT (3<<8) +#define FPPSGL (0<<8) +#define FPPDBL (2<<8) +#define FPPMASK (3<<8) +/* FSR */ +#define FPAINEX FPINEX +#define FPAOVFL FPOVFL +#define FPAUNFL FPUNFL +#define FPAZDIV FPZDIV +#define FPAINVAL FPINVAL + + +#endif diff --git a/bld-lin-fb-k210.sh b/bld-lin-fb-k210.sh new file mode 100755 index 0000000..3024fef --- /dev/null +++ b/bld-lin-fb-k210.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +#mkdir Linux/arm/bin +#mkdir Linux/arm/lib + + +#mkdir out + +export PATH=/opt/kendryte-toolchain/bin:$PWD/Linux/386/bin:$PATH + +export ROOT=$PWD + +export CROSS=riscv64-unknown-elf- + +export K210_SDK=$ROOT/../kendryte-freertos-sdk + +export FreeRTOS_ROOT=$K210_SDK/lib/freertos + +#unzip -n dis_folders.zip + +ln -sf mkconfig.FreeRTOS-riscv64 mkconfig + +mk install + +#cp Linux/arm/bin/emu out \ No newline at end of file diff --git a/bld-lin.sh b/bld-lin.sh index 611f953..ce3cc5f 100755 --- a/bld-lin.sh +++ b/bld-lin.sh @@ -8,6 +8,8 @@ export PATH=$PWD/Linux/386/bin:$PATH unzip -n dis_folders.zip +ln -sf mkconfig.Linux-386 mkconfig + #mk mkdirs mk install diff --git a/emu/FreeRTOS/arm-tas-v5.S b/emu/FreeRTOS/arm-tas-v5.S new file mode 100644 index 0000000..4bd13c2 --- /dev/null +++ b/emu/FreeRTOS/arm-tas-v5.S @@ -0,0 +1,18 @@ + + .file "arm-tas-v5.S" +/* + * ulong _tas(ulong*); + */ + .align 2 + .global _tas + .type _tas, %function +_tas: + @ args = 0, pretend = 0, frame = 0 + @ frame_needed = 0, uses_anonymous_args = 0 + @ link register save eliminated. + @ lr needed for prologue + mov r3, #1 + mov r1, r0 + swp r0, r3, [r1] + bx lr + .size _tas, .-_tas diff --git a/emu/FreeRTOS/arm-tas-v7.S b/emu/FreeRTOS/arm-tas-v7.S new file mode 100644 index 0000000..584b31f --- /dev/null +++ b/emu/FreeRTOS/arm-tas-v7.S @@ -0,0 +1,30 @@ + .file "arm-tas-v7.S" +#ifndef ARMv7 +#define DMB mcr p15, 0, r0, c7, c10, 5 +#else +#define DMB dmb +#endif +.align 2 +.global _tas +.type _tas, %function +_tas: + @ args = 0, pretend = 0, frame = 0 + @ frame_needed = 0, uses_anonymous_args = 0 + @ link register save eliminated. + @ lr needed for prologue + DMB + mov r1, r0 + mov r2, #0xaa +tas1: + ldrex r0, [r1] + cmp r0, #0 + bne lockbusy + strex r3, r2, [r1] + cmp r3, #0 + bne tas1 + DMB + bx lr +lockbusy: + clrex + bx lr + .size _tas, .-_tas diff --git a/emu/FreeRTOS/asm-386.S b/emu/FreeRTOS/asm-386.S new file mode 100644 index 0000000..fe5ce6c --- /dev/null +++ b/emu/FreeRTOS/asm-386.S @@ -0,0 +1,51 @@ + .file "asm-Linux-386.S" + .text + +/* + * umult(ulong m1, ulong m2, ulong *hi) + */ + + .type umult,@function + .global umult +umult: + pushl %ebp + movl %esp, %ebp + pushl %ebx + + movl 8(%ebp), %eax + movl 12(%ebp), %ebx + mull %ebx + movl 16(%ebp), %ebx + movl %edx, (%ebx) + + popl %ebx + popl %ebp + ret + + .type FPsave,@function + .global FPsave +FPsave: + pushl %ebp + movl %esp, %ebp + movl 8(%ebp), %eax + fstenv (%eax) + popl %ebp + ret + + .type FPrestore,@function + .global FPrestore +FPrestore: + pushl %ebp + movl %esp, %ebp + movl 8(%ebp), %eax + fldenv (%eax) + popl %ebp + ret + + .type _tas,@function + .globl _tas +_tas: + movl $1, %eax + movl 4(%esp), %ecx + xchgl %eax, 0(%ecx) + ret diff --git a/emu/FreeRTOS/asm-arm.S b/emu/FreeRTOS/asm-arm.S new file mode 100644 index 0000000..5446dde --- /dev/null +++ b/emu/FreeRTOS/asm-arm.S @@ -0,0 +1,62 @@ + .file "asm-Linux-arm.S" + .text + +/* + * ulong umult(ulong m1, ulong m2, ulong *hi) + */ + + .align 2 + .global umult + .type umult, %function +umult: + @ args = 0, pretend = 0, frame = 12 + @ frame_needed = 1, uses_anonymous_args = 0 + mov ip, sp + stmfd sp!, {fp, ip, lr, pc} + sub fp, ip, #4 + sub sp, sp, #12 + str r0, [fp, #-16] + str r1, [fp, #-20] + str r2, [fp, #-24] + ldr r1, [fp, #-16] + ldr r2, [fp, #-20] + umull r0, r3, r1, r2 + ldr r1, [fp, #-24] + str r3, [r1] + ldmea fp, {fp, sp, pc} + .size umult, .-umult + +/* + * void FPsave(void*); + */ + + .align 2 + .global FPsave + .type FPsave, %function +FPsave: + @ args = 0, pretend = 0, frame = 4 + @ frame_needed = 1, uses_anonymous_args = 0 + mov ip, sp + stmfd sp!, {fp, ip, lr, pc} + sub fp, ip, #4 + sub sp, sp, #4 + str r0, [fp, #-16] + ldmea fp, {fp, sp, pc} + .size FPsave, .-FPsave + +/* + * void FPrestore(void*); + */ + .align 2 + .global FPrestore + .type FPrestore, %function +FPrestore: + @ args = 0, pretend = 0, frame = 4 + @ frame_needed = 1, uses_anonymous_args = 0 + mov ip, sp + stmfd sp!, {fp, ip, lr, pc} + sub fp, ip, #4 + sub sp, sp, #4 + str r0, [fp, #-16] + ldmea fp, {fp, sp, pc} + .size FPrestore, .-FPrestore diff --git a/emu/FreeRTOS/asm-mips.S b/emu/FreeRTOS/asm-mips.S new file mode 100644 index 0000000..d2780f6 --- /dev/null +++ b/emu/FreeRTOS/asm-mips.S @@ -0,0 +1,28 @@ +#include +#include +#include + +LEAF(FPsave) + cfc1 t0, $31 + sw t0, 0(a0) /* a0 is argument */ + j $31 + END(FPsave) + +LEAF(FPrestore) + lw t0, 0(a0) /* a0 is argument */ + ctc1 t0, $31 + j $31 + END(FPrestore) + +LEAF(_tas) + .set noreorder +1: + ll v0,0(a0) /* a0 is argument */ + or t1, v0, 1 + sc t1,0(a0) + beq t1,zero,1b + nop + j $31 /* lock held */ + nop + .set reorder + END(_tas) diff --git a/emu/FreeRTOS/asm-power.S b/emu/FreeRTOS/asm-power.S new file mode 100644 index 0000000..7187f93 --- /dev/null +++ b/emu/FreeRTOS/asm-power.S @@ -0,0 +1,61 @@ + .align 2 + .global FPsave +FPsave: + stfd %f14,0*8(%r3) + stfd %f15,1*8(%r3) + stfd %f16,2*8(%r3) + stfd %f17,3*8(%r3) + stfd %f18,4*8(%r3) + stfd %f19,5*8(%r3) + stfd %f20,6*8(%r3) + stfd %f21,7*8(%r3) + stfd %f22,8*8(%r3) + stfd %f23,9*8(%r3) + stfd %f24,10*8(%r3) + stfd %f25,11*8(%r3) + stfd %f26,12*8(%r3) + stfd %f27,13*8(%r3) + stfd %f28,14*8(%r3) + stfd %f29,15*8(%r3) + stfd %f30,16*8(%r3) + stfd %f31,17*8(%r3) + blr + + .align 2 + .global FPrestore +FPrestore: + lfd %f14,0*8(%r3) + lfd %f15,1*8(%r3) + lfd %f16,2*8(%r3) + lfd %f17,3*8(%r3) + lfd %f18,4*8(%r3) + lfd %f19,5*8(%r3) + lfd %f20,6*8(%r3) + lfd %f21,7*8(%r3) + lfd %f22,8*8(%r3) + lfd %f23,9*8(%r3) + lfd %f24,10*8(%r3) + lfd %f25,11*8(%r3) + lfd %f26,12*8(%r3) + lfd %f27,13*8(%r3) + lfd %f28,14*8(%r3) + lfd %f29,15*8(%r3) + lfd %f30,16*8(%r3) + lfd %f31,17*8(%r3) + blr + + .align 2 + .global _tas +_tas: + sync + mr %r4, %r3 + addi %r5,0,0x1 +1: + lwarx %r3, 0, %r4 + cmpwi %r3, 0 + bne- 2f + stwcx. %r5, 0, %r4 + bne- 1b +2: + sync + blr diff --git a/emu/FreeRTOS/asm-spim.S b/emu/FreeRTOS/asm-spim.S new file mode 100644 index 0000000..77ae3d4 --- /dev/null +++ b/emu/FreeRTOS/asm-spim.S @@ -0,0 +1,29 @@ +#include +#include +#include + + +LEAF(FPsave) + cfc1 t0, $31 + sw t0, 0(a0) /* a0 is argument */ + j $31 + END(FPsave) + +LEAF(FPrestore) + lw t0, 0(a0) /* a0 is argument */ + ctc1 t0, $31 + j $31 + END(FPrestore) + +LEAF(_tas) + .set noreorder +1: + ll v0,0(a0) /* a0 is argument */ + or t1, v0, 1 + sc t1,0(a0) + beq t1,zero,1b + nop + j $31 /* lock held */ + nop + .set reorder + END(_tas) diff --git a/emu/FreeRTOS/audio-oss.c b/emu/FreeRTOS/audio-oss.c new file mode 100644 index 0000000..696e37c --- /dev/null +++ b/emu/FreeRTOS/audio-oss.c @@ -0,0 +1,441 @@ +#include "dat.h" +#include "fns.h" +#include "error.h" +#include "audio.h" +#include +#include + +#define Audio_Mic_Val SOUND_MIXER_MIC +#define Audio_Linein_Val SOUND_MIXER_LINE + +#define Audio_Speaker_Val SOUND_MIXER_PCM // SOUND_MIXER_VOLUME +#define Audio_Headphone_Val SOUND_MIXER_ALTPCM +#define Audio_Lineout_Val SOUND_MIXER_CD + +#define Audio_Pcm_Val AFMT_S16_LE +#define Audio_Ulaw_Val AFMT_MU_LAW +#define Audio_Alaw_Val AFMT_A_LAW + +#include "audio-tbls.c" +#define min(a,b) ((a) < (b) ? (a) : (b)) + +#define DEVAUDIO "/dev/dsp" +#define DEVMIXER "/dev/mixer" + +#define DPRINT if(1)print + +enum { + A_Pause, + A_UnPause, + A_In, + A_Out, +}; + +static struct { + int data; /* dsp data fd */ + int ctl; /* mixer fd */ + int pause; + QLock lk; +} afd = {.data = -1, .ctl = -1, .pause =A_UnPause }; + +static Audio_t av; +static QLock inlock; +static QLock outlock; + +static int audio_open(int); +static int audio_pause(int, int); +static int audio_set_info(int, Audio_d*, int); + +Audio_t* +getaudiodev(void) +{ + return &av; +} + +void +audio_file_init(void) +{ + audio_info_init(&av); +} + +void +audio_file_open(Chan *c, int omode) +{ + USED(c); + DPRINT("audio_file_open %d %d %x\n", afd.data, afd.ctl, omode); + qlock(&afd.lk); + if(waserror()){ + qunlock(&afd.lk); + nexterror(); + } + if(afd.data >= 0) + error(Einuse); + if(afd.ctl < 0){ + afd.ctl = open(DEVMIXER, ORDWR); + if(afd.ctl < 0) + oserror(); + } + afd.data = audio_open(omode); + if(afd.data < 0) + oserror(); + poperror(); + qunlock(&afd.lk); +} + +void +audio_file_close(Chan *c) +{ + USED(c); + DPRINT("audio_file_close %d %d\n", afd.data, afd.ctl); + qlock(&afd.lk); + if(waserror()){ + qunlock(&afd.lk); + nexterror(); + } + close(afd.data); + afd.data = -1; + qunlock(&afd.lk); + poperror(); +} + +long +audio_file_read(Chan *c, void *va, long count, vlong offset) +{ + long ba, status, chunk, total; + + USED(c); + USED(offset); + DPRINT("audio_file_read %d %d\n", afd.data, afd.ctl); + qlock(&inlock); + if(waserror()){ + qunlock(&inlock); + nexterror(); + } + + if(afd.data < 0) + error(Eperm); + + /* check block alignment */ + ba = av.in.bits * av.in.chan / Bits_Per_Byte; + + if(count % ba) + error(Ebadarg); + + if(!audio_pause(afd.data, A_UnPause)) + error(Eio); + + total = 0; + while(total < count){ + chunk = count - total; + status = read (afd.data, va + total, chunk); + if(status < 0) + error(Eio); + total += status; + } + + if(total != count) + error(Eio); + + poperror(); + qunlock(&inlock); + + return count; +} + +long +audio_file_write(Chan *c, void *va, long count, vlong offset) +{ + long status = -1; + long ba, total, chunk, bufsz; + + USED(c); + USED(offset); + DPRINT("audio_file_write %d %d\n", afd.data, afd.ctl); + qlock(&outlock); + if(waserror()){ + qunlock(&outlock); + nexterror(); + } + + if(afd.data < 0) + error(Eperm); + + /* check block alignment */ + ba = av.out.bits * av.out.chan / Bits_Per_Byte; + + if(count % ba) + error(Ebadarg); + + total = 0; + bufsz = av.out.buf * Audio_Max_Buf / Audio_Max_Val; + + if(bufsz == 0) + error(Ebadarg); + + while(total < count){ + chunk = min(bufsz, count - total); + status = write(afd.data, va, chunk); + if(status <= 0) + error(Eio); + total += status; + } + + poperror(); + qunlock(&outlock); + + return count; +} + +long +audio_ctl_write(Chan *c, void *va, long count, vlong offset) +{ + Audio_t tmpav = av; + int tfd; + + USED(c); + USED(offset); + tmpav.in.flags = 0; + tmpav.out.flags = 0; + + DPRINT ("audio_ctl_write %X %X\n", afd.data, afd.ctl); + if(!audioparse(va, count, &tmpav)) + error(Ebadarg); + + if(!canqlock(&inlock)) + error("device busy"); + if(waserror()){ + qunlock(&inlock); + nexterror(); + } + if(!canqlock(&outlock)) + error("device busy"); + if(waserror()){ + qunlock(&outlock); + nexterror(); + } + + /* DEVAUDIO needs to be open to issue an ioctl */ + tfd = afd.data; + if(tfd < 0){ + tfd = open(DEVAUDIO, O_RDONLY|O_NONBLOCK); + if(tfd < 0) + oserror(); + } + if(waserror()){ + if(tfd != afd.data) + close(tfd); + nexterror(); + } + + if(tmpav.in.flags & AUDIO_MOD_FLAG){ + if(!audio_pause(tfd, A_Pause)) + error(Ebadarg); + if(!audio_set_info(tfd, &tmpav.in, A_In)) + error(Ebadarg); + } + + poperror(); + if(tfd != afd.data) + close(tfd); + + tmpav.in.flags = 0; + av = tmpav; + + poperror(); + qunlock(&outlock); + poperror(); + qunlock(&inlock); + return count; +} + +/* Linux/OSS specific stuff */ + +static int +choosefmt(Audio_d *i) +{ + switch(i->bits){ + case 8: + switch(i->enc){ + case Audio_Alaw_Val: + return AFMT_A_LAW; + case Audio_Ulaw_Val: + return AFMT_MU_LAW; + case Audio_Pcm_Val: + return AFMT_U8; + } + break; + case 16: + if(i->enc == Audio_Pcm_Val) + return AFMT_S16_LE; + break; + } + return -1; +} + +static int +setvolume(int fd, int what, int left, int right) +{ + int can, v; + + if(ioctl(fd, SOUND_MIXER_READ_DEVMASK, &can) < 0) + can = ~0; + + DPRINT("setvolume fd%d %X can mix 0x%X (mask %X)\n", fd, what, (can & (1<flags & AUDIO_RATE_FLAG){ + arg = i->rate; + if(ioctl(fd, SNDCTL_DSP_SPEED, &arg) < 0) + return 0; + } + + /* channels */ + if(i->flags & AUDIO_CHAN_FLAG){ + arg = i->chan; + if(ioctl(fd, SNDCTL_DSP_CHANNELS, &arg) < 0) + return 0; + } + + /* precision */ + if(i->flags & AUDIO_BITS_FLAG){ + arg = i->bits; + if(ioctl(fd, SNDCTL_DSP_SAMPLESIZE, &arg) < 0) + return 0; + } + + /* encoding */ + if(i->flags & AUDIO_ENC_FLAG){ + ioctl(fd, SNDCTL_DSP_GETFMTS, &oldfmt); + + newfmt = choosefmt(i); + if(newfmt < 0) + return 0; + if(newfmt != oldfmt){ + status = ioctl(fd, SNDCTL_DSP_SETFMT, &arg); + DPRINT ("enc oldfmt newfmt %x status %d\n", oldfmt, newfmt, status); + } + } + + /* dev volume */ + if(i->flags & (AUDIO_LEFT_FLAG|AUDIO_VOL_FLAG)) + return setvolume(afd.ctl, i->dev, i->left, i->right); + + return 1; +} + +static int +audio_set_blocking(int fd) +{ + int val; + + if((val = fcntl(fd, F_GETFL, 0)) == -1) + return 0; + + val &= ~O_NONBLOCK; + + if(fcntl(fd, F_SETFL, val) < 0) + return 0; + + return 1; +} + +static int +audio_open(int omode) +{ + int fd; + + /* open non-blocking in case someone already has it open */ + /* otherwise we would block until they close! */ + switch (omode){ + case OREAD: + fd = open(DEVAUDIO, O_RDONLY|O_NONBLOCK); + break; + case OWRITE: + fd = open(DEVAUDIO, O_WRONLY|O_NONBLOCK); + break; + case ORDWR: + fd = open(DEVAUDIO, O_RDWR|O_NONBLOCK); + break; + } + + DPRINT("audio_open %d\n", fd); + if(fd < 0) + oserror(); + + /* change device to be blocking */ + if(!audio_set_blocking(fd)){ + close(fd); + error("cannot set blocking mode"); + } + + if(!audio_pause(fd, A_Pause)){ + close(fd); + error(Eio); + } + + /* set audio info */ + av.in.flags = ~0; + av.out.flags = ~0; + + if(!audio_set_info(fd, &av.in, A_In)){ + close(fd); + error(Ebadarg); + } + + av.in.flags = 0; + + /* tada, we're open, blocking, paused and flushed */ + return fd; +} + +static int +dspsync(int fd) +{ + return ioctl(fd, SNDCTL_DSP_RESET, NULL) >= 0 && + ioctl(fd, SNDCTL_DSP_SYNC, NULL) >= 0; +} + +static int +audio_pause(int fd, int f) +{ + int status; + +// DPRINT ("audio_pause (%d) %d %d\n", fd, afd.data, afd.ctl); + if(fd < 0) + return 0; + if(fd != afd.data) + return dspsync(fd); + qlock(&afd.lk); + if(afd.pause == f){ + qunlock(&afd.lk); + return 1; + } + if(waserror()){ + qunlock(&afd.lk); + nexterror(); + } + status = dspsync(afd.data); + if(status) + afd.pause = f; + poperror(); + qunlock(&afd.lk); + return status; +} diff --git a/emu/FreeRTOS/cmd.c b/emu/FreeRTOS/cmd.c new file mode 100644 index 0000000..2671796 --- /dev/null +++ b/emu/FreeRTOS/cmd.c @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include +#include + +#include "dat.h" +#include "fns.h" +#include "error.h" + +enum +{ + Debug = 0 +}; + +/* + * os-specific devcmd support. + * this version should be reasonably portable across Unix systems. + */ +typedef struct Targ Targ; +struct Targ +{ + int fd[3]; /* fd[0] is standard input, fd[1] is standard output, fd[2] is standard error */ + char** args; + char* dir; + int pid; + int wfd; /* child writes errors that occur after the fork or on exec */ + int uid; + int gid; +}; + +extern int gidnobody; +extern int uidnobody; + +static int +childproc(Targ *t) +{ + int i, nfd; + + if(Debug) + print("devcmd: '%s'", t->args[0]); + + nfd = getdtablesize(); + for(i = 0; i < nfd; i++) + if(i != t->fd[0] && i != t->fd[1] && i != t->fd[2] && i != t->wfd) + close(i); + + dup2(t->fd[0], 0); + dup2(t->fd[1], 1); + dup2(t->fd[2], 2); + close(t->fd[0]); + close(t->fd[1]); + close(t->fd[2]); + + /* should have an auth file to do host-specific authorisation? */ + if(t->gid != -1){ + if(setgid(t->gid) < 0 && getegid() == 0){ + fprint(t->wfd, "can't set gid %d: %s", t->gid, strerror(errno)); + _exit(1); + } + } + + if(t->uid != -1){ + if(setuid(t->uid) < 0 && geteuid() == 0){ + fprint(t->wfd, "can't set uid %d: %s", t->uid, strerror(errno)); + _exit(1); + } + } + + if(t->dir != nil && chdir(t->dir) < 0){ + fprint(t->wfd, "can't chdir to %s: %s", t->dir, strerror(errno)); + _exit(1); + } + + signal(SIGPIPE, SIG_DFL); + + execvp(t->args[0], t->args); + if(Debug) + print("execvp: %s\n",strerror(errno)); + fprint(t->wfd, "exec failed: %s", strerror(errno)); + + _exit(1); +} + +void* +oscmd(char **args, int nice, char *dir, int *fd) +{ + Targ *t; + int r, fd0[2], fd1[2], fd2[2], wfd[2], n, pid; + + t = mallocz(sizeof(*t), 1); + if(t == nil) + return nil; + + fd0[0] = fd0[1] = -1; + fd1[0] = fd1[1] = -1; + fd2[0] = fd2[1] = -1; + wfd[0] = wfd[1] = -1; + if(pipe(fd0) < 0 || pipe(fd1) < 0 || pipe(fd2) < 0 || pipe(wfd) < 0) + goto Error; + if(fcntl(wfd[1], F_SETFD, FD_CLOEXEC) < 0) /* close on exec to give end of file on success */ + goto Error; + + t->fd[0] = fd0[0]; + t->fd[1] = fd1[1]; + t->fd[2] = fd2[1]; + t->wfd = wfd[1]; + t->args = args; + t->dir = dir; + t->gid = up->env->gid; + if(t->gid == -1) + t->gid = gidnobody; + t->uid = up->env->uid; + if(t->uid == -1) + t->uid = uidnobody; + + signal(SIGCHLD, SIG_DFL); + switch(pid = fork()) { + case -1: + goto Error; + case 0: + setpgrp(); +// if(nice) +// setpriority(PRIO_PROCESS, 0, 19); + childproc(t); + _exit(1); + default: + t->pid = pid; + if(Debug) + print("cmd pid %d\n", t->pid); + break; + } + + close(fd0[0]); + close(fd1[1]); + close(fd2[1]); + close(wfd[1]); + + n = read(wfd[0], up->genbuf, sizeof(up->genbuf)-1); + close(wfd[0]); + if(n > 0){ + close(fd0[1]); + close(fd1[0]); + close(fd2[0]); + free(t); + up->genbuf[n] = 0; + if(Debug) + print("oscmd: bad exec: %q\n", up->genbuf); + error(up->genbuf); + return nil; + } + + fd[0] = fd0[1]; + fd[1] = fd1[0]; + fd[2] = fd2[0]; + return t; + +Error: + r = errno; + if(Debug) + print("oscmd: %q\n",strerror(r)); + close(fd0[0]); + close(fd0[1]); + close(fd1[0]); + close(fd1[1]); + close(fd2[0]); + close(fd2[1]); + close(wfd[0]); + close(wfd[1]); + error(strerror(r)); + return nil; +} + +int +oscmdkill(void *a) +{ + Targ *t = a; + + if(Debug) + print("kill: %d\n", t->pid); + return kill(-t->pid, SIGTERM); +} + +int +oscmdwait(void *a, char *buf, int n) +{ + Targ *t = a; + int s; + + if(waitpid(t->pid, &s, 0) == -1){ + if(Debug) + print("wait error: %d [in %d] %q\n", t->pid, getpid(), strerror(errno)); + return -1; + } + if(WIFEXITED(s)){ + if(WEXITSTATUS(s) == 0) + return snprint(buf, n, "%d 0 0 0 ''", t->pid); + return snprint(buf, n, "%d 0 0 0 'exit: %d'", t->pid, WEXITSTATUS(s)); + } + if(WIFSIGNALED(s)){ + if(WTERMSIG(s) == SIGTERM || WTERMSIG(s) == SIGKILL) + return snprint(buf, n, "%d 0 0 0 killed", t->pid); + return snprint(buf, n, "%d 0 0 0 'signal: %d'", t->pid, WTERMSIG(s)); + } + return snprint(buf, n, "%d 0 0 0 'odd status: 0x%x'", t->pid, s); +} + +void +oscmdfree(void *a) +{ + free(a); +} diff --git a/emu/FreeRTOS/deveia.c b/emu/FreeRTOS/deveia.c new file mode 100644 index 0000000..a1f0951 --- /dev/null +++ b/emu/FreeRTOS/deveia.c @@ -0,0 +1,44 @@ +/* + * Linux serial port definitions + */ + +static char *sysdev[] = { + "/dev/ttyS0", + "/dev/ttyS1", + "/dev/ttyS2", + "/dev/ttyS3", + "/dev/ttyS4", + "/dev/ttyS5", + "/dev/ttyS6", + "/dev/ttyS7", +}; + +#include +#include "deveia-posix.c" +#include "deveia-bsd.c" + + +static struct tcdef_t bps[] = { + {0, B0}, + {50, B50}, + {75, B75}, + {110, B110}, + {134, B134}, + {150, B150}, + {200, B200}, + {300, B300}, + {600, B600}, + {1200, B1200}, + {1800, B1800}, + {2400, B2400}, + {4800, B4800}, + {9600, B9600}, + {19200, B19200}, + {38400, B38400}, + {57600, B57600}, + {115200, B115200}, + {230400, B230400}, + {460800, B460800}, + {-1, -1} +}; + diff --git a/emu/FreeRTOS/devfs-posix.c b/emu/FreeRTOS/devfs-posix.c new file mode 100644 index 0000000..1f53ac8 --- /dev/null +++ b/emu/FreeRTOS/devfs-posix.c @@ -0,0 +1,1096 @@ +/* + * Unix file system interface + */ +#define _LARGEFILE64_SOURCE 1 +#define _FILE_OFFSET_BITS 64 +#include "dat.h" +#include "fns.h" +#include "error.h" + +#include +#include +#include +#include +#if 0 //{} +#include +#endif //{} +#include +#include +#include +#define __EXTENSIONS__ +#undef getwd +#include +#include +#include + +typedef struct Fsinfo Fsinfo; +struct Fsinfo +{ + int uid; + int gid; + int mode; /* Unix mode */ + DIR* dir; /* open directory */ + struct dirent* de; /* directory reading */ + int fd; /* open files */ + ulong offset; /* offset when reading directory */ + int eod; /* end of directory */ + int issocket; + QLock oq; /* mutex for offset */ + char* spec; + Cname* name; /* Unix's name for file */ + Qid rootqid; /* Plan 9's qid for Inferno's root */ +}; + +#define FS(c) ((Fsinfo*)(c)->aux) + +enum +{ + IDSHIFT = 8, + NID = 1 << IDSHIFT, + IDMASK = NID - 1, + MAXPATH = 1024 /* TO DO: eliminate this */ +}; + +typedef struct User User; +struct User +{ + int id; /* might be user or group ID */ + int gid; /* if it's a user not a group, the group ID (only for setid) */ + char* name; + int nmem; + int* mem; /* member array, if nmem != 0 */ + User* next; +}; + +char rootdir[MAXROOT] = ROOT; + +static User* uidmap[NID]; +static User* gidmap[NID]; +static QLock idl; +static User* name2user(User**, char*, User* (*get)(char*)); +static User* id2user(User**, int, User* (*get)(int)); +static User* newuid(int); +static User* newgid(int); +static User* newuname(char*); +static User* newgname(char*); + +static Qid fsqid(struct stat *); +static void fspath(Cname*, char*, char*); +static int fsdirconv(Chan*, char*, char*, struct stat*, uchar*, int, int); +static Cname* fswalkpath(Cname*, char*, int); +static char* fslastelem(Cname*); +static int ingroup(int id, int gid); +static void fsperm(Chan*, int); +static long fsdirread(Chan*, uchar*, int, vlong); +static int fsomode(int); +static void fsremove(Chan*); +static vlong osdisksize(int); /* defined by including file */ + +/* + * make invalid symbolic links visible; less confusing, and at least you can then delete them. + */ +static int +xstat(char *f, struct stat *sb) +{ + if(stat(f, sb) >= 0) + return 0; + /* could possibly generate ->name as rob once suggested */ + return lstat(f, sb); +} + +static void +fsfree(Chan *c) +{ + cnameclose(FS(c)->name); + free(FS(c)); +} + +Chan* +fsattach(char *spec) +{ + Chan *c; + struct stat st; + static int devno; + static Lock l; + + if(!emptystr(spec) && strcmp(spec, "*") != 0) + error(Ebadspec); + if(stat(rootdir, &st) < 0) + oserror(); + if(!S_ISDIR(st.st_mode)) + error(Enotdir); + + c = devattach('U', spec); + c->qid = fsqid(&st); + c->aux = smalloc(sizeof(Fsinfo)); + FS(c)->dir = nil; + FS(c)->de = nil; + FS(c)->fd = -1; + FS(c)->issocket = 0; + FS(c)->gid = st.st_gid; + FS(c)->uid = st.st_uid; + FS(c)->mode = st.st_mode; + lock(&l); + c->dev = devno++; + unlock(&l); + if(!emptystr(spec)){ + FS(c)->spec = "/"; + FS(c)->name = newcname(FS(c)->spec); + }else + FS(c)->name = newcname(rootdir); + FS(c)->rootqid = c->qid; + + return c; +} + +Walkqid* +fswalk(Chan *c, Chan *nc, char **name, int nname) +{ + int j; + volatile int alloc; + Walkqid *wq; + struct stat st; + char *n; + Cname *next; + Cname *volatile current; + Qid rootqid; + + if(nname > 0) + isdir(c); + + alloc = 0; + current = nil; + wq = smalloc(sizeof(Walkqid)+(nname-1)*sizeof(Qid)); + if(waserror()){ + if(alloc && wq->clone != nil) + cclose(wq->clone); + cnameclose(current); + free(wq); + return nil; + } + if(nc == nil){ + nc = devclone(c); + nc->type = 0; + alloc = 1; + } + wq->clone = nc; + rootqid = FS(c)->rootqid; + current = FS(c)->name; + if(current != nil) + incref(¤t->r); + for(j = 0; j < nname; j++){ + if(!(nc->qid.type&QTDIR)){ + if(j==0) + error(Enotdir); + break; + } + n = name[j]; + if(strcmp(n, ".") != 0 && !(isdotdot(n) && nc->qid.path == rootqid.path)){ + next = current; + incref(&next->r); + next = addelem(current, n); + //print("** ufs walk '%s' -> %s [%s]\n", current->s, n, next->s); + if(xstat(next->s, &st) < 0){ + cnameclose(next); + if(j == 0) + error(Enonexist); + strcpy(up->env->errstr, Enonexist); + break; + } + nc->qid = fsqid(&st); + cnameclose(current); + current = next; + } + wq->qid[wq->nqid++] = nc->qid; + } + poperror(); + if(wq->nqid < nname){ + cnameclose(current); + if(alloc) + cclose(wq->clone); + wq->clone = nil; + }else if(wq->clone){ + nc->aux = smalloc(sizeof(Fsinfo)); + nc->type = c->type; + if(nname > 0) { + FS(nc)->gid = st.st_gid; + FS(nc)->uid = st.st_uid; + FS(nc)->mode = st.st_mode; + FS(nc)->issocket = S_ISSOCK(st.st_mode); + } else { + FS(nc)->gid = FS(c)->gid; + FS(nc)->uid = FS(c)->uid; + FS(nc)->mode = FS(c)->mode; + FS(nc)->issocket = FS(c)->issocket; + } + FS(nc)->name = current; + FS(nc)->spec = FS(c)->spec; + FS(nc)->rootqid = rootqid; + FS(nc)->fd = -1; + FS(nc)->dir = nil; + FS(nc)->de = nil; + } + return wq; +} + +static int +fsstat(Chan *c, uchar *dp, int n) +{ + struct stat st; + char *p; + + if(FS(c)->fd >= 0){ + if(fstat(FS(c)->fd, &st) < 0) + oserror(); + }else{ + if(xstat(FS(c)->name->s, &st) < 0) + oserror(); + } + p = fslastelem(FS(c)->name); + if(*p == 0) + p = "/"; + qlock(&idl); + n = fsdirconv(c, FS(c)->name->s, p, &st, dp, n, 0); + qunlock(&idl); + return n; +} + +static int +opensocket(char *path) +{ + int fd; + struct sockaddr_un su; + + memset(&su, 0, sizeof su); + su.sun_family = AF_UNIX; + if(strlen(path)+1 > sizeof su.sun_path) + error("unix socket name too long"); + strcpy(su.sun_path, path); + if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + return -1; + if(connect(fd, (struct sockaddr*)&su, sizeof su) >= 0) + return fd; + close(fd); + if((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) + return -1; + if(connect(fd, (struct sockaddr*)&su, sizeof su) >= 0) + return fd; + close(fd); + return -1; +} + +static Chan* +fsopen(Chan *c, int mode) +{ + int m, isdir; + + m = mode & (OTRUNC|3); + switch(m) { + case 0: + fsperm(c, 4); + break; + case 1: + case 1|16: + fsperm(c, 2); + break; + case 2: + case 0|16: + case 2|16: + fsperm(c, 4); + fsperm(c, 2); + break; + case 3: + fsperm(c, 1); + break; + default: + error(Ebadarg); + } + + isdir = c->qid.type & QTDIR; + + if(isdir && mode != OREAD) + error(Eperm); + + m = fsomode(m & 3); + c->mode = openmode(mode); + + if(isdir) { + FS(c)->dir = opendir(FS(c)->name->s); + if(FS(c)->dir == nil) + oserror(); + FS(c)->eod = 0; + } + else { + if(!FS(c)->issocket){ + if(mode & OTRUNC) + m |= O_TRUNC; + FS(c)->fd = open(FS(c)->name->s, m, 0666); + }else + FS(c)->fd = opensocket(FS(c)->name->s); + if(FS(c)->fd < 0) + oserror(); + } + + c->offset = 0; + FS(c)->offset = 0; + c->flag |= COPEN; + return c; +} + +static void +fscreate(Chan *c, char *name, int mode, ulong perm) +{ + int fd, m, o; + struct stat st; + Cname *n; + + fsperm(c, 2); + + m = fsomode(mode&3); + openmode(mode); /* get the errors out of the way */ + + if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) + error(Efilename); + n = fswalkpath(FS(c)->name, name, 1); + if(waserror()){ + cnameclose(n); + nexterror(); + } + if(perm & DMDIR) { + if(m) + error(Eperm); + + perm &= ~0777 | (FS(c)->mode & 0777); + if(mkdir(n->s, perm) < 0) + oserror(); + + fd = open(n->s, 0); + if(fd < 0) + oserror(); + fchmod(fd, perm); + fchown(fd, up->env->uid, FS(c)->gid); + if(fstat(fd, &st) <0){ + close(fd); + oserror(); + } + close(fd); + FS(c)->dir = opendir(n->s); + if(FS(c)->dir == nil) + oserror(); + FS(c)->eod = 0; + } else { + o = (O_CREAT | O_EXCL) | (mode&3); + if(mode & OTRUNC) + o |= O_TRUNC; + perm &= ~0666 | (FS(c)->mode & 0666); + fd = open(n->s, o, perm); + if(fd < 0) + oserror(); + fchmod(fd, perm); + fchown(fd, up->env->uid, FS(c)->gid); + if(fstat(fd, &st) < 0){ + close(fd); + oserror(); + } + FS(c)->fd = fd; + } + cnameclose(FS(c)->name); + FS(c)->name = n; + poperror(); + + c->qid = fsqid(&st); + FS(c)->gid = st.st_gid; + FS(c)->uid = st.st_uid; + FS(c)->mode = st.st_mode; + c->mode = openmode(mode); + c->offset = 0; + FS(c)->offset = 0; + FS(c)->issocket = 0; + c->flag |= COPEN; +} + +static void +fsclose(Chan *c) +{ + if((c->flag & COPEN) != 0){ + if(c->qid.type & QTDIR) + closedir(FS(c)->dir); + else + close(FS(c)->fd); + } + if(c->flag & CRCLOSE) { + if(!waserror()) { + fsremove(c); + poperror(); + } + return; + } + fsfree(c); +} + +static long +fsread(Chan *c, void *va, long n, vlong offset) +{ + long r; + + if(c->qid.type & QTDIR){ + qlock(&FS(c)->oq); + if(waserror()) { + qunlock(&FS(c)->oq); + nexterror(); + } + r = fsdirread(c, va, n, offset); + poperror(); + qunlock(&FS(c)->oq); + }else{ + if(!FS(c)->issocket){ + r = pread(FS(c)->fd, va, n, offset); + if(r >= 0) + return r; + if(errno != ESPIPE && errno != EPIPE) + oserror(); + } + r = read(FS(c)->fd, va, n); + if(r < 0) + oserror(); + } + return r; +} + +static long +fswrite(Chan *c, void *va, long n, vlong offset) +{ + long r; + + if(!FS(c)->issocket){ + r = pwrite(FS(c)->fd, va, n, offset); + if(r >= 0) + return r; + if(errno != ESPIPE && errno != EPIPE) + oserror(); + } + r = write(FS(c)->fd, va, n); + if(r < 0) + oserror(); + return r; +} + +static void +fswchk(Cname *c) +{ + struct stat st; + + if(stat(c->s, &st) < 0) + oserror(); + + if(st.st_uid == up->env->uid) + st.st_mode >>= 6; + else if(st.st_gid == up->env->gid || ingroup(up->env->uid, st.st_gid)) + st.st_mode >>= 3; + + if(st.st_mode & S_IWOTH) + return; + + error(Eperm); +} + +static void +fsremove(Chan *c) +{ + int n; + Cname *volatile dir; + + if(waserror()){ + fsfree(c); + nexterror(); + } + dir = fswalkpath(FS(c)->name, "..", 1); + if(waserror()){ + cnameclose(dir); + nexterror(); + } + fswchk(dir); + cnameclose(dir); + poperror(); + if(c->qid.type & QTDIR) + n = rmdir(FS(c)->name->s); + else + n = remove(FS(c)->name->s); + if(n < 0) + oserror(); + poperror(); + fsfree(c); +} + +static int +fswstat(Chan *c, uchar *buf, int nb) +{ + Dir *d; + User *p; + Cname *volatile ph; + struct stat st; + struct utimbuf utbuf; + int tsync; + + if(FS(c)->fd >= 0){ + if(fstat(FS(c)->fd, &st) < 0) + oserror(); + }else{ + if(stat(FS(c)->name->s, &st) < 0) + oserror(); + } + d = malloc(sizeof(*d)+nb); + if(d == nil) + error(Enomem); + if(waserror()){ + free(d); + nexterror(); + } + tsync = 1; + nb = convM2D(buf, nb, d, (char*)&d[1]); + if(nb == 0) + error(Eshortstat); + if(!emptystr(d->name) && strcmp(d->name, fslastelem(FS(c)->name)) != 0) { + tsync = 0; + validname(d->name, 0); + ph = fswalkpath(FS(c)->name, "..", 1); + if(waserror()){ + cnameclose(ph); + nexterror(); + } + fswchk(ph); + ph = fswalkpath(ph, d->name, 0); + if(rename(FS(c)->name->s, ph->s) < 0) + oserror(); + cnameclose(FS(c)->name); + poperror(); + FS(c)->name = ph; + } + + if(d->mode != ~0 && (d->mode&0777) != (st.st_mode&0777)) { + tsync = 0; + if(up->env->uid != st.st_uid) + error(Eowner); + if(FS(c)->fd >= 0){ + if(fchmod(FS(c)->fd, d->mode&0777) < 0) + oserror(); + }else{ + if(chmod(FS(c)->name->s, d->mode&0777) < 0) + oserror(); + } + FS(c)->mode &= ~0777; + FS(c)->mode |= d->mode&0777; + } + + if(d->atime != ~0 && d->atime != st.st_atime || + d->mtime != ~0 && d->mtime != st.st_mtime) { + tsync = 0; + if(up->env->uid != st.st_uid) + error(Eowner); + if(d->mtime != ~0) + utbuf.modtime = d->mtime; + else + utbuf.modtime = st.st_mtime; + if(d->atime != ~0) + utbuf.actime = d->atime; + else + utbuf.actime = st.st_atime; + if(utime(FS(c)->name->s, &utbuf) < 0) /* TO DO: futimes isn't portable */ + oserror(); + } + + if(*d->gid){ + tsync = 0; + qlock(&idl); + if(waserror()){ + qunlock(&idl); + nexterror(); + } + p = name2user(gidmap, d->gid, newgname); + if(p == 0) + error(Eunknown); + if(p->id != st.st_gid) { + if(up->env->uid != st.st_uid) + error(Eowner); + if(FS(c)->fd >= 0){ + if(fchown(FS(c)->fd, st.st_uid, p->id) < 0) + oserror(); + }else{ + if(chown(FS(c)->name->s, st.st_uid, p->id) < 0) + oserror(); + } + FS(c)->gid = p->id; + } + poperror(); + qunlock(&idl); + } + + if(d->length != ~(uvlong)0){ + tsync = 0; + if(FS(c)->fd >= 0){ + fsperm(c, 2); + if(ftruncate(FS(c)->fd, d->length) < 0) + oserror(); + }else{ + fswchk(FS(c)->name); + if(truncate(FS(c)->name->s, d->length) < 0) + oserror(); + } + } + + poperror(); + free(d); + if(tsync && FS(c)->fd >= 0 && fsync(FS(c)->fd) < 0) + oserror(); + return nb; +} + +static Qid +fsqid(struct stat *st) +{ + Qid q; + u16int dev; + + q.type = QTFILE; + if(S_ISDIR(st->st_mode)) + q.type = QTDIR; + + dev = (u16int)st->st_dev; + if(dev & 0x8000){ + static int aware = 1; + if(aware==0){ + aware = 1; + fprint(2, "fs: fsqid: top-bit dev: %#4.4ux\n", dev); + } + dev ^= 0x8080; + } + + q.path = (uvlong)dev<<48; + q.path ^= st->st_ino; + q.vers = st->st_mtime; + + return q; +} + +static void +fspath(Cname *c, char *name, char *path) +{ + int n; + + if(c->len+strlen(name) >= MAXPATH) + panic("fspath: name too long"); + memmove(path, c->s, c->len); + n = c->len; + if(path[n-1] != '/') + path[n++] = '/'; + strcpy(path+n, name); + if(isdotdot(name)) + cleanname(path); +/*print("->%s\n", path);*/ +} + +static Cname * +fswalkpath(Cname *c, char *name, int dup) +{ + if(dup) + c = newcname(c->s); + c = addelem(c, name); + if(isdotdot(name)) + cleancname(c); + return c; +} + +static char * +fslastelem(Cname *c) +{ + char *p; + + p = c->s + c->len; + while(p > c->s && p[-1] != '/') + p--; + return p; +} + +static void +fsperm(Chan *c, int mask) +{ + int m; + + m = FS(c)->mode; +/* + print("fsperm: %o %o uuid %d ugid %d cuid %d cgid %d\n", + m, mask, up->env->uid, up->env->gid, FS(c)->uid, FS(c)->gid); +*/ + if(FS(c)->uid == up->env->uid) + m >>= 6; + else if(FS(c)->gid == up->env->gid || ingroup(up->env->uid, FS(c)->gid)) + m >>= 3; + + m &= mask; + if(m == 0) + error(Eperm); +} + +static int +isdots(char *name) +{ + return name[0] == '.' && (name[1] == '\0' || name[1] == '.' && name[2] == '\0'); +} + +static int +fsdirconv(Chan *c, char *path, char *name, struct stat *s, uchar *va, int nb, int indir) +{ + Dir d; + char uidbuf[NUMSIZE], gidbuf[NUMSIZE]; + User *u; + int fd; + + memset(&d, 0, sizeof(d)); + d.name = name; + u = id2user(uidmap, s->st_uid, newuid); + if(u == nil){ + snprint(uidbuf, sizeof(uidbuf), "#%lud", (long)s->st_uid); + d.uid = uidbuf; + }else + d.uid = u->name; + u = id2user(gidmap, s->st_gid, newgid); + if(u == nil){ + snprint(gidbuf, sizeof(gidbuf), "#%lud", (long)s->st_gid); + d.gid = gidbuf; + }else + d.gid = u->name; + d.muid = ""; + d.qid = fsqid(s); + d.mode = (d.qid.type<<24)|(s->st_mode&0777); + d.atime = s->st_atime; + d.mtime = s->st_mtime; + d.length = s->st_size; + if(d.mode&DMDIR) + d.length = 0; + else if(S_ISBLK(s->st_mode) && s->st_size == 0){ + fd = open(path, O_RDONLY); + if(fd >= 0){ + d.length = osdisksize(fd); + close(fd); + } + } + d.type = 'U'; + d.dev = c->dev; + if(indir && sizeD2M(&d) > nb) + return -1; /* directory reader needs to know it didn't fit */ + return convD2M(&d, va, nb); +} + +static long +fsdirread(Chan *c, uchar *va, int count, vlong offset) +{ + int i; + long n, r; + struct stat st; + char path[MAXPATH], *ep; + struct dirent *de; + static uchar slop[8192]; + + i = 0; + fspath(FS(c)->name, "", path); + ep = path+strlen(path); + if(FS(c)->offset != offset) { + seekdir(FS(c)->dir, 0); + FS(c)->de = nil; + FS(c)->eod = 0; + for(n=0; ndir); + if(de == 0) { + /* EOF, so stash offset and return 0 */ + FS(c)->offset = n; + FS(c)->eod = 1; + return 0; + } + if(de->d_ino==0 || de->d_name[0]==0 || isdots(de->d_name)) + continue; + strecpy(ep, path+sizeof(path), de->d_name); + if(xstat(path, &st) < 0) { + fprint(2, "dir: bad path %s\n", path); + continue; + } + qlock(&idl); + if(waserror()){ + qunlock(&idl); + nexterror(); + } + r = fsdirconv(c, path, de->d_name, &st, slop, sizeof(slop), 1); + poperror(); + qunlock(&idl); + if(r <= 0) { + FS(c)->offset = n; + return 0; + } + n += r; + } + FS(c)->offset = offset; + } + + if(FS(c)->eod) + return 0; + + /* + * Take idl on behalf of id2name. Stalling attach, which is a + * rare operation, until the readdir completes is probably + * preferable to adding lock round-trips. + */ + qlock(&idl); + while(i < count){ + de = FS(c)->de; + FS(c)->de = nil; + if(de == nil) + de = readdir(FS(c)->dir); + if(de == nil){ + FS(c)->eod = 1; + break; + } + + if(de->d_ino==0 || de->d_name[0]==0 || isdots(de->d_name)) + continue; + + strecpy(ep, path+sizeof(path), de->d_name); + if(xstat(path, &st) < 0) { + fprint(2, "dir: bad path %s\n", path); + continue; + } + r = fsdirconv(c, path, de->d_name, &st, va+i, count-i, 1); + if(r <= 0){ + FS(c)->de = de; + break; + } + i += r; + FS(c)->offset += r; + } + qunlock(&idl); + return i; +} + +static int +fsomode(int m) +{ + if(m < 0 || m > 3) + error(Ebadarg); + return m == 3? 0: m; +} + +void +setid(char *name, int owner) +{ + User *u; + + if(owner && !iseve()) + return; + kstrdup(&up->env->user, name); + + qlock(&idl); + u = name2user(uidmap, name, newuname); + if(u == nil){ + qunlock(&idl); + up->env->uid = -1; + up->env->gid = -1; + return; + } + + up->env->uid = u->id; + up->env->gid = u->gid; + qunlock(&idl); +} + +static User** +hashuser(User** tab, int id) +{ + int i; + + i = (id>>IDSHIFT) ^ id; + return &tab[i & IDMASK]; +} + +/* + * the caller of the following functions must hold QLock idl. + */ + +/* + * we could keep separate maps of user and group names to Users to + * speed this up, but the reverse lookup currently isn't common (ie, change group by wstat and setid) + */ +static User* +name2user(User **tab, char *name, User* (*get)(char*)) +{ + int i; + User *u, **h; + static User *prevu; + static User **prevtab; + + if(prevu != nil && prevtab == tab && strcmp(name, prevu->name) == 0) + return prevu; /* it's often the one we've just seen */ + + for(i=0; inext) + if(strcmp(name, u->name) == 0) { + prevtab = tab; + prevu = u; + return u; + } + + u = get(name); + if(u == nil) + return nil; + h = hashuser(tab, u->id); + u->next = *h; + *h = u; + prevtab = tab; + prevu = u; + return u; +} + +static void +freeuser(User *u) +{ + if(u != nil){ + free(u->name); + free(u->mem); + free(u); + } +} + +static User* +newuser(int id, int gid, char *name, int nmem) +{ + User *u; + + u = malloc(sizeof(*u)); + if(u == nil) + return nil; + u->name = strdup(name); + if(u->name == nil){ + free(u); + return nil; + } + u->nmem = nmem; + if(nmem){ + u->mem = malloc(nmem*sizeof(*u->mem)); + if(u->mem == nil){ + free(u->name); + free(u); + return nil; + } + }else + u->mem = nil; + u->id = id; + u->gid = gid; + u->next = nil; + return u; +} + +static User* +newuname(char *name) +{ + struct passwd *p; + + p = getpwnam(name); + if(p == nil) + return nil; + return newuser(p->pw_uid, p->pw_gid, name, 0); +} + +static User* +newuid(int id) +{ + struct passwd *p; + + p = getpwuid(id); + if(p == nil) + return nil; + return newuser(p->pw_uid, p->pw_gid, p->pw_name, 0); +} + +static User* +newgroup(struct group *g) +{ + User *u, *gm; + int n, o; + + if(g == nil) + return nil; + for(n=0; g->gr_mem[n] != nil; n++) + ; + u = newuser(g->gr_gid, g->gr_gid, g->gr_name, n); + if(u == nil) + return nil; + o = 0; + for(n=0; g->gr_mem[n] != nil; n++){ + gm = name2user(uidmap, g->gr_mem[n], newuname); + if(gm != nil) + u->mem[o++] = gm->id; + /* ignore names that don't map to IDs */ + } + u->nmem = o; + return u; +} + +static User* +newgid(int id) +{ + return newgroup(getgrgid(id)); +} + +static User* +newgname(char *name) +{ + return newgroup(getgrnam(name)); +} + +static User* +id2user(User **tab, int id, User* (*get)(int)) +{ + User *u, **h; + + h = hashuser(tab, id); + for(u = *h; u != nil; u = u->next) + if(u->id == id) + return u; + u = get(id); + if(u == nil) + return nil; + u->next = *h; + *h = u; + return u; +} + +static int +ingroup(int id, int gid) +{ + int i; + User *g; + + g = id2user(gidmap, gid, newgid); + if(g == nil || g->mem == nil) + return 0; + for(i = 0; i < g->nmem; i++) + if(g->mem[i] == id) + return 1; + return 0; +} + +Dev fsdevtab = { + 'U', + "fs", + + devinit, + fsattach, + fswalk, + fsstat, + fsopen, + fscreate, + fsclose, + fsread, + devbread, + fswrite, + devbwrite, + fsremove, + fswstat +}; diff --git a/emu/FreeRTOS/devfs.c b/emu/FreeRTOS/devfs.c new file mode 100644 index 0000000..d697d7b --- /dev/null +++ b/emu/FreeRTOS/devfs.c @@ -0,0 +1,26 @@ +#include "devfs-posix.c" + +#include +#include +#include + +static vlong +osdisksize(int fd) +{ + uvlong u64; + long l; + struct hd_geometry geo; + + memset(&geo, 0, sizeof geo); + l = 0; + u64 = 0; +#ifdef BLKGETSIZE64 + if(ioctl(fd, BLKGETSIZE64, &u64) >= 0) + return u64; +#endif + if(ioctl(fd, BLKGETSIZE, &l) >= 0) + return l*512; + if(ioctl(fd, HDIO_GETGEO, &geo) >= 0) + return (vlong)geo.heads*geo.sectors*geo.cylinders*512; + return 0; +} diff --git a/emu/FreeRTOS/emu b/emu/FreeRTOS/emu new file mode 100644 index 0000000..3931b81 --- /dev/null +++ b/emu/FreeRTOS/emu @@ -0,0 +1,112 @@ +dev + root + cons + env + mnt + pipe + prog + prof + srv + dup + ssl + cap + fs + cmd cmd + indir + +# draw win-$WINDOW_BACKEND +# pointer +# snarf + +# gui + +# ip ipif6-posix ipaux +# ip ipif-posix ipaux + eia +# audio audio-oss + mem + +lib + interp +# tk +# freetype + math +# draw + +# memlayer +# memdraw + keyring + sec + mp + + 9 + +# lodepng + +link + +mod + sys +# draw + +# tk + math + srv srv + keyring + crypt + ipints + loader +# freetype + +port + alloc + cache + chan + dev + devtab + + dial + dis + discall + env + error + errstr + exception + exportfs + inferno + latin1 + main + parse + pgrp + print + proc + qio + random + sysfile + uqid + +code + +init + emuinit + +root + /dev / + /fd / + /prog / + /prof / + /net / + /net.alt / + /chan / + /nvfs / + /env / +# /gui / + /n / +# /dis +# /n +# /icons +# /osinit.dis +# /dis/emuinit.dis +# /dis/lib/auth.dis +# /dis/lib/ssl.dis +# /n/local / diff --git a/emu/FreeRTOS/emu-g b/emu/FreeRTOS/emu-g new file mode 100644 index 0000000..8df0b4c --- /dev/null +++ b/emu/FreeRTOS/emu-g @@ -0,0 +1,101 @@ +env + X11LIBS= +dev + root + cons + env + mnt + pipe + prog + prof + srv + dup + ssl + cap + fs + cmd cmd + indir + + ip ipif6-posix ipaux + eia +# audio audio + mem + +lib + interp + math + keyring + sec + mp + + 9 + +link + +mod + sys + math + srv srv + keyring + crypt + ipints + loader + +port + alloc + cache + chan + dev + devtab + + dial + dis + discall + env + error + errstr + exception + exportfs + inferno + latin1 + main + parse + pgrp + print + proc + qio + random + sysfile + uqid + +code + void setpointer(int x, int y){USED(x); USED(y);} + ulong strtochan(char *s){USED(s); return ~0;} + +init + emuinit + +root + /dev / + /fd / + /prog / + /prof / + /net / + /net.alt / + /chan / + /nvfs / + /env / +# /chan +# /dev +# /dis +# /env +# /n +# /net +# /nvfs / +# /prog +# /icons +# /osinit.dis +# /dis/emuinit.dis +# /dis/lib/auth.dis +# /dis/lib/ssl.dis +# /n/local / diff --git a/emu/FreeRTOS/emu-wrt b/emu/FreeRTOS/emu-wrt new file mode 100644 index 0000000..0eac3af --- /dev/null +++ b/emu/FreeRTOS/emu-wrt @@ -0,0 +1,111 @@ +dev + root + cons + env + mnt + pipe + prog + prof + srv + dup + ssl + cap + fs + cmd cmd + indir + +# draw +# pointer +# snarf + + ip ipif6-posix ipaux +# eia +# audio audio + mem + +lib + interp +# tk +# freetype + math +# draw + +# memlayer +# memdraw + keyring + sec + mp + + 9 + +link + +mod + sys +# draw + +# tk + math + srv srv + keyring + crypt + ipints + loader +# freetype + +port + alloc + cache + chan + dev + dial + dis + discall + env + error + errstr + exception + exportfs + inferno + latin1 + main + parse + pgrp + print + proc + qio + random + sysfile + uqid + +code + int dontcompile = 1; + unsigned long strtochan(char *s) {return 0;} + +init + emuinit + +root + /dev / + /fd / + /prog / + /prof / + /net / + /net.alt / + /chan / + /nvfs / + /env / +# /chan +# /dev +# /dis +# /env +# /n +# /net +# /nvfs / +# /prog +# /icons +# /osinit.dis +# /dis/emuinit.dis +# /dis/lib/auth.dis +# /dis/lib/ssl.dis +# /n/local / diff --git a/emu/FreeRTOS/kproc-xthreads.c b/emu/FreeRTOS/kproc-xthreads.c new file mode 100644 index 0000000..ade5e35 --- /dev/null +++ b/emu/FreeRTOS/kproc-xthreads.c @@ -0,0 +1,264 @@ +#include "dat.h" +#include "fns.h" +#include "error.h" + +#undef _POSIX_C_SOURCE +#undef getwd + +#include +#include +#include +#include +#include +//{}#include + + +#include + +#include + +typedef SemaphoreHandle_t sem_t; + +typedef struct Osdep Osdep; +struct Osdep { + sem_t sem; + pthread_t self; +}; + +static pthread_key_t prdakey; + +extern int dflag; + +Proc* +getup(void) +{ + return pvGetLuaState(); +// return pthread_getspecific(prdakey); +} + +void +pexit(char *msg, int t) +{ + Osenv *e; + Proc *p; + Osdep *os; + + USED(t); + + lock(&procs.l); + p = up; + if(p->prev) + p->prev->next = p->next; + else + procs.head = p->next; + + if(p->next) + p->next->prev = p->prev; + else + procs.tail = p->prev; + unlock(&procs.l); + + if(0) + print("pexit: %s: %s\n", p->text, msg); + + e = p->env; + if(e != nil) { + closefgrp(e->fgrp); + closepgrp(e->pgrp); + closeegrp(e->egrp); + closesigs(e->sigs); + free(e->user); + } + free(p->prog); + os = p->os; + if(os != nil){ +// sem_destroy(&os->sem); + vSemaphoreDelete(os->sem); + os->sem = NULL; + free(os); + } + free(p); + pthread_exit(0); +} + +static void* +tramp(void *arg) +{ + Proc *p; + Osdep *os; + + p = arg; + os = p->os; + os->self = pthread_self(); + + uxSetLuaState(arg); +// if(pthread_setspecific(prdakey, arg)) +// panic("set specific data failed in tramp\n"); +#if 0 //{} + if(0){ + pthread_attr_t attr; + memset(&attr, 0, sizeof(attr)); + pthread_getattr_np(pthread_self(), &attr); + size_t s; + pthread_attr_getstacksize(&attr, &s); + print("stack size = %d\n", s); + } +#endif //{} + p->func(p->arg); + pexit("{Tramp}", 0); + return nil; +} + +void +kproc(char *name, void (*func)(void*), void *arg, int flags) +{ + pthread_t thread; + Proc *p; + Pgrp *pg; + Fgrp *fg; + Egrp *eg; + pthread_attr_t attr; + Osdep *os; + + p = newproc(); + if(p == nil) + panic("kproc: no memory"); + + os = malloc(sizeof(*os)); + if(os == nil) + panic("kproc: no memory"); + os->self = 0; /* set by tramp */ +// sem_init(&os->sem, 0, 0); + os->sem = xSemaphoreCreateMutex(); +// os->sem = xSemaphoreCreateCounting(1024, 0); + p->os = os; + + if(flags & KPDUPPG) { + pg = up->env->pgrp; + incref(&pg->r); + p->env->pgrp = pg; + } + if(flags & KPDUPFDG) { + fg = up->env->fgrp; + incref(&fg->r); + p->env->fgrp = fg; + } + if(flags & KPDUPENVG) { + eg = up->env->egrp; + incref(&eg->r); + p->env->egrp = eg; + } + + p->env->uid = up->env->uid; + p->env->gid = up->env->gid; + kstrdup(&p->env->user, up->env->user); + + strcpy(p->text, name); + + p->func = func; + p->arg = arg; + + lock(&procs.l); + if(procs.tail != nil) { + p->prev = procs.tail; + procs.tail->next = p; + } else { + procs.head = p; + p->prev = nil; + } + procs.tail = p; + unlock(&procs.l); + + memset(&attr, 0, sizeof(attr)); + if(pthread_attr_init(&attr) == -1) + panic("pthread_attr_init failed"); + if(flags & KPX11) + pthread_attr_setstacksize(&attr, 512*1024); /* could be a parameter */ + else if(KSTACK > 0) + pthread_attr_setstacksize(&attr, (KSTACK < /*PTHREAD_STACK_MIN*/configMINIMAL_STACK_SIZE ? /*PTHREAD_STACK_MIN*/configMINIMAL_STACK_SIZE : KSTACK)+1024); +#ifndef ANDROID +// pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED); +#endif + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if(pthread_create(&thread, &attr, tramp, p)) + panic("thr_create failed\n"); + pthread_attr_destroy(&attr); +} + +/* called to wake up kproc blocked on a syscall */ +void +oshostintr(Proc *p) +{ + Osdep *os; + + os = p->os; + if(os != nil && os->self != 0) + pthread_kill(os->self, SIGUSR1); +} + +void +osblock(void) +{ + Osdep *os; + + os = up->os; +// while(sem_wait(&os->sem)) + while(xSemaphoreTake(os->sem, ( TickType_t )512) != pdTRUE) + {} /* retry on signals (which shouldn't happen) */ +} + +void +osready(Proc *p) +{ + Osdep *os; + + os = p->os; +// sem_post(&os->sem); + xSemaphoreGive(os->sem); +} + +void +kprocinit(Proc *p) +{ + if(pthread_key_create(&prdakey, NULL)) + panic("key_create failed"); + if(pthread_setspecific(prdakey, p)) + panic("set specific thread data failed"); +} + +#ifdef ANDROID +#include +#endif +void +osyield(void) +{ +// pthread_yield_np(); + /* define pthread_yield to be sched_yield or pthread_yield_np if required */ +#ifndef ANDROID + pthread_yield(); +#else + sched_yield(); +#endif +} + +void +ospause(void) +{ + /* main just wants this thread to go away */ + pthread_exit(0); +} + +void +oslopri(void) +{ +#if 0 //{} + struct sched_param param; + int policy; + pthread_t self; + + self = pthread_self(); + pthread_getschedparam(self, &policy, ¶m); + param.sched_priority = sched_get_priority_min(policy); + pthread_setschedparam(self, policy, ¶m); +#endif //{} +} diff --git a/emu/FreeRTOS/mk-wrt b/emu/FreeRTOS/mk-wrt new file mode 100644 index 0000000..f301dde --- /dev/null +++ b/emu/FreeRTOS/mk-wrt @@ -0,0 +1,8 @@ +#!/bin/sh + +OPENWRT=$HOME/OpenWrt-SDK-Linux-i686-1 +INFERNO=/usr/inferno + +PATH=$OPENWRT/staging_dir_mipsel/bin:$INFERNO/Linux/386/bin:$PATH + +mk OBJTYPE=spim CONF=emu-wrt CONFLIST=emu-wrt SYSLIBS=-lm WIN= $* diff --git a/emu/FreeRTOS/mkfile b/emu/FreeRTOS/mkfile new file mode 100644 index 0000000..88a2620 --- /dev/null +++ b/emu/FreeRTOS/mkfile @@ -0,0 +1,62 @@ +SYSTARG=FreeRTOS +<../../mkconfig +SYSTARG=FreeRTOS + +#Configurable parameters + +CONF=emu #default configuration +CONFLIST=emu +CLEANCONFLIST= + +INSTALLDIR=$ROOT/$SYSTARG/$OBJTYPE/bin #path of directory where kernel is installed + +#end configurable parameters + +#X11LIBS= -lX11 -lXext # can remove or override using env section in config files + +<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE #set vars based on target system + +<| $SHELLNAME ../port/mkdevlist $CONF #sets $IP, $DEVS, $PORT, $LIBS + +//#define getpid() syscall(SYS_getpid) + +enum +{ + DELETE = 0x7f, + CTRLC = 'C'-'@', + NSTACKSPERALLOC = 16, + X11STACK= 256*1024 +}; +char *hosttype = "FreeRTOS"; + +//typedef sem_t Sem; + +extern int dflag; + +int gidnobody = -1; +int uidnobody = -1; +//{} static struct termios tinit; + +static void +sysfsa_flagsault(char *what, void *addr) +{ + char buf[64]; + + snprint(buf, sizeof(buf), "sys: %s%#p", what, addr); + disfault(nil, buf); +} + +#if 0 //{} +static void +trapILL(int signo, siginfo_t *si, void *a) +{ + char buf[64]; + + USED(signo); + USED(a); +// sysfault("illegal instruction pc=", si->si_addr); + + snprint(buf, sizeof(buf), "illegal instruction sig: no=%d, co=%d, va=%d", si->si_signo, si->si_code, si->si_value.sival_int); + sysfault(buf, NULL); +} + +static int +isnilref(siginfo_t *si) +{ + return si != 0 ; // && (si->si_addr == (void*)~(uintptr_t)0 || (uintptr_t)si->si_addr < 512); +} + +static void +trapmemref(int signo, siginfo_t *si, void *a) +{ + USED(a); /* ucontext_t*, could fetch pc in machine-dependent way */ + if(isnilref(si)) + disfault(nil, exNilref); +// else if(signo == SIGBUS) +// sysfault("bad address addr=", si->si_addr); /* eg, misaligned */ +// else +// sysfault("segmentation violation addr=", si->si_addr); +} + +static void +trapFPE(int signo, siginfo_t *si, void *a) +{ + char buf[64]; + + USED(signo); + USED(a); +// snprint(buf, sizeof(buf), "sys: fp: exception status=%.4lux pc=%#p", getfsr(), si->si_addr); + snprint(buf, sizeof(buf), "sys: fp: exception status=%.4lux pc=%#p", getfsr(), si->si_value.sival_ptr); + disfault(nil, buf); +} +#endif //{} + +static void +trapUSR1(int signo) +{ + int intwait; + + USED(signo); + + intwait = up->intwait; + up->intwait = 0; /* clear it to let proc continue in osleave */ + + if(up->type != Interp) /* Used to unblock pending I/O */ + return; + + if(intwait == 0) /* Not posted so it's a sync error */ + disfault(nil, Eintr); /* Should never happen */ +} + +void +oslongjmp(void *regs, osjmpbuf env, int val) +{ + USED(regs); +// siglongjmp(env, val); + longjmp(env, val); +} + +static void +termset(void) +{ + +#if 0 + struct termios t; + + tcgetattr(0, &t); + tinit = t; + t.c_lflag &= ~(ICANON|ECHO|ISIG); + t.c_cc[VMIN] = 1; + t.c_cc[VTIME] = 0; + tcsetattr(0, TCSANOW, &t); +#endif +} + +static void +termrestore(void) +{ +#if 0 + tcsetattr(0, TCSANOW, &tinit); +#endif +} + +void +cleanexit(int x) +{ + USED(x); + + if(up->intwait) { + up->intwait = 0; + return; + } + + if(dflag == 0) + termrestore(); + + kill(0, SIGKILL); + exit(0); +} + +void +osreboot(char *file, char **argv) +{ + if(dflag == 0) + termrestore(); + execvp(file, argv); + error("reboot failure"); +} + +void +libinit(char *imod) +{ + struct sigaction act; + struct passwd *pw; + Proc *p; + char sys[64]; + + setsid(); + + gethostname(sys, sizeof(sys)); + kstrdup(&ossysname, sys); + pw = getpwnam("nobody"); + if(pw != nil) { + uidnobody = pw->pw_uid; + gidnobody = pw->pw_gid; + } + + if(dflag == 0) + termset(); + + memset(&act, 0, sizeof(act)); + act.sa_handler = trapUSR1; + sigaction(SIGUSR1, &act, nil); + + act.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &act, nil); + + /* + * For the correct functioning of devcmd in the + * face of exiting slaves + */ + signal(SIGPIPE, SIG_IGN); + if(signal(SIGTERM, SIG_IGN) != SIG_IGN) + signal(SIGTERM, cleanexit); + if(signal(SIGINT, SIG_IGN) != SIG_IGN) + signal(SIGINT, cleanexit); + + if(sflag == 0) { +/* //{} + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = trapILL; + sigaction(SIGILL, &act, nil); + act.sa_sigaction = trapFPE; + sigaction(SIGFPE, &act, nil); + act.sa_sigaction = trapmemref; + sigaction(SIGBUS, &act, nil); + sigaction(SIGSEGV, &act, nil); + act.sa_flags &= ~SA_SIGINFO; +*/ + } + + p = newproc(); + kprocinit(p); + + pw = getpwuid(getuid()); + if(pw != nil) + kstrdup(&eve, pw->pw_name); + else + print("cannot getpwuid\n"); + + p->env->uid = getuid(); + p->env->gid = getgid(); + + emuinit(imod); +} + +int +readkbd(void) +{ + int n; + char buf[1]; + + n = read(0, buf, sizeof(buf)); + if(n < 0) + print("keyboard close (n=%d, %s)\n", n, strerror(errno)); + if(n <= 0) + pexit("keyboard thread", 0); + + switch(buf[0]) { + case '\r': + buf[0] = '\n'; + break; + case DELETE: + buf[0] = 'H' - '@'; + break; + case CTRLC: + cleanexit(0); + break; + } + return buf[0]; +} + +/* + * Return an abitrary millisecond clock time + */ +long +osmillisec(void) +{ + static long sec0 = 0, usec0; + struct timeval t; + + if(gettimeofday(&t,(struct timezone*)0)<0) + return 0; + + if(sec0 == 0) { + sec0 = t.tv_sec; + usec0 = t.tv_usec; + } + return (t.tv_sec-sec0)*1000+(t.tv_usec-usec0+500)/1000; +} + +/* + * Return the time since the epoch in nanoseconds and microseconds + * The epoch is defined at 1 Jan 1970 + */ +vlong +osnsec(void) +{ + struct timeval t; + + gettimeofday(&t, nil); + return (vlong)t.tv_sec*1000000000L + t.tv_usec*1000; +} + +vlong +osusectime(void) +{ + struct timeval t; + + gettimeofday(&t, nil); + return (vlong)t.tv_sec * 1000000 + t.tv_usec; +} + +int +osmillisleep(ulong milsec) +{ + struct timespec time; + + time.tv_sec = milsec/1000; + time.tv_nsec= (milsec%1000)*1000000; + nanosleep(&time, NULL); + return 0; +} + +int +limbosleep(ulong milsec) +{ + return osmillisleep(milsec); +} diff --git a/emu/FreeRTOS/segflush-386.c b/emu/FreeRTOS/segflush-386.c new file mode 100644 index 0000000..18c940d --- /dev/null +++ b/emu/FreeRTOS/segflush-386.c @@ -0,0 +1,11 @@ +#include +#include + +#include "dat.h" + +int +segflush(void *a, ulong n) +{ + USED(a); USED(n); + return 0; +} diff --git a/emu/FreeRTOS/segflush-arm.c b/emu/FreeRTOS/segflush-arm.c new file mode 100644 index 0000000..0392977 --- /dev/null +++ b/emu/FreeRTOS/segflush-arm.c @@ -0,0 +1,14 @@ +#include +#include + +#include "dat.h" + +#define SYS_cacheflush __ARM_NR_cacheflush + +int +segflush(void *a, ulong n) +{ + if(n) + syscall(SYS_cacheflush, a, (char*)a+n-1, 1); + return 0; +} diff --git a/emu/FreeRTOS/segflush-mips.S b/emu/FreeRTOS/segflush-mips.S new file mode 100644 index 0000000..458172f --- /dev/null +++ b/emu/FreeRTOS/segflush-mips.S @@ -0,0 +1,16 @@ +#include "syscall.h" +#include +#include +#include + +/* + * int segflush(void *p, ulong len) + */ + +LEAF(segflush) + li a2,BCACHE + li v0,SYS_cacheflush + syscall + li v0,0 + j $31 + END(segflush) diff --git a/emu/FreeRTOS/segflush-power.c b/emu/FreeRTOS/segflush-power.c new file mode 100644 index 0000000..0804d68 --- /dev/null +++ b/emu/FreeRTOS/segflush-power.c @@ -0,0 +1,34 @@ +#include +#include + +#include "dat.h" + + +/* + * from geoff collyer's port + * invalidate instruction cache and write back data cache from a to a+n-1, + * at least. + */ +int +segflush(void *a, ulong n) +{ + ulong *p; + + // cache blocks are often eight words (32 bytes) long, sometimes 16 bytes. + // need to determine it dynamically? + for (p = (ulong *)((ulong)a & ~7UL); (char *)p < (char *)a + n; p++) + __asm__("dcbst 0,%0\n\t" // not dcbf, which writes back, then invalidates + "icbi 0,%0\n\t" + : // no output + : "ar" (p) + ); + __asm__("sync\n\t" + : // no output + : + ); + __asm__("isync\n\t" + : // no output + : + ); + return 0; +} diff --git a/emu/FreeRTOS/segflush-riscv64.c b/emu/FreeRTOS/segflush-riscv64.c new file mode 100644 index 0000000..bdc3cb9 --- /dev/null +++ b/emu/FreeRTOS/segflush-riscv64.c @@ -0,0 +1,11 @@ +#include +//#include + +#include "dat.h" + +int +segflush(void *a, ulong n) +{ + USED(a); USED(n); + return 0; +} diff --git a/emu/FreeRTOS/segflush-spim.S b/emu/FreeRTOS/segflush-spim.S new file mode 100644 index 0000000..458172f --- /dev/null +++ b/emu/FreeRTOS/segflush-spim.S @@ -0,0 +1,16 @@ +#include "syscall.h" +#include +#include +#include + +/* + * int segflush(void *p, ulong len) + */ + +LEAF(segflush) + li a2,BCACHE + li v0,SYS_cacheflush + syscall + li v0,0 + j $31 + END(segflush) diff --git a/emu/FreeRTOS/win-fb.c b/emu/FreeRTOS/win-fb.c new file mode 100644 index 0000000..a05af68 --- /dev/null +++ b/emu/FreeRTOS/win-fb.c @@ -0,0 +1,1881 @@ +/* + * This implementation of the screen functions for X11 uses the + * portable implementation of the Inferno drawing operations (libmemdraw) + * to do the work, then has flushmemscreen copy the result to the X11 display. + * Thus it potentially supports all colour depths but with a possible + * performance penalty (although it tries to use the X11 shared memory extension + * to copy the result to the screen, which might reduce the latter). + * + * CraigN + */ + +#define _GNU_SOURCE 1 +//#define XTHREADS +#include "dat.h" +#include "fns.h" +#undef log2 +#include +#include "cursor.h" +#include "keyboard.h" +#include +#include +#include + +#include +#include +#include +#include + +#include + +//#include "keysym2ucs.h" + +#include +//#include + +#define LOG_TAG "Inferno WIN" +#define LOGI(...) //printf(__VA_ARGS__) +#define LOGW(...) //printf(__VA_ARGS__) +#define LOGE(...) //printf(__VA_ARGS__) + +#define DBG(...) printf(__VA_ARGS__) + + +static int displaydepth = 32; +static ulong displaychan = CHAN4(CAlpha, 8, CRed, 8, CGreen, 8, CBlue, 8); + +enum +{ + DblTime = 300 /* double click time in msec */ +}; + +/* screen data .... */ +static uchar* gscreendata = NULL; +static uchar* xscreendata = NULL; + +//typedef unsigned int XColor; + +uchar map7to8[128][2]; + +/* for copy/paste, lifted from plan9ports via drawterm */ +//static Atom clipboard; +//static Atom utf8string; +//static Atom targets; +//static Atom text; +//static Atom compoundtext; + +//static Atom cursorchange; + +static int triedscreen = 0; + +void xexpose(); //XEvent*); +static void xmouse(); //XEvent*); +static void xkeyboard(); ///*XEvent*/void*); +//static void xsetcursor(/*XEvent*/void*); +static void xkbdproc(void*); +//static void xdestroy(XEvent*); +//static void xselect(XEvent*, XDisplay*); +static void xproc_expose(void*); +static void xproc_mouse(void*); + +static int xscreendepth; + +static int putsnarf, assertsnarf; +//char *gkscanid = "emu_x11"; + + +extern int Xsize; +extern int Ysize; + +static int Xmou_max = 0; +static int Ymou_max = 0; + +static int fd_mou = -1; +static int is_mt = 0; + + +static char *supported_mouse_devs[] = { + "Goodix Capacitive TouchScreen", + "TSC2003 Touchscreen", + "TSC2007 Touchscreen", + NULL +}; + + +/* + * The documentation for the XSHM extension implies that if the server + * supports XSHM but is not the local machine, the XShm calls will + * return False; but this turns out not to be the case. Instead, the + * server throws a BadAccess error. So, we need to catch X errors + * around all of our XSHM calls, sigh. + */ +//static int shm_got_x_error = 0; +//static XErrorHandler old_handler = 0; +//static XErrorHandler old_io_handler = 0; + + + +struct { + int fd; + uchar* base; + uchar* data; + + int width; + int height; + int bpp; + int stride; + + int alloc; +} fb; + + + +static uchar* +attach_fb() +{ + struct fb_var_screeninfo fb_var; + struct fb_fix_screeninfo fb_fix; + int off; + + fb.alloc = 0; + fb.fd = -1; + + if ((fb.fd = open ("/dev/fb0", O_RDWR)) < 0) + { + LOGE ("Error opening /dev/fb0"); + return NULL; + } + + if (ioctl (fb.fd, FBIOGET_VSCREENINFO, &fb_var) == -1) + { + LOGE ("Error getting variable framebuffer info"); + return NULL; + } + + if (fb_var.bits_per_pixel < 16) + { + fprintf(stderr, + "Error, no support currently for %i bpp frame buffers\n" + "Trying to change pixel format...\n", + fb_var.bits_per_pixel); + return NULL; + } + + if (ioctl (fb.fd, FBIOGET_VSCREENINFO, &fb_var) == -1) + { + LOGE ("Error getting variable framebuffer info (2)"); + return NULL; + } + + /* NB: It looks like the fbdev concept of fixed vs variable screen info is + * broken. The line_length is part of the fixed info but it can be changed + * if you set a new pixel format. */ + if (ioctl (fb.fd, FBIOGET_FSCREENINFO, &fb_fix) == -1) + { + LOGE ("Error getting fixed framebuffer info"); + return NULL; + } + /*fb.real_width =*/ Xsize = fb.width = fb_var.xres; + /*fb.real_height =*/ Ysize = fb.height = fb_var.yres; + + fb.bpp = fb_var.bits_per_pixel; + fb.stride = fb_fix.line_length; + +/* + fb.type = fb_fix.type; + fb.visual = fb_fix.visual; + + fb.red_offset = fb_var.red.offset; + fb.red_length = fb_var.red.length; + fb.green_offset = fb_var.green.offset; + fb.green_length = fb_var.green.length; + fb.blue_offset = fb_var.blue.offset; + fb.blue_length = fb_var.blue.length; + + if (fb.red_offset == 11 && fb.red_length == 5 && + fb.green_offset == 5 && fb.green_length == 6 && + fb.blue_offset == 0 && fb.blue_length == 5) { + fb.rgbmode = RGB565; + } else if (fb.red_offset == 0 && fb.red_length == 5 && + fb.green_offset == 5 && fb.green_length == 6 && + fb.blue_offset == 11 && fb.blue_length == 5) { + fb.rgbmode = BGR565; + } else if (fb.red_offset == 16 && fb.red_length == 8 && + fb.green_offset == 8 && fb.green_length == 8 && + fb.blue_offset == 0 && fb.blue_length == 8) { + fb.rgbmode = fb.bpp == 32 ? ARGB888 : RGB888; + } else if (fb.red_offset == 0 && fb.red_length == 8 && + fb.green_offset == 8 && fb.green_length == 8 && + fb.blue_offset == 8 && fb.blue_length == 8) { + fb.rgbmode = fb.bpp == 32 ? ABGR888 : BGR888; + } else { + fb.rgbmode = GENERIC; + } +*/ + LOGI("width: %i, height: %i, bpp: %i, stride: %i\n", + fb.width, fb.height, fb.bpp, fb.stride); + + size_t size = fb.stride * fb.height; + + fb.base = (uchar*) mmap ((caddr_t) NULL, + /*fb_fix.smem_len */ + size, + PROT_READ|PROT_WRITE, + MAP_SHARED, + fb.fd, 0); + + if (fb.base == (uchar*)-1) + { + LOGE("Error cannot mmap framebuffer. Using malloc instead.\n"); + fb.base = (char*)malloc(size); + if (!fb.base) + { + LOGE("Error cannot allocate memory."); + return NULL; + } + fb.alloc = 1; + } + + off = (unsigned long) fb_fix.smem_start % (unsigned long) getpagesize(); + + fb.data = fb.base + off; + + return fb.data; +} + + + + + +#define BITS_PER_LONG (sizeof(long) * 8) +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) +#define OFF(x) ((x)%BITS_PER_LONG) +#define BIT(x) (1UL<> OFF(bit)) & 1) + +static int is_event_device(const struct dirent *dir) { + return strncmp("event", dir->d_name, 5) == 0; +} + +static int +scan_mouse() +{ + struct dirent **namelist; + int i, j, ndev; + int fd = -1, fd_cap = -1, fd_res = -1; + char fname[64]; + char *dname = malloc(256); + + unsigned long bits[NBITS(KEY_MAX)]; + struct input_absinfo abs; + + int touch_res; + int touch_cap; + + ndev = scandir("/dev/input", &namelist, is_event_device, versionsort); + if (ndev <= 0) + return -1; +DBG("ndev=%d\n", ndev); + + for(i = 0; i < ndev; i++){ + int is_supported = 0; + + snprintf(fname, sizeof(fname), "/dev/input/%s", namelist[i]->d_name); +DBG("test file: %s\n", fname); + + fd = open(fname, O_RDWR); + if (fd < 0) + continue; + + ioctl(fd, EVIOCGNAME(256), dname); +DBG("test dev: %s\n", dname); + for(j = 0; supported_mouse_devs[j] != NULL; j++){ + if(!strcmp(dname, supported_mouse_devs[j])){ + is_supported = 1; + break; + } + } + + if(!is_supported){ + close(fd); + continue; + } +DBG("supported: %s\n", fname); + + ioctl(fd, EVIOCGBIT(0, EV_MAX), bits); + if (!test_bit(EV_ABS, bits)){ + close(fd); + continue; + } + + ioctl(fd, EVIOCGBIT(EV_ABS, KEY_MAX), bits); + if(!( + test_bit(ABS_MT_POSITION_X, bits) && + test_bit(ABS_MT_POSITION_Y, bits) + ) + ){ + if(fd_res >= 0) + close(fd_res); + fd_res = fd; +DBG("RESISTIVE\n"); + }else{ + if(fd_cap >= 0) + close(fd_cap); + fd_cap = fd; +DBG("CAPACITIVE\n"); + } + } + + if(fd_cap < 0){ + ioctl(fd, EVIOCGABS (ABS_X), &abs); + Xmou_max = abs.maximum; +#if 0 + context->min.x = abs.minimum; + context->max.x = abs.maximum; +#endif + ioctl (fd, EVIOCGABS (ABS_Y), &abs); + Ymou_max = abs.maximum; +#if 0 + context->max.y = abs.maximum; + context->have_multitouch = 0; + context->min.id = -1; + context->max.id = -1; +#endif + is_mt = 0; + fd = fd_res; + }else{ + if(fd_res >= 0) + close(fd_res); + + ioctl (fd, EVIOCGABS (ABS_MT_POSITION_X), &abs); + Xmou_max = abs.maximum; + printf(">O X = (%d -- %d)\n", abs.minimum, abs.maximum); +/* + abs.maximum = 800; + ioctl (fd, EVIOCSABS (ABS_MT_POSITION_X), &abs); + ioctl (fd, EVIOCGABS (ABS_MT_POSITION_X), &abs); + printf(">N X = (%d -- %d)\n", abs.minimum, abs.maximum); +*/ + // ---- + ioctl (fd, EVIOCGABS (ABS_MT_POSITION_Y), &abs); + Ymou_max = abs.maximum; + printf(">O Y = (%d -- %d)\n", abs.minimum, abs.maximum); +/* + abs.maximum = 480; + ioctl (fd, EVIOCSABS (ABS_MT_POSITION_Y), &abs); + ioctl (fd, EVIOCGABS (ABS_MT_POSITION_Y), &abs); + printf(">N Y = (%d -- %d)\n", abs.minimum, abs.maximum); +*/ + // ---- + is_mt = 1; + fd = fd_cap; + } +DBG("is_mt=%d\n", is_mt); + + free(dname); + + return fd; +} + + + + +uchar* +attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen) +{ + int depth; +LOGE("attachscreen\n"); + + if(triedscreen) + return gscreendata; + + // stop cursor blinking + int f = open("/dev/tty1", O_WRONLY); + if(f >= 0){ + char s[]="\033[?17;0;0c"; + write(f, s, strlen(s)); + close(f); + } + + // prepare mouse + if(fd_mou == -1){ + fd_mou = scan_mouse(); //open("/dev/input/event1", O_RDWR); + } + + triedscreen = 1; + + //if(gscreendata != xscreendata) + kproc("xproc_expose", xproc_expose, NULL/*xmcon*/, 0); + kproc("xproc_mouse", xproc_mouse, NULL/*xmcon*/, 0); +// kproc("xkbdproc", xkbdproc, NULL/*xkbdcon*/, 0/*KPX11*/); /* silly stack size for bloated X11 */ + + xscreendata = attach_fb(); //malloc(Xsize * Ysize * 4); + xscreendepth = fb.bpp; //32; + + Xsize &= ~0x3; /* ensure multiple of 4 */ + + r->min.x = 0; + r->min.y = 0; + r->max.x = Xsize; + r->max.y = Ysize; + + /* + * moved xproc from here to end since it could cause an expose event and + * hence a flushmemscreen before xscreendata is initialized + */ + + *chan = displaychan; /* not every channel description will work */ + *d = chantodepth(displaychan); + displaydepth = *d; + + printf("%s:%d displaychan=%X, displaydepth=%d\n", __func__, __LINE__, displaychan, displaydepth); + +// gscreendata = xscreendata; + gscreendata = malloc(Xsize * (Ysize+1) * (displaydepth >> 3)); + + LOGE("attachscreen: gscreendata=%x, (displaydepth>>3)=%d\n", gscreendata, (displaydepth>>3)); + + *width = (Xsize/4)*(*d/8); + *softscreen = 1; + + return gscreendata; +} + + +static void +copy32to32(Rectangle r) +{ + int dx, width; + uchar *p, *ep, *cp; + u32int v, w, *dp, *wp, *edp, *lp; + +LOGE("copy32to32"); + width = Dx(r); + dx = Xsize - width; + dp = (u32int*)(gscreendata + (r.min.y * Xsize + r.min.x) * 4); + wp = (u32int*)(xscreendata + (r.min.y * Xsize + r.min.x) * 4); + edp = (u32int*)(gscreendata + (r.max.y * Xsize + r.max.x) * 4); + while(dp < edp) { + lp = dp + width; + while(dp < lp){ + v = *dp++; + //w = v +// infernortox11[ (v >> 16) & 0xff ] << 16 +// | infernogtox11[ (v >> 8) & 0xff ] << 8 +// | infernobtox11[ (v >> 0) & 0xff ] << 0 + //; + *wp++ = v; //w; + } + dp += dx; + wp += dx; + } +} + +static void +copy8to32(Rectangle r) +{ + int dx, width; + uchar *p, *ep, *lp; + u32int *wp; + +LOGE("copy8to32"); + width = Dx(r); + dx = Xsize - width; + p = gscreendata + r.min.y * Xsize + r.min.x; + wp = (u32int *)(xscreendata + (r.min.y * Xsize + r.min.x) * 4); + ep = gscreendata + r.max.y * Xsize + r.max.x; + while(p < ep) { + lp = p + width; + while(p < lp) + *wp++ = *p++; //infernotox11[*p++]; + p += dx; + wp += dx; + } +} + +static void +copy8to24(Rectangle r) +{ + int dx, width, v; + uchar *p, *cp, *ep, *lp; + +LOGE("copy8to24"); + width = Dx(r); + dx = Xsize - width; + p = gscreendata + r.min.y * Xsize + r.min.x; + cp = xscreendata + (r.min.y * Xsize + r.min.x) * 3; + ep = gscreendata + r.max.y * Xsize + r.max.x; + while(p < ep) { + lp = p + width; + while(p < lp){ + v = *p++; //infernotox11[*p++]; + cp[0] = (v>>16)&0xff; + cp[1] = (v>>8)&0xff; + cp[2] = (v>>0)&0xff; + cp += 3; + } + p += dx; + cp += 3*dx; + } +} + +static void +copy8to16(Rectangle r) +{ + int dx, width; + uchar *p, *ep, *lp; + u16int *sp; + +LOGE("copy8to16"); + width = Dx(r); + dx = Xsize - width; + p = gscreendata + r.min.y * Xsize + r.min.x; + sp = (unsigned short *)(xscreendata + (r.min.y * Xsize + r.min.x) * 2); + ep = gscreendata + r.max.y * Xsize + r.max.x; + while(p < ep) { + lp = p + width; + while(p < lp) + *sp++ = *p++; //infernotox11[*p++]; + p += dx; + sp += dx; + } +} + +static void +copy8to8(Rectangle r) +{ + int dx, width; + uchar *p, *cp, *ep, *lp; + +LOGE("copy8to8"); + width = Dx(r); + dx = Xsize - width; + p = gscreendata + r.min.y * Xsize + r.min.x; + cp = xscreendata + r.min.y * Xsize + r.min.x; + ep = gscreendata + r.max.y * Xsize + r.max.x; + while(p < ep) { + lp = p + width; + while(p < lp) + *cp++ = *p++; //infernotox11[*p++]; + p += dx; + cp += dx; + } +} + +static void +copy8topixel(Rectangle r) +{ + int x, y; + uchar *p; + +LOGE("copy8topixel"); + if(xscreendata == NULL) + return; + + /* mainly for 4-bit greyscale */ + for (y = r.min.y; y < r.max.y; y++) { + x = r.min.x; + p = gscreendata + y * Xsize + x; + while (x < r.max.x){ + xscreendata[y * Xsize * 4 + x] = *p++; + x++; +// XPutPixel(img, x++, y, infernotox11[*p++]); + } + } +} + + +//extern void int_refresh(int xb_, int yb_, int xe_, int ye_); + +void +flushmemscreen(Rectangle r) +{ + char chanbuf[16]; + + // Clip to screen + if(r.min.x < 0) + r.min.x = 0; + if(r.min.y < 0) + r.min.y = 0; + if(r.max.x >= Xsize) + r.max.x = Xsize - 1; + if(r.max.y >= Ysize) + r.max.y = Ysize - 1; + + if(r.max.x <= r.min.x || r.max.y <= r.min.y) + return; + + if(xscreendata == NULL) + return; + + LOGE("flushmemscreen: rect=(%d, %d)-(%d, %d), xscreendata=%x, displaydepth=%d, xscreendepth=%d\n", + r.min.x, r.min.y, r.max.x, r.max.y, xscreendata, displaydepth, xscreendepth + ); + +#if 1 + if(gscreendata != xscreendata) + switch(displaydepth){ + case 32: + copy32to32(r); + break; + case 8: + switch(xscreendepth){ + case 24: + /* copy8to24(r); */ /* doesn't happen? */ + /* break */ + case 32: + copy8to32(r); + break; + case 16: + copy8to16(r); + break; + case 8: + copy8to8(r); + break; + default: + copy8topixel(r); + break; + } + break; + default: + fprint(2, "emu: bad display depth %d chan %s xscreendepth %d\n", displaydepth, + chantostr(chanbuf, displaychan), xscreendepth); + cleanexit(0); + } +#endif + +// int_refresh(r.min.x, r.min.y, r.max.x, r.max.y); +} + +static int +revbyte(int b) +{ + int r; + + r = 0; + r |= (b&0x01) << 7; + r |= (b&0x02) << 5; + r |= (b&0x04) << 3; + r |= (b&0x08) << 1; + r |= (b&0x10) >> 1; + r |= (b&0x20) >> 3; + r |= (b&0x40) >> 5; + r |= (b&0x80) >> 7; + return r; +} + +void +setpointer(int x, int y) +{ +/* + drawqlock(); + XLockDisplay(xdisplay); + XWarpPointer(xdisplay, None, xdrawable, 0, 0, 0, 0, x, y); + XFlush(xdisplay); + XUnlockDisplay(xdisplay); + drawqunlock(); +*/ +} + +static void +xkbdproc(void *arg) +{ +// XEvent event; +// XDisplay *xd; + +// xd = arg; + + /* BEWARE: the value of up is not defined for this proc on some systems */ + +// XLockDisplay(xd); /* should be ours alone */ +// XSelectInput(xd, xdrawable, KeyPressMask | KeyReleaseMask); + for(;;){ +// XNextEvent(xd, &event); + xkeyboard(); //&event); +// xsetcursor(); //&event); + } +} + +static void +xproc_expose(void *arg) +{ +// ulong mask; +// XEvent event; +// XDisplay *xd; + + closepgrp(up->env->pgrp); + closefgrp(up->env->fgrp); + closeegrp(up->env->egrp); + closesigs(up->env->sigs); + +#if 0 + xd = arg; + mask = ButtonPressMask| + ButtonReleaseMask| + PointerMotionMask| + Button1MotionMask| + Button2MotionMask| + Button3MotionMask| + Button4MotionMask| + Button5MotionMask| + ExposureMask| + StructureNotifyMask; + + XLockDisplay(xd); /* should be ours alone */ + XSelectInput(xd, xdrawable, mask); +#endif + for(;;){ + osmillisleep(10); +// XNextEvent(xd, &event); +// xselect(&event, xd); + //xmouse(); //&event); + xexpose(); //&event); +// xdestroy(&event); + } +} + +static void +xproc_mouse(void *arg) +{ +// ulong mask; +// XEvent event; +// XDisplay *xd; + + closepgrp(up->env->pgrp); + closefgrp(up->env->fgrp); + closeegrp(up->env->egrp); + closesigs(up->env->sigs); + +#if 0 + xd = arg; + mask = ButtonPressMask| + ButtonReleaseMask| + PointerMotionMask| + Button1MotionMask| + Button2MotionMask| + Button3MotionMask| + Button4MotionMask| + Button5MotionMask| + ExposureMask| + StructureNotifyMask; + + XLockDisplay(xd); /* should be ours alone */ + XSelectInput(xd, xdrawable, mask); +#endif + for(;;){ + osmillisleep(100); +// XNextEvent(xd, &event); +// xselect(&event, xd); + xmouse(); //&event); + //xexpose(); //&event); +// xdestroy(&event); + } +} + +/* + * this crud is here because X11 can put huge amount of data + * on the stack during keyboard translation and cursor changing(!). + * we do both in a dedicated process with lots of stack, perhaps even enough. + */ + +enum { + CursorSize= 32 /* biggest cursor size */ +}; + +typedef struct ICursor ICursor; +struct ICursor { + int inuse; + int modify; + int hotx; + int hoty; + int w; + int h; + uchar src[(CursorSize/8)*CursorSize]; /* image and mask bitmaps */ + uchar mask[(CursorSize/8)*CursorSize]; +}; +static ICursor icursor; + +static void +xcurslock(void) +{ + while(_tas(&icursor.inuse) != 0) + osyield(); +} + +static void +xcursunlock(void) +{ + coherence(); + icursor.inuse = 0; +} + +#if 0 +static void +xcursnotify(void) +{ + XClientMessageEvent e; + + memset(&e, 0, sizeof e); + e.type = ClientMessage; + e.window = xdrawable; + e.message_type = cursorchange; + e.format = 8; + XLockDisplay(xdisplay); + XSendEvent(xdisplay, xdrawable, True, KeyPressMask, (XEvent*)&e); + XFlush(xdisplay); + XUnlockDisplay(xdisplay); +} +#endif + +void +drawcursor(Drawcursor* c) +{ +#if 0 + uchar *bs, *bc, *ps, *pm; + int i, j, w, h, bpl; + + if(c->data == nil){ + drawqlock(); + if(icursor.h != 0){ + xcurslock(); + icursor.h = 0; + icursor.modify = 1; + xcursunlock(); + } + xcursnotify(); + drawqunlock(); + return; + } + + drawqlock(); + xcurslock(); + icursor.modify = 0; /* xsetcursor will now ignore it */ + xcursunlock(); + + h = (c->maxy-c->miny)/2; /* image, then mask */ + if(h > CursorSize) + h = CursorSize; + bpl = bytesperline(Rect(c->minx, c->miny, c->maxx, c->maxy), 1); + w = bpl; + if(w > CursorSize/8) + w = CursorSize/8; + + ps = icursor.src; + pm = icursor.mask; + bc = c->data; + bs = c->data + h*bpl; + for(i = 0; i < h; i++){ + for(j = 0; j < bpl && j < w; j++) { + *ps++ = revbyte(bs[j]); + *pm++ = revbyte(bs[j] | bc[j]); + } + bs += bpl; + bc += bpl; + } + icursor.h = h; + icursor.w = w*8; + icursor.hotx = c->hotx; + icursor.hoty = c->hoty; + icursor.modify = 1; + xcursnotify(); + drawqunlock(); +#endif +} + +#if 0 +static void +xsetcursor(XEvent *e) +{ + ICursor ic; + XCursor xc; + XColor fg, bg; + Pixmap xsrc, xmask; + static XCursor xcursor; + + if(e->type != ClientMessage || !e->xclient.send_event || e->xclient.message_type != cursorchange) + return; + + xcurslock(); + if(icursor.modify == 0){ + xcursunlock(); + return; + } + icursor.modify = 0; + if(icursor.h == 0){ + xcursunlock(); + /* set the default system cursor */ + if(xcursor != 0) { + XFreeCursor(xkbdcon, xcursor); + xcursor = 0; + } + XUndefineCursor(xkbdcon, xdrawable); + XFlush(xkbdcon); + return; + } + ic = icursor; + xcursunlock(); + + xsrc = XCreateBitmapFromData(xkbdcon, xdrawable, (char*)ic.src, ic.w, ic.h); + xmask = XCreateBitmapFromData(xkbdcon, xdrawable, (char*)ic.mask, ic.w, ic.h); + + fg = map[0]; + bg = map[255]; + fg.pixel = infernotox11[0]; + bg.pixel = infernotox11[255]; + xc = XCreatePixmapCursor(xkbdcon, xsrc, xmask, &fg, &bg, -ic.hotx, -ic.hoty); + if(xc != 0) { + XDefineCursor(xkbdcon, xdrawable, xc); + if(xcursor != 0) + XFreeCursor(xkbdcon, xcursor); + xcursor = xc; + } + XFreePixmap(xkbdcon, xsrc); + XFreePixmap(xkbdcon, xmask); + XFlush(xkbdcon); +} +#endif + +typedef struct Mg Mg; +struct Mg +{ + int code; + int bit; + int len; + ulong mask; +}; + +static int +maskx(Mg* g, int code, ulong mask) +{ + int i; + + for(i=0; i<32; i++) + if(mask & (1<code = code; + g->bit = i; + g->mask = mask; + for(g->len = 0; i<32 && (mask & (1<len++; + return 1; +} + +/* + * for a given depth, we need to check the available formats + * to find how many actual bits are used per pixel. + */ +#if 0 +static int +xactualdepth(int screenno, int depth) +{ + XPixmapFormatValues *pfmt; + int i, n; + + pfmt = XListPixmapFormats(xdisplay, &n); + for(i=0; ivisual; + if(maskx(&r, CRed, xv->red_mask) && + maskx(&g, CGreen, xv->green_mask) && + maskx(&b, CBlue, xv->blue_mask)){ + d = xactualdepth(screenno, reqdepth); + if(d < 0) + return 0; + pad = d - (r.len + g.len + b.len); + if(0){ + fprint(2, "r: %8.8lux %d %d\ng: %8.8lux %d %d\nb: %8.8lux %d %d\n", + xv->red_mask, r.bit, r.len, xv->green_mask, g.bit, g.len, xv->blue_mask, b.bit, b.len); + } + if(r.bit > b.bit) + c = CHAN3(CRed, r.len, CGreen, g.len, CBlue, b.len); + else + c = CHAN3(CBlue, b.len, CGreen, g.len, CRed, r.len); + if(pad > 0) + c |= CHAN1(CIgnore, pad) << 24; + *chan = c; + xscreendepth = reqdepth; + if(0) + fprint(2, "chan=%s reqdepth=%d bits=%d\n", chantostr(buf, c), reqdepth, d); + return 1; + } + } + return 0; +} + +static int +xmapvisual(int screenno, XVisualInfo *vi, ulong *chan) +{ + if(XMatchVisualInfo(xdisplay, screenno, 8, PseudoColor, vi) || + XMatchVisualInfo(xdisplay, screenno, 8, StaticColor, vi)){ + *chan = CMAP8; + xscreendepth = 8; + return 1; + } + return 0; +} +#endif + + +/* + * Initialize and install the Inferno colormap as a private colormap for this + * application. Inferno gets the best colors here when it has the cursor focus. + */ +#if 0 +static void +xdestroy(XEvent *e) +{ + XDestroyWindowEvent *xe; + if(e->type != DestroyNotify) + return; + xe = (XDestroyWindowEvent*)e; + if(xe->window == xdrawable) + cleanexit(0); +} +#endif + +void +xexpose() //XEvent *e) +{ + static int cntr = 0; + Rectangle r; +// XExposeEvent *xe; + +// cntr++; +// if(cntr == 100000) +// cntr = 0; + +// if(cntr != 1) // e->type != Expose) +// return; +// xe = (XExposeEvent*)e; + r.min.x = 0; //xe->x; + r.min.y = 0; //xe->y; + r.max.x = Xsize; //xe->x + xe->width; + r.max.y = Ysize; //xe->y + xe->height; + +LOGE("xexpose"); + drawqlock(); + flushmemscreen(r); + drawqunlock(); +} + +static void +xkeyboard() ///*XEvent*/void *e) +{ + int ind, md; +// KeySym k; + int k; + +// if(gkscanq != nil && (e->type == KeyPress || e->type == KeyRelease)){ +// uchar ch = e->xkey.keycode; +// if(e->xany.type == KeyRelease) +// ch |= 0x80; +// qproduce(gkscanq, &ch, 1); +// return; +// } + +#if 0 + /* + * I tried using XtGetActionKeysym, but it didn't seem to + * do case conversion properly + * (at least, with Xterminal servers and R4 intrinsics) + */ + if(e->xany.type != KeyPress) + return; + + md = e->xkey.state; + ind = 0; + if(md & ShiftMask) + ind = 1; + if(0){ + k = XKeycodeToKeysym(e->xany.display, (KeyCode)e->xkey.keycode, ind); + + /* May have to try unshifted version */ + if(k == NoSymbol && ind == 1) + k = XKeycodeToKeysym(e->xany.display, (KeyCode)e->xkey.keycode, 0); + }else + XLookupString((XKeyEvent*)e, NULL, 0, &k, NULL); + + if(k == XK_Multi_key || k == NoSymbol) + return; + if(k&0xFF00){ + switch(k){ + case XK_BackSpace: + case XK_Tab: + case XK_Escape: + case XK_Delete: + case XK_KP_0: + case XK_KP_1: + case XK_KP_2: + case XK_KP_3: + case XK_KP_4: + case XK_KP_5: + case XK_KP_6: + case XK_KP_7: + case XK_KP_8: + case XK_KP_9: + case XK_KP_Divide: + case XK_KP_Multiply: + case XK_KP_Subtract: + case XK_KP_Add: + case XK_KP_Decimal: + k &= 0x7F; + break; + case XK_Linefeed: + k = '\r'; + break; + case XK_KP_Space: + k = ' '; + break; + case XK_Home: + case XK_KP_Home: + k = Home; + break; + case XK_Left: + case XK_KP_Left: + k = Left; + break; + case XK_Up: + case XK_KP_Up: + k = Up; + break; + case XK_Down: + case XK_KP_Down: + k = Down; + break; + case XK_Right: + case XK_KP_Right: + k = Right; + break; + case XK_Page_Down: + case XK_KP_Page_Down: + k = Pgdown; + break; + case XK_End: + case XK_KP_End: + k = End; + break; + case XK_Page_Up: + case XK_KP_Page_Up: + k = Pgup; + break; + case XK_Insert: + case XK_KP_Insert: + k = Ins; + break; + case XK_KP_Enter: + case XK_Return: + k = '\n'; + break; + case XK_Alt_L: + case XK_Alt_R: + k = Latin; + break; + case XK_Shift_L: + case XK_Shift_R: + case XK_Control_L: + case XK_Control_R: + case XK_Caps_Lock: + case XK_Shift_Lock: + + case XK_Meta_L: + case XK_Meta_R: + case XK_Super_L: + case XK_Super_R: + case XK_Hyper_L: + case XK_Hyper_R: + return; + default: /* not ISO-1 or tty control */ + if(k>0xff){ + k = keysym2ucs(k); /* supplied by X */ + if(k == -1) + return; + } + break; + } + } + + /* Compensate for servers that call a minus a hyphen */ + if(k == XK_hyphen) + k = XK_minus; + /* Do control mapping ourselves if translator doesn't */ + if(md & ControlMask) + k &= 0x9f; +#endif +/* + if(0){ + if(k == '\t' && ind) + k = BackTab; + + if(md & Mod1Mask) + k = APP|(k&0xff); + } + if(k == NoSymbol) + return; + +*/ + gkbdputc(gkbdq, k); +} + + + + +typedef struct { + int x; + int y; + int b; +} touch_evt; + +static touch_evt touch_events[10] = {0}; +static int touch_btns = 0; + +static int capacitive_events(int fd, int* b, int* x, int* y) +{ + struct input_event ev[64]; + int i, j, rd; + fd_set rdfs; + struct timeval to; + + int is_changed = 0; + +// static touch_evt touch_event[10] = {0}; + static int cur_event_n = 0; + + FD_ZERO(&rdfs); + FD_SET(fd, &rdfs); + + *b = touch_btns; + + *x = touch_events[cur_event_n].x; + *y = touch_events[cur_event_n].y; + + int rv = select(fd + 1, &rdfs, NULL, NULL, &to); + if(rv == -1){ + /* an error accured */ + return 1; + } + else if(rv == 0){ + //printf("timeout\n"); + /* a timeout occured */ + //break; + return 1; + } + + rd = read(fd, ev, sizeof(ev)); + if (rd < (int) sizeof(struct input_event)) { + //printf("expected %d bytes, got %d\n", (int) sizeof(struct input_event), rd); + //perror("\nevtest: error reading"); + return 1; + } + + for (i = 0; i < rd / sizeof(struct input_event); i++) { + unsigned int type, code; + type = ev[i].type; + code = ev[i].code; + + if(type == EV_ABS){ + int v = ev[i].value; + + switch(code){ + case ABS_MT_SLOT: + cur_event_n = v; + //if(cur_event_n >= 0 && cur_event_n < 10) + // touch_events[cur_event_n].b = 1; + break; + + case ABS_MT_TRACKING_ID: + if(cur_event_n >= 0 && cur_event_n < 10){ + touch_events[cur_event_n].b = v; + + is_changed = 1; + + if(v > 0) + touch_btns |= 1 << cur_event_n; + else + touch_btns &= ~(1 << cur_event_n); + } + //printf("ABS_MT_TRACKING_ID id=%d, btns=%d\n", v, touch_btns); + break; + + case ABS_MT_POSITION_X: + is_changed = 1; + if(cur_event_n >= 0 && cur_event_n < 10) + touch_events[cur_event_n].x = v; + break; + + case ABS_MT_POSITION_Y: + is_changed = 1; + if(cur_event_n >= 0 && cur_event_n < 10) + touch_events[cur_event_n].y = v; + break; + + } + } + } + + *x = touch_events[0].x; + *y = touch_events[0].y; + + *b = touch_btns; + + return !is_changed; +} + + +/* +** calibration (perfect = 0 0 4096 4096) +*/ +//#define NOMINAL_CALIBRATION +//#ifdef NOMINAL_CALIBRATION +//#define MIN_X 0 +//#define MIN_Y 0 +//#define MAX_X 4096 +//#define MAX_Y 4096 +//#else +#define MIN_X 200 +#define MIN_Y 260 +#define MAX_X 3900 +#define MAX_Y 3800 +//#endif + +//#define NOT_MUCH 200 /* small dx or dy ; about 3% of full width */ +#define SHORT_CLICK (150) /* 200 ms */ + +static int tsc2003_events(int fd, int* b, int* x, int* y) +{ + struct input_event ev[64]; + int i, rd; + fd_set rdfs; + struct timeval to; + static int ox=0, oy = 0, ob = 0; + int jx, jy, nx = 0, ny = 0; + int st = 0; + + to.tv_sec = 0; + to.tv_usec = SHORT_CLICK * 1000; + + FD_ZERO(&rdfs); + FD_SET(fd, &rdfs); + + *b = 0; + + if(ob == 0){ + ox = 0; + oy = 0; + } + *x = ox; + *y = oy; + + for(jx = jy = 0; jx < 5 || jy < 5; ) + { + int rv = select(fd + 1, &rdfs, NULL, NULL, &to); + if(rv == -1){ + /* an error accured */ + return 1; + } + else if(rv == 0){ + //printf("timeout\n"); + /* a timeout occured */ + break; + } + + rd = read(fd, ev, sizeof(ev)); + if (rd < (int) sizeof(struct input_event)) { + //printf("expected %d bytes, got %d\n", (int) sizeof(struct input_event), rd); + //perror("\nevtest: error reading"); + return 1; + } + + int xt = -1, yt = -1; + for (i = 0; i < rd / sizeof(struct input_event); i++) { + unsigned int type, code; + type = ev[i].type; + code = ev[i].code; + + if(type == EV_ABS){ + int v = ev[i].value; + + if(code == ABS_PRESSURE) + *b |= v > 10 ? 1 : 0; + else if(code == ABS_X){ + int t = Xsize * (v - MIN_X) / (MAX_X-MIN_X); +#if 0 + if(/* *b && j > 0 &&*/ ox > 0){ + //int t = nx/j; + if(t < ox - 100) + t = ox - 100; + else + if(t > ox + 100) + t = ox + 100; + } +#endif + if(t >= 0 && t < Xsize){ + xt = t; + jx++; + nx += t; + // st |= 1; + //}else{ + // j--; + // break; + } + }else if(code == ABS_Y){ + int t = Ysize * (v - MIN_Y) / (MAX_Y-MIN_Y); +#if 0 + if(/* *b && j > 0 &&*/ oy > 0){ + //int t = ny/j; + if(t < oy - 100) + t = oy - 100; + else + if(t > oy + 100) + t = oy + 100; + } +#endif + if(t >= 0 && t < Ysize){ + yt = t; + jy++; + ny += t; + // st |=2; + //}else{ + // j--; + // break; + } + } + } + } + //printf("xyt=(%d, %d)\n", xt, yt); + + /* + if(xt >= 0 && yt >= 0){ + nx += xt; + ny += yt + } + */ +// if(st | 3 != 3) +// j--; + } + + //printf("nxy=(%d, %d) jxy=(%d, %d)\n", nx, ny, jx, jy); + + if(jx < 1 || jy < 1){ + *b = ob = 0; + return 0; + } + + *x = nx/jx; + *y = ny/jy; + + if(*x < 0) + *x = 0; + if(*y < 0) + *y = 0; + + if(*x > Xsize) + *x = Xsize; + if(*y > Ysize) + *y = Ysize; + + ox = *x; + oy = *y; + + ob = *b; + + //ioctl(fd, EVIOCGRAB, (void*)0); + return 0; +} + + +static void +xmouse() //XEvent *e) +{ + int s, dbl; +// XButtonEvent *be; +// XMotionEvent *me; +// XEvent motion; + int x=0, y=0, b=0; + //char buf[64]; + static ob = 0; +// static ulong lastb, lastt; + +// if(putsnarf != assertsnarf){ +// assertsnarf = putsnarf; +// XSetSelectionOwner(xmcon, XA_PRIMARY, xdrawable, CurrentTime); +// if(clipboard != None) +// XSetSelectionOwner(xmcon, clipboard, xdrawable, CurrentTime); +// XFlush(xmcon); +// } + + if(fd_mou >= 0){ +//#if TOUCHSCREEN_CAPACITIVE + if(is_mt){ + if(Xmou_max > 0 && Ymou_max && !capacitive_events(fd_mou, &b, &x, &y)){ + x = (x * Xsize) / Xmou_max; + y = (y * Ysize) / Ymou_max; + //printf("capt x=%d, y=%d, b=%x\n", x, y, b); + mousetrack(b, x, y, 0); + } + }else{ +//#else + tsc2003_events(fd_mou, &b, &x, &y); + if(b > 0 || (b == 0 && ob > 0)){ + ob = b; + //printf("rest x=%d, y=%d, b=%x\n", x, y, b); + mousetrack(b, x, y, 0); + //printf("ob = %d, b=%d, xy=(%d, %d)\n", ob, b, x, y); + } + } +//#endif + } +#if 0 + dbl = 0; + switch(e->type){ + case ButtonPress: + be = (XButtonEvent *)e; + /* + * Fake message, just sent to make us announce snarf. + * Apparently state and button are 16 and 8 bits on + * the wire, since they are truncated by the time they + * get to us. + */ + if(be->send_event + && (~be->state&0xFFFF)==0 + && (~be->button&0xFF)==0) + return; + x = be->x; + y = be->y; + s = be->state; + if(be->button == lastb && be->time - lastt < DblTime) + dbl = 1; + lastb = be->button; + lastt = be->time; + switch(be->button){ + case 1: + s |= Button1Mask; + break; + case 2: + s |= Button2Mask; + break; + case 3: + s |= Button3Mask; + break; + case 4: + s |= Button4Mask; + break; + case 5: + s |= Button5Mask; + break; + } + break; + case ButtonRelease: + be = (XButtonEvent *)e; + x = be->x; + y = be->y; + s = be->state; + switch(be->button){ + case 1: + s &= ~Button1Mask; + break; + case 2: + s &= ~Button2Mask; + break; + case 3: + s &= ~Button3Mask; + break; + case 4: + s &= ~Button4Mask; + break; + case 5: + s &= ~Button5Mask; + break; + } + break; + case MotionNotify: + me = (XMotionEvent *) e; + + /* remove excess MotionNotify events from queue and keep last one */ + while(XCheckTypedWindowEvent(xmcon, xdrawable, MotionNotify, &motion) == True) + me = (XMotionEvent *) &motion; + + s = me->state; + x = me->x; + y = me->y; + break; + default: + return; + } + + b = 0; + if(s & Button1Mask) + b |= 1; + if(s & Button2Mask) + b |= 2; + if(s & Button3Mask) + b |= 4; + if(s & Button4Mask) + b |= 8; + if(s & Button5Mask) + b |= 16; + if(dbl) + b |= 1<<8; +#endif + + //mousetrack(b, x, y, 0); +} + + +void xmouse_btn(int x, int y, int btn){ + mousetrack(btn, x, y, 0); +} + + +//#include "x11-keysym2ucs.c" + +/* + * Cut and paste. Just couldn't stand to make this simple... + */ + +enum{ + SnarfSize= 100*1024 +}; + +typedef struct Clip Clip; +struct Clip +{ + char buf[SnarfSize]; + QLock lk; +}; +Clip clip; + +#undef long /* sic */ +#undef ulong + +#if 0 +static char* +_xgetsnarf(XDisplay *xd) +{ + uchar *data, *xdata; + Atom clipboard, type, prop; + unsigned long len, lastlen, dummy; + int fmt, i; + XWindow w; + + qlock(&clip.lk); + /* + * Have we snarfed recently and the X server hasn't caught up? + */ + if(putsnarf != assertsnarf) + goto mine; + + /* + * Is there a primary selection (highlighted text in an xterm)? + */ + clipboard = XA_PRIMARY; + w = XGetSelectionOwner(xd, XA_PRIMARY); + if(w == xdrawable){ + mine: + data = (uchar*)strdup(clip.buf); + goto out; + } + + /* + * If not, is there a clipboard selection? + */ + if(w == None && clipboard != None){ + clipboard = clipboard; + w = XGetSelectionOwner(xd, clipboard); + if(w == xdrawable) + goto mine; + } + + /* + * If not, give up. + */ + if(w == None){ + data = nil; + goto out; + } + + /* + * We should be waiting for SelectionNotify here, but it might never + * come, and we have no way to time out. Instead, we will clear + * local property #1, request our buddy to fill it in for us, and poll + * until he's done or we get tired of waiting. + * + * We should try to go for utf8string instead of XA_STRING, + * but that would add to the polling. + */ + prop = 1; + XChangeProperty(xd, xdrawable, prop, XA_STRING, 8, PropModeReplace, (uchar*)"", 0); + XConvertSelection(xd, clipboard, XA_STRING, prop, xdrawable, CurrentTime); + XFlush(xd); + lastlen = 0; + for(i=0; i<10 || (lastlen!=0 && i<30); i++){ + osmillisleep(100); + XGetWindowProperty(xd, xdrawable, prop, 0, 0, 0, AnyPropertyType, + &type, &fmt, &dummy, &len, &data); + if(lastlen == len && len > 0) + break; + lastlen = len; + } + if(i == 10){ + data = nil; + goto out; + } + /* get the property */ + data = nil; + XGetWindowProperty(xd, xdrawable, prop, 0, SnarfSize/sizeof(unsigned long), 0, + AnyPropertyType, &type, &fmt, &len, &dummy, &xdata); + if((type != XA_STRING && type != utf8string) || len == 0){ + if(xdata) + XFree(xdata); + data = nil; + }else{ + if(xdata){ + data = (uchar*)strdup((char*)xdata); + XFree(xdata); + }else + data = nil; + } +out: + qunlock(&clip.lk); + return (char*)data; +} + +static void +_xputsnarf(XDisplay *xd, char *data) +{ + XButtonEvent e; + + if(strlen(data) >= SnarfSize) + return; + qlock(&clip.lk); + strcpy(clip.buf, data); + + /* leave note for mouse proc to assert selection ownership */ + putsnarf++; + + /* send mouse a fake event so snarf is announced */ + memset(&e, 0, sizeof e); + e.type = ButtonPress; + e.window = xdrawable; + e.state = ~0; + e.button = ~0; + XSendEvent(xd, xdrawable, True, ButtonPressMask, (XEvent*)&e); + XFlush(xd); + qunlock(&clip.lk); +} + +static void +xselect(XEvent *e, XDisplay *xd) +{ + char *name; + XEvent r; + XSelectionRequestEvent *xe; + Atom a[4]; + + if(e->xany.type != SelectionRequest) + return; + + memset(&r, 0, sizeof r); + xe = (XSelectionRequestEvent*)e; +if(0) iprint("xselect target=%d requestor=%d property=%d selection=%d\n", + xe->target, xe->requestor, xe->property, xe->selection); + r.xselection.property = xe->property; + if(xe->target == targets){ + a[0] = XA_STRING; + a[1] = utf8string; + a[2] = text; + a[3] = compoundtext; + + XChangeProperty(xd, xe->requestor, xe->property, xe->target, + 8, PropModeReplace, (uchar*)a, sizeof a); + }else if(xe->target == XA_STRING || xe->target == utf8string || xe->target == text || xe->target == compoundtext){ + /* if the target is STRING we're supposed to reply with Latin1 XXX */ + qlock(&clip.lk); + XChangeProperty(xd, xe->requestor, xe->property, xe->target, + 8, PropModeReplace, (uchar*)clip.buf, strlen(clip.buf)); + qunlock(&clip.lk); + }else{ + iprint("get %d\n", xe->target); + name = XGetAtomName(xd, xe->target); + if(name == nil) + iprint("XGetAtomName failed\n"); + else if(strcmp(name, "TIMESTAMP") != 0) + iprint("%s: cannot handle selection request for '%s' (%d)\n", argv0, name, (int)xe->target); + r.xselection.property = None; + } + + r.xselection.display = xe->display; + /* r.xselection.property filled above */ + r.xselection.target = xe->target; + r.xselection.type = SelectionNotify; + r.xselection.requestor = xe->requestor; + r.xselection.time = xe->time; + r.xselection.send_event = True; + r.xselection.selection = xe->selection; + XSendEvent(xd, xe->requestor, False, 0, &r); + XFlush(xd); +} +#endif + + +char *snarf_buf = nil; + +char* +clipread(void) +{ + char *p = NULL; + +// if(xsnarfcon == nil) +// return nil; +// XLockDisplay(xsnarfcon); +// p = _xgetsnarf(xsnarfcon); +// XUnlockDisplay(xsnarfcon); + + p = strdup(snarf_buf); + + return p; +} + +int +clipwrite(char *buf) +{ +// buf = NULL; + +// if(xsnarfcon == nil) +// return 0; +// XLockDisplay(xsnarfcon); +// _xputsnarf(xsnarfcon, buf); +// XUnlockDisplay(xsnarfcon); + int l = strlen(buf); + + if(snarf_buf) + free(snarf_buf); + snarf_buf = nil; + + if(l >= SnarfSize) + return 0; + + snarf_buf = strdup(buf); + + /* leave note for mouse proc to assert selection ownership */ + putsnarf++; + + return 0; +} diff --git a/include/png/lodepng.h b/include/png/lodepng.h index 1548272..61d0d49 100644 --- a/include/png/lodepng.h +++ b/include/png/lodepng.h @@ -27,8 +27,10 @@ freely, subject to the following restrictions: #define LODEPNG_H //#include /*for size_t*/ -typedef unsigned int size_t; +#ifndef FREERTOS_RISCV64 +typedef unsigned int size_t; +#endif extern const char* LODEPNG_VERSION_STRING; diff --git a/lib9/getcallerpc-FreeRTOS-riscv64.c b/lib9/getcallerpc-FreeRTOS-riscv64.c new file mode 100644 index 0000000..4a4e4fb --- /dev/null +++ b/lib9/getcallerpc-FreeRTOS-riscv64.c @@ -0,0 +1,12 @@ +#include + +ulong +getcallerpc(void *x) +{ +ulong *lp; + + lp = x; + + return lp[-1]; +} + diff --git a/lib9/setfcr-FreeRTOS-riscv64.c b/lib9/setfcr-FreeRTOS-riscv64.c new file mode 100644 index 0000000..0ed32c6 --- /dev/null +++ b/lib9/setfcr-FreeRTOS-riscv64.c @@ -0,0 +1,64 @@ +/* + * Linux 386 fpu support + * Mimic Plan9 floating point support + */ + +#include "lib9.h" + +void +setfcr(ulong fcr) +{ +#if 0 + __asm__( "xorb $0x3f, %%al\n\t" + "pushw %%ax\n\t" + "fwait\n\t" + "fldcw (%%esp)\n\t" + "popw %%ax\n\t" + : /* no output */ + : "al" (fcr) + ); +#endif +} + +ulong +getfcr(void) +{ + ulong fcr = 0; + +#if 0 + __asm__( "pushl %%eax\n\t" + "fwait\n\t" + "fstcw (%%esp)\n\t" + "popl %%eax\n\t" + "xorb $0x3f, %%al\n\t" + : "=a" (fcr) + : "eax" (fcr) + ); +#endif + return fcr; +} + +ulong +getfsr(void) +{ + ulong fsr = -1; + +#if 0 + __asm__( "fwait\n\t" + "fstsw (%%eax)\n\t" + "movl (%%eax), %%eax\n\t" + "andl $0xffff, %%eax\n\t" + : "=a" (fsr) + : "eax" (&fsr) + ); +#endif + return fsr; +} + +void +setfsr(ulong fsr) +{ +#if 0 + __asm__("fclex\n\t"); +#endif +} diff --git a/libdynld/dynld-riscv64.c b/libdynld/dynld-riscv64.c new file mode 100644 index 0000000..040111d --- /dev/null +++ b/libdynld/dynld-riscv64.c @@ -0,0 +1,42 @@ +#include "lib9.h" +#include +#include + +#define CHK(i,ntab) if((unsigned)(i)>=(ntab))return "bad relocation index" + +long +dynmagic(void) +{ + return DYN_MAGIC | I_MAGIC; +} + +char* +dynreloc(uchar *b, ulong p, int m, Dynsym **tab, int ntab) +{ + int i; + ulong v, *pp; + + p += (ulong)b; + pp = (ulong*)p; + v = *pp; + switch(m){ + case 0: + v += (ulong)b; + break; + case 1: + i = v>>22; + v &= 0x3fffff; + CHK(i, ntab); + v += tab[i]->addr; + break; + case 2: + i = v>>22; + CHK(i, ntab); + v = tab[i]->addr -p-4; + break; + default: + return "bad relocation mode"; + } + *pp = v; + return nil; +} diff --git a/libinterp/comp-riscv64.c b/libinterp/comp-riscv64.c new file mode 100644 index 0000000..40009a2 --- /dev/null +++ b/libinterp/comp-riscv64.c @@ -0,0 +1,1998 @@ +#include "lib9.h" +#include "isa.h" +#include "interp.h" +#include "raise.h" + +#if 0 //{} + +#define DOT ((ulong)code) + +#define RESCHED 1 /* check for interpreter reschedule */ + +enum +{ + RAX = 0, + RAH = 4, + RCX = 1, + RDX = 2, + RBX = 3, + RSP = 4, + RBP = 5, + RSI = 6, + RDI = 7, + + RFP = RSI, + RMP = RDI, + RTA = RDX, + RTMP = RBX, + + Omovzxb = 0xb6, + Omovzxw = 0xb7, + Osal = 0xd1, + Oaddf = 0xdc, + Ocall = 0xe8, + Ocallrm = 0xff, + Ocdq = 0x99, + Ocld = 0xfc, + Ocmpb = 0x38, + Ocmpw = 0x39, + Ocmpi = 0x83, + Odecrm = 0xff, + Oincr = 0x40, + Oincrm = 0xff, + Ojccl = 0x83, + Ojcsl = 0x82, + Ojeqb = 0x74, + Ojeql = 0x84, + Ojgel = 0x8d, + Ojgtl = 0x8f, + Ojhil = 0x87, + Ojlel = 0x8e, + Ojlsl = 0x86, + Ojltl = 0x8c, + Ojol = 0x80, + Ojnol = 0x81, + Ojbl = 0x82, + Ojael = 0x83, + Ojal = 0x87, + Ojnel = 0x85, + Ojbel = 0x86, + Ojneb = 0x75, + Ojgtb = 0x7f, + Ojgeb = 0x7d, + Ojleb = 0x7e, + Ojltb = 0x7c, + Ojmp = 0xe9, + Ojmpb = 0xeb, + Ojmprm = 0xff, + Oldb = 0x8a, + Olds = 0x89, + Oldw = 0x8b, + Olea = 0x8d, + Otestib = 0xf6, + Oshld = 0xa5, + Oshrd = 0xad, + Osar = 0xd3, + Osarimm = 0xc1, + Omov = 0xc7, + Omovf = 0xdd, + Omovimm = 0xb8, + Omovsb = 0xa4, + Orep = 0xf3, + Oret = 0xc3, + Oshl = 0xd3, + Oshr = 0xd1, + Ostb = 0x88, + Ostw = 0x89, + Osubf = 0xdc, + Oxchg = 0x87, + OxchgAX = 0x90, + Oxor = 0x31, + Opopl = 0x58, + Opushl = 0x50, + Opushrm = 0xff, + Oneg = 0xf7, + + SRCOP = (1<<0), + DSTOP = (1<<1), + WRTPC = (1<<2), + TCHECK = (1<<3), + NEWPC = (1<<4), + DBRAN = (1<<5), + THREOP = (1<<6), + + ANDAND = 1, + OROR = 2, + EQAND = 3, + + MacFRP = 0, + MacRET = 1, + MacCASE = 2, + MacCOLR = 3, + MacMCAL = 4, + MacFRAM = 5, + MacMFRA = 6, + MacRELQ = 7, + NMACRO +}; + +static uchar* code; +static uchar* base; +static ulong* patch; +static int pass; +static Module* mod; +static uchar* tinit; +static ulong* litpool; +static int nlit; +static void macfrp(void); +static void macret(void); +static void maccase(void); +static void maccolr(void); +static void macmcal(void); +static void macfram(void); +static void macmfra(void); +static void macrelq(void); +static ulong macro[NMACRO]; + void (*comvec)(void); +extern void das(uchar*, int); + +#define T(r) *((void**)(R.r)) + +struct +{ + int idx; + void (*gen)(void); +} mactab[] = +{ + MacFRP, macfrp, /* decrement and free pointer */ + MacRET, macret, /* return instruction */ + MacCASE, maccase, /* case instruction */ + MacCOLR, maccolr, /* increment and color pointer */ + MacMCAL, macmcal, /* mcall bottom half */ + MacFRAM, macfram, /* frame instruction */ + MacMFRA, macmfra, /* punt mframe because t->initialize==0 */ + MacRELQ, macrelq, /* reschedule */ +}; + +static void +bounds(void) +{ + error(exBounds); +} + +static void +rdestroy(void) +{ + destroy(R.s); +} + +static void +rmcall(void) +{ + Prog *p; + Frame *f; + + if((void*)R.dt == H) + error(exModule); + + f = (Frame*)R.FP; + if(f == H) + error(exModule); + + f->mr = nil; + ((void(*)(Frame*))R.dt)(f); + R.SP = (uchar*)f; + R.FP = f->fp; + if(f->t == nil) + unextend(f); + else + freeptrs(f, f->t); + p = currun(); + if(p->kill != nil) + error(p->kill); +} + +static void +rmfram(void) +{ + Type *t; + Frame *f; + uchar *nsp; + + t = (Type*)R.s; + if(t == H) + error(exModule); + + nsp = R.SP + t->size; + if(nsp >= R.TS) { + R.s = t; + extend(); + T(d) = R.s; + return; + } + f = (Frame*)R.SP; + R.SP = nsp; + f->t = t; + f->mr = nil; + initmem(t, f); + T(d) = f; +} + +static int +bc(int o) +{ + if(o < 127 && o > -128) + return 1; + return 0; +} + +static void +urk(void) +{ + error(exCompile); +} + +static void +genb(uchar o) +{ + *code++ = o; +} + +static void +gen2(uchar o1, uchar o2) +{ + code[0] = o1; + code[1] = o2; + code += 2; +} + +static void +genw(ulong o) +{ + *(ulong*)code = o; + code += 4; +} + +static void +modrm(int inst, ulong disp, int rm, int r) +{ + *code++ = inst; + if(disp == 0) { + *code++ = (0<<6)|(r<<3)|rm; + return; + } + if(bc(disp)) { + code[0] = (1<<6)|(r<<3)|rm; + code[1] = disp; + code += 2; + return; + } + *code++ = (2<<6)|(r<<3)|rm; + *(ulong*)code = disp; + code += 4; +} + +static void +con(ulong o, int r) +{ + if(o == 0) { + gen2(Oxor, (3<<6)|(r<<3)|r); + return; + } + genb(Omovimm+r); + genw(o); +} + +static void +opwld(Inst *i, int mi, int r) +{ + int ir, rta; + + switch(UXSRC(i->add)) { + default: + print("%D\n", i); + urk(); + case SRC(AFP): + modrm(mi, i->s.ind, RFP, r); + return; + case SRC(AMP): + modrm(mi, i->s.ind, RMP, r); + return; + case SRC(AIMM): + con(i->s.imm, r); + return; + case SRC(AIND|AFP): + ir = RFP; + break; + case SRC(AIND|AMP): + ir = RMP; + break; + } + rta = RTA; + if(mi == Olea) + rta = r; + modrm(Oldw, i->s.i.f, ir, rta); + modrm(mi, i->s.i.s, rta, r); +} + +static void +opwst(Inst *i, int mi, int r) +{ + int ir, rta; + + switch(UXDST(i->add)) { + default: + print("%D\n", i); + urk(); + case DST(AIMM): + con(i->d.imm, r); + return; + case DST(AFP): + modrm(mi, i->d.ind, RFP, r); + return; + case DST(AMP): + modrm(mi, i->d.ind, RMP, r); + return; + case DST(AIND|AFP): + ir = RFP; + break; + case DST(AIND|AMP): + ir = RMP; + break; + } + rta = RTA; + if(mi == Olea) + rta = r; + modrm(Oldw, i->d.i.f, ir, rta); + modrm(mi, i->d.i.s, rta, r); +} + +static void +bra(ulong dst, int op) +{ + dst -= (DOT+5); + genb(op); + genw(dst); +} + +static void +rbra(ulong dst, int op) +{ + dst += (ulong)base; + dst -= DOT+5; + genb(op); + genw(dst); +} + +static void +literal(ulong imm, int roff) +{ + nlit++; + + genb(Omovimm+RAX); + genw((ulong)litpool); + modrm(Ostw, roff, RTMP, RAX); + + if(pass == 0) + return; + + *litpool = imm; + litpool++; +} + +static void +punt(Inst *i, int m, void (*fn)(void)) +{ + ulong pc; + + con((ulong)&R, RTMP); + + if(m & SRCOP) { + if(UXSRC(i->add) == SRC(AIMM)) + literal(i->s.imm, O(REG, s)); + else { + opwld(i, Olea, RAX); + modrm(Ostw, O(REG, s), RTMP, RAX); + } + } + + if(m & DSTOP) { + opwst(i, Olea, 0); + modrm(Ostw, O(REG, d), RTMP, RAX); + } + if(m & WRTPC) { + modrm(Omov, O(REG, PC), RTMP, 0); + pc = patch[i-mod->prog+1]; + genw((ulong)base + pc); + } + if(m & DBRAN) { + pc = patch[(Inst*)i->d.imm-mod->prog]; + literal((ulong)base+pc, O(REG, d)); + } + + switch(i->add&ARM) { + case AXNON: + if(m & THREOP) { + modrm(Oldw, O(REG, d), RTMP, RAX); + modrm(Ostw, O(REG, m), RTMP, RAX); + } + break; + case AXIMM: + literal((short)i->reg, O(REG, m)); + break; + case AXINF: + modrm(Olea, i->reg, RFP, RAX); + modrm(Ostw, O(REG, m), RTMP, RAX); + break; + case AXINM: + modrm(Olea, i->reg, RMP, RAX); + modrm(Ostw, O(REG, m), RTMP, RAX); + break; + } + modrm(Ostw, O(REG, FP), RTMP, RFP); + + bra((ulong)fn, Ocall); + + con((ulong)&R, RTMP); + if(m & TCHECK) { + modrm(Ocmpi, O(REG, t), RTMP, 7);// CMPL $0, R.t + genb(0x00); + gen2(Ojeqb, 0x06); // JEQ .+6 + genb(Opopl+RDI); + genb(Opopl+RSI); + genb(Opopl+RDX); + genb(Opopl+RCX); + genb(Opopl+RBX); + genb(Oret); + } + + modrm(Oldw, O(REG, FP), RTMP, RFP); + modrm(Oldw, O(REG, MP), RTMP, RMP); + + if(m & NEWPC) { + modrm(Oldw, O(REG, PC), RTMP, RAX); + gen2(Ojmprm, (3<<6)|(4<<3)|RAX); + } +} + +static void +mid(Inst *i, uchar mi, int r) +{ + int ir; + + switch(i->add&ARM) { + default: + opwst(i, mi, r); + return; + case AXIMM: + con((short)i->reg, r); + return; + case AXINF: + ir = RFP; + break; + case AXINM: + ir = RMP; + break; + } + modrm(mi, i->reg, ir, r); +} + +static void +arith(Inst *i, int op2, int rm) +{ + if(UXSRC(i->add) != SRC(AIMM)) { + if(i->add&ARM) { + mid(i, Oldw, RAX); + opwld(i, op2|2, 0); + opwst(i, Ostw, 0); + return; + } + opwld(i, Oldw, RAX); + opwst(i, op2, 0); + return; + } + if(i->add&ARM) { + mid(i, Oldw, RAX); + if(bc(i->s.imm)) { + gen2(0x83, (3<<6)|(rm<<3)|RAX); + genb(i->s.imm); + } + else { + gen2(0x81, (3<<6)|(rm<<3)|RAX); + genw(i->s.imm); + } + opwst(i, Ostw, RAX); + return; + } + if(bc(i->s.imm)) { + opwst(i, 0x83, rm); + genb(i->s.imm); + return; + } + opwst(i, 0x81, rm); + genw(i->s.imm); +} + +static void +arithb(Inst *i, int op2) +{ + if(UXSRC(i->add) == SRC(AIMM)) + urk(); + + if(i->add&ARM) { + mid(i, Oldb, RAX); + opwld(i, op2|2, 0); + opwst(i, Ostb, 0); + return; + } + opwld(i, Oldb, RAX); + opwst(i, op2, RAX); +} + +static void +shift(Inst *i, int ld, int st, int op, int r) +{ + mid(i, ld, RAX); + opwld(i, Oldw, RCX); + gen2(op, (3<<6)|(r<<3)|RAX); + opwst(i, st, RAX); +} + +static void +arithf(Inst *i, int op) +{ + opwld(i, Omovf, 0); + mid(i, 0xdc, op); + opwst(i, Omovf, 3); +} + +static void +cmpl(int r, ulong v) +{ + if(bc(v)) { + gen2(0x83, (3<<6)|(7<<3)|r); + genb(v); + return; + } + gen2(0x81, (3<<6)|(7<<3)|r); + genw(v); +} + +static int +swapbraop(int b) +{ + switch(b) { + case Ojgel: + return Ojlel; + case Ojlel: + return Ojgel; + case Ojgtl: + return Ojltl; + case Ojltl: + return Ojgtl; + } + return b; +} + +static void +schedcheck(Inst *i) +{ + if(RESCHED && i->d.ins <= i){ + con((ulong)&R, RTMP); + /* sub $1, R.IC */ + modrm(0x83, O(REG, IC), RTMP, 5); + genb(1); + gen2(Ojgtb, 5); + rbra(macro[MacRELQ], Ocall); + } +} + +static void +cbra(Inst *i, int jmp) +{ + if(RESCHED) + schedcheck(i); + mid(i, Oldw, RAX); + if(UXSRC(i->add) == SRC(AIMM)) { + cmpl(RAX, i->s.imm); + jmp = swapbraop(jmp); + } + else + opwld(i, Ocmpw, RAX); + genb(0x0f); + rbra(patch[i->d.ins-mod->prog], jmp); +} + +static void +cbral(Inst *i, int jmsw, int jlsw, int mode) +{ + ulong dst; + uchar *label; + + if(RESCHED) + schedcheck(i); + opwld(i, Olea, RTMP); + mid(i, Olea, RTA); + modrm(Oldw, 4, RTA, RAX); + modrm(Ocmpw, 4, RTMP, RAX); + label = 0; + dst = patch[i->d.ins-mod->prog]; + switch(mode) { + case ANDAND: + gen2(Ojneb, 0); + label = code-1; + break; + case OROR: + genb(0x0f); + rbra(dst, jmsw); + break; + case EQAND: + genb(0x0f); + rbra(dst, jmsw); + gen2(Ojneb, 0); + label = code-1; + break; + } + modrm(Oldw, 0, RTA, RAX); + modrm(Ocmpw, 0, RTMP, RAX); + genb(0x0f); + rbra(dst, jlsw); + if(label != nil) + *label = code-label-1; +} + +static void +cbrab(Inst *i, int jmp) +{ + if(RESCHED) + schedcheck(i); + mid(i, Oldb, RAX); + if(UXSRC(i->add) == SRC(AIMM)) + urk(); + + opwld(i, Ocmpb, RAX); + genb(0x0f); + rbra(patch[i->d.ins-mod->prog], jmp); +} + +static void +cbraf(Inst *i, int jmp) +{ + if(RESCHED) + schedcheck(i); + opwld(i, Omovf, 0); + mid(i, 0xdc, 3); // FCOMP + genb(0x9b); // FWAIT + gen2(0xdf, 0xe0); // FSTSW AX + genb(0x9e); // SAHF + + genb(0x0f); + rbra(patch[i->d.ins-mod->prog], jmp); +} + +static void +comcase(Inst *i, int w) +{ + int l; + WORD *t, *e; + + if(w != 0) { + opwld(i, Oldw, RAX); // v + genb(Opushl+RSI); + opwst(i, Olea, RSI); // table + rbra(macro[MacCASE], Ojmp); + } + + t = (WORD*)(mod->origmp+i->d.ind+4); + l = t[-1]; + + /* have to take care not to relocate the same table twice - + * the limbo compiler can duplicate a case instruction + * during its folding phase + */ + + if(pass == 0) { + if(l >= 0) + t[-1] = -l-1; /* Mark it not done */ + return; + } + if(l >= 0) /* Check pass 2 done */ + return; + t[-1] = -l-1; /* Set real count */ + e = t + t[-1]*3; + while(t < e) { + t[2] = (ulong)base + patch[t[2]]; + t += 3; + } + t[0] = (ulong)base + patch[t[0]]; +} + +static void +comcasel(Inst *i) +{ + int l; + WORD *t, *e; + + t = (WORD*)(mod->origmp+i->d.ind+8); + l = t[-2]; + if(pass == 0) { + if(l >= 0) + t[-2] = -l-1; /* Mark it not done */ + return; + } + if(l >= 0) /* Check pass 2 done */ + return; + t[-2] = -l-1; /* Set real count */ + e = t + t[-2]*6; + while(t < e) { + t[4] = (ulong)base + patch[t[4]]; + t += 6; + } + t[0] = (ulong)base + patch[t[0]]; +} + +static void +commframe(Inst *i) +{ + int o; + uchar *punt, *mlnil; + + opwld(i, Oldw, RAX); + cmpl(RAX, (ulong)H); + gen2(Ojeqb, 0); + mlnil = code - 1; + if((i->add&ARM) == AXIMM) { + o = OA(Modlink, links)+i->reg*sizeof(Modl)+O(Modl, frame); + modrm(Oldw, o, RAX, RTA); + } else { + gen2(Oldw, (3<<6)|(RTMP<<3)|RAX); // MOVL AX, RTMP + mid(i, Oldw, RCX); // index + gen2(Olea, (0<<6)|(0<<3)|4); // lea (AX)(RCX*8) + genb((3<<6)|(RCX<<3)|RAX); // assumes sizeof(Modl) == 8 hence 3 + o = OA(Modlink, links)+O(Modl, frame); + modrm(Oldw, o, RAX, RTA); // frame + genb(OxchgAX+RTMP); // get old AX back + } + modrm(0x83, O(Type, initialize), RTA, 7); + genb(0); + gen2(Ojneb, 0); + punt = code - 1; + genb(OxchgAX+RTA); + opwst(i, Olea, RTA); + *mlnil = code-mlnil-1; + rbra(macro[MacMFRA], Ocall); + rbra(patch[i-mod->prog+1], Ojmp); + + *punt = code-punt-1; + rbra(macro[MacFRAM], Ocall); + opwst(i, Ostw, RCX); +} + +static void +commcall(Inst *i) +{ + uchar *mlnil; + + con((ulong)&R, RTMP); // MOVL $R, RTMP + opwld(i, Oldw, RCX); + modrm(Omov, O(Frame, lr), RCX, 0); // MOVL $.+1, lr(CX) f->lr = R.PC + genw((ulong)base+patch[i-mod->prog+1]); + modrm(Ostw, O(Frame, fp), RCX, RFP); // MOVL RFP, fp(CX) f->fp = R.FP + modrm(Oldw, O(REG, M), RTMP, RTA); // MOVL R.M, RTA + modrm(Ostw, O(Frame, mr), RCX, RTA); // MOVL RTA, mr(CX) f->mr = R.M + opwst(i, Oldw, RTA); // MOVL ml, RTA + cmpl(RTA, (ulong)H); + gen2(Ojeqb, 0); + mlnil = code - 1; + if((i->add&ARM) == AXIMM) + modrm(Oldw, OA(Modlink, links)+i->reg*sizeof(Modl)+O(Modl, u.pc), RTA, RAX); + else { + genb(Opushl+RCX); + mid(i, Oldw, RCX); // index + gen2(Olea, (0<<6)|(0<<3)|4); // lea (RTA)(RCX*8) + genb((3<<6)|(RCX<<3)|RTA); // assumes sizeof(Modl) == 8 hence 3 + modrm(Oldw, OA(Modlink, links)+O(Modl, u.pc), RAX, RAX); + genb(Opopl+RCX); + } + *mlnil = code-mlnil-1; + rbra(macro[MacMCAL], Ocall); +} + +static void +larith(Inst *i, int op, int opc) +{ + opwld(i, Olea, RTMP); + mid(i, Olea, RTA); + modrm(Oldw, 0, RTA, RAX); // MOVL 0(RTA), AX + modrm(op, 0, RTMP, RAX); // ADDL 0(RTMP), AX + modrm(Oldw, 4, RTA, RCX); // MOVL 4(RTA), CX + modrm(opc, 4, RTMP, RCX); // ADCL 4(RTMP), CX + if((i->add&ARM) != AXNON) + opwst(i, Olea, RTA); + modrm(Ostw, 0, RTA, RAX); + modrm(Ostw, 4, RTA, RCX); +} + +static void +shll(Inst *i) +{ + uchar *label, *label1; + + opwld(i, Oldw, RCX); + mid(i, Olea, RTA); + gen2(Otestib, (3<<6)|(0<<3)|RCX); + genb(0x20); + gen2(Ojneb, 0); + label = code-1; + modrm(Oldw, 0, RTA, RAX); + modrm(Oldw, 4, RTA, RBX); + genb(0x0f); + gen2(Oshld, (3<<6)|(RAX<<3)|RBX); + gen2(Oshl, (3<<6)|(4<<3)|RAX); + gen2(Ojmpb, 0); + label1 = code-1; + *label = code-label-1; + modrm(Oldw, 0, RTA, RBX); + con(0, RAX); + gen2(Oshl, (3<<6)|(4<<3)|RBX); + *label1 = code-label1-1; + opwst(i, Olea, RTA); + modrm(Ostw, 0, RTA, RAX); + modrm(Ostw, 4, RTA, RBX); +} + +static void +shrl(Inst *i) +{ + uchar *label, *label1; + + opwld(i, Oldw, RCX); + mid(i, Olea, RTA); + gen2(Otestib, (3<<6)|(0<<3)|RCX); + genb(0x20); + gen2(Ojneb, 0); + label = code-1; + modrm(Oldw, 0, RTA, RAX); + modrm(Oldw, 4, RTA, RBX); + genb(0x0f); + gen2(Oshrd, (3<<6)|(RBX<<3)|RAX); + gen2(Osar, (3<<6)|(7<<3)|RBX); + gen2(Ojmpb, 0); + label1 = code-1; + *label = code-label-1; + modrm(Oldw, 4, RTA, RBX); + gen2(Oldw, (3<<6)|(RAX<<3)|RBX); + gen2(Osarimm, (3<<6)|(7<<3)|RBX); + genb(0x1f); + gen2(Osar, (3<<6)|(7<<3)|RAX); + *label1 = code-label1-1; + opwst(i, Olea, RTA); + modrm(Ostw, 0, RTA, RAX); + modrm(Ostw, 4, RTA, RBX); +} + +static +void +compdbg(void) +{ + print("%s:%lud@%.8lux\n", R.M->m->name, *(ulong*)R.m, *(ulong*)R.s); +} + +static void +comp(Inst *i) +{ + int r; + WORD *t, *e; + char buf[64]; + + if(0) { + Inst xx; + xx.add = AXIMM|SRC(AIMM); + xx.s.imm = (ulong)code; + xx.reg = i-mod->prog; + punt(&xx, SRCOP, compdbg); + } + + switch(i->op) { + default: + snprint(buf, sizeof buf, "%s compile, no '%D'", mod->name, i); + error(buf); + break; + case IMCALL: + if((i->add&ARM) == AXIMM) + commcall(i); + else + punt(i, SRCOP|DSTOP|THREOP|WRTPC|NEWPC, optab[i->op]); + break; + case ISEND: + case IRECV: + case IALT: + punt(i, SRCOP|DSTOP|TCHECK|WRTPC, optab[i->op]); + break; + case ISPAWN: + punt(i, SRCOP|DBRAN, optab[i->op]); + break; + case IBNEC: + case IBEQC: + case IBLTC: + case IBLEC: + case IBGTC: + case IBGEC: + punt(i, SRCOP|DBRAN|NEWPC|WRTPC, optab[i->op]); + break; + case ICASEC: + comcase(i, 0); + punt(i, SRCOP|DSTOP|NEWPC, optab[i->op]); + break; + case ICASEL: + comcasel(i); + punt(i, SRCOP|DSTOP|NEWPC, optab[i->op]); + break; + case IADDC: + case IMULL: + case IDIVL: + case IMODL: + case IMNEWZ: + case ILSRW: + case ILSRL: + punt(i, SRCOP|DSTOP|THREOP, optab[i->op]); + break; + case ILOAD: + case INEWA: + case INEWAZ: + case INEW: + case INEWZ: + case ISLICEA: + case ISLICELA: + case ICONSB: + case ICONSW: + case ICONSL: + case ICONSF: + case ICONSM: + case ICONSMP: + case ICONSP: + case IMOVMP: + case IHEADMP: + case IHEADL: + case IINSC: + case ICVTAC: + case ICVTCW: + case ICVTWC: + case ICVTLC: + case ICVTCL: + case ICVTFC: + case ICVTCF: + case ICVTRF: + case ICVTFR: + case ICVTWS: + case ICVTSW: + case IMSPAWN: + case ICVTCA: + case ISLICEC: + case INBALT: + punt(i, SRCOP|DSTOP, optab[i->op]); + break; + case INEWCM: + case INEWCMP: + punt(i, SRCOP|DSTOP|THREOP, optab[i->op]); + break; + case IMFRAME: + if((i->add&ARM) == AXIMM) + commframe(i); + else + punt(i, SRCOP|DSTOP|THREOP, optab[i->op]); + break; + case INEWCB: + case INEWCW: + case INEWCF: + case INEWCP: + case INEWCL: + punt(i, DSTOP|THREOP, optab[i->op]); + break; + case IEXIT: + punt(i, 0, optab[i->op]); + break; + case ICVTBW: + opwld(i, Oldb, RAX); + genb(0x0f); + gen2(0xb6, (3<<6)|(RAX<<3)|RAX); + opwst(i, Ostw, RAX); + break; + case ICVTWB: + opwld(i, Oldw, RAX); + opwst(i, Ostb, RAX); + break; + case ICVTFW: + if(1){ + punt(i, SRCOP|DSTOP, optab[i->op]); + break; + } + opwld(i, Omovf, 0); + opwst(i, 0xdb, 3); + break; + case ICVTWF: + if(1){ + punt(i, SRCOP|DSTOP, optab[i->op]); + break; + } + opwld(i, 0xdb, 0); + opwst(i, Omovf, 3); + break; + case ICVTLF: + if(1){ + punt(i, SRCOP|DSTOP, optab[i->op]); + break; + } + opwld(i, 0xdf, 5); + opwst(i, Omovf, 3); + break; + case ICVTFL: + if(1){ + punt(i, SRCOP|DSTOP, optab[i->op]); + break; + } + opwld(i, Omovf, 0); + opwst(i, 0xdf, 7); + break; + case IHEADM: + opwld(i, Oldw, RAX); + modrm(Olea, OA(List, data), RAX, RAX); + goto movm; + case IMOVM: + opwld(i, Olea, RAX); + movm: + opwst(i, Olea, RBX); + mid(i, Oldw, RCX); + genb(OxchgAX+RSI); + gen2(Oxchg, (3<<6)|(RDI<<3)|RBX); + genb(Ocld); + gen2(Orep, Omovsb); + genb(OxchgAX+RSI); + gen2(Oxchg, (3<<6)|(RDI<<3)|RBX); + break; + case IRET: + rbra(macro[MacRET], Ojmp); + break; + case IFRAME: + if(UXSRC(i->add) != SRC(AIMM)) { + punt(i, SRCOP|DSTOP, optab[i->op]); + break; + } + tinit[i->s.imm] = 1; + con((ulong)mod->type[i->s.imm], RTA); + rbra(macro[MacFRAM], Ocall); + opwst(i, Ostw, RCX); + break; + case ILEA: + if(UXSRC(i->add) == SRC(AIMM)) { + gen2(Ojmpb, 4); + genw(i->s.imm); + con((ulong)(code-4), RAX); + } + else + opwld(i, Olea, RAX); + opwst(i, Ostw, RAX); + break; + case IHEADW: + opwld(i, Oldw, RAX); + modrm(Oldw, OA(List, data), RAX, RAX); + opwst(i, Ostw, RAX); + break; + case IHEADF: + opwld(i, Oldw, RAX); + modrm(Omovf, OA(List, data), RAX, 0); + opwst(i, Omovf, 3); + break; + case IHEADB: + opwld(i, Oldw, RAX); + modrm(Oldb, OA(List, data), RAX, RAX); + opwst(i, Ostb, RAX); + break; + case ITAIL: + opwld(i, Oldw, RAX); + modrm(Oldw, O(List, tail), RAX, RBX); + goto movp; + case IMOVP: + case IHEADP: + opwld(i, Oldw, RBX); + if(i->op == IHEADP) + modrm(Oldw, OA(List, data), RBX, RBX); + movp: + cmpl(RBX, (ulong)H); + gen2(Ojeqb, 0x05); + rbra(macro[MacCOLR], Ocall); + opwst(i, Oldw, RAX); + opwst(i, Ostw, RBX); + rbra(macro[MacFRP], Ocall); + break; + case ILENA: + opwld(i, Oldw, RBX); + con(0, RAX); + cmpl(RBX, (ulong)H); + gen2(Ojeqb, 0x02); + modrm(Oldw, O(Array, len), RBX, RAX); + opwst(i, Ostw, RAX); + break; + case ILENC: + opwld(i, Oldw, RBX); + con(0, RAX); + cmpl(RBX, (ulong)H); + gen2(Ojeqb, 0x09); + modrm(Oldw, O(String, len), RBX, RAX); + cmpl(RAX, 0); + gen2(Ojgeb, 0x02); + gen2(Oneg, (3<<6)|(3<<3)|RAX); + opwst(i, Ostw, RAX); + break; + case ILENL: + con(0, RAX); + opwld(i, Oldw, RBX); + cmpl(RBX, (ulong)H); + gen2(Ojeqb, 0x05); + modrm(Oldw, O(List, tail), RBX, RBX); + genb(Oincr+RAX); + gen2(Ojmpb, 0xf6); + opwst(i, Ostw, RAX); + break; + case IBEQF: + cbraf(i, Ojeql); + break; + case IBNEF: + cbraf(i, Ojnel); + break; + case IBLEF: + cbraf(i, Ojlsl); + break; + case IBLTF: + cbraf(i, Ojcsl); + break; + case IBGEF: + cbraf(i, Ojccl); + break; + case IBGTF: + cbraf(i, Ojhil); + break; + case IBEQW: + cbra(i, Ojeql); + break; + case IBLEW: + cbra(i, Ojlel); + break; + case IBNEW: + cbra(i, Ojnel); + break; + case IBGTW: + cbra(i, Ojgtl); + break; + case IBLTW: + cbra(i, Ojltl); + break; + case IBGEW: + cbra(i, Ojgel); + break; + case IBEQB: + cbrab(i, Ojeql); + break; + case IBLEB: + cbrab(i, Ojlsl); + break; + case IBNEB: + cbrab(i, Ojnel); + break; + case IBGTB: + cbrab(i, Ojhil); + break; + case IBLTB: + cbrab(i, Ojbl); + break; + case IBGEB: + cbrab(i, Ojael); + break; + case ISUBW: + arith(i, 0x29, 5); + break; + case ISUBB: + arithb(i, 0x28); + break; + case ISUBF: + arithf(i, 5); + break; + case IADDW: + arith(i, 0x01, 0); + break; + case IADDB: + arithb(i, 0x00); + break; + case IADDF: + arithf(i, 0); + break; + case IORW: + arith(i, 0x09, 1); + break; + case IORB: + arithb(i, 0x08); + break; + case IANDW: + arith(i, 0x21, 4); + break; + case IANDB: + arithb(i, 0x20); + break; + case IXORW: + arith(i, Oxor, 6); + break; + case IXORB: + arithb(i, 0x30); + break; + case ISHLW: + shift(i, Oldw, Ostw, 0xd3, 4); + break; + case ISHLB: + shift(i, Oldb, Ostb, 0xd2, 4); + break; + case ISHRW: + shift(i, Oldw, Ostw, 0xd3, 7); + break; + case ISHRB: + shift(i, Oldb, Ostb, 0xd2, 5); + break; + case IMOVF: + opwld(i, Omovf, 0); + opwst(i, Omovf, 3); + break; + case INEGF: + opwld(i, Omovf, 0); + genb(0xd9); + genb(0xe0); + opwst(i, Omovf, 3); + break; + case IMOVB: + opwld(i, Oldb, RAX); + opwst(i, Ostb, RAX); + break; + case IMOVW: + case ICVTLW: // Little endian + if(UXSRC(i->add) == SRC(AIMM)) { + opwst(i, Omov, RAX); + genw(i->s.imm); + break; + } + opwld(i, Oldw, RAX); + opwst(i, Ostw, RAX); + break; + case ICVTWL: + opwst(i, Olea, RTMP); + opwld(i, Oldw, RAX); + modrm(Ostw, 0, RTMP, RAX); + genb(0x99); + modrm(Ostw, 4, RTMP, RDX); + break; + case ICALL: + if(UXDST(i->add) != DST(AIMM)) + opwst(i, Oldw, RTA); + opwld(i, Oldw, RAX); + modrm(Omov, O(Frame, lr), RAX, 0); // MOVL $.+1, lr(AX) + genw((ulong)base+patch[i-mod->prog+1]); + modrm(Ostw, O(Frame, fp), RAX, RFP); // MOVL RFP, fp(AX) + gen2(Oldw, (3<<6)|(RFP<<3)|RAX); // MOVL AX,RFP + if(UXDST(i->add) != DST(AIMM)){ + gen2(Ojmprm, (3<<6)|(4<<3)|RTA); + break; + } + /* no break */ + case IJMP: + if(RESCHED) + schedcheck(i); + rbra(patch[i->d.ins-mod->prog], Ojmp); + break; + case IMOVPC: + opwst(i, Omov, RAX); + genw(patch[i->s.imm]+(ulong)base); + break; + case IGOTO: + opwst(i, Olea, RBX); + opwld(i, Oldw, RAX); + gen2(Ojmprm, (0<<6)|(4<<3)|4); + genb((2<<6)|(RAX<<3)|RBX); + + if(pass == 0) + break; + + t = (WORD*)(mod->origmp+i->d.ind); + e = t + t[-1]; + t[-1] = 0; + while(t < e) { + t[0] = (ulong)base + patch[t[0]]; + t++; + } + break; + case IMULF: + arithf(i, 1); + break; + case IDIVF: + arithf(i, 7); + break; + case IMODW: + case IDIVW: + case IMULW: + mid(i, Oldw, RAX); + opwld(i, Oldw, RTMP); + if(i->op == IMULW) + gen2(0xf7, (3<<6)|(4<<3)|RTMP); + else { + genb(Ocdq); + gen2(0xf7, (3<<6)|(7<<3)|RTMP); // IDIV AX, RTMP + if(i->op == IMODW) + genb(0x90+RDX); // XCHG AX, DX + } + opwst(i, Ostw, RAX); + break; + case IMODB: + case IDIVB: + case IMULB: + mid(i, Oldb, RAX); + opwld(i, Oldb, RTMP); + if(i->op == IMULB) + gen2(0xf6, (3<<6)|(4<<3)|RTMP); + else { + genb(Ocdq); + gen2(0xf6, (3<<6)|(7<<3)|RTMP); // IDIV AX, RTMP + if(i->op == IMODB) + genb(0x90+RDX); // XCHG AX, DX + } + opwst(i, Ostb, RAX); + break; + case IINDX: + opwld(i, Oldw, RTMP); // MOVW xx(s), BX + + if(bflag){ + opwst(i, Oldw, RAX); + modrm(0x3b, O(Array, len), RTMP, RAX); /* CMP index, len */ + gen2(0x72, 5); /* JB */ + bra((ulong)bounds, Ocall); + modrm(Oldw, O(Array, t), RTMP, RTA); + modrm(0xf7, O(Type, size), RTA, 5); /* IMULL AX, xx(t) */ + } + else{ + modrm(Oldw, O(Array, t), RTMP, RAX); // MOVW t(BX), AX + modrm(Oldw, O(Type, size), RAX, RAX); // MOVW size(AX), AX + if(UXDST(i->add) == DST(AIMM)) { + gen2(0x69, (3<<6)|(RAX<<3)|0); + genw(i->d.imm); + } + else + opwst(i, 0xf7, 5); // IMULL AX,xx(d) + } + + modrm(0x03, O(Array, data), RBX, RAX); // ADDL data(BX), AX + r = RMP; + if((i->add&ARM) == AXINF) + r = RFP; + modrm(Ostw, i->reg, r, RAX); + break; + case IINDB: + r = 0; + goto idx; + case IINDF: + case IINDL: + r = 3; + goto idx; + case IINDW: + r = 2; + idx: + opwld(i, Oldw, RAX); + opwst(i, Oldw, RTMP); + if(bflag){ + modrm(0x3b, O(Array, len), RAX, RTMP); /* CMP index, len */ + gen2(0x72, 5); /* JB */ + bra((ulong)bounds, Ocall); + } + modrm(Oldw, O(Array, data), RAX, RAX); + gen2(Olea, (0<<6)|(0<<3)|4); /* lea (AX)(RTMP*r) */ + genb((r<<6)|(RTMP<<3)|RAX); + r = RMP; + if((i->add&ARM) == AXINF) + r = RFP; + modrm(Ostw, i->reg, r, RAX); + break; + case IINDC: + opwld(i, Oldw, RAX); // string + mid(i, Oldw, RBX); // index + if(bflag){ + modrm(Oldw, O(String, len), RAX, RTA); + cmpl(RTA, 0); + gen2(Ojltb, 16); + gen2(0x3b, (3<<6)|(RBX<<3)|RTA); /* cmp index, len */ + gen2(0x72, 5); /* JB */ + bra((ulong)bounds, Ocall); + genb(0x0f); + gen2(Omovzxb, (1<<6)|(0<<3)|4); + gen2((0<<6)|(RBX<<3)|RAX, O(String, data)); + gen2(Ojmpb, sizeof(Rune)==4? 10: 11); + gen2(Oneg, (3<<6)|(3<<3)|RTA); + gen2(0x3b, (3<<6)|(RBX<<3)|RTA); /* cmp index, len */ + gen2(0x73, 0xee); /* JNB */ + if(sizeof(Rune) == 4){ + gen2(Oldw, (1<<6)|(0<<3)|4); + gen2((2<<6)|(RBX<<3)|RAX, O(String, data)); + }else{ + genb(0x0f); + gen2(Omovzxw, (1<<6)|(0<<3)|4); + gen2((1<<6)|(RBX<<3)|RAX, O(String, data)); + } + opwst(i, Ostw, RAX); + break; + } + modrm(Ocmpi, O(String, len), RAX, 7); + genb(0); + gen2(Ojltb, 7); + genb(0x0f); + gen2(Omovzxb, (1<<6)|(0<<3)|4); /* movzbx 12(AX)(RBX*1), RAX */ + gen2((0<<6)|(RBX<<3)|RAX, O(String, data)); + if(sizeof(Rune) == 4){ + gen2(Ojmpb, 4); + gen2(Oldw, (1<<6)|(0<<3)|4); /* movl 12(AX)(RBX*4), RAX */ + gen2((2<<6)|(RBX<<3)|RAX, O(String, data)); + }else{ + gen2(Ojmpb, 5); + genb(0x0f); + gen2(Omovzxw, (1<<6)|(0<<3)|4); /* movzwx 12(AX)(RBX*2), RAX */ + gen2((1<<6)|(RBX<<3)|RAX, O(String, data)); + } + opwst(i, Ostw, RAX); + break; + case ICASE: + comcase(i, 1); + break; + case IMOVL: + opwld(i, Olea, RTA); + opwst(i, Olea, RTMP); + modrm(Oldw, 0, RTA, RAX); + modrm(Ostw, 0, RTMP, RAX); + modrm(Oldw, 4, RTA, RAX); + modrm(Ostw, 4, RTMP, RAX); + break; + case IADDL: + larith(i, 0x03, 0x13); + break; + case ISUBL: + larith(i, 0x2b, 0x1b); + break; + case IORL: + larith(i, 0x0b, 0x0b); + break; + case IANDL: + larith(i, 0x23, 0x23); + break; + case IXORL: + larith(i, 0x33, 0x33); + break; + case IBEQL: + cbral(i, Ojnel, Ojeql, ANDAND); + break; + case IBNEL: + cbral(i, Ojnel, Ojnel, OROR); + break; + case IBLEL: + cbral(i, Ojltl, Ojbel, EQAND); + break; + case IBGTL: + cbral(i, Ojgtl, Ojal, EQAND); + break; + case IBLTL: + cbral(i, Ojltl, Ojbl, EQAND); + break; + case IBGEL: + cbral(i, Ojgtl, Ojael, EQAND); + break; + case ISHLL: + shll(i); + break; + case ISHRL: + shrl(i); + break; + case IRAISE: + punt(i, SRCOP|WRTPC|NEWPC, optab[i->op]); + break; + case IMULX: + case IDIVX: + case ICVTXX: + case IMULX0: + case IDIVX0: + case ICVTXX0: + case IMULX1: + case IDIVX1: + case ICVTXX1: + case ICVTFX: + case ICVTXF: + case IEXPW: + case IEXPL: + case IEXPF: + punt(i, SRCOP|DSTOP|THREOP, optab[i->op]); + break; + case ISELF: + punt(i, DSTOP, optab[i->op]); + break; + } +} + +static void +preamble(void) +{ + if(comvec) + return; + + comvec = malloc(32); + if(comvec == nil) + error(exNomem); + code = (uchar*)comvec; + + genb(Opushl+RBX); + genb(Opushl+RCX); + genb(Opushl+RDX); + genb(Opushl+RSI); + genb(Opushl+RDI); + con((ulong)&R, RTMP); + modrm(Oldw, O(REG, FP), RTMP, RFP); + modrm(Oldw, O(REG, MP), RTMP, RMP); + modrm(Ojmprm, O(REG, PC), RTMP, 4); + + segflush(comvec, 32); +} + +static void +maccase(void) +{ + uchar *loop, *def, *lab1; + + modrm(Oldw, 0, RSI, RDX); // n = t[0] + modrm(Olea, 4, RSI, RSI); // t = &t[1] + gen2(Oldw, (3<<6)|(RBX<<3)|RDX); // MOVL DX, BX + gen2(Oshr, (3<<6)|(4<<3)|RBX); // SHL BX,1 + gen2(0x01, (3<<6)|(RDX<<3)|RBX); // ADDL DX, BX BX = n*3 + gen2(Opushrm, (0<<6)|(6<<3)|4); + genb((2<<6)|(RBX<<3)|RSI); // PUSHL 0(SI)(BX*4) + loop = code; + cmpl(RDX, 0); + gen2(Ojleb, 0); + def = code-1; + gen2(Oldw, (3<<6)|(RCX<<3)|RDX); // MOVL DX, CX n2 = n + gen2(Oshr, (3<<6)|(5<<3)|RCX); // SHR CX,1 n2 = n2>>1 + gen2(Oldw, (3<<6)|(RBX<<3)|RCX); // MOVL CX, BX + gen2(Oshr, (3<<6)|(4<<3)|RBX); // SHL BX,1 + gen2(0x01, (3<<6)|(RCX<<3)|RBX); // ADDL CX, BX BX = n2*3 + gen2(0x3b, (0<<6)|(RAX<<3)|4); + genb((2<<6)|(RBX<<3)|RSI); // CMPL AX, 0(SI)(BX*4) + gen2(Ojgeb, 0); // JGE lab1 + lab1 = code-1; + gen2(Oldw, (3<<6)|(RDX<<3)|RCX); + gen2(Ojmpb, loop-code-2); + *lab1 = code-lab1-1; // lab1: + gen2(0x3b, (1<<6)|(RAX<<3)|4); + gen2((2<<6)|(RBX<<3)|RSI, 4); // CMPL AX, 4(SI)(BX*4) + gen2(Ojltb, 0); + lab1 = code-1; + gen2(Olea, (1<<6)|(RSI<<3)|4); + gen2((2<<6)|(RBX<<3)|RSI, 12); // LEA 12(SI)(RBX*4), RSI + gen2(0x2b, (3<<6)|(RDX<<3)|RCX); // SUBL CX, DX n -= n2 + gen2(Odecrm, (3<<6)|(1<<3)|RDX); // DECL DX n -= 1 + gen2(Ojmpb, loop-code-2); + *lab1 = code-lab1-1; // lab1: + gen2(Oldw, (1<<6)|(RAX<<3)|4); + gen2((2<<6)|(RBX<<3)|RSI, 8); // MOVL 8(SI)(BX*4), AX + genb(Opopl+RSI); // ditch default + genb(Opopl+RSI); + gen2(Ojmprm, (3<<6)|(4<<3)|RAX); // JMP*L AX + *def = code-def-1; // def: + genb(Opopl+RAX); // ditch default + genb(Opopl+RSI); + gen2(Ojmprm, (3<<6)|(4<<3)|RAX); +} + +static void +macfrp(void) +{ + cmpl(RAX, (ulong)H); // CMPL AX, $H + gen2(Ojneb, 0x01); // JNE .+1 + genb(Oret); // RET + modrm(0x83, O(Heap, ref)-sizeof(Heap), RAX, 7); + genb(0x01); // CMP AX.ref, $1 + gen2(Ojeqb, 0x04); // JNE .+4 + modrm(Odecrm, O(Heap, ref)-sizeof(Heap), RAX, 1); + genb(Oret); // DEC AX.ref + // RET + con((ulong)&R, RTMP); // MOV $R, RTMP + modrm(Ostw, O(REG, FP), RTMP, RFP); // MOVL RFP, R.FP + modrm(Ostw, O(REG, s), RTMP, RAX); // MOVL RAX, R.s + bra((ulong)rdestroy, Ocall); // CALL rdestroy + con((ulong)&R, RTMP); // MOVL $R, RTMP + modrm(Oldw, O(REG, FP), RTMP, RFP); // MOVL R.MP, RMP + modrm(Oldw, O(REG, MP), RTMP, RMP); // MOVL R.FP, RFP + genb(Oret); +} + +static void +macret(void) +{ + Inst i; + uchar *s; + static ulong lpunt, lnomr, lfrmr, linterp; + + s = code; + + lpunt -= 2; + lnomr -= 2; + lfrmr -= 2; + linterp -= 2; + + con(0, RBX); // MOVL $0, RBX + modrm(Oldw, O(Frame, t), RFP, RAX); // MOVL t(FP), RAX + gen2(Ocmpw, (3<<6)|(RAX<<3)|RBX); // CMPL RAX, RBX + gen2(Ojeqb, lpunt-(code-s)); // JEQ lpunt + modrm(Oldw, O(Type, destroy), RAX, RAX);// MOVL destroy(RAX), RAX + gen2(Ocmpw, (3<<6)|(RAX<<3)|RBX); // CMPL RAX, RBX + gen2(Ojeqb, lpunt-(code-s)); // JEQ lpunt + modrm(Ocmpw, O(Frame, fp), RFP, RBX); // CMPL fp(FP), RBX + gen2(Ojeqb, lpunt-(code-s)); // JEQ lpunt + modrm(Ocmpw, O(Frame, mr), RFP, RBX); // CMPL mr(FP), RBX + gen2(Ojeqb, lnomr-(code-s)); // JEQ lnomr + con((ulong)&R, RTMP); // MOVL $R, RTMP + modrm(Oldw, O(REG, M), RTMP, RTA); // MOVL R.M, RTA + modrm(Odecrm, O(Heap, ref)-sizeof(Heap), RTA, 1); + gen2(Ojneb, lfrmr-(code-s)); // JNE lfrmr + modrm(Oincrm, O(Heap, ref)-sizeof(Heap), RTA, 0); + gen2(Ojmpb, lpunt-(code-s)); // JMP lpunt + lfrmr = code - s; + modrm(Oldw, O(Frame, mr), RFP, RTA); // MOVL mr(FP), RTA + modrm(Ostw, O(REG, M), RTMP, RTA); // MOVL RTA, R.M + modrm(Oldw, O(Modlink, MP), RTA, RMP); // MOVL MP(RTA), RMP + modrm(Ostw, O(REG, MP), RTMP, RMP); // MOVL RMP, R.MP + modrm(Ocmpi, O(Modlink, compiled), RTA, 7);// CMPL $0, M.compiled + genb(0x00); + gen2(Ojeqb, linterp-(code-s)); // JEQ linterp + lnomr = code - s; + gen2(Ocallrm, (3<<6)|(2<<3)|RAX); // CALL* AX + con((ulong)&R, RTMP); // MOVL $R, RTMP + modrm(Ostw, O(REG, SP), RTMP, RFP); // MOVL RFP, R.SP + modrm(Oldw, O(Frame, lr), RFP, RAX); // MOVL lr(RFP), RAX + modrm(Oldw, O(Frame, fp), RFP, RFP); // MOVL fp(RFP), RFP + modrm(Ostw, O(REG, FP), RTMP, RFP); // MOVL RFP, R.FP + gen2(Ojmprm, (3<<6)|(4<<3)|RAX); // JMP*L AX + + linterp = code - s; // return to uncompiled code + gen2(Ocallrm, (3<<6)|(2<<3)|RAX); // CALL* AX + con((ulong)&R, RTMP); // MOVL $R, RTMP + modrm(Ostw, O(REG, SP), RTMP, RFP); // MOVL RFP, R.SP + modrm(Oldw, O(Frame, lr), RFP, RAX); // MOVL lr(RFP), RAX + modrm(Ostw, O(REG, PC), RTMP, RAX); // MOVL RAX, R.PC + modrm(Oldw, O(Frame, fp), RFP, RFP); // MOVL fp(RFP), RFP + modrm(Ostw, O(REG, FP), RTMP, RFP); // MOVL RFP, R.FP + genb(Opopl+RDI); // return to uncompiled code + genb(Opopl+RSI); + genb(Opopl+RDX); + genb(Opopl+RCX); + genb(Opopl+RBX); + genb(Oret); + // label: + lpunt = code - s; + + i.add = AXNON; + punt(&i, TCHECK|NEWPC, optab[IRET]); +} + +static void +maccolr(void) +{ + modrm(Oincrm, O(Heap, ref)-sizeof(Heap), RBX, 0); + gen2(Oldw, (0<<6)|(RAX<<3)|5); // INCL ref(BX) + genw((ulong)&mutator); // MOVL mutator, RAX + modrm(Ocmpw, O(Heap, color)-sizeof(Heap), RBX, RAX); + gen2(Ojneb, 0x01); // CMPL color(BX), RAX + genb(Oret); // MOVL $propagator,RTMP + con(propagator, RAX); // MOVL RTMP, color(BX) + modrm(Ostw, O(Heap, color)-sizeof(Heap), RBX, RAX); + gen2(Ostw, (0<<6)|(RAX<<3)|5); // can be any !0 value + genw((ulong)&nprop); // MOVL RBX, nprop + genb(Oret); +} + +static void +macmcal(void) +{ + uchar *label, *mlnil, *interp; + + cmpl(RAX, (ulong)H); + gen2(Ojeqb, 0); + mlnil = code - 1; + modrm(0x83, O(Modlink, prog), RTA, 7); // CMPL $0, ml->prog + genb(0x00); + gen2(Ojneb, 0); // JNE patch + label = code-1; + *mlnil = code-mlnil-1; + modrm(Ostw, O(REG, FP), RTMP, RCX); + modrm(Ostw, O(REG, dt), RTMP, RAX); + bra((ulong)rmcall, Ocall); // CALL rmcall + con((ulong)&R, RTMP); // MOVL $R, RTMP + modrm(Oldw, O(REG, FP), RTMP, RFP); + modrm(Oldw, O(REG, MP), RTMP, RMP); + genb(Oret); // RET + *label = code-label-1; // patch: + gen2(Oldw, (3<<6)|(RFP<<3)|RCX); // MOVL CX, RFP R.FP = f + modrm(Ostw, O(REG, M), RTMP, RTA); // MOVL RTA, R.M + modrm(Oincrm, O(Heap, ref)-sizeof(Heap), RTA, 0); + modrm(Oldw, O(Modlink, MP), RTA, RMP); // MOVL R.M->mp, RMP + modrm(Ostw, O(REG, MP), RTMP, RMP); // MOVL RMP, R.MP R.MP = ml->MP + modrm(Ocmpi, O(Modlink, compiled), RTA, 7);// CMPL $0, M.compiled + genb(0x00); + genb(Opopl+RTA); // balance call + gen2(Ojeqb, 0); // JEQ interp + interp = code-1; + gen2(Ojmprm, (3<<6)|(4<<3)|RAX); // JMP*L AX + *interp = code-interp-1; // interp: + modrm(Ostw, O(REG, FP), RTMP, RFP); // MOVL FP, R.FP + modrm(Ostw, O(REG, PC), RTMP, RAX); // MOVL PC, R.PC + genb(Opopl+RDI); // call to uncompiled code + genb(Opopl+RSI); + genb(Opopl+RDX); + genb(Opopl+RCX); + genb(Opopl+RBX); + genb(Oret); +} + +static void +macfram(void) +{ + uchar *label; + + con((ulong)&R, RTMP); // MOVL $R, RTMP + modrm(Oldw, O(REG, SP), RTMP, RAX); // MOVL R.SP, AX + modrm(0x03, O(Type, size), RTA, RAX); // ADDL size(RCX), RAX + modrm(0x3b, O(REG, TS), RTMP, RAX); // CMPL AX, R.TS + gen2(0x7c, 0x00); // JL .+(patch) + label = code-1; + + modrm(Ostw, O(REG, s), RTMP, RTA); + modrm(Ostw, O(REG, FP), RTMP, RFP); // MOVL RFP, R.FP + bra((ulong)extend, Ocall); // CALL extend + con((ulong)&R, RTMP); + modrm(Oldw, O(REG, FP), RTMP, RFP); // MOVL R.MP, RMP + modrm(Oldw, O(REG, MP), RTMP, RMP); // MOVL R.FP, RFP + modrm(Oldw, O(REG, s), RTMP, RCX); // MOVL R.s, *R.d + genb(Oret); // RET + *label = code-label-1; + modrm(Oldw, O(REG, SP), RTMP, RCX); // MOVL R.SP, CX + modrm(Ostw, O(REG, SP), RTMP, RAX); // MOVL AX, R.SP + + modrm(Ostw, O(Frame, t), RCX, RTA); // MOVL RTA, t(CX) f->t = t + modrm(Omov, REGMOD*4, RCX, 0); // MOVL $0, mr(CX) f->mr + genw(0); + modrm(Oldw, O(Type, initialize), RTA, RTA); + gen2(Ojmprm, (3<<6)|(4<<3)|RTA); // JMP*L RTA + genb(Oret); // RET +} + +static void +macmfra(void) +{ + con((ulong)&R, RTMP); // MOVL $R, RTMP + modrm(Ostw, O(REG, FP), RTMP, RFP); + modrm(Ostw, O(REG, s), RTMP, RAX); // Save type + modrm(Ostw, O(REG, d), RTMP, RTA); // Save destination + bra((ulong)rmfram, Ocall); // CALL rmfram + con((ulong)&R, RTMP); // MOVL $R, RTMP + modrm(Oldw, O(REG, FP), RTMP, RFP); + modrm(Oldw, O(REG, MP), RTMP, RMP); + genb(Oret); // RET +} + +static void +macrelq(void) +{ + modrm(Ostw, O(REG, FP), RTMP, RFP); // MOVL FP, R.FP + genb(Opopl+RAX); + modrm(Ostw, O(REG, PC), RTMP, RAX); // MOVL PC, R.PC + genb(Opopl+RDI); + genb(Opopl+RSI); + genb(Opopl+RDX); + genb(Opopl+RCX); + genb(Opopl+RBX); + genb(Oret); +} + +void +comd(Type *t) +{ + int i, j, m, c; + + for(i = 0; i < t->np; i++) { + c = t->map[i]; + j = i<<5; + for(m = 0x80; m != 0; m >>= 1) { + if(c & m) { + modrm(Oldw, j, RFP, RAX); + rbra(macro[MacFRP], Ocall); + } + j += sizeof(WORD*); + } + } + genb(Oret); +} + +void +comi(Type *t) +{ + int i, j, m, c; + + con((ulong)H, RAX); + for(i = 0; i < t->np; i++) { + c = t->map[i]; + j = i<<5; + for(m = 0x80; m != 0; m >>= 1) { + if(c & m) + modrm(Ostw, j, RCX, RAX); + j += sizeof(WORD*); + } + } + genb(Oret); +} + +void +typecom(Type *t) +{ + int n; + uchar *tmp; + + if(t == nil || t->initialize != 0) + return; + + tmp = mallocz(4096*sizeof(uchar), 0); + if(tmp == nil) + error(exNomem); + + code = tmp; + comi(t); + n = code - tmp; + code = tmp; + comd(t); + n += code - tmp; + free(tmp); + + code = mallocz(n, 0); + if(code == nil) + return; + + t->initialize = code; + comi(t); + t->destroy = code; + comd(t); + + if(cflag > 3) + print("typ= %.8lux %4d i %.8lux d %.8lux asm=%d\n", + (ulong)t, t->size, (ulong)t->initialize, (ulong)t->destroy, n); + + segflush(t->initialize, n); +} + +static void +patchex(Module *m, ulong *p) +{ + Handler *h; + Except *e; + + if((h = m->htab) == nil) + return; + for( ; h->etab != nil; h++){ + h->pc1 = p[h->pc1]; + h->pc2 = p[h->pc2]; + for(e = h->etab; e->s != nil; e++) + e->pc = p[e->pc]; + if(e->pc != -1) + e->pc = p[e->pc]; + } +} + +#endif //{} + +int +compile(Module *m, int size, Modlink *ml) +{ +#if 0 //{} + ulong v; + Modl *e; + Link *l; + int i, n; + uchar *s, *tmp; + + base = nil; + patch = mallocz(size*sizeof(*patch), 0); + tinit = malloc(m->ntype*sizeof(*tinit)); + tmp = mallocz(4096*sizeof(uchar),0); + if(tinit == nil || patch == nil || tmp == nil) + goto bad; + + preamble(); + + mod = m; + n = 0; + pass = 0; + nlit = 0; + + for(i = 0; i < size; i++) { + code = tmp; + comp(&m->prog[i]); + patch[i] = n; + n += code - tmp; + } + + for(i = 0; i < nelem(mactab); i++) { + code = tmp; + mactab[i].gen(); + macro[mactab[i].idx] = n; + n += code - tmp; + } + + n = (n+3)&~3; + + nlit *= sizeof(ulong); + base = mallocz(n + nlit, 0); + if(base == nil) + goto bad; + + if(cflag > 3) + print("dis=%5d %5d 386=%5d asm=%.8lux lit=%d: %s\n", + size, size*sizeof(Inst), n, (ulong)base, nlit, m->name); + + pass++; + nlit = 0; + litpool = (ulong*)(base+n); + code = base; + + for(i = 0; i < size; i++) { + s = code; + comp(&m->prog[i]); + if(cflag > 4) { + print("%D\n", &m->prog[i]); + das(s, code-s); + } + } + + for(i = 0; i < nelem(mactab); i++) + mactab[i].gen(); + + v = (ulong)base; + for(l = m->ext; l->name; l++) { + l->u.pc = (Inst*)(v+patch[l->u.pc-m->prog]); + typecom(l->frame); + } + if(ml != nil) { + e = &ml->links[0]; + for(i = 0; i < ml->nlinks; i++) { + e->u.pc = (Inst*)(v+patch[e->u.pc-m->prog]); + typecom(e->frame); + e++; + } + } + for(i = 0; i < m->ntype; i++) { + if(tinit[i] != 0) + typecom(m->type[i]); + } + patchex(m, patch); + m->entry = (Inst*)(v+patch[mod->entry-mod->prog]); + free(patch); + free(tinit); + free(tmp); + free(m->prog); + m->prog = (Inst*)base; + m->compiled = 1; + segflush(base, n*sizeof(base)); + return 1; +bad: + free(patch); + free(tinit); + free(tmp); + free(base); +#endif //{} + + return 0; +} + diff --git a/libinterp/das-riscv64.c b/libinterp/das-riscv64.c new file mode 100644 index 0000000..115e0b4 --- /dev/null +++ b/libinterp/das-riscv64.c @@ -0,0 +1,9 @@ +#include +#include + +void +das(uchar *x, int n) +{ + USED(x); + USED(n); +} diff --git a/libiot/freertos/adds.c b/libiot/freertos/adds.c new file mode 100644 index 0000000..3eede33 --- /dev/null +++ b/libiot/freertos/adds.c @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS, FreeRTOS additions + * + */ + +//#include "luartos.h" + +//#include "esp_attr.h" + +//#include "lua.h" +#include +#include +#include "adds.h" + +#include +#include + +#include + +#include + +#define THREAD_LOCAL_STORAGE_POINTER_ID 0 + +// Reference to lua_thread, which is created in app_main +extern pthread_t lua_thread; + +// Global state +static lua_State *gL = NULL; + +static int compare(const void *a, const void *b) { + if (((task_info_t *)a)->task_type < ((task_info_t *)b)->task_type) { + return 1; + } else if (((task_info_t*)a)->task_type > ((task_info_t *)b)->task_type) { + return -1; + } else { + if (((task_info_t *)a)->core < ((task_info_t *)b)->core) { + return -1; + } else if (((task_info_t *)a)->core > ((task_info_t *)b)->core) { + return 1; + } else { + return strcmp(((task_info_t *)a)->name, ((task_info_t *)b)->name); + } + } +} + +void uxSetThreadId(UBaseType_t id) { + lua_rtos_tcb_t *lua_rtos_tcb; + + // Get Lua RTOS specific TCB parts for current task + if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), THREAD_LOCAL_STORAGE_POINTER_ID))) { + // Store thread id into Lua RTOS specific TCB parts + lua_rtos_tcb->threadid = id; + } +} + +UBaseType_t uxGetThreadId() { + lua_rtos_tcb_t *lua_rtos_tcb; + int threadid = 0; + + // Get Lua RTOS specific TCB parts for current task + if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), THREAD_LOCAL_STORAGE_POINTER_ID))) { + // Get current thread od from Lua RTOS specific TCB parts + threadid = lua_rtos_tcb->threadid; + } + + return threadid; +} + +void uxSetThreadStatus(TaskHandle_t h, pthread_status_t status) { + lua_rtos_tcb_t *lua_rtos_tcb; + + // Get Lua RTOS specific TCB parts for current task + if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), THREAD_LOCAL_STORAGE_POINTER_ID))) { + // Store thread id into Lua RTOS specific TCB parts + lua_rtos_tcb->status = status; + } +} + +void uxSetLThread(lthread_t *lthread) { + lua_rtos_tcb_t *lua_rtos_tcb; + + // Get Lua RTOS specific TCB parts for current task + if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), THREAD_LOCAL_STORAGE_POINTER_ID))) { + lua_rtos_tcb->lthread = lthread; + } +} + +void uxSetLuaState(lua_State* L) { + lua_rtos_tcb_t *lua_rtos_tcb; + + // Get Lua RTOS specific TCB parts for current task + if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), THREAD_LOCAL_STORAGE_POINTER_ID))) { + // Store current lua state into Lua RTOS specific TCB parts + if (!lua_rtos_tcb->lthread) { + lua_rtos_tcb->lthread = calloc(1, sizeof(lthread_t)); + if (lua_rtos_tcb->lthread == NULL) { + panic(); + } + //assert(lua_rtos_tcb->lthread); + } + lua_rtos_tcb->lthread->L = L; + } + + // If this is the lua thread, store state in global state + if ((uxGetThreadId() == lua_thread) && (gL == NULL)) { + gL = L; + } +} + +lua_State* pvGetLuaState() { + lua_rtos_tcb_t *lua_rtos_tcb; + lua_State *L = NULL; + + // Get Lua RTOS specific TCB parts for current task + if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(NULL, THREAD_LOCAL_STORAGE_POINTER_ID))) { + + // Get current lua state from Lua RTOS specific TCB parts + if (lua_rtos_tcb->lthread) { + L = lua_rtos_tcb->lthread->L; + } + } + + if (L == NULL) { + L = gL; + } + + return L; +} + +lthread_t *pvGetLThread() { + lua_rtos_tcb_t *lua_rtos_tcb; + lthread_t *lthread = NULL; + + // Get Lua RTOS specific TCB parts for current task + if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(NULL, THREAD_LOCAL_STORAGE_POINTER_ID))) { + lthread = lua_rtos_tcb->lthread; + } + + return lthread; +} + +uint32_t uxGetSignaled(TaskHandle_t h) { + lua_rtos_tcb_t *lua_rtos_tcb; + int signaled = 0; + + // Get Lua RTOS specific TCB parts for current task + if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(h, THREAD_LOCAL_STORAGE_POINTER_ID))) { + // Get current signeled mask from Lua RTOS specific TCB parts + signaled = lua_rtos_tcb->signaled; + } + + return signaled; +} + +void uxSetSignaled(TaskHandle_t h, int s) { + lua_rtos_tcb_t *lua_rtos_tcb; + + // Get Lua RTOS specific TCB parts for current task + if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(h, THREAD_LOCAL_STORAGE_POINTER_ID))) { + // Store current signaled mask into Lua RTOS specific TCB parts + lua_rtos_tcb->signaled = s; + } +} + +uint8_t ucGetCoreID(TaskHandle_t h) { + tskTCB_t *task = (tskTCB_t *)h; + + return task->xCoreID; +} + +int uxGetStack(TaskHandle_t h) { + tskTCB_t *task = (tskTCB_t *)h; + + return task->pxEndOfStack - task->pxStack + 4; +} + +task_info_t *GetTaskInfo() { + tskTCB_t *ctask; + task_info_t *info; + uint8_t task_type; + lua_rtos_tcb_t *lua_rtos_tcb; + TaskStatus_t *status_array; + UBaseType_t task_num = 0; + UBaseType_t start_task_num = 0; + uint32_t total_runtime = 0; + + //Allocate status_array + start_task_num = uxTaskGetNumberOfTasks(); + + status_array = (TaskStatus_t *)calloc(start_task_num, sizeof(TaskStatus_t)); + if (!status_array) { + return NULL; + } + +//#ifndef CONFIG_FREERTOS_USE_TRACE_FACILITY +//#warning Please enable CONFIG_FREERTOS_USE_TRACE_FACILITY to support thread.list +// free(status_array); +// return NULL; +#ifndef configUSE_TRACE_FACILITY +#warning Please enable configUSE_TRACE_FACILITY to support thread.list + free(status_array); + return NULL; +#else + task_num = uxTaskGetSystemState(status_array, (start_task_num), &total_runtime); + // For percentage calculations. + total_runtime /= 100UL; + + info = (task_info_t *)calloc(task_num + 1, sizeof(task_info_t)); + if (!info) { + free(status_array); + return NULL; + } +#endif + + for(int i = 0; i lthread) { + // Lua thread + task_type = 2; + } else { + // pthread + task_type = 1; + } + + info[i].thid = lua_rtos_tcb->threadid; + info[i].lthread = lua_rtos_tcb->lthread; + info[i].status = lua_rtos_tcb->status; + } + + // Populate info item + info[i].prio = status_array[i].uxCurrentPriority; + info[i].task_type = task_type; + info[i].core = ctask->xCoreID; + + // Some system tasks shows 255!! + if (info[i].core > 1) { + info[i].core = 0; + } + + info[i].free_stack = uxTaskGetStackHighWaterMark(status_array[i].xHandle); + info[i].stack_size = ctask->pxEndOfStack - ctask->pxStack + 4; + memcpy(info[i].name, status_array[i].pcTaskName, configMAX_TASK_NAME_LEN); + + info[i].cpu_usage = 0; +#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS + if( total_runtime > 0 ) { + // only gives valid values if CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is defined + info[i].cpu_usage = status_array[i].ulRunTimeCounter / total_runtime; + } +#endif + + } + + free(status_array); + + qsort (info, task_num, sizeof (task_info_t), compare); + + return info; +} diff --git a/libiot/freertos/adds.h b/libiot/freertos/adds.h new file mode 100644 index 0000000..6134c5d --- /dev/null +++ b/libiot/freertos/adds.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS, FreeRTOS additions + * + */ + +#ifndef _FREERTOS_ADDS_H +#define _FREERTOS_ADDS_H + +//#include "lua.h" + +#include +#include + +#include +#include + +typedef void* lua_State; + +/* Value that can be assigned to the eNotifyState member of the TCB. */ +typedef enum +{ + eNotWaitingNotification = 0, + eWaitingNotification, + eNotified +} eNotifyValue_t; + +typedef struct +{ + volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */ + + #if ( portUSING_MPU_WRAPPERS == 1 ) + xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */ + #endif + + ListItem_t xGenericListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */ + ListItem_t xEventListItem; /*< Used to reference a task from an event list. */ + UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */ + StackType_t *pxStack; /*< Points to the start of the stack. */ + char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ + BaseType_t xCoreID; /*< Core this task is pinned to */ + /* If this moves around (other than pcTaskName size changes), please change the define in xtensa_vectors.S as well. */ + #if ( portSTACK_GROWTH > 0 || configENABLE_TASK_SNAPSHOT == 1 ) + StackType_t *pxEndOfStack; /*< Points to the end of the stack on architectures where the stack grows up from low memory. */ + #endif + + #if ( portCRITICAL_NESTING_IN_TCB == 1 ) + UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */ + uint32_t uxOldInterruptState; /*< Interrupt state before the outer taskEnterCritical was called */ + #endif + + #if ( configUSE_TRACE_FACILITY == 1 ) + UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */ + UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */ + #endif + + #if ( configUSE_MUTEXES == 1 ) + UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */ + UBaseType_t uxMutexesHeld; + #endif + + #if ( configUSE_APPLICATION_TASK_TAG == 1 ) + TaskHookFunction_t pxTaskTag; + #endif + + #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) + void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ]; + #if ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS ) + TlsDeleteCallbackFunction_t pvThreadLocalStoragePointersDelCallback[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ]; + #endif + #endif + + #if ( configGENERATE_RUN_TIME_STATS == 1 ) + uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */ + #endif + + #if ( configUSE_NEWLIB_REENTRANT == 1 ) + /* Allocate a Newlib reent structure that is specific to this task. + Note Newlib support has been included by popular demand, but is not + used by the FreeRTOS maintainers themselves. FreeRTOS is not + responsible for resulting newlib operation. User must be familiar with + newlib and must provide system-wide implementations of the necessary + stubs. Be warned that (at the time of writing) the current newlib design + implements a system-wide malloc() that must be provided with locks. */ + struct _reent xNewLib_reent; + #endif + + #if ( configUSE_TASK_NOTIFICATIONS == 1 ) + volatile uint32_t ulNotifiedValue; + volatile eNotifyValue_t eNotifyState; + #endif + + /* See the comments above the definition of + tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */ + #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) + uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */ + #endif + +} tskTCB_t; + +typedef enum { + StatusRunning = 1, + StatusSuspended = 2 +} pthread_status_t; + +typedef struct lthread { + lua_State *PL; // Parent thread + lua_State *L; // Thread state + int function_ref; + int thread_ref; + int status; +} lthread_t; + +typedef struct { + uint8_t task_type; + char name[configMAX_TASK_NAME_LEN]; + uint8_t core; + uint8_t prio; + size_t stack_size; + size_t free_stack; + uint32_t cpu_usage; + int thid; + lthread_t *lthread; + pthread_status_t status; +} task_info_t; + +typedef struct { + int32_t threadid; + uint32_t signaled; + pthread_status_t status; + struct lthread *lthread; +} lua_rtos_tcb_t; + +// This macro is not present in all FreeRTOS ports. In Lua RTOS is used in some places +// in the source code shared by all the supported platforms. ESP32 don't define this macro, +// so we define it for reuse code beetween platforms. +/* //{} +#define portEND_SWITCHING_ISR(xSwitchRequired) \ +if (xSwitchRequired) { \ + _frxt_setup_switch(); \ +} +*/ + +UBaseType_t uxGetTaskId(); +UBaseType_t uxGetThreadId(); +void uxSetThreadStatus(TaskHandle_t h, pthread_status_t status); +void uxSetThreadId(UBaseType_t id); +void uxSetLThread(lthread_t *lthread); +lthread_t *pvGetLThread(); +void uxSetSignaled(TaskHandle_t h, int s); +uint32_t uxGetSignaled(TaskHandle_t h); +TaskHandle_t xGetCurrentTask(); +uint8_t ucGetCoreID(TaskHandle_t h); +int uxGetStack(TaskHandle_t h); +task_info_t *GetTaskInfo(); +void uxSetLuaState(lua_State* L); +lua_State* pvGetLuaState(); + +#endif diff --git a/libiot/freertos/mkfile b/libiot/freertos/mkfile new file mode 100644 index 0000000..a56b48c --- /dev/null +++ b/libiot/freertos/mkfile @@ -0,0 +1,3 @@ +OFILES=\ + $OFILES\ + freertos/adds.$O\ diff --git a/libiot/hal/mkfile b/libiot/hal/mkfile new file mode 100644 index 0000000..73b16cb --- /dev/null +++ b/libiot/hal/mkfile @@ -0,0 +1,3 @@ +OFILES=\ + $OFILES\ + hal/w25qxx.$O\ diff --git a/libiot/hal/w25qxx.c b/libiot/hal/w25qxx.c new file mode 100644 index 0000000..a169617 --- /dev/null +++ b/libiot/hal/w25qxx.c @@ -0,0 +1,288 @@ +/* Copyright 2018 Canaan Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include "w25qxx.h" + +uintptr_t spi_adapter; +uintptr_t spi_stand; + +static enum w25qxx_status_t w25qxx_receive_data(uint8_t* cmd_buff, uint8_t cmd_len, uint8_t* rx_buff, uint32_t rx_len) +{ + spi_dev_transfer_sequential(spi_stand, (uint8_t *)cmd_buff, cmd_len, (uint8_t *)rx_buff, rx_len); + return W25QXX_OK; +} + +static enum w25qxx_status_t w25qxx_receive_data_enhanced(uint32_t* cmd_buff, uint8_t cmd_len, uint8_t* rx_buff, uint32_t rx_len) +{ + memcpy(rx_buff, cmd_buff, cmd_len); + io_read(spi_adapter, (uint8_t *)rx_buff, rx_len); + return W25QXX_OK; +} + +static enum w25qxx_status_t w25qxx_send_data(uintptr_t file, uint8_t* cmd_buff, uint8_t cmd_len, uint8_t* tx_buff, uint32_t tx_len) +{ + configASSERT(cmd_len); + uint8_t* tmp_buf = malloc(cmd_len + tx_len); + memcpy(tmp_buf, cmd_buff, cmd_len); + if (tx_len) + memcpy(tmp_buf + cmd_len, tx_buff, tx_len); + io_write(file, (uint8_t *)tmp_buf, cmd_len + tx_len); + free(tmp_buf); + return W25QXX_OK; +} + +static enum w25qxx_status_t w25qxx_write_enable(void) +{ + uint8_t cmd[1] = {WRITE_ENABLE}; + + w25qxx_send_data(spi_stand, cmd, 1, 0, 0); + return W25QXX_OK; +} + +static enum w25qxx_status_t w25qxx_read_status_reg1(uint8_t* reg_data) +{ + uint8_t cmd[1] = {READ_REG1}; + uint8_t data[1]; + + w25qxx_receive_data(cmd, 1, data, 1); + *reg_data = data[0]; + return W25QXX_OK; +} +static enum w25qxx_status_t w25qxx_read_status_reg2(uint8_t* reg_data) +{ + uint8_t cmd[1] = {READ_REG2}; + uint8_t data[1]; + + w25qxx_receive_data(cmd, 1, data, 1); + *reg_data = data[0]; + return W25QXX_OK; +} +static enum w25qxx_status_t w25qxx_write_status_reg(uint8_t reg1_data, uint8_t reg2_data) +{ + uint8_t cmd[3] = {WRITE_REG1, reg1_data, reg2_data}; + + w25qxx_write_enable(); + w25qxx_send_data(spi_stand, cmd, 3, 0, 0); + return W25QXX_OK; +} + +static enum w25qxx_status_t w25qxx_enable_quad_mode(void) +{ + uint8_t reg_data; + + w25qxx_read_status_reg2(®_data); + if (!(reg_data & REG2_QUAL_MASK)) + { + reg_data |= REG2_QUAL_MASK; + w25qxx_write_status_reg(0x00, reg_data); + } + return W25QXX_OK; +} + +static enum w25qxx_status_t w25qxx_is_busy(void) +{ + uint8_t status; + + w25qxx_read_status_reg1(&status); + if (status & REG1_BUSY_MASK) + return W25QXX_BUSY; + return W25QXX_OK; +} + +enum w25qxx_status_t w25qxx_sector_erase(uint32_t addr) +{ + uint8_t cmd[4] = {SECTOR_ERASE}; + + cmd[1] = (uint8_t)(addr >> 16); + cmd[2] = (uint8_t)(addr >> 8); + cmd[3] = (uint8_t)(addr); + w25qxx_write_enable(); + w25qxx_send_data(spi_stand, cmd, 4, 0, 0); + return W25QXX_OK; +} + +enum w25qxx_status_t w25qxx_read_id(uint8_t *manuf_id, uint8_t *device_id) +{ + uint8_t cmd[4] = {READ_ID, 0x00, 0x00, 0x00}; + uint8_t data[2] = {0}; + + w25qxx_receive_data(cmd, 4, data, 2); + *manuf_id = data[0]; + *device_id = data[1]; + return W25QXX_OK; +} + +static enum w25qxx_status_t w25qxx_read_data_less_64kb(uint32_t addr, uint8_t* data_buf, uint32_t length) +{ + uint32_t cmd[2]; + + switch (WORK_TRANS_MODE) + { + case SPI_FF_DUAL: + *(((uint8_t*)cmd) + 0) = FAST_READ_DUAL_OUTPUT; + *(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 0); + *(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8); + *(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 16); + w25qxx_receive_data_enhanced(cmd, 4, data_buf, length); + break; + case SPI_FF_QUAD: + *(((uint8_t*)cmd) + 0) = FAST_READ_QUAL_OUTPUT; + *(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 0); + *(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8); + *(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 16); + w25qxx_receive_data_enhanced(cmd, 4, data_buf, length); + break; + case SPI_FF_STANDARD: + default: + *(((uint8_t*)cmd) + 0) = READ_DATA; + *(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 16); + *(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8); + *(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 0); + w25qxx_receive_data((uint8_t*)cmd, 4, data_buf, length); + break; + } + return W25QXX_OK; +} + +enum w25qxx_status_t w25qxx_read_data(uint32_t addr, uint8_t* data_buf, uint32_t length) +{ + uint32_t len; + + while (length) + { + len = length >= 0x010000 ? 0x010000 : length; + w25qxx_read_data_less_64kb(addr, data_buf, len); + addr += len; + data_buf += len; + length -= len; + } + return W25QXX_OK; +} + +static enum w25qxx_status_t w25qxx_page_program(uint32_t addr, uint8_t* data_buf, uint32_t length) +{ + uint32_t cmd[2]; + w25qxx_write_enable(); + if (WORK_TRANS_MODE == SPI_FF_QUAD) + { + *(((uint8_t*)cmd) + 0) = QUAD_PAGE_PROGRAM; + *(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 0); + *(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8); + *(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 16); + w25qxx_send_data(spi_adapter, (uint8_t*)cmd, 4, data_buf, length); + } + else + { + *(((uint8_t*)cmd) + 0) = PAGE_PROGRAM; + *(((uint8_t*)cmd) + 1) = (uint8_t)(addr >> 16); + *(((uint8_t*)cmd) + 2) = (uint8_t)(addr >> 8); + *(((uint8_t*)cmd) + 3) = (uint8_t)(addr >> 0); + w25qxx_send_data(spi_stand, (uint8_t*)cmd, 4, data_buf, length); + } + while (w25qxx_is_busy() == W25QXX_BUSY) + ; + return W25QXX_OK; +} + +static enum w25qxx_status_t w25qxx_sector_program(uint32_t addr, uint8_t* data_buf) +{ + uint8_t index; + + for (index = 0; index < w25qxx_FLASH_PAGE_NUM_PER_SECTOR; index++) + { + w25qxx_page_program(addr, data_buf, w25qxx_FLASH_PAGE_SIZE); + addr += w25qxx_FLASH_PAGE_SIZE; + data_buf += w25qxx_FLASH_PAGE_SIZE; + } + return W25QXX_OK; +} + +enum w25qxx_status_t w25qxx_write_data(uint32_t addr, uint8_t* data_buf, uint32_t length) +{ + uint32_t sector_addr, sector_offset, sector_remain, write_len, index; + uint8_t swap_buf[w25qxx_FLASH_SECTOR_SIZE]; + uint8_t *pread, *pwrite; + + while (length) + { + sector_addr = addr & (~(w25qxx_FLASH_SECTOR_SIZE - 1)); + sector_offset = addr & (w25qxx_FLASH_SECTOR_SIZE - 1); + sector_remain = w25qxx_FLASH_SECTOR_SIZE - sector_offset; + write_len = length < sector_remain ? length : sector_remain; + w25qxx_read_data(sector_addr, swap_buf, w25qxx_FLASH_SECTOR_SIZE); + pread = swap_buf + sector_offset; + pwrite = data_buf; + for (index = 0; index < write_len; index++) + { + if ((*pwrite) != ((*pwrite) & (*pread))) + { + w25qxx_sector_erase(sector_addr); + while (w25qxx_is_busy() == W25QXX_BUSY) + ; + break; + } + pwrite++; + pread++; + } + if (write_len == w25qxx_FLASH_SECTOR_SIZE) + w25qxx_sector_program(sector_addr, data_buf); + else + { + pread = swap_buf + sector_offset; + pwrite = data_buf; + for (index = 0; index < write_len; index++) + *pread++ = *pwrite++; + w25qxx_sector_program(sector_addr, swap_buf); + } + length -= write_len; + addr += write_len; + data_buf += write_len; + } + return W25QXX_OK; +} + +enum w25qxx_status_t w25qxx_init(uintptr_t spi_in) +{ + uint8_t manuf_id, device_id; + spi_stand = spi_get_device(spi_in, SPI_MODE_0, SPI_FF_STANDARD, CHIP_SELECT, FRAME_LENGTH); + spi_dev_set_clock_rate(spi_stand, 800000); + w25qxx_read_id(&manuf_id, &device_id); + if ((manuf_id != 0xEF && manuf_id != 0xC8) || (device_id != 0x17 && device_id != 0x16)) + { + printf("manuf_id:0x%02x, device_id:0x%02x\n", manuf_id, device_id); + } + printf("manuf_id:0x%02x, device_id:0x%02x\n", manuf_id, device_id); + switch (WORK_TRANS_MODE) + { + case SPI_FF_DUAL: + spi_adapter = spi_get_device(spi_in, SPI_MODE_0, SPI_FF_DUAL, CHIP_SELECT, FRAME_LENGTH); + spi_dev_config_non_standard(spi_adapter, INSTRUCTION_LENGTH, ADDRESS_LENGTH, WAIT_CYCLE, SPI_AITM_STANDARD); + break; + case SPI_FF_QUAD: + spi_adapter = spi_get_device(spi_in, SPI_MODE_0, SPI_FF_QUAD, CHIP_SELECT, FRAME_LENGTH); + spi_dev_config_non_standard(spi_adapter, INSTRUCTION_LENGTH, ADDRESS_LENGTH, WAIT_CYCLE, SPI_AITM_STANDARD); + w25qxx_enable_quad_mode(); + break; + case SPI_FF_STANDARD: + default: + spi_adapter = spi_stand; + break; + } + return W25QXX_OK; +} + diff --git a/libiot/hal/w25qxx.h b/libiot/hal/w25qxx.h new file mode 100644 index 0000000..5ae32ca --- /dev/null +++ b/libiot/hal/w25qxx.h @@ -0,0 +1,88 @@ +/* Copyright 2018 Canaan Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _W25QXX_H +#define _W25QXX_H +#include + +/* clang-format off */ +#define WORK_TRANS_MODE SPI_FF_STANDARD +/* #define WORK_TRANS_MODE SPI_FF_DUAL */ +/* #define WORK_TRANS_MODE SPI_FF_QUAD */ + +#define CHIP_SELECT 1 +#define WAIT_CYCLE 8 +#define FRAME_LENGTH 8 +#define INSTRUCTION_LENGTH 8 +#define ADDRESS_LENGTH 24 + +#define SPI_SLAVE_SELECT (0x01) + +#define w25qxx_FLASH_PAGE_SIZE 256 +#define w25qxx_FLASH_SECTOR_SIZE 4096 +#define w25qxx_FLASH_PAGE_NUM_PER_SECTOR 16 +#define w25qxx_FLASH_CHIP_SIZE (16777216 UL) + +#define WRITE_ENABLE 0x06 +#define WRITE_DISABLE 0x04 +#define READ_REG1 0x05 +#define READ_REG2 0x35 +#define READ_REG3 0x15 +#define WRITE_REG1 0x01 +#define WRITE_REG2 0x31 +#define WRITE_REG3 0x11 +#define READ_DATA 0x03 +#define FAST_READ 0x0B +#define FAST_READ_DUAL_OUTPUT 0x3B +#define FAST_READ_QUAL_OUTPUT 0x6B +#define FAST_READ_DUAL_IO 0xBB +#define FAST_READ_QUAL_IO 0xEB +#define DUAL_READ_RESET 0xFFFF +#define QUAL_READ_RESET 0xFF +#define PAGE_PROGRAM 0x02 +#define QUAD_PAGE_PROGRAM 0x32 +#define SECTOR_ERASE 0x20 +#define BLOCK_32K_ERASE 0x52 +#define BLOCK_64K_ERASE 0xD8 +#define CHIP_ERASE 0x60 +#define READ_ID 0x90 +#define ENABLE_QPI 0x38 +#define EXIT_QPI 0xFF +#define ENABLE_RESET 0x66 +#define RESET_DEVICE 0x99 + +#define REG1_BUSY_MASK 0x01 +#define REG2_QUAL_MASK 0x02 + +#define LETOBE(x) ((x >> 24) | ((x & 0x00FF0000) >> 8) | ((x & 0x0000FF00) << 8) | (x << 24)) +/* clang-format on */ + +/** + * @brief w25qxx operating status enumerate + */ +enum w25qxx_status_t +{ + W25QXX_OK = 0, + W25QXX_BUSY, + W25QXX_ERROR, +}; + +enum w25qxx_status_t w25qxx_init(uintptr_t spi_in); +enum w25qxx_status_t w25qxx_write_data(uint32_t addr, uint8_t* data_buf, uint32_t length); +enum w25qxx_status_t w25qxx_read_data(uint32_t addr, uint8_t* data_buf, uint32_t length); + +enum w25qxx_status_t w25qxx_sector_erase(uint32_t addr); + +#endif + diff --git a/libiot/include/esp_err.h b/libiot/include/esp_err.h new file mode 100644 index 0000000..794f32e --- /dev/null +++ b/libiot/include/esp_err.h @@ -0,0 +1,148 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int32_t esp_err_t; + +/* Definitions for error constants. */ +#define ESP_OK 0 /*!< esp_err_t value indicating success (no error) */ +#define ESP_FAIL -1 /*!< Generic esp_err_t code indicating failure */ + +#define ESP_ERR_NO_MEM 0x101 /*!< Out of memory */ +#define ESP_ERR_INVALID_ARG 0x102 /*!< Invalid argument */ +#define ESP_ERR_INVALID_STATE 0x103 /*!< Invalid state */ +#define ESP_ERR_INVALID_SIZE 0x104 /*!< Invalid size */ +#define ESP_ERR_NOT_FOUND 0x105 /*!< Requested resource not found */ +#define ESP_ERR_NOT_SUPPORTED 0x106 /*!< Operation or feature not supported */ +#define ESP_ERR_TIMEOUT 0x107 /*!< Operation timed out */ +#define ESP_ERR_INVALID_RESPONSE 0x108 /*!< Received response was invalid */ +#define ESP_ERR_INVALID_CRC 0x109 /*!< CRC or checksum was invalid */ +#define ESP_ERR_INVALID_VERSION 0x10A /*!< Version was invalid */ +#define ESP_ERR_INVALID_MAC 0x10B /*!< MAC address was invalid */ + +#define ESP_ERR_WIFI_BASE 0x3000 /*!< Starting number of WiFi error codes */ +#define ESP_ERR_MESH_BASE 0x4000 /*!< Starting number of MESH error codes */ + +/** + * @brief Returns string for esp_err_t error codes + * + * This function finds the error code in a pre-generated lookup-table and + * returns its string representation. + * + * The function is generated by the Python script + * tools/gen_esp_err_to_name.py which should be run each time an esp_err_t + * error is modified, created or removed from the IDF project. + * + * @param code esp_err_t error code + * @return string error message + */ +const char *esp_err_to_name(esp_err_t code); + +/** + * @brief Returns string for esp_err_t and system error codes + * + * This function finds the error code in a pre-generated lookup-table of + * esp_err_t errors and returns its string representation. If the error code + * is not found then it is attempted to be found among system errors. + * + * The function is generated by the Python script + * tools/gen_esp_err_to_name.py which should be run each time an esp_err_t + * error is modified, created or removed from the IDF project. + * + * @param code esp_err_t error code + * @param[out] buf buffer where the error message should be written + * @param buflen Size of buffer buf. At most buflen bytes are written into the buf buffer (including the terminating null byte). + * @return buf containing the string error message + */ +const char *esp_err_to_name_r(esp_err_t code, char *buf, size_t buflen); + +/** @cond */ +void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression) __attribute__((noreturn)); + +/** @cond */ +void _esp_error_check_failed_without_abort(esp_err_t rc, const char *file, int line, const char *function, const char *expression); + +#ifndef __ASSERT_FUNC +/* This won't happen on IDF, which defines __ASSERT_FUNC in assert.h, but it does happen when building on the host which + uses /usr/include/assert.h or equivalent. +*/ +#ifdef __ASSERT_FUNCTION +#define __ASSERT_FUNC __ASSERT_FUNCTION /* used in glibc assert.h */ +#else +#define __ASSERT_FUNC "??" +#endif +#endif +/** @endcond */ + +/** + * Macro which can be used to check the error code, + * and terminate the program in case the code is not ESP_OK. + * Prints the error code, error location, and the failed statement to serial output. + * + * Disabled if assertions are disabled. + */ +#ifdef NDEBUG +#define ESP_ERROR_CHECK(x) do { \ + esp_err_t __err_rc = (x); \ + (void) sizeof(__err_rc); \ + } while(0) +#elif defined(CONFIG_OPTIMIZATION_ASSERTIONS_SILENT) +#define ESP_ERROR_CHECK(x) do { \ + esp_err_t __err_rc = (x); \ + if (__err_rc != ESP_OK) { \ + abort(); \ + } \ + } while(0) +#else +#define ESP_ERROR_CHECK(x) do { \ + esp_err_t __err_rc = (x); \ + if (__err_rc != ESP_OK) { \ + _esp_error_check_failed(__err_rc, __FILE__, __LINE__, \ + __ASSERT_FUNC, #x); \ + } \ + } while(0) +#endif + +/** + * Macro which can be used to check the error code. Prints the error code, error location, and the failed statement to + * serial output. + * In comparison with ESP_ERROR_CHECK(), this prints the same error message but isn't terminating the program. + */ +#ifdef NDEBUG +#define ESP_ERROR_CHECK_WITHOUT_ABORT(x) ({ \ + esp_err_t __err_rc = (x); \ + __err_rc; \ + }) +#else +#define ESP_ERROR_CHECK_WITHOUT_ABORT(x) ({ \ + esp_err_t __err_rc = (x); \ + if (__err_rc != ESP_OK) { \ + _esp_error_check_failed_without_abort(__err_rc, __FILE__, __LINE__, \ + __ASSERT_FUNC, #x); \ + } \ + __err_rc; \ + }) +#endif //NDEBUG + +#ifdef __cplusplus +} +#endif diff --git a/libiot/lfs/lfs.c b/libiot/lfs/lfs.c new file mode 100644 index 0000000..ebbe7c9 --- /dev/null +++ b/libiot/lfs/lfs.c @@ -0,0 +1,2592 @@ +/* + * The little filesystem + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs.h" +#include "lfs_util.h" + +#include + + +/// Caching block device operations /// +static int lfs_cache_read(lfs_t *lfs, lfs_cache_t *rcache, + const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + LFS_ASSERT(block < lfs->cfg->block_count); + + while (size > 0) { + if (pcache && block == pcache->block && off >= pcache->off && + off < pcache->off + lfs->cfg->prog_size) { + // is already in pcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->prog_size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + if (block == rcache->block && off >= rcache->off && + off < rcache->off + lfs->cfg->read_size) { + // is already in rcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->read_size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + if (off % lfs->cfg->read_size == 0 && size >= lfs->cfg->read_size) { + // bypass cache? + lfs_size_t diff = size - (size % lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); + if (err) { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // load to cache, first condition can no longer fail + rcache->block = block; + rcache->off = off - (off % lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, rcache->block, + rcache->off, rcache->buffer, lfs->cfg->read_size); + if (err) { + return err; + } + } + + return 0; +} + +static int lfs_cache_cmp(lfs_t *lfs, lfs_cache_t *rcache, + const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + int err = lfs_cache_read(lfs, rcache, pcache, + block, off+i, &c, 1); + if (err) { + return err; + } + + if (c != data[i]) { + return false; + } + } + + return true; +} + +static int lfs_cache_crc(lfs_t *lfs, lfs_cache_t *rcache, + const lfs_cache_t *pcache, lfs_block_t block, + lfs_off_t off, lfs_size_t size, uint32_t *crc) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + int err = lfs_cache_read(lfs, rcache, pcache, + block, off+i, &c, 1); + if (err) { + return err; + } + + lfs_crc(crc, &c, 1); + } + + return 0; +} + +static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) { + // do not zero, cheaper if cache is readonly or only going to be + // written with identical data (during relocates) + (void)lfs; + rcache->block = 0xffffffff; +} + +static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) { + // zero to avoid information leak + memset(pcache->buffer, 0xff, lfs->cfg->prog_size); + pcache->block = 0xffffffff; +} + +static int lfs_cache_flush(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache) { + if (pcache->block != 0xffffffff) { + int err = lfs->cfg->prog(lfs->cfg, pcache->block, + pcache->off, pcache->buffer, lfs->cfg->prog_size); + if (err) { + return err; + } + + if (rcache) { + int res = lfs_cache_cmp(lfs, rcache, NULL, pcache->block, + pcache->off, pcache->buffer, lfs->cfg->prog_size); + if (res < 0) { + return res; + } + + if (!res) { + return LFS_ERR_CORRUPT; + } + } + + lfs_cache_zero(lfs, pcache); + } + + return 0; +} + +static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache, + lfs_cache_t *rcache, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + LFS_ASSERT(block < lfs->cfg->block_count); + + while (size > 0) { + if (block == pcache->block && off >= pcache->off && + off < pcache->off + lfs->cfg->prog_size) { + // is already in pcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->prog_size - (off-pcache->off)); + memcpy(&pcache->buffer[off-pcache->off], data, diff); + + data += diff; + off += diff; + size -= diff; + + if (off % lfs->cfg->prog_size == 0) { + // eagerly flush out pcache if we fill up + int err = lfs_cache_flush(lfs, pcache, rcache); + if (err) { + return err; + } + } + + continue; + } + + // pcache must have been flushed, either by programming and + // entire block or manually flushing the pcache + LFS_ASSERT(pcache->block == 0xffffffff); + + if (off % lfs->cfg->prog_size == 0 && + size >= lfs->cfg->prog_size) { + // bypass pcache? + lfs_size_t diff = size - (size % lfs->cfg->prog_size); + int err = lfs->cfg->prog(lfs->cfg, block, off, data, diff); + if (err) { + return err; + } + + if (rcache) { + int res = lfs_cache_cmp(lfs, rcache, NULL, + block, off, data, diff); + if (res < 0) { + return res; + } + + if (!res) { + return LFS_ERR_CORRUPT; + } + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // prepare pcache, first condition can no longer fail + pcache->block = block; + pcache->off = off - (off % lfs->cfg->prog_size); + } + + return 0; +} + + +/// General lfs block device operations /// +static int lfs_bd_read(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + // if we ever do more than writes to alternating pairs, + // this may need to consider pcache + return lfs_cache_read(lfs, &lfs->rcache, NULL, + block, off, buffer, size); +} + +static int lfs_bd_prog(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + return lfs_cache_prog(lfs, &lfs->pcache, NULL, + block, off, buffer, size); +} + +static int lfs_bd_cmp(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + return lfs_cache_cmp(lfs, &lfs->rcache, NULL, block, off, buffer, size); +} + +static int lfs_bd_crc(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, lfs_size_t size, uint32_t *crc) { + return lfs_cache_crc(lfs, &lfs->rcache, NULL, block, off, size, crc); +} + +static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { + return lfs->cfg->erase(lfs->cfg, block); +} + +static int lfs_bd_sync(lfs_t *lfs) { + lfs_cache_drop(lfs, &lfs->rcache); + + int err = lfs_cache_flush(lfs, &lfs->pcache, NULL); + if (err) { + return err; + } + + return lfs->cfg->sync(lfs->cfg); +} + + +/// Internal operations predeclared here /// +int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); +static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir); +static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], + lfs_dir_t *parent, lfs_entry_t *entry); +static int lfs_moved(lfs_t *lfs, const void *e); +static int lfs_relocate(lfs_t *lfs, + const lfs_block_t oldpair[2], const lfs_block_t newpair[2]); +int lfs_deorphan(lfs_t *lfs); + + +/// Block allocator /// +static int lfs_alloc_lookahead(void *p, lfs_block_t block) { + lfs_t *lfs = p; + + lfs_block_t off = ((block - lfs->free.off) + + lfs->cfg->block_count) % lfs->cfg->block_count; + + if (off < lfs->free.size) { + lfs->free.buffer[off / 32] |= 1U << (off % 32); + } + + return 0; +} + +static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { + while (true) { + while (lfs->free.i != lfs->free.size) { + lfs_block_t off = lfs->free.i; + lfs->free.i += 1; + lfs->free.ack -= 1; + + if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) { + // found a free block + *block = (lfs->free.off + off) % lfs->cfg->block_count; + + // eagerly find next off so an alloc ack can + // discredit old lookahead blocks + while (lfs->free.i != lfs->free.size && + (lfs->free.buffer[lfs->free.i / 32] + & (1U << (lfs->free.i % 32)))) { + lfs->free.i += 1; + lfs->free.ack -= 1; + } + + return 0; + } + } + + // check if we have looked at all blocks since last ack + if (lfs->free.ack == 0) { + LFS_WARN("No more free space %" PRIu32, + lfs->free.i + lfs->free.off); + return LFS_ERR_NOSPC; + } + + lfs->free.off = (lfs->free.off + lfs->free.size) + % lfs->cfg->block_count; + lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->free.ack); + lfs->free.i = 0; + + // find mask of free blocks from tree + memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8); + int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs); + if (err) { + return err; + } + } +} + +static void lfs_alloc_ack(lfs_t *lfs) { + lfs->free.ack = lfs->cfg->block_count; +} + + +/// Endian swapping functions /// +static void lfs_dir_fromle32(struct lfs_disk_dir *d) { + d->rev = lfs_fromle32(d->rev); + d->size = lfs_fromle32(d->size); + d->tail[0] = lfs_fromle32(d->tail[0]); + d->tail[1] = lfs_fromle32(d->tail[1]); +} + +static void lfs_dir_tole32(struct lfs_disk_dir *d) { + d->rev = lfs_tole32(d->rev); + d->size = lfs_tole32(d->size); + d->tail[0] = lfs_tole32(d->tail[0]); + d->tail[1] = lfs_tole32(d->tail[1]); +} + +static void lfs_entry_fromle32(struct lfs_disk_entry *d) { + d->u.dir[0] = lfs_fromle32(d->u.dir[0]); + d->u.dir[1] = lfs_fromle32(d->u.dir[1]); +} + +static void lfs_entry_tole32(struct lfs_disk_entry *d) { + d->u.dir[0] = lfs_tole32(d->u.dir[0]); + d->u.dir[1] = lfs_tole32(d->u.dir[1]); +} + +static void lfs_superblock_fromle32(struct lfs_disk_superblock *d) { + d->root[0] = lfs_fromle32(d->root[0]); + d->root[1] = lfs_fromle32(d->root[1]); + d->block_size = lfs_fromle32(d->block_size); + d->block_count = lfs_fromle32(d->block_count); + d->version = lfs_fromle32(d->version); +} + +static void lfs_superblock_tole32(struct lfs_disk_superblock *d) { + d->root[0] = lfs_tole32(d->root[0]); + d->root[1] = lfs_tole32(d->root[1]); + d->block_size = lfs_tole32(d->block_size); + d->block_count = lfs_tole32(d->block_count); + d->version = lfs_tole32(d->version); +} + + +/// Metadata pair and directory operations /// +static inline void lfs_pairswap(lfs_block_t pair[2]) { + lfs_block_t t = pair[0]; + pair[0] = pair[1]; + pair[1] = t; +} + +static inline bool lfs_pairisnull(const lfs_block_t pair[2]) { + return pair[0] == 0xffffffff || pair[1] == 0xffffffff; +} + +static inline int lfs_paircmp( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return !(paira[0] == pairb[0] || paira[1] == pairb[1] || + paira[0] == pairb[1] || paira[1] == pairb[0]); +} + +static inline bool lfs_pairsync( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return (paira[0] == pairb[0] && paira[1] == pairb[1]) || + (paira[0] == pairb[1] && paira[1] == pairb[0]); +} + +static inline lfs_size_t lfs_entry_size(const lfs_entry_t *entry) { + return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; +} + +static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) { + // allocate pair of dir blocks + for (int i = 0; i < 2; i++) { + int err = lfs_alloc(lfs, &dir->pair[i]); + if (err) { + return err; + } + } + + // rather than clobbering one of the blocks we just pretend + // the revision may be valid + int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->d.rev, 4); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + if (err != LFS_ERR_CORRUPT) { + dir->d.rev = lfs_fromle32(dir->d.rev); + } + + // set defaults + dir->d.rev += 1; + dir->d.size = sizeof(dir->d)+4; + dir->d.tail[0] = 0xffffffff; + dir->d.tail[1] = 0xffffffff; + dir->off = sizeof(dir->d); + + // don't write out yet, let caller take care of that + return 0; +} + +static int lfs_dir_fetch(lfs_t *lfs, + lfs_dir_t *dir, const lfs_block_t pair[2]) { + // copy out pair, otherwise may be aliasing dir + const lfs_block_t tpair[2] = {pair[0], pair[1]}; + bool valid = false; + + // check both blocks for the most recent revision + for (int i = 0; i < 2; i++) { + struct lfs_disk_dir test; + int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); + lfs_dir_fromle32(&test); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) { + continue; + } + + if ((0x7fffffff & test.size) < sizeof(test)+4 || + (0x7fffffff & test.size) > lfs->cfg->block_size) { + continue; + } + + uint32_t crc = 0xffffffff; + lfs_dir_tole32(&test); + lfs_crc(&crc, &test, sizeof(test)); + lfs_dir_fromle32(&test); + err = lfs_bd_crc(lfs, tpair[i], sizeof(test), + (0x7fffffff & test.size) - sizeof(test), &crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (crc != 0) { + continue; + } + + valid = true; + + // setup dir in case it's valid + dir->pair[0] = tpair[(i+0) % 2]; + dir->pair[1] = tpair[(i+1) % 2]; + dir->off = sizeof(dir->d); + dir->d = test; + } + + if (!valid) { + LFS_ERROR("Corrupted dir pair at %" PRIu32 " %" PRIu32 , + tpair[0], tpair[1]); + return LFS_ERR_CORRUPT; + } + + return 0; +} + +struct lfs_region { + lfs_off_t oldoff; + lfs_size_t oldlen; + const void *newdata; + lfs_size_t newlen; +}; + +static int lfs_dir_commit(lfs_t *lfs, lfs_dir_t *dir, + const struct lfs_region *regions, int count) { + // increment revision count + dir->d.rev += 1; + + // keep pairs in order such that pair[0] is most recent + lfs_pairswap(dir->pair); + for (int i = 0; i < count; i++) { + dir->d.size += regions[i].newlen - regions[i].oldlen; + } + + const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; + bool relocated = false; + + while (true) { + if (true) { + int err = lfs_bd_erase(lfs, dir->pair[0]); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + uint32_t crc = 0xffffffff; + lfs_dir_tole32(&dir->d); + lfs_crc(&crc, &dir->d, sizeof(dir->d)); + err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); + lfs_dir_fromle32(&dir->d); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + int i = 0; + lfs_off_t oldoff = sizeof(dir->d); + lfs_off_t newoff = sizeof(dir->d); + while (newoff < (0x7fffffff & dir->d.size)-4) { + if (i < count && regions[i].oldoff == oldoff) { + lfs_crc(&crc, regions[i].newdata, regions[i].newlen); + err = lfs_bd_prog(lfs, dir->pair[0], + newoff, regions[i].newdata, regions[i].newlen); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + oldoff += regions[i].oldlen; + newoff += regions[i].newlen; + i += 1; + } else { + uint8_t data; + err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); + if (err) { + return err; + } + + lfs_crc(&crc, &data, 1); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + oldoff += 1; + newoff += 1; + } + } + + crc = lfs_tole32(crc); + err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); + crc = lfs_fromle32(crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + err = lfs_bd_sync(lfs); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // successful commit, check checksum to make sure + uint32_t ncrc = 0xffffffff; + err = lfs_bd_crc(lfs, dir->pair[0], 0, + (0x7fffffff & dir->d.size)-4, &ncrc); + if (err) { + return err; + } + + if (ncrc != crc) { + goto relocate; + } + } + + break; +relocate: + //commit was corrupted + LFS_DEBUG("Bad block at %" PRIu32, dir->pair[0]); + + // drop caches and prepare to relocate block + relocated = true; + lfs_cache_drop(lfs, &lfs->pcache); + + // can't relocate superblock, filesystem is now frozen + if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) { + LFS_WARN("Superblock %" PRIu32 " has become unwritable", + oldpair[0]); + return LFS_ERR_CORRUPT; + } + + // relocate half of pair + int err = lfs_alloc(lfs, &dir->pair[0]); + if (err) { + return err; + } + } + + if (relocated) { + // update references if we relocated + LFS_DEBUG("Relocating %" PRIu32 " %" PRIu32 " to %" PRIu32 " %" PRIu32, + oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); + int err = lfs_relocate(lfs, oldpair, dir->pair); + if (err) { + return err; + } + } + + // shift over any directories that are affected + for (lfs_dir_t *d = lfs->dirs; d; d = d->next) { + if (lfs_paircmp(d->pair, dir->pair) == 0) { + d->pair[0] = dir->pair[0]; + d->pair[1] = dir->pair[1]; + } + } + + return 0; +} + +static int lfs_dir_update(lfs_t *lfs, lfs_dir_t *dir, + lfs_entry_t *entry, const void *data) { + lfs_entry_tole32(&entry->d); + int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d)}, + {entry->off+sizeof(entry->d), entry->d.nlen, data, entry->d.nlen} + }, data ? 2 : 1); + lfs_entry_fromle32(&entry->d); + return err; +} + +static int lfs_dir_append(lfs_t *lfs, lfs_dir_t *dir, + lfs_entry_t *entry, const void *data) { + // check if we fit, if top bit is set we do not and move on + while (true) { + if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) { + entry->off = dir->d.size - 4; + + lfs_entry_tole32(&entry->d); + int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, 0, &entry->d, sizeof(entry->d)}, + {entry->off, 0, data, entry->d.nlen} + }, 2); + lfs_entry_fromle32(&entry->d); + return err; + } + + // we need to allocate a new dir block + if (!(0x80000000 & dir->d.size)) { + lfs_dir_t olddir = *dir; + int err = lfs_dir_alloc(lfs, dir); + if (err) { + return err; + } + + dir->d.tail[0] = olddir.d.tail[0]; + dir->d.tail[1] = olddir.d.tail[1]; + entry->off = dir->d.size - 4; + lfs_entry_tole32(&entry->d); + err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, 0, &entry->d, sizeof(entry->d)}, + {entry->off, 0, data, entry->d.nlen} + }, 2); + lfs_entry_fromle32(&entry->d); + if (err) { + return err; + } + + olddir.d.size |= 0x80000000; + olddir.d.tail[0] = dir->pair[0]; + olddir.d.tail[1] = dir->pair[1]; + return lfs_dir_commit(lfs, &olddir, NULL, 0); + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + } +} + +static int lfs_dir_remove(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { + // check if we should just drop the directory block + if ((dir->d.size & 0x7fffffff) == sizeof(dir->d)+4 + + lfs_entry_size(entry)) { + lfs_dir_t pdir; + int res = lfs_pred(lfs, dir->pair, &pdir); + if (res < 0) { + return res; + } + + if (pdir.d.size & 0x80000000) { + pdir.d.size &= dir->d.size | 0x7fffffff; + pdir.d.tail[0] = dir->d.tail[0]; + pdir.d.tail[1] = dir->d.tail[1]; + return lfs_dir_commit(lfs, &pdir, NULL, 0); + } + } + + // shift out the entry + int err = lfs_dir_commit(lfs, dir, (struct lfs_region[]){ + {entry->off, lfs_entry_size(entry), NULL, 0}, + }, 1); + if (err) { + return err; + } + + // shift over any files/directories that are affected + for (lfs_file_t *f = lfs->files; f; f = f->next) { + if (lfs_paircmp(f->pair, dir->pair) == 0) { + if (f->poff == entry->off) { + f->pair[0] = 0xffffffff; + f->pair[1] = 0xffffffff; + } else if (f->poff > entry->off) { + f->poff -= lfs_entry_size(entry); + } + } + } + + for (lfs_dir_t *d = lfs->dirs; d; d = d->next) { + if (lfs_paircmp(d->pair, dir->pair) == 0) { + if (d->off > entry->off) { + d->off -= lfs_entry_size(entry); + d->pos -= lfs_entry_size(entry); + } + } + } + + return 0; +} + +static int lfs_dir_next(lfs_t *lfs, lfs_dir_t *dir, lfs_entry_t *entry) { + while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) { + if (!(0x80000000 & dir->d.size)) { + entry->off = dir->off; + return LFS_ERR_NOENT; + } + + int err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + + dir->off = sizeof(dir->d); + dir->pos += sizeof(dir->d) + 4; + } + + int err = lfs_bd_read(lfs, dir->pair[0], dir->off, + &entry->d, sizeof(entry->d)); + lfs_entry_fromle32(&entry->d); + if (err) { + return err; + } + + entry->off = dir->off; + dir->off += lfs_entry_size(entry); + dir->pos += lfs_entry_size(entry); + return 0; +} + +static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir, + lfs_entry_t *entry, const char **path) { + const char *pathname = *path; + size_t pathlen; + entry->d.type = LFS_TYPE_DIR; + entry->d.elen = sizeof(entry->d) - 4; + entry->d.alen = 0; + entry->d.nlen = 0; + entry->d.u.dir[0] = lfs->root[0]; + entry->d.u.dir[1] = lfs->root[1]; + + while (true) { +nextname: + // skip slashes + pathname += strspn(pathname, "/"); + pathlen = strcspn(pathname, "/"); + + // skip '.' and root '..' + if ((pathlen == 1 && memcmp(pathname, ".", 1) == 0) || + (pathlen == 2 && memcmp(pathname, "..", 2) == 0)) { + pathname += pathlen; + goto nextname; + } + + // skip if matched by '..' in name + const char *suffix = pathname + pathlen; + size_t sufflen; + int depth = 1; + while (true) { + suffix += strspn(suffix, "/"); + sufflen = strcspn(suffix, "/"); + if (sufflen == 0) { + break; + } + + if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { + depth -= 1; + if (depth == 0) { + pathname = suffix + sufflen; + goto nextname; + } + } else { + depth += 1; + } + + suffix += sufflen; + } + + // found path + if (pathname[0] == '\0') { + return 0; + } + + // update what we've found + *path = pathname; + + // continue on if we hit a directory + if (entry->d.type != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + int err = lfs_dir_fetch(lfs, dir, entry->d.u.dir); + if (err) { + return err; + } + + // find entry matching name + while (true) { + err = lfs_dir_next(lfs, dir, entry); + if (err) { + return err; + } + + if (((0x7f & entry->d.type) != LFS_TYPE_REG && + (0x7f & entry->d.type) != LFS_TYPE_DIR) || + entry->d.nlen != pathlen) { + continue; + } + + int res = lfs_bd_cmp(lfs, dir->pair[0], + entry->off + 4+entry->d.elen+entry->d.alen, + pathname, pathlen); + if (res < 0) { + return res; + } + + // found match + if (res) { + break; + } + } + + // check that entry has not been moved + if (entry->d.type & 0x80) { + int moved = lfs_moved(lfs, &entry->d.u); + if (moved < 0 || moved) { + return (moved < 0) ? moved : LFS_ERR_NOENT; + } + + entry->d.type &= ~0x80; + } + + // to next name + pathname += pathlen; + } +} + + +/// Top level directory operations /// +int lfs_mkdir(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + if (!lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + // fetch parent directory + lfs_dir_t cwd; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) { + return err ? err : LFS_ERR_EXIST; + } + + // build up new directory + lfs_alloc_ack(lfs); + + lfs_dir_t dir; + err = lfs_dir_alloc(lfs, &dir); + if (err) { + return err; + } + dir.d.tail[0] = cwd.d.tail[0]; + dir.d.tail[1] = cwd.d.tail[1]; + + err = lfs_dir_commit(lfs, &dir, NULL, 0); + if (err) { + return err; + } + + entry.d.type = LFS_TYPE_DIR; + entry.d.elen = sizeof(entry.d) - 4; + entry.d.alen = 0; + entry.d.nlen = strlen(path); + entry.d.u.dir[0] = dir.pair[0]; + entry.d.u.dir[1] = dir.pair[1]; + + cwd.d.tail[0] = dir.pair[0]; + cwd.d.tail[1] = dir.pair[1]; + + err = lfs_dir_append(lfs, &cwd, &entry, path); + if (err) { + return err; + } + + lfs_alloc_ack(lfs); + return 0; +} + +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + dir->pair[0] = lfs->root[0]; + dir->pair[1] = lfs->root[1]; + + lfs_entry_t entry; + int err = lfs_dir_find(lfs, dir, &entry, &path); + if (err) { + return err; + } else if (entry.d.type != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + err = lfs_dir_fetch(lfs, dir, entry.d.u.dir); + if (err) { + return err; + } + + // setup head dir + // special offset for '.' and '..' + dir->head[0] = dir->pair[0]; + dir->head[1] = dir->pair[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); + + // add to list of directories + dir->next = lfs->dirs; + lfs->dirs = dir; + + return 0; +} + +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { + // remove from list of directories + for (lfs_dir_t **p = &lfs->dirs; *p; p = &(*p)->next) { + if (*p == dir) { + *p = dir->next; + break; + } + } + + return 0; +} + +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { + memset(info, 0, sizeof(*info)); + + // special offset for '.' and '..' + if (dir->pos == sizeof(dir->d) - 2) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, "."); + dir->pos += 1; + return 1; + } else if (dir->pos == sizeof(dir->d) - 1) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, ".."); + dir->pos += 1; + return 1; + } + + lfs_entry_t entry; + while (true) { + int err = lfs_dir_next(lfs, dir, &entry); + if (err) { + return (err == LFS_ERR_NOENT) ? 0 : err; + } + + if ((0x7f & entry.d.type) != LFS_TYPE_REG && + (0x7f & entry.d.type) != LFS_TYPE_DIR) { + continue; + } + + // check that entry has not been moved + if (entry.d.type & 0x80) { + int moved = lfs_moved(lfs, &entry.d.u); + if (moved < 0) { + return moved; + } + + if (moved) { + continue; + } + + entry.d.type &= ~0x80; + } + + break; + } + + info->type = entry.d.type; + if (info->type == LFS_TYPE_REG) { + info->size = entry.d.u.file.size; + } + + int err = lfs_bd_read(lfs, dir->pair[0], + entry.off + 4+entry.d.elen+entry.d.alen, + info->name, entry.d.nlen); + if (err) { + return err; + } + + return 1; +} + +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + // simply walk from head dir + int err = lfs_dir_rewind(lfs, dir); + if (err) { + return err; + } + dir->pos = off; + + while (off > (0x7fffffff & dir->d.size)) { + off -= 0x7fffffff & dir->d.size; + if (!(0x80000000 & dir->d.size)) { + return LFS_ERR_INVAL; + } + + err = lfs_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + } + + dir->off = off; + return 0; +} + +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { + (void)lfs; + return dir->pos; +} + +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { + // reload the head dir + int err = lfs_dir_fetch(lfs, dir, dir->head); + if (err) { + return err; + } + + dir->pair[0] = dir->head[0]; + dir->pair[1] = dir->head[1]; + dir->pos = sizeof(dir->d) - 2; + dir->off = sizeof(dir->d); + return 0; +} + + +/// File index list operations /// +static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) { + lfs_off_t size = *off; + lfs_off_t b = lfs->cfg->block_size - 2*4; + lfs_off_t i = size / b; + if (i == 0) { + return 0; + } + + i = (size - 4*(lfs_popc(i-1)+2)) / b; + *off = size - b*i - 4*lfs_popc(i); + return i; +} + +static int lfs_ctz_find(lfs_t *lfs, + lfs_cache_t *rcache, const lfs_cache_t *pcache, + lfs_block_t head, lfs_size_t size, + lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) { + if (size == 0) { + *block = 0xffffffff; + *off = 0; + return 0; + } + + lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + lfs_off_t target = lfs_ctz_index(lfs, &pos); + + while (current > target) { + lfs_size_t skip = lfs_min( + lfs_npw2(current-target+1) - 1, + lfs_ctz(current)); + + int err = lfs_cache_read(lfs, rcache, pcache, head, 4*skip, &head, 4); + head = lfs_fromle32(head); + if (err) { + return err; + } + + LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); + current -= 1 << skip; + } + + *block = head; + *off = pos; + return 0; +} + +static int lfs_ctz_extend(lfs_t *lfs, + lfs_cache_t *rcache, lfs_cache_t *pcache, + lfs_block_t head, lfs_size_t size, + lfs_block_t *block, lfs_off_t *off) { + while (true) { + // go ahead and grab a block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count); + + if (true) { + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (size == 0) { + *block = nblock; + *off = 0; + return 0; + } + + size -= 1; + lfs_off_t index = lfs_ctz_index(lfs, &size); + size += 1; + + // just copy out the last block if it is incomplete + if (size != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t data; + err = lfs_cache_read(lfs, rcache, NULL, + head, i, &data, 1); + if (err) { + return err; + } + + err = lfs_cache_prog(lfs, pcache, rcache, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + *block = nblock; + *off = size; + return 0; + } + + // append block + index += 1; + lfs_size_t skips = lfs_ctz(index) + 1; + + for (lfs_off_t i = 0; i < skips; i++) { + head = lfs_tole32(head); + err = lfs_cache_prog(lfs, pcache, rcache, + nblock, 4*i, &head, 4); + head = lfs_fromle32(head); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (i != skips-1) { + err = lfs_cache_read(lfs, rcache, NULL, + head, 4*i, &head, 4); + head = lfs_fromle32(head); + if (err) { + return err; + } + } + + LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); + } + + *block = nblock; + *off = 4*skips; + return 0; + } + +relocate: + LFS_DEBUG("Bad block at %" PRIu32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, &lfs->pcache); + } +} + +static int lfs_ctz_traverse(lfs_t *lfs, + lfs_cache_t *rcache, const lfs_cache_t *pcache, + lfs_block_t head, lfs_size_t size, + int (*cb)(void*, lfs_block_t), void *data) { + if (size == 0) { + return 0; + } + + lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + + while (true) { + int err = cb(data, head); + if (err) { + return err; + } + + if (index == 0) { + return 0; + } + + lfs_block_t heads[2]; + int count = 2 - (index & 1); + err = lfs_cache_read(lfs, rcache, pcache, head, 0, &heads, count*4); + heads[0] = lfs_fromle32(heads[0]); + heads[1] = lfs_fromle32(heads[1]); + if (err) { + return err; + } + + for (int i = 0; i < count-1; i++) { + err = cb(data, heads[i]); + if (err) { + return err; + } + } + + head = heads[count-1]; + index -= count; + } +} + + +/// Top level file operations /// +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *cfg) { + // deorphan if we haven't yet, needed at most once after poweron + if ((flags & 3) != LFS_O_RDONLY && !lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + // allocate entry for file if it doesn't exist + lfs_dir_t cwd; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) { + return err; + } + + if (err == LFS_ERR_NOENT) { + if (!(flags & LFS_O_CREAT)) { + return LFS_ERR_NOENT; + } + + // create entry to remember name + entry.d.type = LFS_TYPE_REG; + entry.d.elen = sizeof(entry.d) - 4; + entry.d.alen = 0; + entry.d.nlen = strlen(path); + entry.d.u.file.head = 0xffffffff; + entry.d.u.file.size = 0; + err = lfs_dir_append(lfs, &cwd, &entry, path); + if (err) { + return err; + } + } else if (entry.d.type == LFS_TYPE_DIR) { + return LFS_ERR_ISDIR; + } else if (flags & LFS_O_EXCL) { + return LFS_ERR_EXIST; + } + + // setup file struct + file->cfg = cfg; + file->pair[0] = cwd.pair[0]; + file->pair[1] = cwd.pair[1]; + file->poff = entry.off; + file->head = entry.d.u.file.head; + file->size = entry.d.u.file.size; + file->flags = flags; + file->pos = 0; + + if (flags & LFS_O_TRUNC) { + if (file->size != 0) { + file->flags |= LFS_F_DIRTY; + } + file->head = 0xffffffff; + file->size = 0; + } + + // allocate buffer if needed + file->cache.block = 0xffffffff; + if (file->cfg && file->cfg->buffer) { + file->cache.buffer = file->cfg->buffer; + } else if (lfs->cfg->file_buffer) { + if (lfs->files) { + // already in use + return LFS_ERR_NOMEM; + } + file->cache.buffer = lfs->cfg->file_buffer; + } else if ((file->flags & 3) == LFS_O_RDONLY) { + file->cache.buffer = lfs_malloc(lfs->cfg->read_size); + if (!file->cache.buffer) { + return LFS_ERR_NOMEM; + } + } else { + file->cache.buffer = lfs_malloc(lfs->cfg->prog_size); + if (!file->cache.buffer) { + return LFS_ERR_NOMEM; + } + } + + // zero to avoid information leak + lfs_cache_zero(lfs, &file->cache); + + // add to list of files + file->next = lfs->files; + lfs->files = file; + + return 0; +} + +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags) { + return lfs_file_opencfg(lfs, file, path, flags, NULL); +} + +int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { + int err = lfs_file_sync(lfs, file); + + // remove from list of files + for (lfs_file_t **p = &lfs->files; *p; p = &(*p)->next) { + if (*p == file) { + *p = file->next; + break; + } + } + + // clean up memory + if (!(file->cfg && file->cfg->buffer) && !lfs->cfg->file_buffer) { + lfs_free(file->cache.buffer); + } + + return err; +} + +static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { +relocate: + LFS_DEBUG("Bad block at %" PRIu32, file->block); + + // just relocate what exists into new block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // either read from dirty cache or disk + for (lfs_off_t i = 0; i < file->off; i++) { + uint8_t data; + err = lfs_cache_read(lfs, &lfs->rcache, &file->cache, + file->block, i, &data, 1); + if (err) { + return err; + } + + err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // copy over new state of file + memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size); + file->cache.block = lfs->pcache.block; + file->cache.off = lfs->pcache.off; + lfs_cache_zero(lfs, &lfs->pcache); + + file->block = nblock; + return 0; +} + +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { + if (file->flags & LFS_F_READING) { + // just drop read cache + lfs_cache_drop(lfs, &file->cache); + file->flags &= ~LFS_F_READING; + } + + if (file->flags & LFS_F_WRITING) { + lfs_off_t pos = file->pos; + + // copy over anything after current branch + lfs_file_t orig = { + .head = file->head, + .size = file->size, + .flags = LFS_O_RDONLY, + .pos = file->pos, + .cache = lfs->rcache, + }; + lfs_cache_drop(lfs, &lfs->rcache); + + while (file->pos < file->size) { + // copy over a byte at a time, leave it up to caching + // to make this efficient + uint8_t data; + lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1); + if (res < 0) { + return res; + } + + res = lfs_file_write(lfs, file, &data, 1); + if (res < 0) { + return res; + } + + // keep our reference to the rcache in sync + if (lfs->rcache.block != 0xffffffff) { + lfs_cache_drop(lfs, &orig.cache); + lfs_cache_drop(lfs, &lfs->rcache); + } + } + + // write out what we have + while (true) { + int err = lfs_cache_flush(lfs, &file->cache, &lfs->rcache); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + } + + // actual file updates + file->head = file->block; + file->size = file->pos; + file->flags &= ~LFS_F_WRITING; + file->flags |= LFS_F_DIRTY; + + file->pos = pos; + } + + return 0; +} + +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + if ((file->flags & LFS_F_DIRTY) && + !(file->flags & LFS_F_ERRED) && + !lfs_pairisnull(file->pair)) { + // update dir entry + lfs_dir_t cwd; + err = lfs_dir_fetch(lfs, &cwd, file->pair); + if (err) { + return err; + } + + lfs_entry_t entry = {.off = file->poff}; + err = lfs_bd_read(lfs, cwd.pair[0], entry.off, + &entry.d, sizeof(entry.d)); + lfs_entry_fromle32(&entry.d); + if (err) { + return err; + } + + LFS_ASSERT(entry.d.type == LFS_TYPE_REG); + entry.d.u.file.head = file->head; + entry.d.u.file.size = file->size; + + err = lfs_dir_update(lfs, &cwd, &entry, NULL); + if (err) { + return err; + } + + file->flags &= ~LFS_F_DIRTY; + } + + return 0; +} + +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + lfs_size_t nsize = size; + + if ((file->flags & 3) == LFS_O_WRONLY) { + return LFS_ERR_BADF; + } + + if (file->flags & LFS_F_WRITING) { + // flush out any writes + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } + + if (file->pos >= file->size) { + // eof if past end + return 0; + } + + size = lfs_min(size, file->size - file->pos); + nsize = size; + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_READING) || + file->off == lfs->cfg->block_size) { + int err = lfs_ctz_find(lfs, &file->cache, NULL, + file->head, file->size, + file->pos, &file->block, &file->off); + if (err) { + return err; + } + + file->flags |= LFS_F_READING; + } + + // read as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + int err = lfs_cache_read(lfs, &file->cache, NULL, + file->block, file->off, data, diff); + if (err) { + return err; + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + } + + return size; +} + +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + lfs_size_t nsize = size; + + if ((file->flags & 3) == LFS_O_RDONLY) { + return LFS_ERR_BADF; + } + + if (file->flags & LFS_F_READING) { + // drop any reads + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + } + + if ((file->flags & LFS_O_APPEND) && file->pos < file->size) { + file->pos = file->size; + } + + if (!(file->flags & LFS_F_WRITING) && file->pos > file->size) { + // fill with zeros + lfs_off_t pos = file->pos; + file->pos = file->size; + + while (file->pos < pos) { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return res; + } + } + } + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_WRITING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { + // find out which block we're extending from + int err = lfs_ctz_find(lfs, &file->cache, NULL, + file->head, file->size, + file->pos-1, &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + // mark cache as dirty since we may have read data into it + lfs_cache_zero(lfs, &file->cache); + } + + // extend file with new blocks + lfs_alloc_ack(lfs); + int err = lfs_ctz_extend(lfs, &lfs->rcache, &file->cache, + file->block, file->pos, + &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + + file->flags |= LFS_F_WRITING; + } + + // program as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + while (true) { + int err = lfs_cache_prog(lfs, &file->cache, &lfs->rcache, + file->block, file->off, data, diff); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + file->flags |= LFS_F_ERRED; + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + + lfs_alloc_ack(lfs); + } + + file->flags &= ~LFS_F_ERRED; + return size; +} + +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + // write out everything beforehand, may be noop if rdonly + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // update pos + if (whence == LFS_SEEK_SET) { + file->pos = off; + } else if (whence == LFS_SEEK_CUR) { + if (off < 0 && (lfs_off_t)-off > file->pos) { + return LFS_ERR_INVAL; + } + + file->pos = file->pos + off; + } else if (whence == LFS_SEEK_END) { + if (off < 0 && (lfs_off_t)-off > file->size) { + return LFS_ERR_INVAL; + } + + file->pos = file->size + off; + } + + return file->pos; +} + +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + if ((file->flags & 3) == LFS_O_RDONLY) { + return LFS_ERR_BADF; + } + + lfs_off_t oldsize = lfs_file_size(lfs, file); + if (size < oldsize) { + // need to flush since directly changing metadata + int err = lfs_file_flush(lfs, file); + if (err) { + return err; + } + + // lookup new head in ctz skip list + err = lfs_ctz_find(lfs, &file->cache, NULL, + file->head, file->size, + size, &file->head, &(lfs_off_t){0}); + if (err) { + return err; + } + + file->size = size; + file->flags |= LFS_F_DIRTY; + } else if (size > oldsize) { + lfs_off_t pos = file->pos; + + // flush+seek if not already at end + if (file->pos != oldsize) { + int err = lfs_file_seek(lfs, file, 0, LFS_SEEK_END); + if (err < 0) { + return err; + } + } + + // fill with zeros + while (file->pos < size) { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + return res; + } + } + + // restore pos + int err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET); + if (err < 0) { + return err; + } + } + + return 0; +} + +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { + (void)lfs; + return file->pos; +} + +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { + lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + return res; + } + + return 0; +} + +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { + (void)lfs; + if (file->flags & LFS_F_WRITING) { + return lfs_max(file->pos, file->size); + } else { + return file->size; + } +} + + +/// General fs operations /// +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { + lfs_dir_t cwd; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err) { + return err; + } + + memset(info, 0, sizeof(*info)); + info->type = entry.d.type; + if (info->type == LFS_TYPE_REG) { + info->size = entry.d.u.file.size; + } + + if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) { + strcpy(info->name, "/"); + } else { + err = lfs_bd_read(lfs, cwd.pair[0], + entry.off + 4+entry.d.elen+entry.d.alen, + info->name, entry.d.nlen); + if (err) { + return err; + } + } + + return 0; +} + +int lfs_remove(lfs_t *lfs, const char *path) { + // deorphan if we haven't yet, needed at most once after poweron + if (!lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + lfs_dir_t cwd; + lfs_entry_t entry; + int err = lfs_dir_find(lfs, &cwd, &entry, &path); + if (err) { + return err; + } + + lfs_dir_t dir; + if (entry.d.type == LFS_TYPE_DIR) { + // must be empty before removal, checking size + // without masking top bit checks for any case where + // dir is not empty + err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir); + if (err) { + return err; + } else if (dir.d.size != sizeof(dir.d)+4) { + return LFS_ERR_NOTEMPTY; + } + } + + // remove the entry + err = lfs_dir_remove(lfs, &cwd, &entry); + if (err) { + return err; + } + + // if we were a directory, find pred, replace tail + if (entry.d.type == LFS_TYPE_DIR) { + int res = lfs_pred(lfs, dir.pair, &cwd); + if (res < 0) { + return res; + } + + LFS_ASSERT(res); // must have pred + cwd.d.tail[0] = dir.d.tail[0]; + cwd.d.tail[1] = dir.d.tail[1]; + + err = lfs_dir_commit(lfs, &cwd, NULL, 0); + if (err) { + return err; + } + } + + return 0; +} + +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { + // deorphan if we haven't yet, needed at most once after poweron + if (!lfs->deorphaned) { + int err = lfs_deorphan(lfs); + if (err) { + return err; + } + } + + // find old entry + lfs_dir_t oldcwd; + lfs_entry_t oldentry; + int err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath); + if (err) { + return err; + } + + // allocate new entry + lfs_dir_t newcwd; + lfs_entry_t preventry; + err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath); + if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) { + return err; + } + + bool prevexists = (err != LFS_ERR_NOENT); + bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0); + + // must have same type + if (prevexists && preventry.d.type != oldentry.d.type) { + return LFS_ERR_ISDIR; + } + + lfs_dir_t dir; + if (prevexists && preventry.d.type == LFS_TYPE_DIR) { + // must be empty before removal, checking size + // without masking top bit checks for any case where + // dir is not empty + err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir); + if (err) { + return err; + } else if (dir.d.size != sizeof(dir.d)+4) { + return LFS_ERR_NOTEMPTY; + } + } + + // mark as moving + oldentry.d.type |= 0x80; + err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL); + if (err) { + return err; + } + + // update pair if newcwd == oldcwd + if (samepair) { + newcwd = oldcwd; + } + + // move to new location + lfs_entry_t newentry = preventry; + newentry.d = oldentry.d; + newentry.d.type &= ~0x80; + newentry.d.nlen = strlen(newpath); + + if (prevexists) { + err = lfs_dir_update(lfs, &newcwd, &newentry, newpath); + if (err) { + return err; + } + } else { + err = lfs_dir_append(lfs, &newcwd, &newentry, newpath); + if (err) { + return err; + } + } + + // update pair if newcwd == oldcwd + if (samepair) { + oldcwd = newcwd; + } + + // remove old entry + err = lfs_dir_remove(lfs, &oldcwd, &oldentry); + if (err) { + return err; + } + + // if we were a directory, find pred, replace tail + if (prevexists && preventry.d.type == LFS_TYPE_DIR) { + int res = lfs_pred(lfs, dir.pair, &newcwd); + if (res < 0) { + return res; + } + + LFS_ASSERT(res); // must have pred + newcwd.d.tail[0] = dir.d.tail[0]; + newcwd.d.tail[1] = dir.d.tail[1]; + + err = lfs_dir_commit(lfs, &newcwd, NULL, 0); + if (err) { + return err; + } + } + + return 0; +} + + +/// Filesystem operations /// +static void lfs_deinit(lfs_t *lfs) { + // free allocated memory + if (!lfs->cfg->read_buffer) { + lfs_free(lfs->rcache.buffer); + } + + if (!lfs->cfg->prog_buffer) { + lfs_free(lfs->pcache.buffer); + } + + if (!lfs->cfg->lookahead_buffer) { + lfs_free(lfs->free.buffer); + } +} + +static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { + lfs->cfg = cfg; + + // setup read cache + if (lfs->cfg->read_buffer) { + lfs->rcache.buffer = lfs->cfg->read_buffer; + } else { + lfs->rcache.buffer = lfs_malloc(lfs->cfg->read_size); + if (!lfs->rcache.buffer) { + goto cleanup; + } + } + + // setup program cache + if (lfs->cfg->prog_buffer) { + lfs->pcache.buffer = lfs->cfg->prog_buffer; + } else { + lfs->pcache.buffer = lfs_malloc(lfs->cfg->prog_size); + if (!lfs->pcache.buffer) { + goto cleanup; + } + } + + // zero to avoid information leaks + lfs_cache_zero(lfs, &lfs->rcache); + lfs_cache_zero(lfs, &lfs->pcache); + + // setup lookahead, round down to nearest 32-bits + LFS_ASSERT(lfs->cfg->lookahead % 32 == 0); + LFS_ASSERT(lfs->cfg->lookahead > 0); + if (lfs->cfg->lookahead_buffer) { + lfs->free.buffer = lfs->cfg->lookahead_buffer; + } else { + lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead/8); + if (!lfs->free.buffer) { + goto cleanup; + } + } + + // check that program and read sizes are multiples of the block size + LFS_ASSERT(lfs->cfg->prog_size % lfs->cfg->read_size == 0); + LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->prog_size == 0); + + // check that the block size is large enough to fit ctz pointers + LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) + <= lfs->cfg->block_size); + + // setup default state + lfs->root[0] = 0xffffffff; + lfs->root[1] = 0xffffffff; + lfs->files = NULL; + lfs->dirs = NULL; + lfs->deorphaned = false; + + return 0; + +cleanup: + lfs_deinit(lfs); + return LFS_ERR_NOMEM; +} + +int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { + int err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + // create free lookahead + memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8); + lfs->free.off = 0; + lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count); + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + // create superblock dir + lfs_dir_t superdir; + err = lfs_dir_alloc(lfs, &superdir); + if (err) { + goto cleanup; + } + + // write root directory + lfs_dir_t root; + err = lfs_dir_alloc(lfs, &root); + if (err) { + goto cleanup; + } + + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + goto cleanup; + } + + lfs->root[0] = root.pair[0]; + lfs->root[1] = root.pair[1]; + + // write superblocks + lfs_superblock_t superblock = { + .off = sizeof(superdir.d), + .d.type = LFS_TYPE_SUPERBLOCK, + .d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4, + .d.nlen = sizeof(superblock.d.magic), + .d.version = LFS_DISK_VERSION, + .d.magic = {"littlefs"}, + .d.block_size = lfs->cfg->block_size, + .d.block_count = lfs->cfg->block_count, + .d.root = {lfs->root[0], lfs->root[1]}, + }; + superdir.d.tail[0] = root.pair[0]; + superdir.d.tail[1] = root.pair[1]; + superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4; + + // write both pairs to be safe + lfs_superblock_tole32(&superblock.d); + bool valid = false; + for (int i = 0; i < 2; i++) { + err = lfs_dir_commit(lfs, &superdir, (struct lfs_region[]){ + {sizeof(superdir.d), sizeof(superblock.d), + &superblock.d, sizeof(superblock.d)} + }, 1); + if (err && err != LFS_ERR_CORRUPT) { + goto cleanup; + } + + valid = valid || !err; + } + + if (!valid) { + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + lfs_alloc_ack(lfs); + +cleanup: + lfs_deinit(lfs); + return err; +} + +int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { + int err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + // setup free lookahead + lfs->free.off = 0; + lfs->free.size = 0; + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + // load superblock + lfs_dir_t dir; + lfs_superblock_t superblock; + err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (err && err != LFS_ERR_CORRUPT) { + goto cleanup; + } + + if (!err) { + err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), + &superblock.d, sizeof(superblock.d)); + lfs_superblock_fromle32(&superblock.d); + if (err) { + goto cleanup; + } + + lfs->root[0] = superblock.d.root[0]; + lfs->root[1] = superblock.d.root[1]; + } + + if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { + LFS_ERROR("Invalid superblock at %d %d", 0, 1); + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + uint16_t major_version = (0xffff & (superblock.d.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); + if ((major_version != LFS_DISK_VERSION_MAJOR || + minor_version > LFS_DISK_VERSION_MINOR)) { + LFS_ERROR("Invalid version %d.%d", major_version, minor_version); + err = LFS_ERR_INVAL; + goto cleanup; + } + + return 0; + +cleanup: + + lfs_deinit(lfs); + return err; +} + +int lfs_umount(lfs_t *lfs) { + lfs_deinit(lfs); + return 0; +} + + +/// Littlefs specific operations /// +int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + // iterate over metadata pairs + lfs_dir_t dir; + lfs_entry_t entry; + lfs_block_t cwd[2] = {0, 1}; + + while (true) { + for (int i = 0; i < 2; i++) { + int err = cb(data, cwd[i]); + if (err) { + return err; + } + } + + int err = lfs_dir_fetch(lfs, &dir, cwd); + if (err) { + return err; + } + + // iterate over contents + while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { + err = lfs_bd_read(lfs, dir.pair[0], dir.off, + &entry.d, sizeof(entry.d)); + lfs_entry_fromle32(&entry.d); + if (err) { + return err; + } + + dir.off += lfs_entry_size(&entry); + if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) { + err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL, + entry.d.u.file.head, entry.d.u.file.size, cb, data); + if (err) { + return err; + } + } + } + + cwd[0] = dir.d.tail[0]; + cwd[1] = dir.d.tail[1]; + + if (lfs_pairisnull(cwd)) { + break; + } + } + + // iterate over any open files + for (lfs_file_t *f = lfs->files; f; f = f->next) { + if (f->flags & LFS_F_DIRTY) { + int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, + f->head, f->size, cb, data); + if (err) { + return err; + } + } + + if (f->flags & LFS_F_WRITING) { + int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, + f->block, f->pos, cb, data); + if (err) { + return err; + } + } + } + + return 0; +} + +static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + // iterate over all directory directory entries + int err = lfs_dir_fetch(lfs, pdir, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + while (!lfs_pairisnull(pdir->d.tail)) { + if (lfs_paircmp(pdir->d.tail, dir) == 0) { + return true; + } + + err = lfs_dir_fetch(lfs, pdir, pdir->d.tail); + if (err) { + return err; + } + } + + return false; +} + +static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2], + lfs_dir_t *parent, lfs_entry_t *entry) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + parent->d.tail[0] = 0; + parent->d.tail[1] = 1; + + // iterate over all directory directory entries + while (!lfs_pairisnull(parent->d.tail)) { + int err = lfs_dir_fetch(lfs, parent, parent->d.tail); + if (err) { + return err; + } + + while (true) { + err = lfs_dir_next(lfs, parent, entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) && + lfs_paircmp(entry->d.u.dir, dir) == 0) { + return true; + } + } + } + + return false; +} + +static int lfs_moved(lfs_t *lfs, const void *e) { + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + // skip superblock + lfs_dir_t cwd; + int err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + // iterate over all directory directory entries + lfs_entry_t entry; + while (!lfs_pairisnull(cwd.d.tail)) { + err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + while (true) { + err = lfs_dir_next(lfs, &cwd, &entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (!(0x80 & entry.d.type) && + memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { + return true; + } + } + } + + return false; +} + +static int lfs_relocate(lfs_t *lfs, + const lfs_block_t oldpair[2], const lfs_block_t newpair[2]) { + // find parent + lfs_dir_t parent; + lfs_entry_t entry; + int res = lfs_parent(lfs, oldpair, &parent, &entry); + if (res < 0) { + return res; + } + + if (res) { + // update disk, this creates a desync + entry.d.u.dir[0] = newpair[0]; + entry.d.u.dir[1] = newpair[1]; + + int err = lfs_dir_update(lfs, &parent, &entry, NULL); + if (err) { + return err; + } + + // update internal root + if (lfs_paircmp(oldpair, lfs->root) == 0) { + LFS_DEBUG("Relocating root %" PRIu32 " %" PRIu32, + newpair[0], newpair[1]); + lfs->root[0] = newpair[0]; + lfs->root[1] = newpair[1]; + } + + // clean up bad block, which should now be a desync + return lfs_deorphan(lfs); + } + + // find pred + res = lfs_pred(lfs, oldpair, &parent); + if (res < 0) { + return res; + } + + if (res) { + // just replace bad pair, no desync can occur + parent.d.tail[0] = newpair[0]; + parent.d.tail[1] = newpair[1]; + + return lfs_dir_commit(lfs, &parent, NULL, 0); + } + + // couldn't find dir, must be new + return 0; +} + +int lfs_deorphan(lfs_t *lfs) { + lfs->deorphaned = true; + + if (lfs_pairisnull(lfs->root)) { + return 0; + } + + lfs_dir_t pdir = {.d.size = 0x80000000}; + lfs_dir_t cwd = {.d.tail[0] = 0, .d.tail[1] = 1}; + + // iterate over all directory directory entries + while (!lfs_pairisnull(cwd.d.tail)) { + int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + // check head blocks for orphans + if (!(0x80000000 & pdir.d.size)) { + // check if we have a parent + lfs_dir_t parent; + lfs_entry_t entry; + int res = lfs_parent(lfs, pdir.d.tail, &parent, &entry); + if (res < 0) { + return res; + } + + if (!res) { + // we are an orphan + LFS_DEBUG("Found orphan %" PRIu32 " %" PRIu32, + pdir.d.tail[0], pdir.d.tail[1]); + + pdir.d.tail[0] = cwd.d.tail[0]; + pdir.d.tail[1] = cwd.d.tail[1]; + + err = lfs_dir_commit(lfs, &pdir, NULL, 0); + if (err) { + return err; + } + + break; + } + + if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) { + // we have desynced + LFS_DEBUG("Found desync %" PRIu32 " %" PRIu32, + entry.d.u.dir[0], entry.d.u.dir[1]); + + pdir.d.tail[0] = entry.d.u.dir[0]; + pdir.d.tail[1] = entry.d.u.dir[1]; + + err = lfs_dir_commit(lfs, &pdir, NULL, 0); + if (err) { + return err; + } + + break; + } + } + + // check entries for moves + lfs_entry_t entry; + while (true) { + err = lfs_dir_next(lfs, &cwd, &entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + // found moved entry + if (entry.d.type & 0x80) { + int moved = lfs_moved(lfs, &entry.d.u); + if (moved < 0) { + return moved; + } + + if (moved) { + LFS_DEBUG("Found move %" PRIu32 " %" PRIu32, + entry.d.u.dir[0], entry.d.u.dir[1]); + err = lfs_dir_remove(lfs, &cwd, &entry); + if (err) { + return err; + } + } else { + LFS_DEBUG("Found partial move %" PRIu32 " %" PRIu32, + entry.d.u.dir[0], entry.d.u.dir[1]); + entry.d.type &= ~0x80; + err = lfs_dir_update(lfs, &cwd, &entry, NULL); + if (err) { + return err; + } + } + } + } + + memcpy(&pdir, &cwd, sizeof(pdir)); + } + + return 0; +} + +static int lfs_statvfs_count(void *p, lfs_block_t b) +{ + *(lfs_size_t *)p += 1; + return 0; +} + +int lfs_info(lfs_t *lfs, uint32_t *total, uint32_t *used) +{ + lfs_size_t in_use = 0; + int err = lfs_traverse(lfs, lfs_statvfs_count, &in_use); + if (err) { + LFS_ERROR("lfs_info got error %d", err); + return err; + } + + if (total) { + *total = lfs->cfg->block_count * lfs->cfg->block_size; + } + + if (used) { + *used = in_use * lfs->cfg->block_size; + } + + return 0; +} diff --git a/libiot/lfs/lfs.h b/libiot/lfs/lfs.h new file mode 100644 index 0000000..edc86d2 --- /dev/null +++ b/libiot/lfs/lfs.h @@ -0,0 +1,495 @@ +/* + * The little filesystem + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_H +#define LFS_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_VERSION 0x00010006 +#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) +#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_DISK_VERSION 0x00010001 +#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) +#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) + + +/// Definitions /// + +// Type definitions +typedef uint32_t lfs_size_t; +typedef uint32_t lfs_off_t; + +typedef int32_t lfs_ssize_t; +typedef int32_t lfs_soff_t; + +typedef uint32_t lfs_block_t; + +// Max name size in bytes +#ifndef LFS_NAME_MAX +#define LFS_NAME_MAX 255 +#endif + +// Possible error codes, these are negative to allow +// valid positive return values +enum lfs_error { + LFS_ERR_OK = 0, // No error + LFS_ERR_IO = -5, // Error during device operation + LFS_ERR_CORRUPT = -52, // Corrupted + LFS_ERR_NOENT = -2, // No directory entry + LFS_ERR_EXIST = -17, // Entry already exists + LFS_ERR_NOTDIR = -20, // Entry is not a dir + LFS_ERR_ISDIR = -21, // Entry is a dir + LFS_ERR_NOTEMPTY = -39, // Dir is not empty + LFS_ERR_BADF = -9, // Bad file number + LFS_ERR_INVAL = -22, // Invalid parameter + LFS_ERR_NOSPC = -28, // No space left on device + LFS_ERR_NOMEM = -12, // No more memory available +}; + +// File types +enum lfs_type { + LFS_TYPE_REG = 0x11, + LFS_TYPE_DIR = 0x22, + LFS_TYPE_SUPERBLOCK = 0x2e, +}; + +// File open flags +enum lfs_open_flags { + // open flags + LFS_O_RDONLY = 1, // Open a file as read only + LFS_O_WRONLY = 2, // Open a file as write only + LFS_O_RDWR = 3, // Open a file as read and write + LFS_O_CREAT = 0x0100, // Create a file if it does not exist + LFS_O_EXCL = 0x0200, // Fail if a file already exists + LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size + LFS_O_APPEND = 0x0800, // Move to end of file on every write + + // internally used flags + LFS_F_DIRTY = 0x10000, // File does not match storage + LFS_F_WRITING = 0x20000, // File has been written since last flush + LFS_F_READING = 0x40000, // File has been read since last flush + LFS_F_ERRED = 0x80000, // An error occured during write +}; + +// File seek flags +enum lfs_whence_flags { + LFS_SEEK_SET = 0, // Seek relative to an absolute position + LFS_SEEK_CUR = 1, // Seek relative to the current file position + LFS_SEEK_END = 2, // Seek relative to the end of the file +}; + + +// Configuration provided during initialization of the littlefs +struct lfs_config { + // Opaque user provided context that can be used to pass + // information to the block device operations + void *context; + + // Read a region in a block. Negative error codes are propogated + // to the user. + int (*read)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + + // Program a region in a block. The block must have previously + // been erased. Negative error codes are propogated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*prog)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + + // Erase a block. A block must be erased before being programmed. + // The state of an erased block is undefined. Negative error codes + // are propogated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*erase)(const struct lfs_config *c, lfs_block_t block); + + // Sync the state of the underlying block device. Negative error codes + // are propogated to the user. + int (*sync)(const struct lfs_config *c); + + // Minimum size of a block read. This determines the size of read buffers. + // This may be larger than the physical read size to improve performance + // by caching more of the block device. + lfs_size_t read_size; + + // Minimum size of a block program. This determines the size of program + // buffers. This may be larger than the physical program size to improve + // performance by caching more of the block device. + // Must be a multiple of the read size. + lfs_size_t prog_size; + + // Size of an erasable block. This does not impact ram consumption and + // may be larger than the physical erase size. However, this should be + // kept small as each file currently takes up an entire block. + // Must be a multiple of the program size. + lfs_size_t block_size; + + // Number of erasable blocks on the device. + lfs_size_t block_count; + + // Number of blocks to lookahead during block allocation. A larger + // lookahead reduces the number of passes required to allocate a block. + // The lookahead buffer requires only 1 bit per block so it can be quite + // large with little ram impact. Should be a multiple of 32. + lfs_size_t lookahead; + + // Optional, statically allocated read buffer. Must be read sized. + void *read_buffer; + + // Optional, statically allocated program buffer. Must be program sized. + void *prog_buffer; + + // Optional, statically allocated lookahead buffer. Must be 1 bit per + // lookahead block. + void *lookahead_buffer; + + // Optional, statically allocated buffer for files. Must be program sized. + // If enabled, only one file may be opened at a time. + void *file_buffer; +}; + +// Optional configuration provided during lfs_file_opencfg +struct lfs_file_config { + // Optional, statically allocated buffer for files. Must be program sized. + // If NULL, malloc will be used by default. + void *buffer; +}; + +// File info structure +struct lfs_info { + // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR + uint8_t type; + + // Size of the file, only valid for REG files + lfs_size_t size; + + // Name of the file stored as a null-terminated string + char name[LFS_NAME_MAX+1]; +}; + + +/// littlefs data structures /// +typedef struct lfs_entry { + lfs_off_t off; + + struct lfs_disk_entry { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + union { + struct { + lfs_block_t head; + lfs_size_t size; + } file; + lfs_block_t dir[2]; + } u; + } d; +} lfs_entry_t; + +typedef struct lfs_cache { + lfs_block_t block; + lfs_off_t off; + uint8_t *buffer; +} lfs_cache_t; + +typedef struct lfs_file { + struct lfs_file *next; + lfs_block_t pair[2]; + lfs_off_t poff; + + lfs_block_t head; + lfs_size_t size; + + const struct lfs_file_config *cfg; + uint32_t flags; + lfs_off_t pos; + lfs_block_t block; + lfs_off_t off; + lfs_cache_t cache; +} lfs_file_t; + +typedef struct lfs_dir { + struct lfs_dir *next; + lfs_block_t pair[2]; + lfs_off_t off; + + lfs_block_t head[2]; + lfs_off_t pos; + + struct lfs_disk_dir { + uint32_t rev; + lfs_size_t size; + lfs_block_t tail[2]; + } d; +} lfs_dir_t; + +typedef struct lfs_superblock { + lfs_off_t off; + + struct lfs_disk_superblock { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + lfs_block_t root[2]; + uint32_t block_size; + uint32_t block_count; + uint32_t version; + char magic[8]; + } d; +} lfs_superblock_t; + +typedef struct lfs_free { + lfs_block_t off; + lfs_block_t size; + lfs_block_t i; + lfs_block_t ack; + uint32_t *buffer; +} lfs_free_t; + +// The littlefs type +typedef struct lfs { + const struct lfs_config *cfg; + + lfs_block_t root[2]; + lfs_file_t *files; + lfs_dir_t *dirs; + + lfs_cache_t rcache; + lfs_cache_t pcache; + + lfs_free_t free; + bool deorphaned; +} lfs_t; + + +/// Filesystem functions /// + +// Format a block device with the littlefs +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_format(lfs_t *lfs, const struct lfs_config *config); + +// Mounts a littlefs +// +// Requires a littlefs object and config struct. Multiple filesystems +// may be mounted simultaneously with multiple littlefs objects. Both +// lfs and config must be allocated while mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_mount(lfs_t *lfs, const struct lfs_config *config); + +// Unmounts a littlefs +// +// Does nothing besides releasing any allocated resources. +// Returns a negative error code on failure. +int lfs_umount(lfs_t *lfs); + +/// General operations /// + +// Removes a file or directory +// +// If removing a directory, the directory must be empty. +// Returns a negative error code on failure. +int lfs_remove(lfs_t *lfs, const char *path); + +// Rename or move a file or directory +// +// If the destination exists, it must match the source in type. +// If the destination is a directory, the directory must be empty. +// +// Returns a negative error code on failure. +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); + +// Find info about a file or directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a negative error code on failure. +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); + + +/// File operations /// + +// Open a file +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// Returns a negative error code on failure. +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags); + +// Open a file with extra configuration +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// The config struct provides additional config options per file as described +// above. The config struct must be allocated while the file is open, and the +// config struct must be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *config); + +// Close a file +// +// Any pending writes are written out to storage as though +// sync had been called and releases any allocated resources. +// +// Returns a negative error code on failure. +int lfs_file_close(lfs_t *lfs, lfs_file_t *file); + +// Synchronize a file on storage +// +// Any pending writes are written out to storage. +// Returns a negative error code on failure. +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); + +// Read data from file +// +// Takes a buffer and size indicating where to store the read data. +// Returns the number of bytes read, or a negative error code on failure. +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); + +// Write data to file +// +// Takes a buffer and size indicating the data to write. The file will not +// actually be updated on the storage until either sync or close is called. +// +// Returns the number of bytes written, or a negative error code on failure. +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); + +// Change the position of the file +// +// The change in position is determined by the offset and whence flag. +// Returns the old position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence); + +// Truncates the size of the file to the specified size +// +// Returns a negative error code on failure. +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); + +// Return the position of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) +// Returns the position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); + +// Change the position of the file to the beginning of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) +// Returns a negative error code on failure. +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); + +// Return the size of the file +// +// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) +// Returns the size of the file, or a negative error code on failure. +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); + + +/// Directory operations /// + +// Create a directory +// +// Returns a negative error code on failure. +int lfs_mkdir(lfs_t *lfs, const char *path); + +// Open a directory +// +// Once open a directory can be used with read to iterate over files. +// Returns a negative error code on failure. +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); + +// Close a directory +// +// Releases any allocated resources. +// Returns a negative error code on failure. +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); + +// Read an entry in the directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a negative error code on failure. +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); + +// Change the position of the directory +// +// The new off must be a value previous returned from tell and specifies +// an absolute offset in the directory seek. +// +// Returns a negative error code on failure. +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); + +// Return the position of the directory +// +// The returned offset is only meant to be consumed by seek and may not make +// sense, but does indicate the current position in the directory iteration. +// +// Returns the position of the directory, or a negative error code on failure. +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); + +// Change the position of the directory to the beginning of the directory +// +// Returns a negative error code on failure. +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); + + +/// Miscellaneous littlefs specific operations /// + +// Traverse through all blocks in use by the filesystem +// +// The provided callback will be called with each block address that is +// currently in use by the filesystem. This can be used to determine which +// blocks are in use or how much of the storage is available. +// +// Returns a negative error code on failure. +int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); + +// Prunes any recoverable errors that may have occured in the filesystem +// +// Not needed to be called by user unless an operation is interrupted +// but the filesystem is still mounted. This is already called on first +// allocation. +// +// Returns a negative error code on failure. +int lfs_deorphan(lfs_t *lfs); + +int lfs_info(lfs_t *lfs, uint32_t *total, uint32_t *used); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/libiot/lfs/lfs_util.c b/libiot/lfs/lfs_util.c new file mode 100644 index 0000000..9ca0756 --- /dev/null +++ b/libiot/lfs/lfs_util.c @@ -0,0 +1,31 @@ +/* + * lfs util functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs_util.h" + +// Only compile if user does not provide custom config +#ifndef LFS_CONFIG + + +// Software CRC implementation with small lookup table +void lfs_crc(uint32_t *restrict crc, const void *buffer, size_t size) { + static const uint32_t rtable[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, + }; + + const uint8_t *data = buffer; + + for (size_t i = 0; i < size; i++) { + *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf]; + *crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf]; + } +} + + +#endif diff --git a/libiot/lfs/lfs_util.h b/libiot/lfs/lfs_util.h new file mode 100644 index 0000000..b2dc237 --- /dev/null +++ b/libiot/lfs/lfs_util.h @@ -0,0 +1,186 @@ +/* + * lfs utility functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_UTIL_H +#define LFS_UTIL_H + +// Users can override lfs_util.h with their own configuration by defining +// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). +// +// If LFS_CONFIG is used, none of the default utils will be emitted and must be +// provided by the config file. To start I would suggest copying lfs_util.h and +// modifying as needed. +#ifdef LFS_CONFIG +#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) +#define LFS_STRINGIZE2(x) #x +#include LFS_STRINGIZE(LFS_CONFIG) +#else + +// System includes +#include +#include +#include + +#ifndef LFS_NO_MALLOC +#include +#endif +#ifndef LFS_NO_ASSERT +#include +#endif +#if !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR) +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Macros, may be replaced by system specific wrappers. Arguments to these +// macros must not have side-effects as the macros can be removed for a smaller +// code footprint + +// Logging functions +#ifndef LFS_NO_DEBUG +#define LFS_DEBUG(fmt, ...) \ + printf("lfs debug:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_DEBUG(fmt, ...) +#endif + +#ifndef LFS_NO_WARN +#define LFS_WARN(fmt, ...) \ + printf("lfs warn:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_WARN(fmt, ...) +#endif + +#ifndef LFS_NO_ERROR +#define LFS_ERROR(fmt, ...) \ + printf("lfs error:%d: " fmt "\n", __LINE__, __VA_ARGS__) +#else +#define LFS_ERROR(fmt, ...) +#endif + +// Runtime assertions +#ifndef LFS_NO_ASSERT +#define LFS_ASSERT(test) assert(test) +#else +#define LFS_ASSERT(test) +#endif + + +// Builtin functions, these may be replaced by more efficient +// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more +// expensive basic C implementation for debugging purposes + +// Min/max functions for unsigned 32-bit numbers +static inline uint32_t lfs_max(uint32_t a, uint32_t b) { + return (a > b) ? a : b; +} + +static inline uint32_t lfs_min(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + +// Find the next smallest power of 2 less than or equal to a +static inline uint32_t lfs_npw2(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return 32 - __builtin_clz(a-1); +#else + uint32_t r = 0; + uint32_t s; + a -= 1; + s = (a > 0xffff) << 4; a >>= s; r |= s; + s = (a > 0xff ) << 3; a >>= s; r |= s; + s = (a > 0xf ) << 2; a >>= s; r |= s; + s = (a > 0x3 ) << 1; a >>= s; r |= s; + return (r | (a >> 1)) + 1; +#endif +} + +// Count the number of trailing binary zeros in a +// lfs_ctz(0) may be undefined +static inline uint32_t lfs_ctz(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) + return __builtin_ctz(a); +#else + return lfs_npw2((a & -a) + 1) - 1; +#endif +} + +// Count the number of binary ones in a +static inline uint32_t lfs_popc(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return __builtin_popcount(a); +#else + a = a - ((a >> 1) & 0x55555555); + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; +#endif +} + +// Find the sequence comparison of a and b, this is the distance +// between a and b ignoring overflow +static inline int lfs_scmp(uint32_t a, uint32_t b) { + return (int)(unsigned)(a - b); +} + +// Convert from 32-bit little-endian to native order +static inline uint32_t lfs_fromle32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return a; +#elif !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return __builtin_bswap32(a); +#else + return (((uint8_t*)&a)[0] << 0) | + (((uint8_t*)&a)[1] << 8) | + (((uint8_t*)&a)[2] << 16) | + (((uint8_t*)&a)[3] << 24); +#endif +} + +// Convert to 32-bit little-endian from native order +static inline uint32_t lfs_tole32(uint32_t a) { + return lfs_fromle32(a); +} + +// Calculate CRC-32 with polynomial = 0x04c11db7 +void lfs_crc(uint32_t *crc, const void *buffer, size_t size); + +// Allocate memory, only used if buffers are not provided to littlefs +static inline void *lfs_malloc(size_t size) { +#ifndef LFS_NO_MALLOC + return malloc(size); +#else + (void)size; + return NULL; +#endif +} + +// Deallocate memory, only used if buffers are not provided to littlefs +static inline void lfs_free(void *p) { +#ifndef LFS_NO_MALLOC + free(p); +#else + (void)p; +#endif +} + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif +#endif diff --git a/libiot/linenoise/linenoise.c b/libiot/linenoise/linenoise.c new file mode 100644 index 0000000..0b30c92 --- /dev/null +++ b/libiot/linenoise/linenoise.c @@ -0,0 +1,637 @@ +/* linenoise.c -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/antirez/linenoise + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ------------------------------------------------------------------------ + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Todo list: + * - Filter bogus Ctrl+ combinations. + * - Win32 support + * + * Bloat: + * - History search like Ctrl+r in readline? + * + * List of escape sequences used by this program, we do everything just + * with three sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * EL (Erase Line) + * Sequence: ESC [ n K + * Effect: if n is 0 or missing, clear from cursor to end of line + * Effect: if n is 1, clear from beginning of line to cursor + * Effect: if n is 2, clear entire line + * + * CUF (CUrsor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward n chars + * + * CUB (CUrsor Backward) + * Sequence: ESC [ n D + * Effect: moves cursor backward n chars + * + * The following is used to get the terminal width if getting + * the width with the TIOCGWINSZ ioctl fails + * + * DSR (Device Status Report) + * Sequence: ESC [ 6 n + * Effect: reports the current cusor position as ESC [ n ; m R + * where n is the row and m is the column + * + * When multi line mode is enabled, we also use an additional escape + * sequence. However multi line editing is disabled by default. + * + * CUU (Cursor Up) + * Sequence: ESC [ n A + * Effect: moves cursor up of n chars. + * + * CUD (Cursor Down) + * Sequence: ESC [ n B + * Effect: moves cursor down of n chars. + * + * When linenoiseClearScreen() is called, two additional escape sequences + * are used in order to clear the screen and position the cursor at home + * position. + * + * CUP (Cursor position) + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED (Erase display) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + */ + +#include "luartos.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "linenoise.h" +#include "lua.h" + +#define LINENOISE_MAX_LINE LUA_MAXINPUT + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + int ifd; /* Terminal stdin file descriptor. */ + int ofd; /* Terminal stdout file descriptor. */ + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldpos; /* Previous refresh cursor position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal-> */ + size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ + int history_index; /* The history index we are currently editing. */ + bool password; /* A password is being entered. */ +}; + +enum KEY_ACTION{ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ +}; + +static void refreshLine(struct linenoiseState *l); + +static void linenoiseHistoryAdd(struct linenoiseState *l); +static void linenoiseHistoryGet(struct linenoiseState *l, int up); + +/* Debugging macro. */ +#if 0 +FILE *lndebug_fp = NULL; +#define lndebug(...) \ + do { \ + if (lndebug_fp == NULL) { \ + lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ + fprintf(lndebug_fp, \ + "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ + (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ + (int)l->maxrows,old_rows); \ + } \ + fprintf(lndebug_fp, ", " __VA_ARGS__); \ + fflush(lndebug_fp); \ + } while (0) +#else +#define lndebug(fmt, ...) +#endif + +/* ======================= Low level terminal handling ====================== */ + +/* Clear the screen. Used to handle ctrl+l */ +void linenoiseClearScreen(void) { + if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { + /* nothing to do, just to avoid warning. */ + } +} + + +/* =========================== Line editing ================================= */ + +/* We define a very simple "append buffer" structure, that is an heap + * allocated string where we can append to. This is useful in order to + * write all the escape sequences in a buffer and flush them to the standard + * output in a single call, to avoid flickering effects. */ +struct abuf { + char *b; + int len; +}; + +static void abInit(struct abuf *ab) { + ab->b = NULL; + ab->len = 0; +} + +static void abAppend(struct abuf *ab, const char *s, int len) { + char *new = (ab->b ? realloc(ab->b,ab->len+len) : malloc(ab->len+len)); + + if (new == NULL) return; + memcpy(new+ab->len,s,len); + ab->b = new; + ab->len += len; +} + +static void abFree(struct abuf *ab) { + free(ab->b); +} + +/* Single line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshSingleLine(struct linenoiseState *l) { + char seq[64]; + size_t plen = strlen(l->prompt); + int fd = l->ofd; + char *buf = l->buf; + size_t len = l->len; + size_t pos = l->pos; + struct abuf ab; + + while((plen+pos) >= l->cols) { + buf++; + len--; + pos--; + } + while (plen+len > l->cols) { + len--; + } + + abInit(&ab); + /* Cursor to left edge */ + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (!l->password) { + abAppend(&ab,buf,len); + } + /* Erase to right */ + snprintf(seq,64,"\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + /* Move cursor to original position. */ + if (!l->password) { + snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); + abAppend(&ab,seq,strlen(seq)); + } + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Calls the two low level functions refreshSingleLine() or + * refreshMultiLine() according to the selected mode. */ +static void refreshLine(struct linenoiseState *l) { + refreshSingleLine(l); +} + +/* Insert the character 'c' at cursor current position. + * + * On error writing to the terminal -1 is returned, otherwise 0. */ +int linenoiseEditInsert(struct linenoiseState *l, char c) { + if (l->len < l->buflen) { + if (l->len == l->pos) { + l->buf[l->pos] = c; + l->pos++; + l->len++; + l->buf[l->len] = '\0'; + if ((l->plen+l->len < l->cols)) { + /* Avoid a full update of the line in the + * trivial case. */ + if (!l->password) { + if (write(l->ofd,&c,1) == -1) return -1; + } + } else { + refreshLine(l); + } + } else { + memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); + l->buf[l->pos] = c; + l->len++; + l->pos++; + l->buf[l->len] = '\0'; + refreshLine(l); + } + } + return 0; +} + +/* Move cursor on the left. */ +void linenoiseEditMoveLeft(struct linenoiseState *l) { + if (l->pos > 0) { + l->pos--; + refreshLine(l); + } +} + +/* Move cursor on the right. */ +void linenoiseEditMoveRight(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos++; + refreshLine(l); + } +} + +/* Move cursor to the start of the line. */ +void linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} + +/* Move cursor to the end of the line. */ +void linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } +} + +/* Delete the character at the right of the cursor without altering the cursor + * position. Basically this is what happens with the "Delete" keyboard key. */ +void linenoiseEditDelete(struct linenoiseState *l) { + if (l->len > 0 && l->pos < l->len) { + memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Backspace implementation. */ +void linenoiseEditBackspace(struct linenoiseState *l) { + if (l->pos > 0 && l->len > 0) { + memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); + l->pos--; + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Delete the previosu word, maintaining the cursor at the start of the + * current word. */ +void linenoiseEditDeletePrevWord(struct linenoiseState *l) { + size_t old_pos = l->pos; + size_t diff; + + while (l->pos > 0 && l->buf[l->pos-1] == ' ') + l->pos--; + while (l->pos > 0 && l->buf[l->pos-1] != ' ') + l->pos--; + diff = old_pos - l->pos; + memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); + l->len -= diff; + refreshLine(l); +} + +/* This function is the core of the line editing capability of linenoise. + * It expects 'fd' to be already in "raw mode" so that every key pressed + * will be returned ASAP to read(). + * + * The resulting string is put into 'buf' when the user type enter, or + * when ctrl+d is typed. + * + * The function returns the length of the current buffer. */ +static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt, bool password) +{ + struct linenoiseState *l; + + l = malloc(sizeof(struct linenoiseState)); + if (!l) { + return 0; + } + + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l->ifd = stdin_fd; + l->ofd = stdout_fd; + l->buf = buf; + l->buflen = buflen; + l->prompt = prompt; + l->plen = strlen(prompt); + l->oldpos = l->pos = 0; + l->len = 0; + l->cols = buflen; + l->maxrows = 0; + l->history_index = -1; + l->password = password; + + /* Buffer starts empty. */ + l->buf[0] = '\0'; + l->buflen--; /* Make sure there is always space for the nulterm */ + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + + if (write(l->ofd,prompt,l->plen) == -1) { + free(l); + return -1; + } + + while(1) { + char c; + int nread; + char seq[3]; + + nread = read(l->ifd,&c,1); + + if (nread <= 0) { + int len = l->len; + free(l); + return len; + } + + switch(c) { + case ENTER: /* enter */ + if (l->len > 0) { + if(!l->password) linenoiseHistoryAdd(l); + } + + int len = l->len; + free(l); + return (int)len; + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + free(l); + return -1; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (l->len > 0) { + linenoiseEditDelete(l); + } else { + free(l); + return -1; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l->pos > 0 && l->pos < l->len) { + int aux = buf[l->pos-1]; + buf[l->pos-1] = buf[l->pos]; + buf[l->pos] = aux; + if (l->pos != l->len-1) l->pos++; + refreshLine(l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(l); + break; + case CTRL_P: /* ctrl-p */ + break; + case CTRL_N: /* ctrl-n */ + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l->ifd,seq,1) == -1) break; + if (read(l->ifd,seq+1,1) == -1) break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l->ifd,seq+2,1) == -1) break; + if (seq[2] == '~') { + switch(seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(l); + break; + } + } + } else { + switch(seq[1]) { + case 'A': /* Up */ + linenoiseHistoryGet(l,1); + break; + case 'B': /* Down */ + linenoiseHistoryGet(l,0); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(l); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(l); + break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(l); + break; + } + } + break; + default: + if (linenoiseEditInsert(l,c)) { + free(l); + return -1; + } + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + l->pos = l->len = 0; + refreshLine(l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + buf[l->pos] = '\0'; + l->len = l->pos; + refreshLine(l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(l); + break; + } + } + + int len = l->len; + free(l); + + return len; +} + +/* This function calls the line editing function linenoiseEdit() using + * the STDIN file descriptor set in raw mode. */ +static int linenoiseRaw(char *buf, size_t buflen, const char *prompt, bool password) { + int count; + + count = linenoiseEdit(fileno(__getreent()->_stdin), fileno(__getreent()->_stdout), buf, buflen, prompt, password); + printf("\n"); + + return count; +} + +static void linenoiseHistoryAdd(struct linenoiseState *l) { + if (!status_get(STATUS_LUA_HISTORY)) return; + + history_add(l->buf); + + l->history_index = -1; +} + +static void linenoiseHistoryGet(struct linenoiseState *l, int up) { + if (!status_get(STATUS_LUA_HISTORY)) return; + + int res = history_get(l->history_index, up, l->buf, l->cols); + if (res >= -1) { + l->history_index = res; + l->len = strlen(l->buf); + l->pos = l->len; + } + + refreshLine(l); +} + +void linenoiseHistoryClear() { + if (status_get(STATUS_LUA_HISTORY)) return; + + char fname[PATH_MAX + 1]; + + if (!mount_history_file(fname, sizeof(fname))) { + return; + } + + remove(fname); +} + +/* The high level function that is the main API of the linenoise library. + * This function checks if the terminal has basic capabilities, just checking + * for a blacklist of stupid terminals, and later either calls the line + * editing function or uses dummy fgets() so that you will be able to type + * something even in the most desperate of the conditions. */ +int linenoisePassword(char *buf, const char *prompt, bool password) { + int count; + count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt,password); + if (count == -1) return -1; + return count; +} + +int linenoise(char *buf, const char *prompt) { + return linenoisePassword(buf, prompt, false); +} diff --git a/libiot/linenoise/linenoise.h b/libiot/linenoise/linenoise.h new file mode 100644 index 0000000..2ed07a9 --- /dev/null +++ b/libiot/linenoise/linenoise.h @@ -0,0 +1,56 @@ +/* linenoise.h -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LINENOISE_H +#define __LINENOISE_H + +#ifdef __cplusplus +extern "C" { +#endif + +int linenoise(char *buf, const char *prompt); +int linenoisePassword(char *buf, const char *prompt, bool password); +void linenoiseClearScreen(void); +void linenoiseSetMultiLine(int ml); +void linenoisePrintKeyCodes(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __LINENOISE_H */ diff --git a/libiot/mkfile b/libiot/mkfile new file mode 100644 index 0000000..516eb9d --- /dev/null +++ b/libiot/mkfile @@ -0,0 +1,26 @@ +# Directories common to all architectures. +# Build in order: +# - critical libraries used by the limbo compiler +# - the limbo compiler (used to build some subsequent libraries) +# - the remaining libraries +# - commands +# - utilities + +<$ROOT/mkconfig + +LIB=libiot.a + +OFILES=\ + +CFLAGS=\ + $CFLAGS\ + -Iinclude\ + +# nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +#include "luartos.h" + +#include "esp_err.h" +#include "esp_attr.h" + +#include "thread.h" +#include "_pthread.h" + +#include +#include +#include + +#include +#include + +// A mutex to sync with critical parts +static struct mtx thread_mtx; + +// List of active threads. An active thread is one that is created +// and has not yet terminated. +static struct list active_threads; + +// List of inactive threads. An inactive thread is one that has yet +// terminated, but that was not joined. We must to store this threads +// in a list to allow joins after the thread termination. +static struct list inactive_threads; + +struct list key_list; + +static uint8_t inited = 0; + +// Arguments for the thread task +struct pthreadTaskArg { + struct pthread *thread; // Thread data + void *(*pthread_function)(void *); // Thread start routine + void *args; // Thread start routine arguments + xTaskHandle parent_task; // Handle of parent task +}; + +void pthreadTask(void *task_arguments); + +void _pthread_lock() { + mtx_lock(&thread_mtx); +} + +void _pthread_unlock() { + mtx_unlock(&thread_mtx); +} + +void _pthread_init() { + if (!inited) { + // Create mutexes + mtx_init(&thread_mtx, NULL, NULL, 0); + + // Init lists + lstinit(&active_threads, 1, LIST_NOT_INDEXED); + lstinit(&inactive_threads, 1, LIST_NOT_INDEXED); + + lstinit(&key_list, 1, LIST_DEFAULT); + + inited = 1; + } +} + +int _pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine)(void *), void *args) { + BaseType_t res; + + // Get the creation attributes + pthread_attr_t cattr; + if (attr) { + // Creation attributes passed as function arguments, copy to thread data + memcpy(&cattr, attr, sizeof(pthread_attr_t)); + } else { + // Apply default creation attributes + pthread_attr_init(&cattr); + } + + // Create and populate the thread data + struct pthread *threadd; + + threadd = (struct pthread *) calloc(1, sizeof(struct pthread)); + if (!threadd) { + return EAGAIN; + } + + // Thread is active + threadd->active = 1; + + // Copy creation attributes + memcpy(&threadd->attr, &cattr, sizeof(pthread_attr_t)); + + // No task joined + threadd->joined_task = NULL; + + // No result + threadd->res = NULL; + + // Initialize signal handlers + pthread_t self; + + if ((self = pthread_self())) { + // Copy signal handlers from current thread + bcopy(((struct pthread *) self)->signals, threadd->signals, + sizeof(sig_t) * PTHREAD_NSIG); + } else { + // Set default signal handlers + int i; + + for (i = 0; i < PTHREAD_NSIG; i++) { + threadd->signals[i] = SIG_DFL; + } + } + + // Init clean list + lstinit(&threadd->clean_list, 1, LIST_DEFAULT); + + // Add thread to the thread list + res = lstadd(&active_threads, (void *) threadd, NULL); + if (res) { + free(threadd); + return EAGAIN; + } + + *thread = (pthread_t) threadd; + + // Create and populate the arguments for the thread task + struct pthreadTaskArg *taskArgs; + + taskArgs = (struct pthreadTaskArg *) calloc(1, + sizeof(struct pthreadTaskArg)); + if (!taskArgs) { + lstremove(&active_threads, (int) threadd, 1); + return EAGAIN; + } + + taskArgs->thread = threadd; + taskArgs->pthread_function = start_routine; + taskArgs->args = args; + taskArgs->parent_task = xTaskGetCurrentTaskHandle(); + + // This is the parent thread. Now we need to wait for the initialization of critical information + // that is provided by the pthreadTask: + // + // * Allocate Lua RTOS specific TCB parts, using local storage pointer assigned to pthreads + // * CPU core id when thread is running + // * The thread id, stored in Lua RTOS specific TCB parts + // * The Lua state, stored in Lua RTOS specific TCB parts + // + + // Create related task + int cpu = 0; + + if (cattr.schedparam.affinityset != CPU_INITIALIZER) { + if (CPU_ISSET(0, &cattr.schedparam.affinityset)) { + cpu = 0; + } else if (CPU_ISSET(1, &cattr.schedparam.affinityset)) { + cpu = 1; + } + } else { + cpu = tskNO_AFFINITY; + } + + xTaskHandle xCreatedTask; // Related task + + if (cpu == tskNO_AFFINITY) { + res = xTaskCreate(pthreadTask, "thread", cattr.stacksize, taskArgs, + cattr.schedparam.sched_priority, &xCreatedTask); + } else { + res = xTaskCreatePinnedToCore(pthreadTask, "thread", cattr.stacksize, + taskArgs, cattr.schedparam.sched_priority, &xCreatedTask, cpu); + } + + if (res != pdPASS) { + // Remove from thread list + lstremove(&active_threads, *thread, 1); + free(taskArgs); + + return EAGAIN; + } + + // Wait for the initialization of Lua RTOS specific TCB parts + xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); + + threadd->task = xCreatedTask; + + return 0; +} + +int _pthread_join(pthread_t id, void **value_ptr) { + _pthread_lock(); + + // Get thread + struct pthread *thread; + + if (lstget(&active_threads, id, (void **) &thread) != 0) { + // Thread not found in active threads, may be is inactive + if (lstget(&inactive_threads, id, (void **) &thread) != 0) { + // Thread not found + _pthread_unlock(); + return ESRCH; + } + } + + // Check that thread is joinable + if (thread->attr.detachstate == PTHREAD_CREATE_DETACHED) { + _pthread_unlock(); + + return EINVAL; + } + + if (thread->active) { + if (thread->joined_task != NULL) { + _pthread_unlock(); + + // Another thread is already waiting to join with this thread + return EINVAL; + } + + // Join the current task to the thread + thread->joined_task = xTaskGetCurrentTaskHandle(); + } + + if (thread->active) { + _pthread_unlock(); + + // Wait until the thread ends + uint32_t ret; + + xTaskNotifyWait(0, 0, &ret, portMAX_DELAY); + + if (value_ptr) { + *value_ptr = (void *) ret; + } + } else { + if (value_ptr) { + *value_ptr = (void *) thread->res; + } + + _pthread_free((pthread_t) thread, 1); + + _pthread_unlock(); + } + + return 0; +} + +int _pthread_detach(pthread_t id) { + _pthread_lock(); + + // Get thread + struct pthread *thread; + + if (lstget(&active_threads, id, (void **) &thread) != 0) { + // Thread not found in active threads, may be is inactive + if (lstget(&inactive_threads, id, (void **) &thread) != 0) { + // Thread not found + _pthread_unlock(); + return ESRCH; + } + } + + if (thread->attr.detachstate == PTHREAD_CREATE_DETACHED) { + return EINVAL; + } + + thread->attr.detachstate = PTHREAD_CREATE_DETACHED; + + lstremove(&inactive_threads, id, 1); + + _pthread_unlock(); + + return 0; +} + +void _pthread_cleanup_push(void (*routine)(void *), void *arg) { + struct pthread *thread; + struct pthread_clean *clean; + + _pthread_lock(); + + // Get current thread + if (lstget(&active_threads, pthread_self(), (void **) &thread) == 0) { + // Create the clean structure + clean = (struct pthread_clean *) malloc(sizeof(struct pthread_clean)); + if (!clean) { + _pthread_unlock(); + return; + } + + clean->clean = routine; + clean->args = arg; + + // Add to clean list + lstadd(&thread->clean_list, clean, NULL); + } + + _pthread_unlock(); +} + +void _pthread_cleanup_pop(int execute) { + struct pthread *thread; + struct pthread_clean *clean; + + _pthread_lock(); + + // Get current thread + if (lstget(&active_threads, pthread_self(), (void **) &thread) == 0) { + // Get last element in clean list, so we must pop handlers in reverse order + int index; + + if ((index = lstlast(&thread->clean_list)) >= 0) { + if (lstget(&thread->clean_list, index, (void **) &clean) == 0) { + // Execute handler + if (clean->clean && execute) { + clean->clean(clean->args); + } + + // Remove handler from list + lstremove(&thread->clean_list, index, 1); + } + } + } + + _pthread_unlock(); +} + +void _pthread_cleanup() { + struct pthread *thread; + struct pthread_clean *clean; + + _pthread_lock(); + + // Get current thread + if (lstget(&active_threads, pthread_self(), (void **) &thread) == 0) { + // Get all elements in clean list, in reverse order + int index; + + while ((index = lstlast(&thread->clean_list)) >= 0) { + if (lstget(&thread->clean_list, index, (void **) &clean) == 0) { + // Execute handler + if (clean->clean) { + clean->clean(clean->args); + } + + // Remove handler from list + lstremove(&thread->clean_list, index, 1); + } + } + } + + _pthread_unlock(); +} + +int _pthread_free(pthread_t id, int destroy) { + // Get thread + struct pthread *thread = (struct pthread *) id; + + // Destroy clean list + lstdestroy(&thread->clean_list, 1); + + // Remove thread + lstremove(&active_threads, id, destroy); + lstremove(&inactive_threads, id, destroy); + + return 0; +} + +sig_t _pthread_signal(int s, sig_t h) { + struct pthread *thread; // Current thread + sig_t prev_h; // Previous handler + + if (s > PTHREAD_NSIG) { + return NULL; + } + + if (lstget(&active_threads, pthread_self(), (void **) &thread) == 0) { + // Add handler + prev_h = thread->signals[s]; + thread->signals[s] = h; + + return prev_h; + } + + return NULL; +} + +void _pthread_exec_signal(int dst, int s) { + if (s > PTHREAD_NSIG) { + return; + } + + // Get destination thread + struct pthread *thread; + + if (lstget(&active_threads, dst, (void **) &thread) == 0) { + // If destination thread has a handler for the signal, execute it + if ((thread->signals[s] != SIG_DFL) + && (thread->signals[s] != SIG_IGN)) { + if (thread->is_delayed && ((s == (SIGINT) || (s == SIGABRT)))) { + xTaskAbortDelay(thread->task); + } + + thread->signals[s](s); + } + } +} + +int IRAM_ATTR _pthread_has_signal(int dst, int s) { + if (s > PTHREAD_NSIG) { + return 0; + } + + // Get destination thread + struct pthread *thread; + + if (lstget(&active_threads, dst, (void **) &thread) == 0) { + return ((thread->signals[s] != SIG_DFL) + && (thread->signals[s] != SIG_IGN)); + } + + return 0; +} + +int _pthread_sleep(uint32_t msecs) { + struct pthread *thread; + int res; + + // Get the current thread + res = lstget(&active_threads, pthread_self(), (void **) &thread); + if (res) { + // Its not a thread, simply delay task + vTaskDelay(msecs / portTICK_PERIOD_MS); + return 0; + } + + // Is a thread. Mark it as delayed. + thread->is_delayed = 1; + thread->delay_interrupted = 0; + + vTaskDelay(msecs / portTICK_PERIOD_MS); + + thread->is_delayed = 0; + + if (thread->delay_interrupted) { + thread->delay_interrupted = 0; + return -1; + } else { + return 0; + } +} + +int _pthread_stop(pthread_t id) { + struct pthread *thread; + int res; + + // Get thread + res = lstget(&active_threads, id, (void **) &thread); + if (res) { + return res; + } + + // Stop + vTaskDelete(thread->task); + + return 0; +} + +int _pthread_core(pthread_t id) { + struct pthread *thread; + int res; + + // Get thread + res = lstget(&active_threads, id, (void **) &thread); + if (res) { + return res; + } + + return (int) ucGetCoreID(thread->task); +} + +int _pthread_stack(pthread_t id) { + struct pthread *thread; + int res; + + // Get thread + res = lstget(&active_threads, id, (void **) &thread); + if (res) { + return res; + } + + return (int) uxGetStack(thread->task); +} + +int _pthread_stack_free(pthread_t id) { + struct pthread *thread; + int res; + + // Get thread + res = lstget(&active_threads, id, (void **) &thread); + if (res) { + return res; + } + + return (int) uxTaskGetStackHighWaterMark(thread->task); +} + +int _pthread_suspend(pthread_t id) { + struct pthread *thread; + int res; + + // Get thread + res = lstget(&active_threads, id, (void **) &thread); + if (res) { + return res; + } + + UBaseType_t priority = uxTaskPriorityGet(NULL); + vTaskPrioritySet(NULL, 22); + + // Suspend + uxSetThreadStatus(thread->task, StatusSuspended); + vTaskSuspend(thread->task); + + vTaskPrioritySet(NULL, priority); + + return 0; +} + +int _pthread_resume(pthread_t id) { + struct pthread *thread; + int res; + + // Get thread + res = lstget(&active_threads, id, (void **) &thread); + if (res) { + return res; + } + + // Resume + uxSetThreadStatus(thread->task, StatusRunning); + vTaskResume(thread->task); + + return 0; +} + +struct pthread *_pthread_get(pthread_t id) { + struct pthread *thread; + int res; + + // Get the thread + res = lstget(&active_threads, id, (void **) &thread); + if (res) { + return NULL; + } + + return thread; +} + +// This is the callback function for free Lua RTOS specific TCB parts +static void pthreadLocaleStoragePointerCallback(int index, void* data) { + if (index == THREAD_LOCAL_STORAGE_POINTER_ID) { + free(data); + } +} + +void pthreadTask(void *taskArgs) { + struct pthreadTaskArg *args; // Task arguments + struct pthread *thread; // Current thread + lua_rtos_tcb_t *lua_rtos_tcb; // Lua RTOS specific TCB parts + + // This is the new thread + args = (struct pthreadTaskArg *) taskArgs; + + // Get thread + thread = (struct pthread *) args->thread; + + // Allocate and initialize Lua RTOS specific TCB parts, and store it into a FreeRTOS + // local storage pointer + lua_rtos_tcb = (lua_rtos_tcb_t *) calloc(1, sizeof(lua_rtos_tcb_t)); + assert(lua_rtos_tcb != NULL); + + lua_rtos_tcb->status = StatusRunning; + + vTaskSetThreadLocalStoragePointerAndDelCallback(NULL, + THREAD_LOCAL_STORAGE_POINTER_ID, (void *) lua_rtos_tcb, + pthreadLocaleStoragePointerCallback); + + // Set thread id + uxSetThreadId((UBaseType_t) args->thread); + + // Call additional thread init function + if (args->thread->attr.init_func) { + args->thread->attr.init_func(args->args); + } + + // Lua RTOS specific TCB parts are set, parent thread can continue + xTaskNotify(args->parent_task, 0, eNoAction); + + if (args->thread->attr.schedparam.initial_state + == PTHREAD_INITIAL_STATE_SUSPEND) { + vTaskSuspend(NULL); + } + + // Call start function + void *ret = args->pthread_function(args->args); + + _pthread_lock(); + + if (args->thread->attr.detachstate == PTHREAD_CREATE_JOINABLE) { + if (thread->joined_task != NULL) { + // Notify to joined thread + xTaskNotify(thread->joined_task, (uint32_t) ret, + eSetValueWithOverwrite); + + thread->joined_task = NULL; + + _pthread_free((pthread_t) thread, 1); + } else { + _pthread_free((pthread_t) thread, 0); + + // Put the thread into the inactive threads, because join can occur later, and + // the thread's result must be stored + thread->active = 0; + thread->res = ret; + + lstadd(&inactive_threads, (void *) thread, NULL); + } + } else { + _pthread_free((pthread_t) thread, 1); + } + + // Free args + free(taskArgs); + + _pthread_unlock(); + + // End related task + vTaskDelete(NULL); +} + +int pthread_cancel(pthread_t thread) { + return 0; +} + +esp_err_t esp_pthread_init(void) { + _pthread_init(); + + return ESP_OK; +} + +int pthread_setname_np(pthread_t id, const char *name) { + struct pthread *thread; + int res; + + // Sanity checks + if (strlen(name) > configMAX_TASK_NAME_LEN - 1) { + return ERANGE; + } + + // Get the thread + res = lstget(&active_threads, id, (void **) &thread); + if (res) { + return EINVAL; + } + + // Get the TCB task for this thread + tskTCB_t *task = (tskTCB_t *) (thread->task); + + // Copy the name into the TCB + strncpy(task->pcTaskName, name, configMAX_TASK_NAME_LEN - 1); + + return 0; +} + +int pthread_getname_np(pthread_t id, char *name, size_t len) { + struct pthread *thread; + int res; + + // Get the thread + res = lstget(&active_threads, id, (void **) &thread); + if (res) { + return EINVAL; + } + + // Get the TCB task for this thread + tskTCB_t *task = (tskTCB_t *) (thread->task); + + // Sanity checks + if (strlen(task->pcTaskName) < len - 1) { + return ERANGE; + } + + // Copy the name from the TCB + strncpy(name, task->pcTaskName, configMAX_TASK_NAME_LEN - 1); + + return 0; +} diff --git a/libiot/pthread/_pthread.h b/libiot/pthread/_pthread.h new file mode 100644 index 0000000..c6df9aa --- /dev/null +++ b/libiot/pthread/_pthread.h @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +//#include "luartos.h" + +#ifndef __PTHREAD_H +#define __PTHREAD_H + +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/adds.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" + +#include + +#include + +#include +#include +#include + +#include + + +#define CPU_INITIALIZER 0 + +// Add CPU cpu to set +#define CPU_SET(ncpu, cpuset) \ + *(cpuset) |= (1 << ncpu) + +// Test to see if CPU cpu is a member of set. +#define CPU_ISSET(ncpu, cpuset) \ + (*(cpuset) & (1 << ncpu)) + +// Each thread maintains a signal handler copy. Typically there are around 32 defined +// signals, but not signals are required for applications. For example, in Lua only +// SIGINT is used. +// +// This defines how many signals will be available in threads +#define PTHREAD_NSIG (SIGINT + 1) + +// 1 for activate debug log when thread can't lock a mutex +#define PTHREAD_MTX_DEBUG 0 + +#if PTHREAD_MTX_DEBUG +#define PTHREAD_MTX_LOCK_TIMEOUT (3000 / portTICK_PERIOD_MS) +#define PTHREAD_MTX_DEBUG_LOCK() printf("phread can't lock\n"); +#else +#define PTHREAD_MTX_LOCK_TIMEOUT portMAX_DELAY +#define PTHREAD_MTX_DEBUG_LOCK() +#endif + +// Minimal stack size per thread +#define PTHREAD_STACK_MIN (1024 * 2) + +#define PTHREAD_CREATE_DETACHED 0 + +// Initial states for a thread +#define PTHREAD_INITIAL_STATE_RUN 1 +#define PTHREAD_INITIAL_STATE_SUSPEND 2 + +// Thread types +#define PTHREAD_TYPE_DEFAULT 0 +#define PTHREAD_TYPE_LUA 1 + +// Required structures and types +struct pthread_mutex { + SemaphoreHandle_t sem; + int owner; + int type; +}; + +struct pthread_cond { + struct mtx mutex; + EventGroupHandle_t ev; + int referenced; +}; + +struct pthread_key_specific { + pthread_t thread; + const void *value; +}; + +struct pthread_key { + struct list specific; + void (*destructor)(void*); +}; + +struct pthread_join { + QueueHandle_t queue; +}; + +struct pthread_clean { + void (*clean)(void*); + void *args; +}; + +struct pthread { + struct list clean_list; + sig_t signals[PTHREAD_NSIG]; + xTaskHandle task; + uint8_t is_delayed; + uint8_t delay_interrupted; + uint8_t active; + xTaskHandle joined_task; + void *res; + pthread_attr_t attr; +}; + +// Helper functions, only for internal use +void _pthread_lock(); +void _pthread_unlock(); +void _pthread_init(); +int _pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *args); +int _pthread_join(pthread_t id, void **value_ptr); +int _pthread_free(pthread_t id, int destroy); +sig_t _pthread_signal(int s, sig_t h); +void _pthread_exec_signal(int dst, int s); +void _pthread_process_signal(); +int _pthread_has_signal(int dst, int s); +int _pthread_stop(pthread_t id); +int _pthread_suspend(pthread_t id); +int _pthread_resume(pthread_t id); +int _pthread_core(pthread_t id); +sig_t _pthread_signal(int s, sig_t h); +int _pthread_get_prio(); +int _pthread_stack_free(pthread_t id); +int _pthread_stack(pthread_t id); +struct pthread *_pthread_get(pthread_t id); +int _pthread_sleep(uint32_t msecs); +void _pthread_cleanup_push(void (*routine)(void *), void *arg); +void _pthread_cleanup_pop(int execute); +void _pthread_cleanup(); +int _pthread_detach(pthread_t id); + +// API functions +int pthread_attr_init(pthread_attr_t *attr); +int pthread_attr_destroy(pthread_attr_t *attr); +int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); +int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize); +int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param); +int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param); +int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); +int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); + +int pthread_mutex_init(pthread_mutex_t *mut, const pthread_mutexattr_t *attr); +int pthread_mutex_lock(pthread_mutex_t *mut); +int pthread_mutex_unlock(pthread_mutex_t *mut); +int pthread_mutex_trylock(pthread_mutex_t *mut); +int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); +int pthread_mutexattr_init(pthread_mutexattr_t *attr); +int pthread_mutex_destroy(pthread_mutex_t *mutex); + +int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); +int pthread_cond_destroy(pthread_cond_t *cond); +int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); +int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); +int pthread_cond_signal(pthread_cond_t *cond); + +int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)); +int pthread_setcancelstate(int state, int *oldstate); +int pthread_key_create(pthread_key_t *k, void (*destructor)(void*)); +int pthread_setspecific(pthread_key_t k, const void *value); +void *pthread_getspecific(pthread_key_t k); +int pthread_join(pthread_t thread, void **value_ptr); +int pthread_cancel(pthread_t thread); +int pthread_kill(pthread_t thread, int signal); + +int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset); +int pthread_attr_getaffinity_np(const pthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset); +int pthread_setname_np(pthread_t id, const char *name); +int pthread_getname_np(pthread_t id, char *name, size_t len); +int pthread_attr_setinitialstate_np(pthread_attr_t *attr, int initial_state); +int pthread_attr_setinitfunc_np(pthread_attr_t *attr, void (*init_func)(void *)); + +pthread_t pthread_self(void); + +int pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *args); + +#define pthread_exit(ret) \ +do { \ + _pthread_cleanup(); \ + return ((void *)ret); \ +} while (0) + + +#define pthread_cleanup_push(routine, args) \ +do { \ + _pthread_cleanup_push(routine, args) + +#define pthread_cleanup_pop(exec) \ + _pthread_cleanup_pop(exec); \ +} while(0) + +#endif /* __PTHREAD_H */ + diff --git a/libiot/pthread/attr.c b/libiot/pthread/attr.c new file mode 100644 index 0000000..fe5c2a6 --- /dev/null +++ b/libiot/pthread/attr.c @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +#include "_pthread.h" + +int pthread_attr_init(pthread_attr_t *attr) { + if (!attr) { + return EINVAL; + } + + attr->is_initialized = 1; + + attr->stacksize = (CONFIG_LUA_RTOS_LUA_THREAD_STACK_SIZE >= PTHREAD_STACK_MIN?CONFIG_LUA_RTOS_LUA_THREAD_STACK_SIZE:PTHREAD_STACK_MIN); + + attr->schedparam.initial_state = PTHREAD_INITIAL_STATE_RUN; + attr->schedparam.sched_priority = CONFIG_LUA_RTOS_LUA_TASK_PRIORITY; + attr->schedparam.affinityset = CPU_INITIALIZER; // No affinity + attr->init_func = NULL; + attr->detachstate = PTHREAD_CREATE_JOINABLE; + + return 0; +} + +int pthread_attr_destroy(pthread_attr_t *attr) { + if (!attr) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + return 0; +} + +int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize) { + if (!attr) { + return EINVAL; + } + + if (stacksize < PTHREAD_STACK_MIN) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + attr->stacksize = stacksize; + + return 0; +} + +int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize) { + if (!attr || !stacksize) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + *stacksize = attr->stacksize; + + return 0; +} + +int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param) { + if (!attr || !param) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + if ((param->sched_priority > configMAX_PRIORITIES - 1) || (param->sched_priority < 1)) { + return EINVAL; + } + + attr->schedparam.sched_priority = param->sched_priority; + + return 0; +} + +int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param) { + if (!attr || !param) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + param->sched_priority = attr->schedparam.sched_priority; + + return 0; +} + +int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) { + if (!attr) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + if ((detachstate != PTHREAD_CREATE_DETACHED) && (detachstate != PTHREAD_CREATE_JOINABLE)) { + return EINVAL; + } + + attr->detachstate = detachstate; + + return 0; +} + +int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate) { + if (!attr || !detachstate) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + *detachstate = attr->detachstate; + + return 0; +} + +int pthread_setcancelstate(int state, int *oldstate) { + return 0; +} + +int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset) { + if (!attr || !cpuset) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + if ((*cpuset < 0) || (*cpuset > portNUM_PROCESSORS)) { + return EINVAL; + } + + attr->schedparam.affinityset = *cpuset; + + return 0; +} + +int pthread_attr_getaffinity_np(const pthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset) { + if (!attr || !cpuset) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + if ((*cpuset < 0) || (*cpuset > portNUM_PROCESSORS)) { + return EINVAL; + } + + *cpuset = attr->schedparam.affinityset; + + return 0; +} + +int pthread_attr_setinitialstate_np(pthread_attr_t *attr, int initial_state) { + if (!attr) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + if ((initial_state != PTHREAD_INITIAL_STATE_RUN) && (initial_state != PTHREAD_INITIAL_STATE_SUSPEND)) { + return EINVAL; + } + + attr->schedparam.initial_state = initial_state; + + return 0; +} + +int pthread_attr_setinitfunc_np(pthread_attr_t *attr, void (*init_routine)(void *)) { + if (!attr || !init_routine) { + return EINVAL; + } + + if (!attr->is_initialized) { + return EINVAL; + } + + attr->init_func = init_routine; + + return 0; +} diff --git a/libiot/pthread/cond.c b/libiot/pthread/cond.c new file mode 100644 index 0000000..288e548 --- /dev/null +++ b/libiot/pthread/cond.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +#include "_pthread.h" + +#include +#include + +#if configUSE_16_BIT_TICKS +#define BITS_PER_EVENT_GROUP 8 +#else +#define BITS_PER_EVENT_GROUP 24 +#endif + +int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr) { + // Avoid to reinitialize the object referenced by cond, a previously + // initialized, but not yet destroyed, condition variable + if (*cond != PTHREAD_COND_INITIALIZER) { + return EBUSY; + } + + // Initialize the internal cond structure + struct pthread_cond *scond = calloc(1, sizeof(struct pthread_cond)); + if (!scond) { + return ENOMEM; + } + + // Init the cond mutex + mtx_init(&scond->mutex, NULL, NULL, 0); + if (!scond->mutex.lock) { + free(scond); + return ENOMEM; + } + + // Create the event group + scond->ev = xEventGroupCreate(); + if (scond->ev == NULL) { + mtx_destroy(&scond->mutex); + free(scond); + return ENOMEM; + } + + // Return the cond reference + *cond = (pthread_cond_t)scond; + + return 0; +} + +int pthread_cond_destroy(pthread_cond_t *cond) { + if (*cond == PTHREAD_COND_INITIALIZER) { + return EINVAL; + } + + struct pthread_cond *scond = (struct pthread_cond *) *cond; + + mtx_lock(&scond->mutex); + + if (scond->referenced > 0) { + mtx_unlock(&scond->mutex); + + return EBUSY; + } + + mtx_destroy(&scond->mutex); + + return 0; +} + +int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) { + return pthread_cond_timedwait(cond, mutex, NULL); +} + +int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, + const struct timespec *abstime) { + + if (*cond == PTHREAD_COND_INITIALIZER) { + return EINVAL; + } + + struct pthread_cond *scond = (struct pthread_cond *)*cond; + + // Find an unused bit in the event group to sync the condition + mtx_lock(&scond->mutex); + + uint8_t bit; + for(bit=0;bit < BITS_PER_EVENT_GROUP;bit++) { + if (!((1 << bit) & scond->referenced)) { + scond->referenced |= (1 << bit); + + break; + } + } + + mtx_unlock(&scond->mutex); + + if (bit >= BITS_PER_EVENT_GROUP) { + // All bits are used in the event group. This is a rare condition, because + // in Lua RTOS an event group has 24 bits, which means that 24 conditions + // (or 24 threads) can be handled for each condition variable. + // + // Although is not part of the pthread specification we indicate this + // condition returning the EINVAL error code. + return EINVAL; + } + + // Get the timeout in ticks + TickType_t ticks; + + if (!abstime) { + ticks = portMAX_DELAY; + } else { + struct timeval now, future, diff; + + gettimeofday(&now, NULL); + + future.tv_sec = abstime->tv_sec; + future.tv_usec = abstime->tv_nsec / 1000; + + if (timercmp(&future, &now, <)) { + return ETIMEDOUT; + } else { + timersub(&future, &now, &diff); + ticks = ((diff.tv_sec * 1000) + (diff.tv_usec / 1000)) / portTICK_PERIOD_MS; + } + } + + pthread_mutex_unlock(mutex); + EventBits_t uxBits = xEventGroupWaitBits(scond->ev, (1 << bit), pdTRUE, pdTRUE, ticks); + if (!(uxBits & (1 << bit))) { + pthread_mutex_lock(mutex); + scond->referenced &= ~(1 << bit); + pthread_mutex_unlock(mutex); + + return ETIMEDOUT; + } + pthread_mutex_lock(mutex); + + return 0; +} + +int pthread_cond_signal(pthread_cond_t *cond) { + if (*cond == PTHREAD_COND_INITIALIZER) { + return EINVAL; + } + + struct pthread_cond *scond = (struct pthread_cond *)*cond; + + mtx_lock(&scond->mutex); + + uint8_t bit; + for(bit=0;bit < BITS_PER_EVENT_GROUP;bit++) { + if ((1 << bit) & scond->referenced) { + xEventGroupSetBits(scond->ev, (1 << bit)); + + scond->referenced &= ~(1 << bit); + } + } + + mtx_unlock(&scond->mutex); + + return 0; +} diff --git a/libiot/pthread/create.c b/libiot/pthread/create.c new file mode 100644 index 0000000..fe591c7 --- /dev/null +++ b/libiot/pthread/create.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +#include "luartos.h" +#include "_pthread.h" + +#include + +int pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine)(void *), void *args) { + + return _pthread_create(thread, attr, start_routine, args); +} diff --git a/libiot/pthread/join.c b/libiot/pthread/join.c new file mode 100644 index 0000000..bd173f3 --- /dev/null +++ b/libiot/pthread/join.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +#include "_pthread.h" + +int pthread_join(pthread_t thread, void **value_ptr) { + return _pthread_join(thread, value_ptr); +} + +int pthread_detach(pthread_t thread) { + return _pthread_detach(thread); +} + +int sched_yield( void ) { + return 0; +} + +int pthread_equal(pthread_t t1, pthread_t t2) { + return 0; +} + +int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type) { + return ENOSYS; +} + +int pthread_mutexattr_destroy(pthread_mutexattr_t *attr) { + return 0; +} diff --git a/libiot/pthread/key.c b/libiot/pthread/key.c new file mode 100644 index 0000000..ebb757f --- /dev/null +++ b/libiot/pthread/key.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +#include "_pthread.h" + +#include + +extern struct list key_list; + +int pthread_key_create(pthread_key_t *k, void (*destructor)(void*)) { + struct pthread_key *key; + int res; + + // Allocate space for the key + key = (struct pthread_key *)malloc(sizeof(struct pthread_key)); + if (!key) { + return ENOMEM; + } + + // Init key + key->destructor = destructor; + + lstinit(&key->specific, 1, LIST_DEFAULT); + + // Add key to key list + res = lstadd(&key_list, (void *)key, (int*)k); + if (res) { + free(key); + return res; + } + + return 0; +} + +int pthread_setspecific(pthread_key_t k, const void *value) { + struct pthread_key_specific *specific; + struct pthread_key *key; + pthread_t thread; + int res; + int index; + + // Get key + res = lstget(&key_list, k, (void **)&key); + if (res) { + return res; + } + + if (value) { + // Allocate space for specific + specific = (struct pthread_key_specific *)malloc(sizeof(struct pthread_key_specific)); + if (!specific) { + return ENOMEM; + } + + specific->thread = pthread_self(); + specific->value = value; + + lstadd(&key->specific, (void **)specific, &index); + } else { + thread = pthread_self(); + + index = lstfirst(&key->specific); + while (index >= 0) { + lstget(&key->specific, index, (void **)&specific); + + if (specific->thread == thread) { + lstremove(&key->specific, k, 1); + break; + } + + index = lstnext(&key->specific, index); + } + } + + return 0; +} + +void *pthread_getspecific(pthread_key_t k) { + struct pthread_key_specific *specific; + struct pthread_key *key; + pthread_t thread; + int res; + int index; + + // Get key + res = lstget(&key_list, k, (void **)&key); + if (res) { + return NULL; + } + + // Get specific value + thread = pthread_self(); + + index = lstfirst(&key->specific); + while (index >= 0) { + lstget(&key->specific, index, (void **)&specific); + + if (specific->thread == thread) { + return (void *)specific->value; + } + + index = lstnext(&key->specific, index); + } + + return NULL; +} + +int pthread_key_delete(pthread_key_t k) { + struct pthread_key *key; + int res; + + // Get key + res = lstget(&key_list, k, (void **)&key); + if (res) { + return res; + } + + lstremove(&key_list, k, 1); + + return 0; +} diff --git a/libiot/pthread/kill.c b/libiot/pthread/kill.c new file mode 100644 index 0000000..1de80ee --- /dev/null +++ b/libiot/pthread/kill.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +#include "_pthread.h" + +#include +#include +#include + +int pthread_kill(pthread_t thread, int signal) { + if (signal > PTHREAD_NSIG) { + return EINVAL; + } + + _signal_queue(thread, signal); + + return 0; +} diff --git a/libiot/pthread/mkfile b/libiot/pthread/mkfile new file mode 100644 index 0000000..2fc273c --- /dev/null +++ b/libiot/pthread/mkfile @@ -0,0 +1,10 @@ +#<$ROOT/mkconfig + +#LIB=libspiffs.a +OFILES=\ + $OFILES\ + pthread/mutex.$O\ + +#HFILES= $ROOT/include/bio.h + +#<$ROOT/mkfiles/mksyslib-$SHELLTYPE diff --git a/libiot/pthread/mutex.c b/libiot/pthread/mutex.c new file mode 100644 index 0000000..c4850d1 --- /dev/null +++ b/libiot/pthread/mutex.c @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +#include "_pthread.h" + +//#include "esp_attr.h" + +#include + +static int _check_attr(const pthread_mutexattr_t *attr) { + int type = attr->type; + + if ((type < PTHREAD_MUTEX_NORMAL) || (type > PTHREAD_MUTEX_DEFAULT)) { + return EINVAL; + } + + return 0; +} + +int pthread_mutex_init(pthread_mutex_t *mut, const pthread_mutexattr_t *attr) { + struct pthread_mutex *mutex; + int res; + + if (!mut) { + return EINVAL; + } + + // Check attr + if (attr) { + res = _check_attr(attr); + if (res) { + return res; + } + } + + // Test if it's init yet + if (*mut != PTHREAD_MUTEX_INITIALIZER) { + return EBUSY; + } + + // Create mutex structure + mutex = (struct pthread_mutex *)malloc(sizeof(struct pthread_mutex)); + if (!mutex) { + return EINVAL; + } + + if (attr) { + mutex->type = attr->type; + } else { + mutex->type = PTHREAD_MUTEX_NORMAL; + } + // Create semaphore + if (mutex->type == PTHREAD_MUTEX_RECURSIVE) { + mutex->sem = xSemaphoreCreateRecursiveMutex(); + } else { + mutex->sem = xSemaphoreCreateMutex(); + } + if(!mutex->sem){ + *mut = PTHREAD_MUTEX_INITIALIZER; + free(mutex->sem); + free(mutex); + return ENOMEM; + } + + mutex->owner = pthread_self(); + + *mut = (unsigned int )mutex; + + return 0; +} + +int IRAM_ATTR pthread_mutex_lock(pthread_mutex_t *mut) { + struct pthread_mutex *mutex; + int res; + + if (!mut) { + return EINVAL; + } + + if ((intptr_t) *mut == PTHREAD_MUTEX_INITIALIZER) { + if ((res = pthread_mutex_init(mut, NULL))) { + return res; + } + } + + mutex = (struct pthread_mutex *)(*mut); + + // Lock + if (mutex->type == PTHREAD_MUTEX_RECURSIVE) { + if (xSemaphoreTakeRecursive(mutex->sem, PTHREAD_MTX_LOCK_TIMEOUT) != pdPASS) { + PTHREAD_MTX_DEBUG_LOCK(); + return EINVAL; + } + } else { + if (xSemaphoreTake(mutex->sem, PTHREAD_MTX_LOCK_TIMEOUT) != pdPASS) { + PTHREAD_MTX_DEBUG_LOCK(); + return EINVAL; + } + } + + return 0; +} + +int IRAM_ATTR pthread_mutex_unlock(pthread_mutex_t *mut) { + if (!mut) { + return EINVAL; + } + + struct pthread_mutex *mutex = ( struct pthread_mutex *)(*mut); + + // Unlock + if (mutex->type == PTHREAD_MUTEX_RECURSIVE) { + xSemaphoreGiveRecursive(mutex->sem); + } else { + xSemaphoreGive(mutex->sem); + } + + return 0; +} + +int pthread_mutex_trylock(pthread_mutex_t *mut) { + struct pthread_mutex *mutex; + int res; + + if (!mut) { + return EINVAL; + } + + if ((intptr_t) *mut == PTHREAD_MUTEX_INITIALIZER) { + if ((res = pthread_mutex_init(mut, NULL))) { + return res; + } + } + + mutex = ( struct pthread_mutex *)(*mut); + + // Try lock + if (mutex->type == PTHREAD_MUTEX_RECURSIVE) { + if (xSemaphoreTakeRecursive(mutex->sem,0 ) != pdTRUE) { + return EBUSY; + } + } else { + if (xSemaphoreTake(mutex->sem,0 ) != pdTRUE) { + return EBUSY; + } + } + + return 0; +} + +int pthread_mutex_destroy(pthread_mutex_t *mut) { + if (!mut) { + return EINVAL; + } + + struct pthread_mutex *mutex = ( struct pthread_mutex *)(*mut); + + if (mutex->type == PTHREAD_MUTEX_RECURSIVE) { + xSemaphoreGiveRecursive(mutex->sem); + } else { + xSemaphoreGive(mutex->sem); + } + + vSemaphoreDelete(mutex->sem); + + return 0; +} + +int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) { + pthread_mutexattr_t temp_attr; + int res; + + // Check attr + if (!attr) { + return EINVAL; + } + + temp_attr.type = type; + + res = _check_attr(&temp_attr); + if (res) { + return res; + } + + attr->type = type; + + return 0; +} + +int pthread_mutexattr_init(pthread_mutexattr_t *attr) { + if (!attr) { + return EINVAL; + } + + attr->type = PTHREAD_MUTEX_NORMAL; + attr->is_initialized = 1; + + return 0; +} diff --git a/libiot/pthread/once.c b/libiot/pthread/once.c new file mode 100644 index 0000000..646de27 --- /dev/null +++ b/libiot/pthread/once.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +#include "_pthread.h" + +int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)) { + if (!once_control || !init_routine) { + return EINVAL; + } + + if (once_control->is_initialized != 1) { + return EINVAL; + } + + _pthread_lock(); + if (!once_control->init_executed) { + once_control->init_executed = 1; + _pthread_unlock(); + init_routine(); + } else { + _pthread_unlock(); + } + + return 0; +} diff --git a/libiot/pthread/self.c b/libiot/pthread/self.c new file mode 100644 index 0000000..017ac5b --- /dev/null +++ b/libiot/pthread/self.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS pthread implementation for FreeRTOS + * + */ + +#include "_pthread.h" +#include "esp_attr.h" + +extern UBaseType_t uxGetThreadId(); + +pthread_t IRAM_ATTR pthread_self(void) { + return (pthread_t)uxGetThreadId(); +} diff --git a/libiot/pthread/test/pthread-cleanup.c b/libiot/pthread/test/pthread-cleanup.c new file mode 100644 index 0000000..c11aeb4 --- /dev/null +++ b/libiot/pthread/test/pthread-cleanup.c @@ -0,0 +1,200 @@ +#include "unity.h" + +#include +#include +#include +#include +#include + +#include + +#define NUM_TERMS 4 + +static int term[NUM_TERMS]; + +void term_0(void *args) { + term[0] = 0; +} + +void term_1(void *args) { + term[1] = 1; +} + +void term_2(void *args) { + term[2] = 2; +} + +void term_3(void *args) { + term[3] = 3; +} + +// Test that handlers are called in reverse order +static void *thread1(void *args) { + int count = 0; + + memset(term, -1, sizeof(term)); + + pthread_cleanup_push(term_0, NULL); + pthread_cleanup_push(term_1, NULL); + pthread_cleanup_push(term_2, NULL); + pthread_cleanup_push(term_3, NULL); + + for (count = 0; count < 100; count++) { + // Simulate some work + usleep(1000); + } + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + TEST_ASSERT(term[0] == 0); + TEST_ASSERT(term[1] == 1); + TEST_ASSERT(term[2] == 2); + TEST_ASSERT(term[3] == 3); + + return NULL; +} + +// Test that only handlers poped with execute argument set to true are +// executed +static void *thread2(void *args) { + int count = 0; + + memset(term, -1, sizeof(term)); + + pthread_cleanup_push(term_0, NULL); + pthread_cleanup_push(term_1, NULL); + pthread_cleanup_push(term_2, NULL); + pthread_cleanup_push(term_3, NULL); + + for (count = 0; count < 100; count++) { + // Simulate some work + usleep(1000); + } + + pthread_cleanup_pop(0); + pthread_cleanup_pop(1); + pthread_cleanup_pop(0); + pthread_cleanup_pop(1); + + TEST_ASSERT(term[0] == 0); + TEST_ASSERT(term[1] == -1); + TEST_ASSERT(term[2] == 2); + TEST_ASSERT(term[3] == -1); + + pthread_exit(NULL); +} + +// Test that pthread_exit execute the cleanups +static void *thread3(void *args) { + int count = 0; + + memset(term, -1, sizeof(term)); + + pthread_cleanup_push(term_0, NULL); + pthread_cleanup_push(term_1, NULL); + pthread_cleanup_push(term_2, NULL); + pthread_cleanup_push(term_3, NULL); + + for (count = 0; count < 100; count++) { + // Simulate some work + usleep(1000); + + if (count == 50) { + pthread_exit(NULL); + } + } + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + // This point is never reached + TEST_ASSERT(0); + + pthread_exit(NULL); +} + +// Test that when exit from thread with return don't execute the cleanups +static void *thread4(void *args) { + int count = 0; + + memset(term, -1, sizeof(term)); + + pthread_cleanup_push(term_0, NULL); + pthread_cleanup_push(term_1, NULL); + pthread_cleanup_push(term_2, NULL); + pthread_cleanup_push(term_3, NULL); + + for (count = 0; count < 100; count++) { + // Simulate some work + usleep(1000); + + if (count == 50) { + return NULL; + } + } + + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + pthread_cleanup_pop(1); + + // This point is never reached + TEST_ASSERT(0); + + return NULL; +} + +TEST_CASE("pthread cleanup", "[pthread]") { + pthread_attr_t attr; + pthread_t th; + int ret; + + pthread_attr_init(&attr); + + // Test that handlers are called in reverse order + ret = pthread_create(&th, &attr, thread1, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_join(th, NULL); + TEST_ASSERT(ret == 0); + + // Test that only handlers poped with execute argument set to true are + // executed + ret = pthread_create(&th, &attr, thread2, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_join(th, NULL); + TEST_ASSERT(ret == 0); + + // Test that pthread_exit execute the cleanups + ret = pthread_create(&th, &attr, thread3, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_join(th, NULL); + TEST_ASSERT(ret == 0); + + TEST_ASSERT(term[0] == 0); + TEST_ASSERT(term[1] == 1); + TEST_ASSERT(term[2] == 2); + TEST_ASSERT(term[3] == 3); + + // Test that when exit from thread with return don't execute the cleanups + ret = pthread_create(&th, &attr, thread4, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_join(th, NULL); + TEST_ASSERT(ret == 0); + + TEST_ASSERT(term[0] == -1); + TEST_ASSERT(term[1] == -1); + TEST_ASSERT(term[2] == -1); + TEST_ASSERT(term[3] == -1); + + ret = pthread_attr_destroy(&attr); + TEST_ASSERT(ret == 0); +} diff --git a/libiot/pthread/test/pthread-cond.c b/libiot/pthread/test/pthread-cond.c new file mode 100644 index 0000000..8e1ce41 --- /dev/null +++ b/libiot/pthread/test/pthread-cond.c @@ -0,0 +1,107 @@ +#include "unity.h" + +#include +#include +#include + +#include + +#include + +#define NUM_THREADS 3 +#define COUNT_LIMIT 12 +#define TCOUNT 10 + +static int count = 0; +static pthread_t threads[NUM_THREADS]; +static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t count_threshold_cv = PTHREAD_COND_INITIALIZER; + +static void *inc_count(void *args) { + int i; + int ret; + + for (i=0; i< TCOUNT; i++) { + ret = pthread_mutex_lock(&count_mutex); + TEST_ASSERT(ret == 0); + + count++; + + // If condition is reached signal condition + if (count == COUNT_LIMIT) { + ret = pthread_cond_signal(&count_threshold_cv); + TEST_ASSERT(ret == 0); + } + + ret = pthread_mutex_unlock(&count_mutex); + TEST_ASSERT(ret == 0); + + // Simulate some work + usleep(1000); + } + + return NULL; + } + +static void *watch_count(void *args) { + int ret; + + ret = pthread_mutex_lock(&count_mutex); + TEST_ASSERT(ret == 0); + + while (count < COUNT_LIMIT) { + ret = pthread_cond_wait(&count_threshold_cv, &count_mutex); + TEST_ASSERT(ret == 0); + } + + count += 125; + TEST_ASSERT (count == 125 + COUNT_LIMIT); + + ret = pthread_mutex_unlock(&count_mutex); + TEST_ASSERT(ret == 0); + + return NULL; +} + +TEST_CASE("pthread conditions", "[pthread]") { + pthread_attr_t attr; + int i, ret; + + // Initialize mutex and condition variable objects + ret = pthread_mutex_init(&count_mutex, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_cond_init(&count_threshold_cv, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_attr_init(&attr); + TEST_ASSERT(ret == 0); + + ret = pthread_attr_setstacksize(&attr, 10240); + TEST_ASSERT(ret == 0); + + ret = pthread_create(&threads[0], &attr, watch_count, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_create(&threads[1], &attr, inc_count, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_create(&threads[2], &attr, inc_count, NULL); + TEST_ASSERT(ret == 0); + + // Wait for all threads completion + for (i=0; i< NUM_THREADS; i++) { + ret = pthread_join(threads[i], NULL); + TEST_ASSERT(ret == 0); + } + + // Clean up + ret = pthread_attr_destroy(&attr); + TEST_ASSERT(ret == 0); + + ret = pthread_mutex_destroy(&count_mutex); + TEST_ASSERT(ret == 0); + + ret = pthread_cond_destroy(&count_threshold_cv); + TEST_ASSERT(ret == 0); +} diff --git a/libiot/pthread/test/pthread-join.c b/libiot/pthread/test/pthread-join.c new file mode 100644 index 0000000..ba5f976 --- /dev/null +++ b/libiot/pthread/test/pthread-join.c @@ -0,0 +1,85 @@ +#include "unity.h" + +#include +#include +#include + +#include + +#include + +#define NUM_THREADS 2 + +static pthread_t threads[NUM_THREADS]; + +static void *thread1(void *args) { + int count = 0; + + for (count = 0; count < 100; count++) { + // Simulate some work + usleep(1000); + } + + int *ret = malloc(sizeof(int)); + *ret = count; + + pthread_exit(ret); + } + +static void *thread2(void *args) { + int count; + + for (count = 0; count < 50; count++) { + // Simulate some work + usleep(1000); + } + + int *ret = malloc(sizeof(int)); + *ret = count; + + pthread_exit(ret); + } + + +TEST_CASE("pthread join", "[pthread]") { + pthread_attr_t attr; + int i, ret; + int *res; + + ret = pthread_attr_init(&attr); + TEST_ASSERT(ret == 0); + + ret = pthread_create(&threads[0], &attr, thread1, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_create(&threads[1], &attr, thread2, NULL); + TEST_ASSERT(ret == 0); + + // Wait for all threads completion + for (i=0; i< NUM_THREADS; i++) { + ret = pthread_join(threads[i], (void **)&res); + TEST_ASSERT(ret == 0); + + if (i == 0) { + TEST_ASSERT(*res == 100); + } else if (i == 1) { + TEST_ASSERT(*res == 50); + } + + free(res); + } + + // Check that a detached thread is not joinable + ret = pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); + TEST_ASSERT(ret == 0); + + ret = pthread_create(&threads[0], &attr, thread1, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_join(threads[0], (void **)&res); + TEST_ASSERT(ret == EINVAL); + + // Clean up + ret = pthread_attr_destroy(&attr); + TEST_ASSERT(ret == 0); +} diff --git a/libiot/pthread/test/pthread-once.c b/libiot/pthread/test/pthread-once.c new file mode 100644 index 0000000..d3a3db6 --- /dev/null +++ b/libiot/pthread/test/pthread-once.c @@ -0,0 +1,68 @@ +#include "unity.h" + +#include +#include + +#define NUM_THREADS 2 + +static pthread_t threads[NUM_THREADS]; + +static pthread_once_t once = PTHREAD_ONCE_INIT; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static int execs; + +static void init() { + int ret; + + ret = pthread_mutex_lock(&mutex); + TEST_ASSERT(ret == 0); + + execs++; + + ret = pthread_mutex_unlock(&mutex); + TEST_ASSERT(ret == 0); +} + +static void *thread1(void *args) { + int ret; + + ret = pthread_once(&once, init); + TEST_ASSERT(ret == 0); + + // Simulate some work + usleep(1000); + + pthread_exit(NULL); + } + +TEST_CASE("pthread once", "[pthread]") { + pthread_attr_t attr; + int i, ret; + + ret = pthread_attr_init(&attr); + TEST_ASSERT(ret == 0); + + ret = pthread_mutex_init(&mutex, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_create(&threads[0], &attr, thread1, NULL); + TEST_ASSERT(ret == 0); + + ret = pthread_create(&threads[1], &attr, thread1, NULL); + TEST_ASSERT(ret == 0); + + // Wait for all threads completion + for (i=0; i< NUM_THREADS; i++) { + ret = pthread_join(threads[i], NULL); + TEST_ASSERT(ret == 0); + } + + TEST_ASSERT(execs == 1); + + // Clean up + ret = pthread_attr_destroy(&attr); + TEST_ASSERT(ret == 0); + + ret = pthread_mutex_destroy(&mutex); + TEST_ASSERT(ret == 0); +} diff --git a/libiot/ramfs/ramfs.c b/libiot/ramfs/ramfs.c new file mode 100644 index 0000000..3362814 --- /dev/null +++ b/libiot/ramfs/ramfs.c @@ -0,0 +1,1088 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS, RAM file system + * + */ + +#include "ramfs.h" + +#include +#include +#include +#include +#include + +static int add_reference(ramfs_t *fs, ramfs_entry_t *entry); +static void remove_reference(ramfs_t *fs, ramfs_entry_t *entry); +static int get_reference_uses(ramfs_t *fs, ramfs_entry_t *entry); +static void remove_block(ramfs_t *fs, ramfs_file_t *file); +static int add_block(ramfs_t *fs, ramfs_file_t *file); +static int add_entry(ramfs_t *fs, const char *name, ramfs_entry_t *parent, ramfs_entry_t **entry, ramfs_entry_type_t entry_type); +static void remove_entry(ramfs_t *fs, ramfs_entry_t *entry, ramfs_entry_t *parent_entry, ramfs_entry_t *prev_entry, int remove); +static int traverse(ramfs_t *fs, const char *path, ramfs_entry_t **entry, ramfs_entry_t **parent_entry, ramfs_entry_t **prev_entry, int creat, ramfs_entry_type_t type); +static ramfs_off_t ramfs_file_seek_internal(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t offset, ramfs_whence_t whence); +static int ramfs_file_truncate_internal(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t size); + +static int add_reference(ramfs_t *fs, ramfs_entry_t *entry) { + ramfs_entry_ref_t *cref; + + // Find entry into the reference set + cref = fs->ref; + while (cref && (cref->entry != entry)) { + cref = cref->next; + } + + if (!cref) { + // Entry not found, add it + ramfs_entry_ref_t *ref = calloc(1, sizeof(ramfs_entry_ref_t)); + if (!ref) { + return RAMFS_ERR_NOMEM; + } + + ref->entry = entry; + ref->next = fs->ref; + ref->uses = 1; + + fs->ref = ref; + } else { + // Entry found, just increment the uses counter + cref->uses++; + } + + return RAMFS_ERR_OK; +} + +static void remove_reference(ramfs_t *fs, ramfs_entry_t *entry) { + ramfs_entry_ref_t *cref; + ramfs_entry_ref_t *prev_ref = NULL; + + if (!entry) return; + + // Find entry into the reference set + cref = fs->ref; + while (cref && (cref->entry != entry)) { + prev_ref = cref; + cref = cref->next; + } + + if (cref) { + // Entry found + if (cref->uses > 0) { + // Decrement the uses counter + cref->uses--; + } + + if (cref->uses == 0) { + // Not used, remove it + if (!prev_ref) { + // It is the first reference + fs->ref = cref->next; + } else { + prev_ref->next = cref->next; + } + + free(cref); + + if (entry->flags & RAMFS_ENTRY_RM_LEN_MSK) { + // The entry is marked for remove, remove now + remove_entry(fs, entry, NULL, NULL, ((entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_FILE)); + } + } + } +} + +static int get_reference_uses(ramfs_t *fs, ramfs_entry_t *entry) { + ramfs_entry_ref_t *cref; + + // Find entry into the reference set + cref = fs->ref; + while (cref && (cref->entry != entry)) { + cref = cref->next; + } + + int uses = 0; + + if (cref) { + uses = cref->uses; + } + + return uses; +} + +static void remove_block(ramfs_t *fs, ramfs_file_t *file) { + ramfs_block_t *block; + + block = file->entry->file.header->tail; + if (block) { + // Find the block into the file block chain to + // update the block chain + ramfs_block_t *prev_block = NULL; + ramfs_block_t *cblock = file->entry->file.header->head; + + while (cblock && (cblock != block)) { + prev_block = cblock; + cblock = cblock->next; + } + + if (prev_block) { + prev_block->next = NULL; + file->entry->file.header->tail = prev_block; + } else { + file->entry->file.header->head = NULL; + file->entry->file.header->tail = NULL; + } + + // Free block + free(block); + + // Update the file system size + fs->current_size -= sizeof(ramfs_block_t) + fs->block_size - 1; + } +} + +static int add_block(ramfs_t *fs, ramfs_file_t *file) { + // Check for space + ramfs_size_t size = sizeof(ramfs_block_t) + fs->block_size - 1; + + if (fs->current_size + size > fs->size) { + return RAMFS_ERR_NOSPC; + } + + // Create block + ramfs_block_t *block = (ramfs_block_t *)calloc(1, size); + if (!block) { + return RAMFS_ERR_NOMEM; + } + + if (!file->entry->file.header->head) { + // First block of the file + file->entry->file.header->head = block; + file->entry->file.header->tail = block; + } else { + // Add the block to the end of the file block chain + file->entry->file.header->tail->next = block; + file->entry->file.header->tail = block; + } + + file->block = block; + file->ptr = block->data; + + // Update the file system size + fs->current_size += size; + + return RAMFS_ERR_OK; +} + +static int add_entry(ramfs_t *fs, const char *name, ramfs_entry_t *parent, ramfs_entry_t **entry, ramfs_entry_type_t entry_type) { + int name_len = strlen(name); + + // Check for space + ramfs_size_t entry_size = sizeof(ramfs_entry_t) + name_len - 1; + ramfs_size_t header_size = 0; + + if (entry_type == RAMFS_FILE) { + header_size = sizeof(ram_file_header_t); + } + + if (fs->current_size + entry_size + header_size > fs->size) { + return RAMFS_ERR_NOSPC; + } + + // Create the entry + *entry = (ramfs_entry_t *)calloc(1, entry_size); + if (!*entry) { + return RAMFS_ERR_NOMEM; + } + + (*entry)->flags |= entry_type; + + // Create the file header + if (entry_type == RAMFS_FILE) { + (*entry)->file.header = (ram_file_header_t *)calloc(1, header_size); + if (!((*entry)->file.header)) { + free(*entry); + + return RAMFS_ERR_NOMEM; + } + + (*entry)->file.header->head = NULL; + (*entry)->file.header->tail = NULL; + (*entry)->file.header->size = 0; + } + + // Set the entry name and len + memcpy((*entry)->name, name, name_len); + + (*entry)->flags |= (name_len << RAMFS_ENTRY_NAME_LEN_POS); + + // Update the file system size + fs->current_size += entry_size + header_size; + + // Add the entry to the file system + if (!fs->child) { + // The entry is the first child of the root directory + fs->child = *entry; + } else { + if (parent) { + // Add the entry to its parent (which is always a directory) + if (!parent->dir.child) { + // The entry is the first child of the directory + parent->dir.child = *entry; + } else { + // Add the entry into the directory child chain + ramfs_entry_t *centry = parent->dir.child; + + while (centry->next) { + centry = centry->next; + } + + centry->next = *entry; + } + } else { + // Add the entry to the child chain of the root directory + ramfs_entry_t *centry = fs->child; + + while (centry->next) { + centry = centry->next; + } + + centry->next = *entry; + } + } + + return RAMFS_ERR_OK; +} + +static void remove_entry(ramfs_t *fs, ramfs_entry_t *entry, ramfs_entry_t *parent_entry, ramfs_entry_t *prev_entry, int remove) { + if (!entry) return; + + // Update the chain + if (prev_entry) { + prev_entry->next = entry->next; + } + + if (parent_entry && (parent_entry->dir.child == entry)) { + parent_entry->dir.child = entry->next; + } else if (!parent_entry) { + if (fs->child == entry) { + fs->child = entry->next; + } + } + + // If the entry to remove is used, mark it for remove later + if (get_reference_uses(fs, entry) > 0) { + entry->flags |= RAMFS_ENTRY_RM_LEN_MSK; + return; + } + + // While removing the entry, compute the entry size to update the file system size later + ramfs_size_t size = sizeof(ramfs_entry_t) + ((entry->flags & RAMFS_ENTRY_NAME_LEN_MSK) >> RAMFS_ENTRY_NAME_LEN_POS) - 1; + + if (remove && ((entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_FILE)) { + // Free file blocks + ramfs_block_t *block = entry->file.header->head; + ramfs_block_t *tmp; + + size += sizeof(ram_file_header_t); + + while (block) { + size += sizeof(ramfs_block_t) + fs->block_size - 1; + + tmp = block; + block = block->next; + + free(tmp); + } + + free(entry->file.header); + } + + free(entry); + + // Update the file system size + fs->current_size -= size; +} + +static int traverse(ramfs_t *fs, const char *path, ramfs_entry_t **entry, ramfs_entry_t **parent_entry, ramfs_entry_t **prev_entry, int creat, ramfs_entry_type_t type) { + ramfs_error_t ret; + + ramfs_entry_t *centry; // Curren entry + + // A copy of path to use with strtok_r + char path_copy[PATH_MAX + 1]; + + char *component; // Current path component + char *rest; // Rest of path + + *entry = NULL; + + if (prev_entry) { + *prev_entry = NULL; + } + + if (!path || !*path) { + return RAMFS_ERR_NOENT; + } + + if (strlen(path) > PATH_MAX) { + return RAMFS_ERR_NAMETOOLONG; + } + + // Make a copy of path to use it with strtok_r + strcpy(path_copy, path); + rest = path_copy; + + // Start at the root directory + centry = fs->child; + *entry = centry; + + if (!centry) { + // The file system doesn't contain any entry yet, then create a new entry + component = strtok_r(rest, "/", &rest); + + if (creat && ((ret = add_entry(fs, component, NULL, entry, type)) != RAMFS_ERR_OK)) { + return ret; + } + + return RAMFS_ERR_NOENT; + } + + + int name_len; + + ramfs_entry_t *parent = NULL; + + if (parent_entry) { + *parent_entry = parent; + } + + while ((component = strtok_r(rest, "/", &rest))) { + while (centry) { + name_len = ((centry->flags & RAMFS_ENTRY_NAME_LEN_MSK) >> RAMFS_ENTRY_NAME_LEN_POS); + if ((strlen(component) == name_len) && bcmp(centry->name, component, name_len) == 0) { + // The entry has been found + *entry = centry; + break; + } else { + // Next entry + if (prev_entry) { + *prev_entry = centry; + } + + centry = centry->next; + } + } + + if (!centry) { + // The entry was not found, create it, if it corresponds to the last + // path component + if (!rest) { + if (creat && ((ret = add_entry(fs, component, parent, entry, type)) != RAMFS_ERR_OK)) { + return ret; + } + } else { + *entry = NULL; + + if (parent_entry) { + *parent_entry = NULL; + } + + if (prev_entry) { + *prev_entry = NULL; + } + } + + return RAMFS_ERR_NOENT; + } else { + parent = centry; + + if ((centry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) { + // For next path component, start the search into the directory child chain + centry = centry->dir.child; + + if (rest) { + if (parent_entry) { + *parent_entry = parent; + } + + if (prev_entry) { + *prev_entry = NULL; + } + } + } else { + // The current path component is a file, check that there are not more + // path components + if (rest) { + return RAMFS_ERR_NOTDIR; + } + } + } + } + + return RAMFS_ERR_OK; +} + +static ramfs_off_t ramfs_file_seek_internal(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t offset, ramfs_whence_t whence) { + if (whence == RAMFS_SEEK_SET) { + if (offset >= 0) { + file->offset = offset; + } else { + return RAMFS_ERR_INVAL; + } + } else if (whence == RAMFS_SEEK_CUR) { + if (file->offset + offset >= 0) { + file->offset += offset; + } else { + return RAMFS_ERR_INVAL; + } + } else if (whence == RAMFS_SEEK_END) { + if (offset + file->entry->file.header->size >= 0) { + file->offset = offset + file->entry->file.header->size; + } else { + return RAMFS_ERR_INVAL; + } + } else { + return RAMFS_ERR_INVAL; + } + + int block_num = file->offset / fs->block_size; + int block_off = file->offset % fs->block_size; + + if (file->entry->file.header->head) { + ramfs_block_t *block = file->entry->file.header->head; + int curr_block; + + for(curr_block = 0;block && (curr_block < block_num);curr_block++) { + block = block->next; + } + + file->block = block; + file->ptr = (block?block->data + block_off:NULL); + } else { + file->block = NULL; + file->ptr = NULL; + } + + return block_num * fs->block_size + block_off; +} + +static int ramfs_file_truncate_internal(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t size) { + ramfs_error_t ret; + + int access_mode = (file->flags & RAMFS_ACCMODE); + + if ((access_mode != RAMFS_O_WRONLY) && (access_mode != RAMFS_O_RDWR)) { + return RAMFS_ERR_BADF; + } + + if ((file->entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) { + return RAMFS_ERR_ISDIR; + } + + if (size < 0) { + return RAMFS_ERR_INVAL; + } + + int block_delta = ((size - 1) / fs->block_size) - ((file->entry->file.header->size - 1) / fs->block_size); + + if (block_delta < 0) { + // Decrease size + while (block_delta < 0) { + remove_block(fs, file); + block_delta++; + } + } else if (block_delta > 0) { + // Increase size + while (block_delta > 0) { + if ((ret = add_block(fs, file)) != RAMFS_ERR_OK) { + return ret; + } + + block_delta--; + } + } + + file->entry->file.header->size = size; + + ramfs_file_seek_internal(fs, file, file->offset, RAMFS_SEEK_SET); + + return RAMFS_ERR_OK; +} + +int ramfs_mount(ramfs_t *fs, ramfs_config_t *config) { + memset(fs, 0, sizeof(ramfs_t)); + + ramfs_lock_init(fs->lock); + + fs->size = config->size; + fs->block_size = config->block_size; + + return RAMFS_ERR_OK; +} + +int ramfs_umount(ramfs_t *fs) { + int top = 0; + ramfs_entry_t *stack[256]; + ramfs_entry_t *centry; + ramfs_entry_t *next_entry = NULL; + ramfs_entry_t *parent_entry = NULL; + + ramfs_lock(fs->lock); + + stack[top] = NULL; + + while (top >= 0) { + centry = stack[top]; + if (!centry) { + top--; + centry = fs->child; + parent_entry = NULL; + } else { + parent_entry = centry; + centry = centry->dir.child; + } + + while (centry) { + next_entry = centry->next; + + if ((centry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_FILE) { + remove_entry(fs, centry, NULL, NULL, 1); + } else if ((centry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) { + if (!centry->dir.child) { + remove_entry(fs, centry, NULL, NULL, 1); + } else { + stack[++top] = centry; + } + } + + centry = next_entry; + } + + if (parent_entry && (stack[top] == parent_entry)) { + remove_entry(fs, parent_entry, NULL, NULL, 1); + top--; + } + } + + ramfs_lock_destroy(fs->lock); + + memset(fs, 0, sizeof(ramfs_t)); + + return RAMFS_ERR_OK; +} + + +int ramfs_mkdir(ramfs_t *fs, const char *path) { + ramfs_entry_t *entry; + ramfs_error_t ret; + + if (strcmp(path,"/") == 0) { + return RAMFS_ERR_EXIST; + } + + ramfs_lock(fs->lock); + ret = traverse(fs, path, &entry, NULL, NULL, 1, RAMFS_DIR); + ramfs_unlock(fs->lock); + + if (ret != RAMFS_ERR_OK) { + if (ret == RAMFS_ERR_NOENT) { + if (entry) { + return RAMFS_ERR_OK; + } else { + return RAMFS_ERR_NOENT; + } + } else { + return ret; + } + } else { + return RAMFS_ERR_EXIST; + } + + return RAMFS_ERR_OK; +} + +int ramfs_dir_open(ramfs_t *fs, ramfs_dir_t *dir, const char *path) { + ramfs_entry_t *entry; + ramfs_error_t ret; + + memset(dir, 0, sizeof(ramfs_dir_t)); + + if (strcmp(path,"/") == 0) { + ramfs_lock(fs->lock); + dir->child = fs->child; + ramfs_unlock(fs->lock); + } else { + ramfs_lock(fs->lock); + + ret = traverse(fs, path, &entry, NULL, NULL, 0, 0); + if (ret != RAMFS_ERR_OK) { + ramfs_unlock(fs->lock); + + dir->offset = -1; + + return ret; + } + + if ((entry->flags & RAMFS_ENTRY_TYPE_MSK) != RAMFS_DIR) { + ramfs_unlock(fs->lock); + + return RAMFS_ERR_NOTDIR; + } + + ret = add_reference(fs, entry); + if (ret != RAMFS_ERR_OK) { + ramfs_unlock(fs->lock); + + return ret; + } + + dir->entry = entry; + dir->child = entry->dir.child; + + ramfs_unlock(fs->lock); + } + + return RAMFS_ERR_OK; +} + +int ramfs_dir_read(ramfs_t *fs, ramfs_dir_t *dir, ramfs_info_t *info) { + if (dir->offset < 0) { + return RAMFS_ERR_BADF; + } + + if (!dir->child) { + dir->offset = -1; + + return RAMFS_ERR_NOENT; + } + + int name_len = ((dir->child->flags & RAMFS_ENTRY_NAME_LEN_MSK) >> RAMFS_ENTRY_NAME_LEN_POS); + + memcpy(info->name, dir->child->name, name_len); + *(info->name + name_len) = 0; + + info->type = (dir->child->flags & RAMFS_ENTRY_TYPE_MSK); + info->size = 0; + + if (info->type == RAMFS_FILE) { + info->size = dir->child->file.header->size; + } + + dir->child = dir->child->next; + + dir->offset++; + + return RAMFS_ERR_OK; +} + +int ramfs_dir_close(ramfs_t *fs, ramfs_dir_t *dir) { + ramfs_lock(fs->lock); + remove_reference(fs, dir->entry); + ramfs_unlock(fs->lock); + + return RAMFS_ERR_OK; +} + +ramfs_off_t ramfs_telldir(ramfs_t *fs, ramfs_dir_t *dir) { + if (dir->offset < 0) { + return RAMFS_ERR_BADF; + } + + return dir->offset; +} + +int ramfs_stat(ramfs_t *fs, const char *path, ramfs_info_t *info) { + ramfs_entry_t *entry; + ramfs_error_t ret; + + if (strcmp(path,"/") == 0) { + strcpy(info->name, ""); + info->type = RAMFS_DIR; + } else { + ramfs_lock(fs->lock); + + ret = traverse(fs, path, &entry, NULL, NULL, 0, 0); + if (ret != RAMFS_ERR_OK) { + ramfs_unlock(fs->lock); + + return ret; + } + + int name_len = ((entry->flags & RAMFS_ENTRY_NAME_LEN_MSK) >> RAMFS_ENTRY_NAME_LEN_POS); + + memcpy(info->name, entry->name, name_len); + *(info->name + name_len) = 0; + + info->type = entry->flags & RAMFS_ENTRY_TYPE_MSK; + info->size = 0; + + if (info->type == RAMFS_FILE) { + info->size = entry->file.header->size; + } + + ramfs_unlock(fs->lock); + } + + return RAMFS_ERR_OK; +} + +int ramfs_file_stat(ramfs_t *fs, ramfs_file_t *file, ramfs_info_t *info) { + if (!file->entry) { + return RAMFS_ERR_BADF; + + } + + ramfs_lock(fs->lock); + + int name_len = ((file->entry->flags & RAMFS_ENTRY_NAME_LEN_MSK) >> RAMFS_ENTRY_NAME_LEN_POS); + + memcpy(info->name, file->entry->name, name_len); + *(info->name + name_len) = 0; + + info->type = file->entry->flags & RAMFS_ENTRY_TYPE_MSK; + info->size = 0; + + if (info->type == RAMFS_FILE) { + info->size = file->entry->file.header->size; + } + + ramfs_unlock(fs->lock); + + return RAMFS_ERR_OK; +} + +int ramfs_file_open(ramfs_t *fs, ramfs_file_t *file, const char *path, int flags) { + ramfs_entry_t *entry; + ramfs_error_t ret; + + int access_mode = (flags & RAMFS_ACCMODE); + if ((access_mode != RAMFS_O_RDONLY) && (access_mode != RAMFS_O_WRONLY) && (access_mode != RAMFS_O_RDWR)) { + return RAMFS_ERR_ACCESS; + } + + ramfs_lock(fs->lock); + + ret = traverse(fs, path, &entry, NULL, NULL, flags & RAMFS_O_CREAT, RAMFS_FILE); + if (ret == RAMFS_ERR_NOENT) { + // File doesn't exist + if (!(flags & RAMFS_O_CREAT)) { + ramfs_unlock(fs->lock); + + // If no O_CREAT file is present, exit + return RAMFS_ERR_NOENT; + } + } else if ((ret == RAMFS_ERR_OK) && (flags & RAMFS_O_EXCL) && (flags & RAMFS_O_CREAT)) { + ramfs_unlock(fs->lock); + + return RAMFS_ERR_EXIST; + } else if (ret != RAMFS_ERR_OK) { + ramfs_unlock(fs->lock); + + return ret; + } + + ret = add_reference(fs, entry); + if (ret != RAMFS_ERR_OK) { + ramfs_unlock(fs->lock); + + return ret; + } + + // Prepare file structure + memset(file, 0, sizeof(ramfs_file_t)); + + file->entry = entry; + file->flags = flags; + + // Truncate file, if required + if ((flags & RAMFS_O_TRUNC) && ((access_mode == RAMFS_O_WRONLY) || (access_mode == RAMFS_O_RDWR))) { + ret = ramfs_file_truncate_internal(fs, file, 0); + if (ret != RAMFS_ERR_OK) { + ramfs_unlock(fs->lock); + + return ret; + } + } + + // Set file position + if (flags & RAMFS_O_APPEND){ + ret = ramfs_file_seek_internal(fs, file, 0, RAMFS_SEEK_END); + assert(ret >= 0); + } else { + ret = ramfs_file_seek_internal(fs, file, 0, RAMFS_SEEK_SET); + assert(ret >= 0); + } + + ramfs_unlock(fs->lock); + + return RAMFS_ERR_OK; +} + +int ramfs_file_sync(ramfs_t *fs, ramfs_file_t *file) { + return RAMFS_ERR_OK; +} + +ramfs_size_t ramfs_file_read(ramfs_t *fs, ramfs_file_t *file, void *buffer, ramfs_size_t size) { + int access_mode = (file->flags & RAMFS_ACCMODE); + + if ((access_mode != RAMFS_O_RDONLY) && (access_mode != RAMFS_O_RDWR)) { + return RAMFS_ERR_BADF; + } + + ramfs_lock(fs->lock); + + ramfs_size_t reads = 0; + while ((reads < size) && (file->offset < file->entry->file.header->size)) { + if ((!file->block) || (file->ptr > file->block->data + fs->block_size - 1)) { + if (file->block) { + file->block = file->block->next; + if (file->block) { + file->ptr = file->block->data; + } else { + file->ptr = NULL; + ramfs_unlock(fs->lock); + + return reads; + } + } else { + file->block = NULL; + file->ptr = NULL; + + ramfs_unlock(fs->lock); + return reads; + } + } + + *(((uint8_t *)buffer++)) = *(file->ptr++); + + reads++; + file->offset++; + } + + ramfs_unlock(fs->lock); + + return reads; +} + +ramfs_size_t ramfs_file_write(ramfs_t *fs, ramfs_file_t *file, const void *buffer, ramfs_size_t size) { + ramfs_error_t ret; + + int access_mode = (file->flags & RAMFS_ACCMODE); + + if ((access_mode != RAMFS_O_WRONLY) && (access_mode != RAMFS_O_RDWR)) { + return RAMFS_ERR_BADF; + } + + ramfs_lock(fs->lock); + + ramfs_size_t writes = 0; + while (writes < size) { + if ((!file->block) || (!file->ptr) || (file->ptr > file->block->data + fs->block_size - 1)) { + if (file->block && file->block->next) { + file->block = file->block->next; + file->ptr = file->block->data; + } else { + ret = add_block(fs, file); + if (ret != RAMFS_ERR_OK) { + file->block = NULL; + file->ptr = NULL; + ramfs_unlock(fs->lock); + return ret; + } + } + } + + *(file->ptr++) = *(((uint8_t *)buffer++)); + + writes++; + file->offset++; + + if (file->offset > file->entry->file.header->size) { + file->entry->file.header->size++; + } + } + + ramfs_unlock(fs->lock); + + return writes; +} + +int ramfs_file_truncate(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t size) { + ramfs_error_t ret; + + ramfs_lock(fs->lock); + ret = ramfs_file_truncate_internal(fs, file, size); + ramfs_unlock(fs->lock); + + return ret; +} + +int ramfs_file_close(ramfs_t *fs, ramfs_file_t *file) { + ramfs_lock(fs->lock); + remove_reference(fs, file->entry); + ramfs_unlock(fs->lock); + + memset(file, 0, sizeof(ramfs_file_t)); + + return RAMFS_ERR_OK; +} + +ramfs_off_t ramfs_file_seek(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t offset, ramfs_whence_t whence) { + ramfs_off_t ret; + + ramfs_lock(fs->lock); + ret = ramfs_file_seek_internal(fs, file, offset, whence); + ramfs_unlock(fs->lock); + + return ret; +} + +int ramfs_rename(ramfs_t *fs, const char *oldpath, const char *newpath) { + ramfs_entry_t *old_entry; + ramfs_entry_t *parent_old_entry; + ramfs_entry_t *prev_old_entry; + ramfs_entry_t *new_entry; + ramfs_error_t ret; + + ramfs_lock(fs->lock); + + ret = traverse(fs, oldpath, &old_entry, &parent_old_entry, &prev_old_entry, 0, 0); + if (ret != RAMFS_ERR_OK) { + ramfs_unlock(fs->lock); + return ret; + } + + ret = traverse(fs, newpath, &new_entry, NULL, NULL, 1, old_entry->flags & RAMFS_ENTRY_TYPE_MSK); + if (ret == RAMFS_ERR_OK) { + if (((new_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) && ((old_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_FILE)) { + ramfs_unlock(fs->lock); + return RAMFS_ERR_ISDIR; + } else if (((new_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) && (new_entry->dir.child)) { + ramfs_unlock(fs->lock); + return RAMFS_ERR_NOTEMPTY; + } + } else if (ret != RAMFS_ERR_NOENT) { + ramfs_unlock(fs->lock); + return ret; + } + + if ((new_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) { + // New and old entries are directories + // New directory is empty + + // Move all sub-directories and files of the old entry to the new entry + new_entry->dir.child = old_entry->dir.child; + } else if ((old_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_DIR) { + new_entry->dir.child = old_entry->dir.child; + } else if ((old_entry->flags & RAMFS_ENTRY_TYPE_MSK) == RAMFS_FILE) { + new_entry->file.header->head = old_entry->file.header->head; + new_entry->file.header->tail = old_entry->file.header->tail; + new_entry->file.header->size = old_entry->file.header->size; + } + + remove_entry(fs, old_entry, parent_old_entry, prev_old_entry, 0); + + ramfs_unlock(fs->lock); + + return RAMFS_ERR_OK; +} + +int ramfs_rmdir(ramfs_t *fs, const char *path) { + ramfs_entry_t *entry; + ramfs_entry_t *parent_entry; + ramfs_entry_t *prev_entry; + ramfs_error_t ret; + + if (strcmp(path,"/") == 0) { + return RAMFS_ERR_BUSY; + } + + ramfs_lock(fs->lock); + + ret = traverse(fs, path, &entry, &parent_entry, &prev_entry, 0, 0); + if (ret != RAMFS_ERR_OK) { + ramfs_unlock(fs->lock); + return ret; + } + + if ((entry->flags & RAMFS_ENTRY_TYPE_MSK) != RAMFS_DIR) { + ramfs_unlock(fs->lock); + return RAMFS_ERR_NOTDIR; + } + + if (entry->dir.child) { + ramfs_unlock(fs->lock); + return RAMFS_ERR_NOTEMPTY; + } + + remove_entry(fs, entry, parent_entry, prev_entry, 0); + + ramfs_unlock(fs->lock); + + return RAMFS_ERR_OK; +} + +int ramfs_unlink(ramfs_t *fs, const char *pathname) { + ramfs_entry_t *entry; + ramfs_entry_t *parent_entry; + ramfs_entry_t *prev_entry; + ramfs_error_t ret; + + ramfs_lock(fs->lock); + + ret = traverse(fs, pathname, &entry, &parent_entry, &prev_entry, 0, 0); + if (ret != RAMFS_ERR_OK) { + ramfs_unlock(fs->lock); + return ret; + } + + if ((entry->flags & RAMFS_ENTRY_TYPE_MSK) != RAMFS_FILE) { + ramfs_unlock(fs->lock); + return RAMFS_ERR_PERM; + } + + remove_entry(fs, entry, parent_entry, prev_entry, 1); + + ramfs_unlock(fs->lock); + + return RAMFS_ERR_OK; +} diff --git a/libiot/ramfs/ramfs.h b/libiot/ramfs/ramfs.h new file mode 100644 index 0000000..cde76cf --- /dev/null +++ b/libiot/ramfs/ramfs.h @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS, RAM file system + * + */ + +/** + * @brief RAM file system (RAMFS) + * + * The RAMFS is a POSIX-compliance file system entirely stored in RAM, without + * persistence, which means that all the data stored in the file system is + * lost on each reboot. + * + * In RAMFS, the file system is stored in a tree structure, in which there are + * 2 types of nodes (entries): directories, and files. + * + * RAMFS structure overview: + * + * ---------- + * - RAMFS - + * ---------- + * | + * | child + * \|/ + * ---------------- next ---------------- next ---------------- + * - directory or - ------> - file - ------> - directory - + * - file entry - - entry - - entry - + * ---------------- ---------------- ---------------- + * | | + * | header | child + * \|/ \|/ + * --------------- ---------------- + * - file header - - directory or - + * --------------- - file entry - + * | ---------------- + * | + * \|/ + * --------- next --------- + * - block - ----> - block - + * --------- --------- + */ + +#ifndef _RAMFS_H_ +#define _RAMFS_H_ + +#include +#include +#include +#include +#include +#include + +#if PATH_MAX > 64 +#error "ramfs, PATH_MAX must be <= 64" +#endif + +/* + * To use the file system in a multi-threaded environment, + * define the lock type (mutex) used in your platform. + */ +#define ramfs_lock_t struct mtx + +/* + * If ramfs_lock_t is defined, define the following macros: + * + * ramfs_lock_init(l): initialize / create the lock + * ramfs_lock_destroy(l): destroy the lock + * ramfs_lock(l): obtain the lock + * ramfs_unlock(l): release the lock + */ +#ifdef ramfs_lock_t +#define ramfs_lock_init(l) \ + mtx_init(&l, NULL, NULL, 0); + +#define ramfs_lock_destroy(l) \ + mtx_destroy(&l); + +#define ramfs_lock(l) \ + mtx_lock(&l); + +#define ramfs_unlock(l) \ + mtx_unlock(&l); +#else +#define ramfs_lock_init() +#define ramfs_lock_destroy() +#define ramfs_lock() +#define ramfs_unlock() +#endif + +typedef int32_t ramfs_off_t; +typedef int32_t ramfs_size_t; + +/** + * @brief File system entry flags. + * + * bit0..bit0: entry type + * bit1..bit6: length of the name of the entry + * bit7..bit7: entry is deleted from the file system, but not still removed + * + */ +typedef uint8_t ramfs_entry_flags_t; + +#define RAMFS_ENTRY_TYPE_MSK 0b00000001 +#define RAMFS_ENTRY_NAME_LEN_MSK 0b01111110 +#define RAMFS_ENTRY_NAME_LEN_POS 1 +#define RAMFS_ENTRY_RM_LEN_MSK 0b10000000 + +typedef enum { + RAMFS_DIR = 0, + RAMFS_FILE = 1 +} ramfs_entry_type_t; + +typedef enum { + RAMFS_ERR_OK = 0, + RAMFS_ERR_NOMEM = -1, + RAMFS_ERR_NOENT = -2, + RAMFS_ERR_EXIST = -3, + RAMFS_ERR_NOTDIR = -4, + RAMFS_ERR_BADF = -5, + RAMFS_ERR_ACCESS = -6, + RAMFS_ERR_NOSPC = -7, + RAMFS_ERR_INVAL = -8, + RAMFS_ERR_ISDIR = -9, + RAMFS_ERR_NOTEMPTY = -10, + RAMFS_ERR_BUSY = -11, + RAMFS_ERR_PERM = -12, + RAMFS_ERR_NAMETOOLONG = -13, +} ramfs_error_t; + +typedef enum { + RAMFS_O_RDONLY = 1, // Open a file as read only + RAMFS_O_WRONLY = 2, // Open a file as write only + RAMFS_O_RDWR = 3, // Open a file as read and write + RAMFS_O_CREAT = 0x0100, // Create a file if it does not exist + RAMFS_O_EXCL = 0x0200, // Fail if a file already exists + RAMFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size + RAMFS_O_APPEND = 0x0800, // Move to end of file on every write +} ramfs_flags_t; + +#define RAMFS_ACCMODE (RAMFS_O_RDONLY | RAMFS_O_WRONLY | RAMFS_O_RDWR) + +typedef enum { + RAMFS_SEEK_SET = 1, + RAMFS_SEEK_CUR = 2, + RAMFS_SEEK_END = 3, +} ramfs_whence_t; + +typedef struct ramfs_block { + struct ramfs_block *next; /*!< Next block in chain */ + uint8_t data[1]; /*!< Block data */ +} ramfs_block_t; + +typedef struct ram_file_header { + ramfs_block_t *head; /*!< File head */ + ramfs_block_t *tail; /*!< File tail */ + ramfs_size_t size; /*!< File size */ +} ram_file_header_t; + +typedef struct ramfs_entry { + ramfs_entry_flags_t flags; /*!< Entry flags */ + struct ramfs_entry *next; /*!< Next entry */ + union { + struct { + struct ram_file_header *header; /*!< File header */ + } file; + struct { + struct ramfs_entry *child; /*!< Entry type */ + } dir; + }; + char name[1]; /*!< Entry name */ +} ramfs_entry_t; + +typedef struct { + ramfs_off_t offset; /*!< Current seek offset */ + ramfs_entry_t *entry; /*!< Directory entry */ + ramfs_entry_t *child; /*!< Directory child chain */ +} ramfs_dir_t; + +typedef struct { + ramfs_entry_t *entry; /*!< File entry reference */ + uint32_t flags; /*!< Open flags */ + ramfs_off_t offset; /*!< Current seek offset */ + ramfs_block_t *block; /*!< Current read/write block */ + uint8_t *ptr; /*!< Current read/write pointer into current block */ +} ramfs_file_t; + +typedef struct { + char name[PATH_MAX + 1]; + ramfs_entry_type_t type; + ramfs_size_t size; +} ramfs_info_t; + +typedef struct ramfs_entry_ref { + ramfs_entry_t *entry; + uint8_t uses; + struct ramfs_entry_ref *next; +} ramfs_entry_ref_t; + +typedef struct { + ramfs_entry_t *child; /*!< Root directory child chain */ + ramfs_entry_ref_t *ref; /*< Open references to file system entries */ + ramfs_size_t size; + ramfs_size_t current_size; + ramfs_size_t block_size; +#ifdef ramfs_lock_t + ramfs_lock_t lock; +#endif +} ramfs_t; + +typedef struct { + ramfs_size_t size; + ramfs_size_t block_size; +} ramfs_config_t; + +int ramfs_mount(ramfs_t *fs, ramfs_config_t *config); +int ramfs_umount(ramfs_t *fs); +int ramfs_mkdir(ramfs_t *fs, const char *path); +int ramfs_dir_open(ramfs_t *fs, ramfs_dir_t *dir, const char *path); +int ramfs_dir_read(ramfs_t *fs, ramfs_dir_t *dir, ramfs_info_t *info); +int ramfs_dir_close(ramfs_t *fs, ramfs_dir_t *dir); +int ramfs_stat(ramfs_t *fs, const char *path, ramfs_info_t *info); +int ramfs_file_open(ramfs_t *fs, ramfs_file_t *file, const char *path, int flags); +int ramfs_file_sync(ramfs_t *fs, ramfs_file_t *file); +ramfs_size_t ramfs_file_read(ramfs_t *fs, ramfs_file_t *file, void *buffer, ramfs_size_t size); +ramfs_size_t ramfs_file_write(ramfs_t *fs, ramfs_file_t *file, const void *buffer, ramfs_size_t size); +int ramfs_file_close(ramfs_t *fs, ramfs_file_t *file); +ramfs_off_t ramfs_file_seek(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t offset, ramfs_whence_t whence); +int ramfs_rename(ramfs_t *fs, const char *oldpath, const char *newpath); +int ramfs_rmdir(ramfs_t *fs, const char *path); +int ramfs_unlink(ramfs_t *fs, const char *pathname); +ramfs_off_t ramfs_telldir(ramfs_t *fs, ramfs_dir_t *dir); +int ramfs_file_truncate(ramfs_t *fs, ramfs_file_t *file, ramfs_off_t size); +int ramfs_file_stat(ramfs_t *fs, ramfs_file_t *file, ramfs_info_t *info); + +#endif /* _RAMFS_H_ */ diff --git a/libiot/spiffs/k210_spiffs.c b/libiot/spiffs/k210_spiffs.c new file mode 100644 index 0000000..f87a60c --- /dev/null +++ b/libiot/spiffs/k210_spiffs.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS, spiffs port functions + * + */ + +#include "k210_spiffs.h" +//#include "esp_attr.h" +#include "spiffs.h" +#include "../hal/w25qxx.h" + +#include + +#include "../sys/mutex.h" + +#include +//#include + + +#define SPI_FLASH_ALIGN 0 + + + + +static handle_t spi3 = 0; + + +s32_t k210_spi_flash_init() { + spi3 = io_open("/dev/spi3"); + if (!spi3) { + return 0; + } + w25qxx_init(spi3); + + return 1; +} + +void spiffs_lock(spiffs *fs) { + mtx_lock(fs->user_data); +} + +void spiffs_unlock(spiffs *fs) { + mtx_unlock(fs->user_data); +} + +s32_t k210_spi_flash_read(u32_t addr, u32_t size, u8_t *dst) { +#if SPI_FLASH_ALIGN + u32_t aaddr; + u8_t *buff = NULL; + u8_t *abuff = NULL; + u32_t asize; + + asize = size; + + // Align address to 4 byte + aaddr = (addr + (4 - 1)) & (u32_t) -4; + if (aaddr != addr) { + aaddr -= 4; + asize += (addr - aaddr); + } + + // Align size to 4 byte + asize = (asize + (4 - 1)) & (u32_t) -4; + + if ((aaddr != addr) || (asize != size)) { + // Align buffer + buff = malloc(asize + 4); + if (!buff) { + return SPIFFS_ERR_INTERNAL; + } + + abuff = (u8_t *) (((ptrdiff_t) buff + (4 - 1)) & (u32_t) -4); + + if (w25qxx_read_data(aaddr, (void *) abuff, asize) != W25QXX_OK) { + free(buff); + return SPIFFS_ERR_INTERNAL; + } + + memcpy(dst, abuff + (addr - aaddr), size); + + free(buff); + } else { +#endif + if (w25qxx_read_data(addr, (void *) dst, size) != W25QXX_OK) { + return SPIFFS_ERR_INTERNAL; + } +#if SPI_FLASH_ALIGN + } +#endif + return SPIFFS_OK; +} + +s32_t k210_spi_flash_write(u32_t addr, u32_t size, const u8_t *src) { +#if SPI_FLASH_ALIGN + u32_t aaddr; + u8_t *buff = NULL; + u8_t *abuff = NULL; + u32_t asize; + + asize = size; + + // Align address to 4 byte + aaddr = (addr + (4 - 1)) & -4; + if (aaddr != addr) { + aaddr -= 4; + asize += (addr - aaddr); + } + + // Align size to 4 byte + asize = (asize + (4 - 1)) & -4; + + if ((aaddr != addr) || (asize != size)) { + // Align buffer + buff = malloc(asize + 4); + if (!buff) { + return SPIFFS_ERR_INTERNAL; + } + + abuff = (u8_t *) (((ptrdiff_t) buff + (4 - 1)) & -4); + + if (w25qxx_read_data(aaddr, (void *) abuff, asize) != W25QXX_OK) { + free(buff); + return SPIFFS_ERR_INTERNAL; + } + + memcpy(abuff + (addr - aaddr), src, size); + + if (w25qxx_write_data(aaddr, (uint8_t *) abuff, asize) != W25QXX_OK) { + free(buff); + return SPIFFS_ERR_INTERNAL; + } + + free(buff); + } else { +#endif + if (w25qxx_write_data(addr, (uint8_t *) src, size) != W25QXX_OK) { + return SPIFFS_ERR_INTERNAL; + } +#if SPI_FLASH_ALIGN + } +#endif + return SPIFFS_OK; +} + +s32_t k210_spi_flash_erase(u32_t addr, u32_t size) { + if (w25qxx_sector_erase(addr >> 12) != W25QXX_OK) { + return SPIFFS_ERR_INTERNAL; + } + + return SPIFFS_OK; +} diff --git a/libiot/spiffs/k210_spiffs.h b/libiot/spiffs/k210_spiffs.h new file mode 100644 index 0000000..9edfa2d --- /dev/null +++ b/libiot/spiffs/k210_spiffs.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS, spiffs port functions + * + */ + +#ifndef __K210_SPIFFS_H__ +#define __K210_SPIFFS_H__ + +#include "spiffs.h" + +s32_t k210_spi_flash_read(u32_t addr, u32_t size, u8_t *dst); +s32_t k210_spi_flash_write(u32_t addr, u32_t size, const u8_t *src); +s32_t k210_spi_flash_erase(u32_t addr, u32_t size); + +#define low_spiffs_read (spiffs_read *)k210_spi_flash_read +#define low_spiffs_write (spiffs_write *)k210_spi_flash_write +#define low_spiffs_erase (spiffs_erase *)k210_spi_flash_erase + +#endif // __K210_SPIFFS_H__ diff --git a/libiot/spiffs/mkfile b/libiot/spiffs/mkfile new file mode 100644 index 0000000..8dc4473 --- /dev/null +++ b/libiot/spiffs/mkfile @@ -0,0 +1,9 @@ +OFILES=\ + $OFILES\ + spiffs/spiffs_check.$O\ + spiffs/spiffs_hydrogen.$O\ + spiffs/spiffs_cache.$O\ + spiffs/spiffs_gc.$O\ + spiffs/spiffs_nucleus.$O\ + spiffs/k210_spiffs.$O\ + diff --git a/libiot/spiffs/spiffs.h b/libiot/spiffs/spiffs.h new file mode 100644 index 0000000..96c0389 --- /dev/null +++ b/libiot/spiffs/spiffs.h @@ -0,0 +1,816 @@ +/* + * spiffs.h + * + * Created on: May 26, 2013 + * Author: petera + */ + +#ifndef SPIFFS_H_ +#define SPIFFS_H_ +#if defined(__cplusplus) +extern "C" { +#endif + +#include "spiffs_config.h" + +#define SPIFFS_OK 0 +#define SPIFFS_ERR_NOT_MOUNTED -10000 +#define SPIFFS_ERR_FULL -10001 +#define SPIFFS_ERR_NOT_FOUND -10002 +#define SPIFFS_ERR_END_OF_OBJECT -10003 +#define SPIFFS_ERR_DELETED -10004 +#define SPIFFS_ERR_NOT_FINALIZED -10005 +#define SPIFFS_ERR_NOT_INDEX -10006 +#define SPIFFS_ERR_OUT_OF_FILE_DESCS -10007 +#define SPIFFS_ERR_FILE_CLOSED -10008 +#define SPIFFS_ERR_FILE_DELETED -10009 +#define SPIFFS_ERR_BAD_DESCRIPTOR -10010 +#define SPIFFS_ERR_IS_INDEX -10011 +#define SPIFFS_ERR_IS_FREE -10012 +#define SPIFFS_ERR_INDEX_SPAN_MISMATCH -10013 +#define SPIFFS_ERR_DATA_SPAN_MISMATCH -10014 +#define SPIFFS_ERR_INDEX_REF_FREE -10015 +#define SPIFFS_ERR_INDEX_REF_LU -10016 +#define SPIFFS_ERR_INDEX_REF_INVALID -10017 +#define SPIFFS_ERR_INDEX_FREE -10018 +#define SPIFFS_ERR_INDEX_LU -10019 +#define SPIFFS_ERR_INDEX_INVALID -10020 +#define SPIFFS_ERR_NOT_WRITABLE -10021 +#define SPIFFS_ERR_NOT_READABLE -10022 +#define SPIFFS_ERR_CONFLICTING_NAME -10023 +#define SPIFFS_ERR_NOT_CONFIGURED -10024 + +#define SPIFFS_ERR_NOT_A_FS -10025 +#define SPIFFS_ERR_MOUNTED -10026 +#define SPIFFS_ERR_ERASE_FAIL -10027 +#define SPIFFS_ERR_MAGIC_NOT_POSSIBLE -10028 + +#define SPIFFS_ERR_NO_DELETED_BLOCKS -10029 + +#define SPIFFS_ERR_FILE_EXISTS -10030 + +#define SPIFFS_ERR_NOT_A_FILE -10031 +#define SPIFFS_ERR_RO_NOT_IMPL -10032 +#define SPIFFS_ERR_RO_ABORTED_OPERATION -10033 +#define SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS -10034 +#define SPIFFS_ERR_PROBE_NOT_A_FS -10035 +#define SPIFFS_ERR_NAME_TOO_LONG -10036 + +#define SPIFFS_ERR_IX_MAP_UNMAPPED -10037 +#define SPIFFS_ERR_IX_MAP_MAPPED -10038 +#define SPIFFS_ERR_IX_MAP_BAD_RANGE -10039 + +#define SPIFFS_ERR_INTERNAL -10050 + +#define SPIFFS_ERR_TEST -10100 + + +// spiffs file descriptor index type. must be signed +typedef s16_t spiffs_file; +// spiffs file descriptor flags +typedef u16_t spiffs_flags; +// spiffs file mode +typedef u16_t spiffs_mode; +// object type +typedef u8_t spiffs_obj_type; + +struct spiffs_t; + +#if SPIFFS_HAL_CALLBACK_EXTRA + +/* spi read call function type */ +typedef s32_t (*spiffs_read)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *dst); +/* spi write call function type */ +typedef s32_t (*spiffs_write)(struct spiffs_t *fs, u32_t addr, u32_t size, u8_t *src); +/* spi erase call function type */ +typedef s32_t (*spiffs_erase)(struct spiffs_t *fs, u32_t addr, u32_t size); + +#else // SPIFFS_HAL_CALLBACK_EXTRA + +/* spi read call function type */ +typedef s32_t (*spiffs_read)(u32_t addr, u32_t size, u8_t *dst); +/* spi write call function type */ +typedef s32_t (*spiffs_write)(u32_t addr, u32_t size, u8_t *src); +/* spi erase call function type */ +typedef s32_t (*spiffs_erase)(u32_t addr, u32_t size); +#endif // SPIFFS_HAL_CALLBACK_EXTRA + +/* file system check callback report operation */ +typedef enum { + SPIFFS_CHECK_LOOKUP = 0, + SPIFFS_CHECK_INDEX, + SPIFFS_CHECK_PAGE +} spiffs_check_type; + +/* file system check callback report type */ +typedef enum { + SPIFFS_CHECK_PROGRESS = 0, + SPIFFS_CHECK_ERROR, + SPIFFS_CHECK_FIX_INDEX, + SPIFFS_CHECK_FIX_LOOKUP, + SPIFFS_CHECK_DELETE_ORPHANED_INDEX, + SPIFFS_CHECK_DELETE_PAGE, + SPIFFS_CHECK_DELETE_BAD_FILE +} spiffs_check_report; + +/* file system check callback function */ +#if SPIFFS_HAL_CALLBACK_EXTRA +typedef void (*spiffs_check_callback)(struct spiffs_t *fs, spiffs_check_type type, spiffs_check_report report, + u32_t arg1, u32_t arg2); +#else // SPIFFS_HAL_CALLBACK_EXTRA +typedef void (*spiffs_check_callback)(spiffs_check_type type, spiffs_check_report report, + u32_t arg1, u32_t arg2); +#endif // SPIFFS_HAL_CALLBACK_EXTRA + +/* file system listener callback operation */ +typedef enum { + /* the file has been created */ + SPIFFS_CB_CREATED = 0, + /* the file has been updated or moved to another page */ + SPIFFS_CB_UPDATED, + /* the file has been deleted */ + SPIFFS_CB_DELETED +} spiffs_fileop_type; + +/* file system listener callback function */ +typedef void (*spiffs_file_callback)(struct spiffs_t *fs, spiffs_fileop_type op, spiffs_obj_id obj_id, spiffs_page_ix pix); + +#ifndef SPIFFS_DBG +#define SPIFFS_DBG(...) \ + print(__VA_ARGS__) +#endif +#ifndef SPIFFS_GC_DBG +#define SPIFFS_GC_DBG(...) printf(__VA_ARGS__) +#endif +#ifndef SPIFFS_CACHE_DBG +#define SPIFFS_CACHE_DBG(...) printf(__VA_ARGS__) +#endif +#ifndef SPIFFS_CHECK_DBG +#define SPIFFS_CHECK_DBG(...) printf(__VA_ARGS__) +#endif + +/* Any write to the filehandle is appended to end of the file */ +#define SPIFFS_APPEND (1<<0) +#define SPIFFS_O_APPEND SPIFFS_APPEND +/* If the opened file exists, it will be truncated to zero length before opened */ +#define SPIFFS_TRUNC (1<<1) +#define SPIFFS_O_TRUNC SPIFFS_TRUNC +/* If the opened file does not exist, it will be created before opened */ +#define SPIFFS_CREAT (1<<2) +#define SPIFFS_O_CREAT SPIFFS_CREAT +/* The opened file may only be read */ +#define SPIFFS_RDONLY (1<<3) +#define SPIFFS_O_RDONLY SPIFFS_RDONLY +/* The opened file may only be written */ +#define SPIFFS_WRONLY (1<<4) +#define SPIFFS_O_WRONLY SPIFFS_WRONLY +/* The opened file may be both read and written */ +#define SPIFFS_RDWR (SPIFFS_RDONLY | SPIFFS_WRONLY) +#define SPIFFS_O_RDWR SPIFFS_RDWR +/* Any writes to the filehandle will never be cached but flushed directly */ +#define SPIFFS_DIRECT (1<<5) +#define SPIFFS_O_DIRECT SPIFFS_DIRECT +/* If SPIFFS_O_CREAT and SPIFFS_O_EXCL are set, SPIFFS_open() shall fail if the file exists */ +#define SPIFFS_EXCL (1<<6) +#define SPIFFS_O_EXCL SPIFFS_EXCL + +#define SPIFFS_SEEK_SET (0) +#define SPIFFS_SEEK_CUR (1) +#define SPIFFS_SEEK_END (2) + +#define SPIFFS_TYPE_FILE (1) +#define SPIFFS_TYPE_DIR (2) +#define SPIFFS_TYPE_HARD_LINK (3) +#define SPIFFS_TYPE_SOFT_LINK (4) + +#ifndef SPIFFS_LOCK +#define SPIFFS_LOCK(fs) +#endif + +#ifndef SPIFFS_UNLOCK +#define SPIFFS_UNLOCK(fs) +#endif + +// phys structs + +// spiffs spi configuration struct +typedef struct { + // physical read function + spiffs_read hal_read_f; + // physical write function + spiffs_write hal_write_f; + // physical erase function + spiffs_erase hal_erase_f; +#if SPIFFS_SINGLETON == 0 + // physical size of the spi flash + u32_t phys_size; + // physical offset in spi flash used for spiffs, + // must be on block boundary + u32_t phys_addr; + // physical size when erasing a block + u32_t phys_erase_block; + + // logical size of a block, must be on physical + // block size boundary and must never be less than + // a physical block + u32_t log_block_size; + // logical size of a page, must be at least + // log_block_size / 8 + u32_t log_page_size; + +#endif +#if SPIFFS_FILEHDL_OFFSET + // an integer offset added to each file handle + u16_t fh_ix_offset; +#endif +} spiffs_config; + +typedef struct spiffs_t { + // file system configuration + spiffs_config cfg; + // number of logical blocks + u32_t block_count; + + // cursor for free blocks, block index + spiffs_block_ix free_cursor_block_ix; + // cursor for free blocks, entry index + int free_cursor_obj_lu_entry; + // cursor when searching, block index + spiffs_block_ix cursor_block_ix; + // cursor when searching, entry index + int cursor_obj_lu_entry; + + // primary work buffer, size of a logical page + u8_t *lu_work; + // secondary work buffer, size of a logical page + u8_t *work; + // file descriptor memory area + u8_t *fd_space; + // available file descriptors + u32_t fd_count; + + // last error + s32_t err_code; + + // current number of free blocks + u32_t free_blocks; + // current number of busy pages + u32_t stats_p_allocated; + // current number of deleted pages + u32_t stats_p_deleted; + // flag indicating that garbage collector is cleaning + u8_t cleaning; + // max erase count amongst all blocks + spiffs_obj_id max_erase_count; + +#if SPIFFS_GC_STATS + u32_t stats_gc_runs; +#endif + +#if SPIFFS_CACHE + // cache memory + void *cache; + // cache size + u32_t cache_size; +#if SPIFFS_CACHE_STATS + u32_t cache_hits; + u32_t cache_misses; +#endif +#endif + + // check callback function + spiffs_check_callback check_cb_f; + // file callback function + spiffs_file_callback file_cb_f; + // mounted flag + u8_t mounted; + // user data + void *user_data; + // config magic + u32_t config_magic; +} spiffs; + +/* spiffs file status struct */ +typedef struct { + spiffs_obj_id obj_id; + u32_t size; + spiffs_obj_type type; + spiffs_page_ix pix; + u8_t name[SPIFFS_OBJ_NAME_LEN]; +#if SPIFFS_OBJ_META_LEN + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif +} spiffs_stat; + +struct spiffs_dirent { + spiffs_obj_id obj_id; + u8_t name[SPIFFS_OBJ_NAME_LEN]; + spiffs_obj_type type; + u32_t size; + spiffs_page_ix pix; +#if SPIFFS_OBJ_META_LEN + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif +}; + +typedef struct { + spiffs *fs; + spiffs_block_ix block; + int entry; +} spiffs_DIR; + +#if SPIFFS_IX_MAP + +typedef struct { + // buffer with looked up data pixes + spiffs_page_ix *map_buf; + // precise file byte offset + u32_t offset; + // start data span index of lookup buffer + spiffs_span_ix start_spix; + // end data span index of lookup buffer + spiffs_span_ix end_spix; +} spiffs_ix_map; + +#endif + +// functions + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 +/** + * Special function. This takes a spiffs config struct and returns the number + * of blocks this file system was formatted with. This function relies on + * that following info is set correctly in given config struct: + * + * phys_addr, log_page_size, and log_block_size. + * + * Also, hal_read_f must be set in the config struct. + * + * One must be sure of the correct page size and that the physical address is + * correct in the probed file system when calling this function. It is not + * checked if the phys_addr actually points to the start of the file system, + * so one might get a false positive if entering a phys_addr somewhere in the + * middle of the file system at block boundary. In addition, it is not checked + * if the page size is actually correct. If it is not, weird file system sizes + * will be returned. + * + * If this function detects a file system it returns the assumed file system + * size, which can be used to set the phys_size. + * + * Otherwise, it returns an error indicating why it is not regarded as a file + * system. + * + * Note: this function is not protected with SPIFFS_LOCK and SPIFFS_UNLOCK + * macros. It returns the error code directly, instead of as read by + * SPIFFS_errno. + * + * @param config essential parts of the physical and logical + * configuration of the file system. + */ +s32_t SPIFFS_probe_fs(spiffs_config *config); +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + +/** + * Initializes the file system dynamic parameters and mounts the filesystem. + * If SPIFFS_USE_MAGIC is enabled the mounting may fail with SPIFFS_ERR_NOT_A_FS + * if the flash does not contain a recognizable file system. + * In this case, SPIFFS_format must be called prior to remounting. + * @param fs the file system struct + * @param config the physical and logical configuration of the file system + * @param work a memory work buffer comprising 2*config->log_page_size + * bytes used throughout all file system operations + * @param fd_space memory for file descriptors + * @param fd_space_size memory size of file descriptors + * @param cache memory for cache, may be null + * @param cache_size memory size of cache + * @param check_cb_f callback function for reporting during consistency checks + */ +s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, + u8_t *fd_space, u32_t fd_space_size, + void *cache, u32_t cache_size, + spiffs_check_callback check_cb_f); + +/** + * Unmounts the file system. All file handles will be flushed of any + * cached writes and closed. + * @param fs the file system struct + */ +void SPIFFS_unmount(spiffs *fs); + +/** + * Creates a new file. + * @param fs the file system struct + * @param path the path of the new file + * @param mode ignored, for posix compliance + */ +s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode); + +/** + * Opens/creates a file. + * @param fs the file system struct + * @param path the path of the new file + * @param flags the flags for the open command, can be combinations of + * SPIFFS_O_APPEND, SPIFFS_O_TRUNC, SPIFFS_O_CREAT, SPIFFS_O_RDONLY, + * SPIFFS_O_WRONLY, SPIFFS_O_RDWR, SPIFFS_O_DIRECT, SPIFFS_O_EXCL + * @param mode ignored, for posix compliance + */ +spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode); + +/** + * Opens a file by given dir entry. + * Optimization purposes, when traversing a file system with SPIFFS_readdir + * a normal SPIFFS_open would need to traverse the filesystem again to find + * the file, whilst SPIFFS_open_by_dirent already knows where the file resides. + * @param fs the file system struct + * @param e the dir entry to the file + * @param flags the flags for the open command, can be combinations of + * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, + * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT. + * SPIFFS_CREAT will have no effect in this case. + * @param mode ignored, for posix compliance + */ +spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode); + +/** + * Opens a file by given page index. + * Optimization purposes, opens a file by directly pointing to the page + * index in the spi flash. + * If the page index does not point to a file header SPIFFS_ERR_NOT_A_FILE + * is returned. + * @param fs the file system struct + * @param page_ix the page index + * @param flags the flags for the open command, can be combinations of + * SPIFFS_APPEND, SPIFFS_TRUNC, SPIFFS_CREAT, SPIFFS_RD_ONLY, + * SPIFFS_WR_ONLY, SPIFFS_RDWR, SPIFFS_DIRECT. + * SPIFFS_CREAT will have no effect in this case. + * @param mode ignored, for posix compliance + */ +spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode); + +/** + * Reads from given filehandle. + * @param fs the file system struct + * @param fh the filehandle + * @param buf where to put read data + * @param len how much to read + * @returns number of bytes read, or -1 if error + */ +s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len); + +/** + * Writes to given filehandle. + * @param fs the file system struct + * @param fh the filehandle + * @param buf the data to write + * @param len how much to write + * @returns number of bytes written, or -1 if error + */ +s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len); + +/** + * Moves the read/write file offset. Resulting offset is returned or negative if error. + * lseek(fs, fd, 0, SPIFFS_SEEK_CUR) will thus return current offset. + * @param fs the file system struct + * @param fh the filehandle + * @param offs how much/where to move the offset + * @param whence if SPIFFS_SEEK_SET, the file offset shall be set to offset bytes + * if SPIFFS_SEEK_CUR, the file offset shall be set to its current location plus offset + * if SPIFFS_SEEK_END, the file offset shall be set to the size of the file plus offse, which should be negative + */ +s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence); + +/** + * Removes a file by path + * @param fs the file system struct + * @param path the path of the file to remove + */ +s32_t SPIFFS_remove(spiffs *fs, const char *path); + +/** + * Removes a file by filehandle + * @param fs the file system struct + * @param fh the filehandle of the file to remove + */ +s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh); + +/** + * Gets file status by path + * @param fs the file system struct + * @param path the path of the file to stat + * @param s the stat struct to populate + */ +s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s); + +/** + * Gets file status by filehandle + * @param fs the file system struct + * @param fh the filehandle of the file to stat + * @param s the stat struct to populate + */ +s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s); + +/** + * Flushes all pending write operations from cache for given file + * @param fs the file system struct + * @param fh the filehandle of the file to flush + */ +s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh); + +/** + * Closes a filehandle. If there are pending write operations, these are finalized before closing. + * @param fs the file system struct + * @param fh the filehandle of the file to close + */ +s32_t SPIFFS_close(spiffs *fs, spiffs_file fh); + +/** + * Renames a file + * @param fs the file system struct + * @param old path of file to rename + * @param newPath new path of file + */ +s32_t SPIFFS_rename(spiffs *fs, const char *old, const char *newPath); + +#if SPIFFS_OBJ_META_LEN +/** + * Updates file's metadata + * @param fs the file system struct + * @param path path to the file + * @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long. + */ +s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta); + +/** + * Updates file's metadata + * @param fs the file system struct + * @param fh file handle of the file + * @param meta new metadata. must be SPIFFS_OBJ_META_LEN bytes long. + */ +s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta); +#endif + +/** + * Returns last error of last file operation. + * @param fs the file system struct + */ +s32_t SPIFFS_errno(spiffs *fs); + +/** + * Clears last error. + * @param fs the file system struct + */ +void SPIFFS_clearerr(spiffs *fs); + +/** + * Opens a directory stream corresponding to the given name. + * The stream is positioned at the first entry in the directory. + * On hydrogen builds the name argument is ignored as hydrogen builds always correspond + * to a flat file structure - no directories. + * @param fs the file system struct + * @param name the name of the directory + * @param d pointer the directory stream to be populated + */ +spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d); + +/** + * Closes a directory stream + * @param d the directory stream to close + */ +s32_t SPIFFS_closedir(spiffs_DIR *d); + +/** + * Reads a directory into given spifs_dirent struct. + * @param d pointer to the directory stream + * @param e the dirent struct to be populated + * @returns null if error or end of stream, else given dirent is returned + */ +struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e); + +/** + * Runs a consistency check on given filesystem. + * @param fs the file system struct + */ +s32_t SPIFFS_check(spiffs *fs); + +/** + * Returns number of total bytes available and number of used bytes. + * This is an estimation, and depends on if there a many files with little + * data or few files with much data. + * NB: If used number of bytes exceeds total bytes, a SPIFFS_check should + * run. This indicates a power loss in midst of things. In worst case + * (repeated powerlosses in mending or gc) you might have to delete some files. + * + * @param fs the file system struct + * @param total total number of bytes in filesystem + * @param used used number of bytes in filesystem + */ +s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used); + +/** + * Formats the entire file system. All data will be lost. + * The filesystem must not be mounted when calling this. + * + * NB: formatting is awkward. Due to backwards compatibility, SPIFFS_mount + * MUST be called prior to formatting in order to configure the filesystem. + * If SPIFFS_mount succeeds, SPIFFS_unmount must be called before calling + * SPIFFS_format. + * If SPIFFS_mount fails, SPIFFS_format can be called directly without calling + * SPIFFS_unmount first. + * + * @param fs the file system struct + */ +s32_t SPIFFS_format(spiffs *fs); + +/** + * Returns nonzero if spiffs is mounted, or zero if unmounted. + * @param fs the file system struct + */ +u8_t SPIFFS_mounted(spiffs *fs); + +/** + * Tries to find a block where most or all pages are deleted, and erase that + * block if found. Does not care for wear levelling. Will not move pages + * around. + * If parameter max_free_pages are set to 0, only blocks with only deleted + * pages will be selected. + * + * NB: the garbage collector is automatically called when spiffs needs free + * pages. The reason for this function is to give possibility to do background + * tidying when user knows the system is idle. + * + * Use with care. + * + * Setting max_free_pages to anything larger than zero will eventually wear + * flash more as a block containing free pages can be erased. + * + * Will set err_no to SPIFFS_OK if a block was found and erased, + * SPIFFS_ERR_NO_DELETED_BLOCK if no matching block was found, + * or other error. + * + * @param fs the file system struct + * @param max_free_pages maximum number allowed free pages in block + */ +s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages); + +/** + * Will try to make room for given amount of bytes in the filesystem by moving + * pages and erasing blocks. + * If it is physically impossible, err_no will be set to SPIFFS_ERR_FULL. If + * there already is this amount (or more) of free space, SPIFFS_gc will + * silently return. It is recommended to call SPIFFS_info before invoking + * this method in order to determine what amount of bytes to give. + * + * NB: the garbage collector is automatically called when spiffs needs free + * pages. The reason for this function is to give possibility to do background + * tidying when user knows the system is idle. + * + * Use with care. + * + * @param fs the file system struct + * @param size amount of bytes that should be freed + */ +s32_t SPIFFS_gc(spiffs *fs, u32_t size); + +/** + * Check if EOF reached. + * @param fs the file system struct + * @param fh the filehandle of the file to check + */ +s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh); + +/** + * Get position in file. + * @param fs the file system struct + * @param fh the filehandle of the file to check + */ +s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh); + +/** + * Registers a callback function that keeps track on operations on file + * headers. Do note, that this callback is called from within internal spiffs + * mechanisms. Any operations on the actual file system being callbacked from + * in this callback will mess things up for sure - do not do this. + * This can be used to track where files are and move around during garbage + * collection, which in turn can be used to build location tables in ram. + * Used in conjuction with SPIFFS_open_by_page this may improve performance + * when opening a lot of files. + * Must be invoked after mount. + * + * @param fs the file system struct + * @param cb_func the callback on file operations + */ +s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func); + +#if SPIFFS_IX_MAP + +/** + * Maps the first level index lookup to a given memory map. + * This will make reading big files faster, as the memory map will be used for + * looking up data pages instead of searching for the indices on the physical + * medium. When mapping, all affected indicies are found and the information is + * copied to the array. + * Whole file or only parts of it may be mapped. The index map will cover file + * contents from argument offset until and including arguments (offset+len). + * It is valid to map a longer range than the current file size. The map will + * then be populated when the file grows. + * On garbage collections and file data page movements, the map array will be + * automatically updated. Do not tamper with the map array, as this contains + * the references to the data pages. Modifying it from outside will corrupt any + * future readings using this file descriptor. + * The map will no longer be used when the file descriptor closed or the file + * is unmapped. + * This can be useful to get faster and more deterministic timing when reading + * large files, or when seeking and reading a lot within a file. + * @param fs the file system struct + * @param fh the file handle of the file to map + * @param map a spiffs_ix_map struct, describing the index map + * @param offset absolute file offset where to start the index map + * @param len length of the mapping in actual file bytes + * @param map_buf the array buffer for the look up data - number of required + * elements in the array can be derived from function + * SPIFFS_bytes_to_ix_map_entries given the length + */ +s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map, + u32_t offset, u32_t len, spiffs_page_ix *map_buf); + +/** + * Unmaps the index lookup from this filehandle. All future readings will + * proceed as normal, requiring reading of the first level indices from + * physical media. + * The map and map buffer given in function SPIFFS_ix_map will no longer be + * referenced by spiffs. + * It is not strictly necessary to unmap a file before closing it, as closing + * a file will automatically unmap it. + * @param fs the file system struct + * @param fh the file handle of the file to unmap + */ +s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh); + +/** + * Moves the offset for the index map given in function SPIFFS_ix_map. Parts or + * all of the map buffer will repopulated. + * @param fs the file system struct + * @param fh the mapped file handle of the file to remap + * @param offset new absolute file offset where to start the index map + */ +s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offs); + +/** + * Utility function to get number of spiffs_page_ix entries a map buffer must + * contain on order to map given amount of file data in bytes. + * See function SPIFFS_ix_map and SPIFFS_ix_map_entries_to_bytes. + * @param fs the file system struct + * @param bytes number of file data bytes to map + * @return needed number of elements in a spiffs_page_ix array needed to + * map given amount of bytes in a file + */ +s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes); + +/** + * Utility function to amount of file data bytes that can be mapped when + * mapping a file with buffer having given number of spiffs_page_ix entries. + * See function SPIFFS_ix_map and SPIFFS_bytes_to_ix_map_entries. + * @param fs the file system struct + * @param map_page_ix_entries number of entries in a spiffs_page_ix array + * @return amount of file data in bytes that can be mapped given a map + * buffer having given amount of spiffs_page_ix entries + */ +s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries); + +#endif // SPIFFS_IX_MAP + + +#if SPIFFS_TEST_VISUALISATION +/** + * Prints out a visualization of the filesystem. + * @param fs the file system struct + */ +s32_t SPIFFS_vis(spiffs *fs); +#endif + +#if SPIFFS_BUFFER_HELP +/** + * Returns number of bytes needed for the filedescriptor buffer given + * amount of file descriptors. + */ +u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs); + +#if SPIFFS_CACHE +/** + * Returns number of bytes needed for the cache buffer given + * amount of cache pages. + */ +u32_t SPIFFS_buffer_bytes_for_cache(spiffs *fs, u32_t num_pages); +#endif +#endif + +#if SPIFFS_CACHE +#endif +#if defined(__cplusplus) +} +#endif + +void spiffs_lock(spiffs *fs); +void spiffs_unlock(spiffs *fs); + +#endif /* SPIFFS_H_ */ diff --git a/libiot/spiffs/spiffs_cache.c b/libiot/spiffs/spiffs_cache.c new file mode 100644 index 0000000..018f763 --- /dev/null +++ b/libiot/spiffs/spiffs_cache.c @@ -0,0 +1,314 @@ +/* + * spiffs_cache.c + * + * Created on: Jun 23, 2013 + * Author: petera + */ + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if SPIFFS_CACHE + +// returns cached page for give page index, or null if no such cached page +static spiffs_cache_page *spiffs_cache_page_get(spiffs *fs, spiffs_page_ix pix) { + spiffs_cache *cache = spiffs_get_cache(fs); + if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) return 0; + int i; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); + if ((cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && + cp->pix == pix ) { + SPIFFS_CACHE_DBG("CACHE_GET: have cache page "_SPIPRIi" for "_SPIPRIpg"\n", i, pix); + cp->last_access = cache->last_access; + return cp; + } + } + //SPIFFS_CACHE_DBG("CACHE_GET: no cache for "_SPIPRIpg"\n", pix); + return 0; +} + +// frees cached page +static s32_t spiffs_cache_page_free(spiffs *fs, int ix, u8_t write_back) { + s32_t res = SPIFFS_OK; + spiffs_cache *cache = spiffs_get_cache(fs); + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, ix); + if (cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) == 0 && + (cp->flags & SPIFFS_CACHE_FLAG_DIRTY)) { + u8_t *mem = spiffs_get_cache_page(fs, cache, ix); + res = SPIFFS_HAL_WRITE(fs, SPIFFS_PAGE_TO_PADDR(fs, cp->pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), mem); + } + + cp->flags = 0; + cache->cpage_use_map &= ~(1 << ix); + + if (cp->flags & SPIFFS_CACHE_FLAG_TYPE_WR) { + SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" objid "_SPIPRIid"\n", ix, cp->obj_id); + } else { + SPIFFS_CACHE_DBG("CACHE_FREE: free cache page "_SPIPRIi" pix "_SPIPRIpg"\n", ix, cp->pix); + } + } + + return res; +} + +// removes the oldest accessed cached page +static s32_t spiffs_cache_page_remove_oldest(spiffs *fs, u8_t flag_mask, u8_t flags) { + s32_t res = SPIFFS_OK; + spiffs_cache *cache = spiffs_get_cache(fs); + + if ((cache->cpage_use_map & cache->cpage_use_mask) != cache->cpage_use_mask) { + // at least one free cpage + return SPIFFS_OK; + } + + // all busy, scan thru all to find the cpage which has oldest access + int i; + int cand_ix = -1; + u32_t oldest_val = 0; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); + if ((cache->last_access - cp->last_access) > oldest_val && + (cp->flags & flag_mask) == flags) { + oldest_val = cache->last_access - cp->last_access; + cand_ix = i; + } + } + + if (cand_ix >= 0) { + res = spiffs_cache_page_free(fs, cand_ix, 1); + } + + return res; +} + +// allocates a new cached page and returns it, or null if all cache pages are busy +static spiffs_cache_page *spiffs_cache_page_allocate(spiffs *fs) { + spiffs_cache *cache = spiffs_get_cache(fs); + if (cache->cpage_use_map == 0xffffffff) { + // out of cache memory + return 0; + } + int i; + for (i = 0; i < cache->cpage_count; i++) { + if ((cache->cpage_use_map & (1<cpage_use_map |= (1<last_access = cache->last_access; + SPIFFS_CACHE_DBG("CACHE_ALLO: allocated cache page "_SPIPRIi"\n", i); + return cp; + } + } + // out of cache entries + return 0; +} + +// drops the cache page for give page index +void spiffs_cache_drop_page(spiffs *fs, spiffs_page_ix pix) { + spiffs_cache_page *cp = spiffs_cache_page_get(fs, pix); + if (cp) { + spiffs_cache_page_free(fs, cp->ix, 0); + } +} + +// ------------------------------ + +// reads from spi flash or the cache +s32_t spiffs_phys_rd( + spiffs *fs, + u8_t op, + spiffs_file fh, + u32_t addr, + u32_t len, + u8_t *dst) { + (void)fh; + s32_t res = SPIFFS_OK; + spiffs_cache *cache = spiffs_get_cache(fs); + spiffs_cache_page *cp = spiffs_cache_page_get(fs, SPIFFS_PADDR_TO_PAGE(fs, addr)); + cache->last_access++; + if (cp) { + // we've already got one, you see +#if SPIFFS_CACHE_STATS + fs->cache_hits++; +#endif + cp->last_access = cache->last_access; + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); + } else { + if ((op & SPIFFS_OP_TYPE_MASK) == SPIFFS_OP_T_OBJ_LU2) { + // for second layer lookup functions, we do not cache in order to prevent shredding + return SPIFFS_HAL_READ(fs, addr, len, dst); + } +#if SPIFFS_CACHE_STATS + fs->cache_misses++; +#endif + // this operation will always free one cache page (unless all already free), + // the result code stems from the write operation of the possibly freed cache page + res = spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); + + cp = spiffs_cache_page_allocate(fs); + if (cp) { + cp->flags = SPIFFS_CACHE_FLAG_WRTHRU; + cp->pix = SPIFFS_PADDR_TO_PAGE(fs, addr); + + s32_t res2 = SPIFFS_HAL_READ(fs, + addr - SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr), + SPIFFS_CFG_LOG_PAGE_SZ(fs), + spiffs_get_cache_page(fs, cache, cp->ix)); + if (res2 != SPIFFS_OK) { + // honor read failure before possible write failure (bad idea?) + res = res2; + } + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + memcpy(dst, &mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], len); + } else { + // this will never happen, last resort for sake of symmetry + s32_t res2 = SPIFFS_HAL_READ(fs, addr, len, dst); + if (res2 != SPIFFS_OK) { + // honor read failure before possible write failure (bad idea?) + res = res2; + } + } + } + return res; +} + +// writes to spi flash and/or the cache +s32_t spiffs_phys_wr( + spiffs *fs, + u8_t op, + spiffs_file fh, + u32_t addr, + u32_t len, + u8_t *src) { + (void)fh; + spiffs_page_ix pix = SPIFFS_PADDR_TO_PAGE(fs, addr); + spiffs_cache *cache = spiffs_get_cache(fs); + spiffs_cache_page *cp = spiffs_cache_page_get(fs, pix); + + if (cp && (op & SPIFFS_OP_COM_MASK) != SPIFFS_OP_C_WRTHRU) { + // have a cache page + // copy in data to cache page + + if ((op & SPIFFS_OP_COM_MASK) == SPIFFS_OP_C_DELE && + (op & SPIFFS_OP_TYPE_MASK) != SPIFFS_OP_T_OBJ_LU) { + // page is being deleted, wipe from cache - unless it is a lookup page + spiffs_cache_page_free(fs, cp->ix, 0); + return SPIFFS_HAL_WRITE(fs, addr, len, src); + } + + u8_t *mem = spiffs_get_cache_page(fs, cache, cp->ix); + memcpy(&mem[SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr)], src, len); + + cache->last_access++; + cp->last_access = cache->last_access; + + if (cp->flags & SPIFFS_CACHE_FLAG_WRTHRU) { + // page is being updated, no write-cache, just pass thru + return SPIFFS_HAL_WRITE(fs, addr, len, src); + } else { + return SPIFFS_OK; + } + } else { + // no cache page, no write cache - just write thru + return SPIFFS_HAL_WRITE(fs, addr, len, src); + } +} + +#if SPIFFS_CACHE_WR +// returns the cache page that this fd refers, or null if no cache page +spiffs_cache_page *spiffs_cache_page_get_by_fd(spiffs *fs, spiffs_fd *fd) { + spiffs_cache *cache = spiffs_get_cache(fs); + + if ((cache->cpage_use_map & cache->cpage_use_mask) == 0) { + // all cpages free, no cpage cannot be assigned to obj_id + return 0; + } + + int i; + for (i = 0; i < cache->cpage_count; i++) { + spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i); + if ((cache->cpage_use_map & (1<flags & SPIFFS_CACHE_FLAG_TYPE_WR) && + cp->obj_id == fd->obj_id) { + return cp; + } + } + + return 0; +} + +// allocates a new cache page and refers this to given fd - flushes an old cache +// page if all cache is busy +spiffs_cache_page *spiffs_cache_page_allocate_by_fd(spiffs *fs, spiffs_fd *fd) { + // before this function is called, it is ensured that there is no already existing + // cache page with same object id + spiffs_cache_page_remove_oldest(fs, SPIFFS_CACHE_FLAG_TYPE_WR, 0); + spiffs_cache_page *cp = spiffs_cache_page_allocate(fs); + if (cp == 0) { + // could not get cache page + return 0; + } + + cp->flags = SPIFFS_CACHE_FLAG_TYPE_WR; + cp->obj_id = fd->obj_id; + fd->cache_page = cp; + return cp; +} + +// unrefers all fds that this cache page refers to and releases the cache page +void spiffs_cache_fd_release(spiffs *fs, spiffs_cache_page *cp) { + if (cp == 0) return; + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr != 0 && cur_fd->cache_page == cp) { + cur_fd->cache_page = 0; + } + } + spiffs_cache_page_free(fs, cp->ix, 0); + + cp->obj_id = 0; +} + +#endif + +// initializes the cache +void spiffs_cache_init(spiffs *fs) { + if (fs->cache == 0) return; + u32_t sz = fs->cache_size; + u32_t cache_mask = 0; + int i; + int cache_entries = + (sz - sizeof(spiffs_cache)) / (SPIFFS_CACHE_PAGE_SIZE(fs)); + if (cache_entries <= 0) return; + + for (i = 0; i < cache_entries; i++) { + cache_mask <<= 1; + cache_mask |= 1; + } + + spiffs_cache cache; + memset(&cache, 0, sizeof(spiffs_cache)); + cache.cpage_count = cache_entries; + cache.cpages = (u8_t *)((u8_t *)fs->cache + sizeof(spiffs_cache)); + + cache.cpage_use_map = 0xffffffff; + cache.cpage_use_mask = cache_mask; + memcpy(fs->cache, &cache, sizeof(spiffs_cache)); + + spiffs_cache *c = spiffs_get_cache(fs); + + memset(c->cpages, 0, c->cpage_count * SPIFFS_CACHE_PAGE_SIZE(fs)); + + c->cpage_use_map &= ~(c->cpage_use_mask); + for (i = 0; i < cache.cpage_count; i++) { + spiffs_get_cache_page_hdr(fs, c, i)->ix = i; + } +} + +#endif // SPIFFS_CACHE diff --git a/libiot/spiffs/spiffs_check.c b/libiot/spiffs/spiffs_check.c new file mode 100644 index 0000000..dde85ef --- /dev/null +++ b/libiot/spiffs/spiffs_check.c @@ -0,0 +1,995 @@ +/* + * spiffs_check.c + * + * Contains functionality for checking file system consistency + * and mending problems. + * Three levels of consistency checks are implemented: + * + * Look up consistency + * Checks if indices in lookup pages are coherent with page headers + * Object index consistency + * Checks if there are any orphaned object indices (missing object index headers). + * If an object index is found but not its header, the object index is deleted. + * This is critical for the following page consistency check. + * Page consistency + * Checks for pages that ought to be indexed, ought not to be indexed, are multiple indexed + * + * + * Created on: Jul 7, 2013 + * Author: petera + */ + + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if !SPIFFS_READ_ONLY + +#if SPIFFS_HAL_CALLBACK_EXTRA +#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \ + do { \ + if ((_fs)->check_cb_f) (_fs)->check_cb_f((_fs), (_type), (_rep), (_arg1), (_arg2)); \ + } while (0) +#else +#define CHECK_CB(_fs, _type, _rep, _arg1, _arg2) \ + do { \ + if ((_fs)->check_cb_f) (_fs)->check_cb_f((_type), (_rep), (_arg1), (_arg2)); \ + } while (0) +#endif + +//--------------------------------------- +// Look up consistency + +// searches in the object indices and returns the referenced page index given +// the object id and the data span index +// destroys fs->lu_work +static s32_t spiffs_object_get_data_page_index_reference( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix data_spix, + spiffs_page_ix *pix, + spiffs_page_ix *objix_pix) { + s32_t res; + + // calculate object index span index for given data page span index + spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // find obj index for obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, 0, objix_pix); + SPIFFS_CHECK_RES(res); + + // load obj index entry + u32_t addr = SPIFFS_PAGE_TO_PADDR(fs, *objix_pix); + if (objix_spix == 0) { + // get referenced page from object index header + addr += sizeof(spiffs_page_object_ix_header) + data_spix * sizeof(spiffs_page_ix); + } else { + // get referenced page from object index + addr += sizeof(spiffs_page_object_ix) + SPIFFS_OBJ_IX_ENTRY(fs, data_spix) * sizeof(spiffs_page_ix); + } + + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, 0, addr, sizeof(spiffs_page_ix), (u8_t *)pix); + + return res; +} + +// copies page contents to a new page +static s32_t spiffs_rewrite_page(spiffs *fs, spiffs_page_ix cur_pix, spiffs_page_header *p_hdr, spiffs_page_ix *new_pix) { + s32_t res; + res = spiffs_page_allocate_data(fs, p_hdr->obj_id, p_hdr, 0,0,0,0, new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_phys_cpy(fs, 0, + SPIFFS_PAGE_TO_PADDR(fs, *new_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header), + SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_CHECK_RES(res); + return res; +} + +// rewrites the object index for given object id and replaces the +// data page index to a new page index +static s32_t spiffs_rewrite_index(spiffs *fs, spiffs_obj_id obj_id, spiffs_span_ix data_spix, spiffs_page_ix new_data_pix, spiffs_page_ix objix_pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + spiffs_page_ix free_pix; + obj_id |= SPIFFS_OBJ_ID_IX_FLAG; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + + // calculate object index span index for given data page span index + spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (objix_spix == 0) { + // calc index in index header + entry = data_spix; + } else { + // calc entry in index + entry = SPIFFS_OBJ_IX_ENTRY(fs, data_spix); + + } + // load index + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + spiffs_page_header *objix_p_hdr = (spiffs_page_header *)fs->lu_work; + + // be ultra safe, double check header against provided data + if (objix_p_hdr->obj_id != obj_id) { + spiffs_page_delete(fs, free_pix); + return SPIFFS_ERR_CHECK_OBJ_ID_MISM; + } + if (objix_p_hdr->span_ix != objix_spix) { + spiffs_page_delete(fs, free_pix); + return SPIFFS_ERR_CHECK_SPIX_MISM; + } + if ((objix_p_hdr->flags & (SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_IXDELE | SPIFFS_PH_FLAG_INDEX | + SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET)) != + (SPIFFS_PH_FLAG_IXDELE | SPIFFS_PH_FLAG_DELET)) { + spiffs_page_delete(fs, free_pix); + return SPIFFS_ERR_CHECK_FLAGS_BAD; + } + + // rewrite in mem + if (objix_spix == 0) { + ((spiffs_page_ix*)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix; + } else { + ((spiffs_page_ix*)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix; + } + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&obj_id); + SPIFFS_CHECK_RES(res); + res = spiffs_page_delete(fs, objix_pix); + + return res; +} + +// deletes an object just by marking object index header as deleted +static s32_t spiffs_delete_obj_lazy(spiffs *fs, spiffs_obj_id obj_id) { + spiffs_page_ix objix_hdr_pix; + s32_t res; + res = spiffs_obj_lu_find_id_and_span(fs, obj_id, 0, 0, &objix_hdr_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + return SPIFFS_OK; + } + SPIFFS_CHECK_RES(res); + u8_t flags = 0xff & ~SPIFFS_PH_FLAG_IXDELE; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&flags); + return res; +} + +// validates the given look up entry +static s32_t spiffs_lookup_check_validate(spiffs *fs, spiffs_obj_id lu_obj_id, spiffs_page_header *p_hdr, + spiffs_page_ix cur_pix, spiffs_block_ix cur_block, int cur_entry, int *reload_lu) { + (void)cur_block; + (void)cur_entry; + u8_t delete_page = 0; + s32_t res = SPIFFS_OK; + spiffs_page_ix objix_pix; + spiffs_page_ix ref_pix; + // check validity, take actions + if (((lu_obj_id == SPIFFS_OBJ_ID_DELETED) && (p_hdr->flags & SPIFFS_PH_FLAG_DELET)) || + ((lu_obj_id == SPIFFS_OBJ_ID_FREE) && (p_hdr->flags & SPIFFS_PH_FLAG_USED) == 0)) { + // look up entry deleted / free but used in page header + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" deleted/free in lu but not on page\n", cur_pix); + *reload_lu = 1; + delete_page = 1; + if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) { + // header says data page + // data page can be removed if not referenced by some object index + res = spiffs_object_get_data_page_index_reference(fs, p_hdr->obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no object with this id, so remove page safely + res = SPIFFS_OK; + } else { + SPIFFS_CHECK_RES(res); + if (ref_pix == cur_pix) { + // data page referenced by object index but deleted in lu + // copy page to new place and re-write the object index to new place + spiffs_page_ix new_pix; + res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: data page not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + SPIFFS_CHECK_DBG("LU: FIXUP: "_SPIPRIpg" rewritten to "_SPIPRIpg", affected objix_pix "_SPIPRIpg"\n", cur_pix, new_pix, objix_pix); + res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + res = spiffs_page_delete(fs, new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); + } else { + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, p_hdr->obj_id, p_hdr->span_ix); + } + SPIFFS_CHECK_RES(res); + } + } + } else { + // header says index page + // index page can be removed if other index with same obj_id and spanix is found + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, 0); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no such index page found, check for a data page amongst page headers + // lu cannot be trusted + res = spiffs_obj_lu_find_id_and_span_by_phdr(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, 0); + if (res == SPIFFS_OK) { // ignore other errors + // got a data page also, assume lu corruption only, rewrite to new page + spiffs_page_ix new_pix; + res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: ix page with data not found elsewhere, rewriting "_SPIPRIpg" to new page "_SPIPRIpg"\n", cur_pix, new_pix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + } + } else { + SPIFFS_CHECK_RES(res); + } + } + } + if (lu_obj_id != SPIFFS_OBJ_ID_FREE && lu_obj_id != SPIFFS_OBJ_ID_DELETED) { + // look up entry used + if ((p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG) != (lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG)) { + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" differ in obj_id lu:"_SPIPRIid" ph:"_SPIPRIid"\n", cur_pix, lu_obj_id, p_hdr->obj_id); + delete_page = 1; + if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0 || + (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) || + (p_hdr->flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_IXDELE)) == 0) { + // page deleted or not finalized, just remove it + } else { + if (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) { + // if data page, check for reference to this page + res = spiffs_object_get_data_page_index_reference(fs, p_hdr->obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no object with this id, so remove page safely + res = SPIFFS_OK; + } else { + SPIFFS_CHECK_RES(res); + // if found, rewrite page with object id, update index, and delete current + if (ref_pix == cur_pix) { + spiffs_page_ix new_pix; + res = spiffs_rewrite_page(fs, cur_pix, p_hdr, &new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_rewrite_index(fs, p_hdr->obj_id, p_hdr->span_ix, new_pix, objix_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("LU: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + res = spiffs_page_delete(fs, new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_delete_obj_lazy(fs, p_hdr->obj_id); + *reload_lu = 1; + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr->obj_id, 0); + } + SPIFFS_CHECK_RES(res); + } + } + } else { + // else if index, check for other pages with both obj_id's and spanix + spiffs_page_ix objix_pix_lu, objix_pix_ph; + // see if other object index page exists for lookup obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, 0, &objix_pix_lu); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_lu = 0; + } + SPIFFS_CHECK_RES(res); + // see if other object index exists for page header obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, 0, &objix_pix_ph); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_ph = 0; + } + SPIFFS_CHECK_RES(res); + // if both obj_id's found, just delete current + if (objix_pix_ph == 0 || objix_pix_lu == 0) { + // otherwise try finding first corresponding data pages + spiffs_page_ix data_pix_lu, data_pix_ph; + // see if other data page exists for look up obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &data_pix_lu); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_lu = 0; + } + SPIFFS_CHECK_RES(res); + // see if other data page exists for page header obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &data_pix_ph); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_ph = 0; + } + SPIFFS_CHECK_RES(res); + + spiffs_page_header new_ph; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL); + new_ph.span_ix = p_hdr->span_ix; + spiffs_page_ix new_pix; + if ((objix_pix_lu && data_pix_lu && data_pix_ph && objix_pix_ph == 0) || + (objix_pix_lu == 0 && data_pix_ph && objix_pix_ph == 0)) { + // got a data page for page header obj id + // rewrite as obj_id_ph + new_ph.obj_id = p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG; + res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix); + SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid" to pix "_SPIPRIpg"\n", cur_pix, new_ph.obj_id, new_pix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + } else if ((objix_pix_ph && data_pix_ph && data_pix_lu && objix_pix_lu == 0) || + (objix_pix_ph == 0 && data_pix_lu && objix_pix_lu == 0)) { + // got a data page for look up obj id + // rewrite as obj_id_lu + new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG; + SPIFFS_CHECK_DBG("LU: FIXUP: rewrite page "_SPIPRIpg" as "_SPIPRIid"\n", cur_pix, new_ph.obj_id); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + res = spiffs_rewrite_page(fs, cur_pix, &new_ph, &new_pix); + SPIFFS_CHECK_RES(res); + *reload_lu = 1; + } else { + // cannot safely do anything + SPIFFS_CHECK_DBG("LU: FIXUP: nothing to do, just delete\n"); + } + } + } + } + } else if (((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX)) || + ((lu_obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0 && (p_hdr->flags & SPIFFS_PH_FLAG_INDEX) == 0)) { + SPIFFS_CHECK_DBG("LU: "_SPIPRIpg" lu/page index marking differ\n", cur_pix); + spiffs_page_ix data_pix, objix_pix_d; + // see if other data page exists for given obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &data_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + data_pix = 0; + } + SPIFFS_CHECK_RES(res); + // see if other object index exists for given obj id and span index + res = spiffs_obj_lu_find_id_and_span(fs, lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG, p_hdr->span_ix, cur_pix, &objix_pix_d); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + objix_pix_d = 0; + } + SPIFFS_CHECK_RES(res); + + delete_page = 1; + // if other data page exists and object index exists, just delete page + if (data_pix && objix_pix_d) { + SPIFFS_CHECK_DBG("LU: FIXUP: other index and data page exists, simply remove\n"); + } else + // if only data page exists, make this page index + if (data_pix && objix_pix_d == 0) { + SPIFFS_CHECK_DBG("LU: FIXUP: other data page exists, make this index\n"); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_INDEX, lu_obj_id, p_hdr->span_ix); + spiffs_page_header new_ph; + spiffs_page_ix new_pix; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); + new_ph.obj_id = lu_obj_id | SPIFFS_OBJ_ID_IX_FLAG; + new_ph.span_ix = p_hdr->span_ix; + res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header), + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header)); + SPIFFS_CHECK_RES(res); + } else + // if only index exists, make data page + if (data_pix == 0 && objix_pix_d) { + SPIFFS_CHECK_DBG("LU: FIXUP: other index page exists, make this data\n"); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, lu_obj_id, p_hdr->span_ix); + spiffs_page_header new_ph; + spiffs_page_ix new_pix; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL); + new_ph.obj_id = lu_obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + new_ph.span_ix = p_hdr->span_ix; + res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &new_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_phys_cpy(fs, 0, SPIFFS_PAGE_TO_PADDR(fs, new_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + sizeof(spiffs_page_header), + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header)); + SPIFFS_CHECK_RES(res); + } else { + // if nothing exists, we cannot safely make a decision - delete + } + } + else if ((p_hdr->flags & SPIFFS_PH_FLAG_DELET) == 0) { + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy in lu but deleted on page\n", cur_pix); + delete_page = 1; + } else if ((p_hdr->flags & SPIFFS_PH_FLAG_FINAL)) { + SPIFFS_CHECK_DBG("LU: pix "_SPIPRIpg" busy but not final\n", cur_pix); + // page can be removed if not referenced by object index + *reload_lu = 1; + res = spiffs_object_get_data_page_index_reference(fs, lu_obj_id, p_hdr->span_ix, &ref_pix, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // no object with this id, so remove page safely + res = SPIFFS_OK; + delete_page = 1; + } else { + SPIFFS_CHECK_RES(res); + if (ref_pix != cur_pix) { + SPIFFS_CHECK_DBG("LU: FIXUP: other finalized page is referred, just delete\n"); + delete_page = 1; + } else { + // page referenced by object index but not final + // just finalize + SPIFFS_CHECK_DBG("LU: FIXUP: unfinalized page is referred, finalizing\n"); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_FIX_LOOKUP, p_hdr->obj_id, p_hdr->span_ix); + u8_t flags = 0xff & ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), (u8_t*)&flags); + } + } + } + } + + if (delete_page) { + SPIFFS_CHECK_DBG("LU: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + } + + return res; +} + +static s32_t spiffs_lookup_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, int cur_entry, + const void *user_const_p, void *user_var_p) { + (void)user_const_p; + (void)user_var_p; + s32_t res = SPIFFS_OK; + spiffs_page_header p_hdr; + spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry); + + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, + (cur_block * 256)/fs->block_count, 0); + + // load header + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + int reload_lu = 0; + + res = spiffs_lookup_check_validate(fs, obj_id, &p_hdr, cur_pix, cur_block, cur_entry, &reload_lu); + SPIFFS_CHECK_RES(res); + + if (res == SPIFFS_OK) { + return reload_lu ? SPIFFS_VIS_COUNTINUE_RELOAD : SPIFFS_VIS_COUNTINUE; + } + return res; +} + + +// Scans all object look up. For each entry, corresponding page header is checked for validity. +// If an object index header page is found, this is also checked +s32_t spiffs_lookup_consistency_check(spiffs *fs, u8_t check_all_objects) { + (void)check_all_objects; + s32_t res = SPIFFS_OK; + + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 0, 0); + + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_lookup_check_v, 0, 0, 0, 0); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + + if (res != SPIFFS_OK) { + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_ERROR, res, 0); + } + + CHECK_CB(fs, SPIFFS_CHECK_LOOKUP, SPIFFS_CHECK_PROGRESS, 256, 0); + + return res; +} + +//--------------------------------------- +// Page consistency + +// Scans all pages (except lu pages), reserves 4 bits in working memory for each page +// bit 0: 0 == FREE|DELETED, 1 == USED +// bit 1: 0 == UNREFERENCED, 1 == REFERENCED +// bit 2: 0 == NOT_INDEX, 1 == INDEX +// bit 3: unused +// A consistent file system will have only pages being +// * x000 free, unreferenced, not index +// * x011 used, referenced only once, not index +// * x101 used, unreferenced, index +// The working memory might not fit all pages so several scans might be needed +static s32_t spiffs_page_consistency_check_i(spiffs *fs) { + const u32_t bits = 4; + const spiffs_page_ix pages_per_scan = SPIFFS_CFG_LOG_PAGE_SZ(fs) * 8 / bits; + + s32_t res = SPIFFS_OK; + spiffs_page_ix pix_offset = 0; + + // for each range of pages fitting into work memory + while (pix_offset < SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) { + // set this flag to abort all checks and rescan the page range + u8_t restart = 0; + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + + spiffs_block_ix cur_block = 0; + // build consistency bitmap for id range traversing all blocks + while (!restart && cur_block < fs->block_count) { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, + (pix_offset*256)/(SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count) + + ((((cur_block * pages_per_scan * 256)/ (SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count))) / fs->block_count), + 0); + // traverse each page except for lookup pages + spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_PAGES(fs) + SPIFFS_PAGES_PER_BLOCK(fs) * cur_block; + while (!restart && cur_pix < SPIFFS_PAGES_PER_BLOCK(fs) * (cur_block+1)) { + //if ((cur_pix & 0xff) == 0) + // SPIFFS_CHECK_DBG("PA: processing pix "_SPIPRIpg", block "_SPIPRIbl" of pix "_SPIPRIpg", block "_SPIPRIbl"\n", + // cur_pix, cur_block, SPIFFS_PAGES_PER_BLOCK(fs) * fs->block_count, fs->block_count); + + // read header + spiffs_page_header p_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + u8_t within_range = (cur_pix >= pix_offset && cur_pix < pix_offset + pages_per_scan); + const u32_t pix_byte_ix = (cur_pix - pix_offset) / (8/bits); + const u8_t pix_bit_ix = (cur_pix & ((8/bits)-1)) * bits; + + if (within_range && + (p_hdr.flags & SPIFFS_PH_FLAG_DELET) && (p_hdr.flags & SPIFFS_PH_FLAG_USED) == 0) { + // used + fs->work[pix_byte_ix] |= (1<<(pix_bit_ix + 0)); + } + if ((p_hdr.flags & SPIFFS_PH_FLAG_DELET) && + (p_hdr.flags & SPIFFS_PH_FLAG_IXDELE) && + (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) == 0) { + // found non-deleted index + if (within_range) { + fs->work[pix_byte_ix] |= (1<<(pix_bit_ix + 2)); + } + + // load non-deleted index + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + + // traverse index for referenced pages + spiffs_page_ix *object_page_index; + spiffs_page_header *objix_p_hdr = (spiffs_page_header *)fs->lu_work; + + int entries; + int i; + spiffs_span_ix data_spix_offset; + if (p_hdr.span_ix == 0) { + // object header page index + entries = SPIFFS_OBJ_HDR_IX_LEN(fs); + data_spix_offset = 0; + object_page_index = (spiffs_page_ix *)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix_header)); + } else { + // object page index + entries = SPIFFS_OBJ_IX_LEN(fs); + data_spix_offset = SPIFFS_OBJ_HDR_IX_LEN(fs) + SPIFFS_OBJ_IX_LEN(fs) * (p_hdr.span_ix - 1); + object_page_index = (spiffs_page_ix *)((u8_t *)fs->lu_work + sizeof(spiffs_page_object_ix)); + } + + // for all entries in index + for (i = 0; !restart && i < entries; i++) { + spiffs_page_ix rpix = object_page_index[i]; + u8_t rpix_within_range = rpix >= pix_offset && rpix < pix_offset + pages_per_scan; + + if ((rpix != (spiffs_page_ix)-1 && rpix > SPIFFS_MAX_PAGES(fs)) + || (rpix_within_range && SPIFFS_IS_LOOKUP_PAGE(fs, rpix))) { + + // bad reference + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg"x bad pix / LU referenced from page "_SPIPRIpg"\n", + rpix, cur_pix); + // check for data page elsewhere + spiffs_page_ix data_pix; + res = spiffs_obj_lu_find_id_and_span(fs, objix_p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + data_spix_offset + i, 0, &data_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + data_pix = 0; + } + SPIFFS_CHECK_RES(res); + if (data_pix == 0) { + // if not, allocate free page + spiffs_page_header new_ph; + new_ph.flags = 0xff & ~(SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL); + new_ph.obj_id = objix_p_hdr->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + new_ph.span_ix = data_spix_offset + i; + res = spiffs_page_allocate_data(fs, new_ph.obj_id, &new_ph, 0, 0, 0, 1, &data_pix); + SPIFFS_CHECK_RES(res); + SPIFFS_CHECK_DBG("PA: FIXUP: found no existing data page, created new @ "_SPIPRIpg"\n", data_pix); + } + // remap index + SPIFFS_CHECK_DBG("PA: FIXUP: rewriting index pix "_SPIPRIpg"\n", cur_pix); + res = spiffs_rewrite_index(fs, objix_p_hdr->obj_id | SPIFFS_OBJ_ID_IX_FLAG, + data_spix_offset + i, data_pix, cur_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend - delete object\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, objix_p_hdr->obj_id, 0); + // delete file + res = spiffs_page_delete(fs, cur_pix); + } else { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, objix_p_hdr->obj_id, objix_p_hdr->span_ix); + } + SPIFFS_CHECK_RES(res); + restart = 1; + + } else if (rpix_within_range) { + + // valid reference + // read referenced page header + spiffs_page_header rp_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, rpix), sizeof(spiffs_page_header), (u8_t*)&rp_hdr); + SPIFFS_CHECK_RES(res); + + // cross reference page header check + if (rp_hdr.obj_id != (p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) || + rp_hdr.span_ix != data_spix_offset + i || + (rp_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED)) != + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_INDEX)) { + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" has inconsistent page header ix id/span:"_SPIPRIid"/"_SPIPRIsp", ref id/span:"_SPIPRIid"/"_SPIPRIsp" flags:"_SPIPRIfl"\n", + rpix, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, data_spix_offset + i, + rp_hdr.obj_id, rp_hdr.span_ix, rp_hdr.flags); + // try finding correct page + spiffs_page_ix data_pix; + res = spiffs_obj_lu_find_id_and_span(fs, p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + data_spix_offset + i, rpix, &data_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + data_pix = 0; + } + SPIFFS_CHECK_RES(res); + if (data_pix == 0) { + // not found, this index is badly borked + SPIFFS_CHECK_DBG("PA: FIXUP: index bad, delete object id "_SPIPRIid"\n", p_hdr.obj_id); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + SPIFFS_CHECK_RES(res); + break; + } else { + // found it, so rewrite index + SPIFFS_CHECK_DBG("PA: FIXUP: found correct data pix "_SPIPRIpg", rewrite ix pix "_SPIPRIpg" id "_SPIPRIid"\n", + data_pix, cur_pix, p_hdr.obj_id); + res = spiffs_rewrite_index(fs, p_hdr.obj_id, data_spix_offset + i, data_pix, cur_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + } else { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); + } + SPIFFS_CHECK_RES(res); + restart = 1; + } + } + else { + // mark rpix as referenced + const u32_t rpix_byte_ix = (rpix - pix_offset) / (8/bits); + const u8_t rpix_bit_ix = (rpix & ((8/bits)-1)) * bits; + if (fs->work[rpix_byte_ix] & (1<<(rpix_bit_ix + 1))) { + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" multiple referenced from page "_SPIPRIpg"\n", + rpix, cur_pix); + // Here, we should have fixed all broken references - getting this means there + // must be multiple files with same object id. Only solution is to delete + // the object which is referring to this page + SPIFFS_CHECK_DBG("PA: FIXUP: removing object "_SPIPRIid" and page "_SPIPRIpg"\n", + p_hdr.obj_id, cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + SPIFFS_CHECK_RES(res); + // extra precaution, delete this page also + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + restart = 1; + } + fs->work[rpix_byte_ix] |= (1<<(rpix_bit_ix + 1)); + } + } + } // for all index entries + } // found index + + // next page + cur_pix++; + } + // next block + cur_block++; + } + // check consistency bitmap + if (!restart) { + spiffs_page_ix objix_pix; + spiffs_page_ix rpix; + + u32_t byte_ix; + u8_t bit_ix; + for (byte_ix = 0; !restart && byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs); byte_ix++) { + for (bit_ix = 0; !restart && bit_ix < 8/bits; bit_ix ++) { + u8_t bitmask = (fs->work[byte_ix] >> (bit_ix * bits)) & 0x7; + spiffs_page_ix cur_pix = pix_offset + byte_ix * (8/bits) + bit_ix; + + // 000 ok - free, unreferenced, not index + + if (bitmask == 0x1) { + + // 001 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, UNREFERENCED, not index\n", cur_pix); + + u8_t rewrite_ix_to_this = 0; + u8_t delete_page = 0; + // check corresponding object index entry + spiffs_page_header p_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + res = spiffs_object_get_data_page_index_reference(fs, p_hdr.obj_id, p_hdr.span_ix, + &rpix, &objix_pix); + if (res == SPIFFS_OK) { + if (((rpix == (spiffs_page_ix)-1 || rpix > SPIFFS_MAX_PAGES(fs)) || (SPIFFS_IS_LOOKUP_PAGE(fs, rpix)))) { + // pointing to a bad page altogether, rewrite index to this + rewrite_ix_to_this = 1; + SPIFFS_CHECK_DBG("PA: corresponding ref is bad: "_SPIPRIpg", rewrite to this "_SPIPRIpg"\n", rpix, cur_pix); + } else { + // pointing to something else, check what + spiffs_page_header rp_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, rpix), sizeof(spiffs_page_header), (u8_t*)&rp_hdr); + SPIFFS_CHECK_RES(res); + if (((p_hdr.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) == rp_hdr.obj_id) && + ((rp_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_FINAL)) == + (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_DELET))) { + // pointing to something else valid, just delete this page then + SPIFFS_CHECK_DBG("PA: corresponding ref is good but different: "_SPIPRIpg", delete this "_SPIPRIpg"\n", rpix, cur_pix); + delete_page = 1; + } else { + // pointing to something weird, update index to point to this page instead + if (rpix != cur_pix) { + SPIFFS_CHECK_DBG("PA: corresponding ref is weird: "_SPIPRIpg" %s%s%s%s, rewrite this "_SPIPRIpg"\n", rpix, + (rp_hdr.flags & SPIFFS_PH_FLAG_INDEX) ? "" : "INDEX ", + (rp_hdr.flags & SPIFFS_PH_FLAG_DELET) ? "" : "DELETED ", + (rp_hdr.flags & SPIFFS_PH_FLAG_USED) ? "NOTUSED " : "", + (rp_hdr.flags & SPIFFS_PH_FLAG_FINAL) ? "NOTFINAL " : "", + cur_pix); + rewrite_ix_to_this = 1; + } else { + // should not happen, destined for fubar + } + } + } + } else if (res == SPIFFS_ERR_NOT_FOUND) { + SPIFFS_CHECK_DBG("PA: corresponding ref not found, delete "_SPIPRIpg"\n", cur_pix); + delete_page = 1; + res = SPIFFS_OK; + } + + if (rewrite_ix_to_this) { + // if pointing to invalid page, redirect index to this page + SPIFFS_CHECK_DBG("PA: FIXUP: rewrite index id "_SPIPRIid" data spix "_SPIPRIsp" to point to this pix: "_SPIPRIpg"\n", + p_hdr.obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_rewrite_index(fs, p_hdr.obj_id, p_hdr.span_ix, cur_pix, objix_pix); + if (res <= _SPIFFS_ERR_CHECK_FIRST && res > _SPIFFS_ERR_CHECK_LAST) { + // index bad also, cannot mend this file + SPIFFS_CHECK_DBG("PA: FIXUP: index bad "_SPIPRIi", cannot mend!\n", res); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_BAD_FILE, p_hdr.obj_id, 0); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + res = spiffs_delete_obj_lazy(fs, p_hdr.obj_id); + } else { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_FIX_INDEX, p_hdr.obj_id, p_hdr.span_ix); + } + SPIFFS_CHECK_RES(res); + restart = 1; + continue; + } else if (delete_page) { + SPIFFS_CHECK_DBG("PA: FIXUP: deleting page "_SPIPRIpg"\n", cur_pix); + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_DELETE_PAGE, cur_pix, 0); + res = spiffs_page_delete(fs, cur_pix); + } + SPIFFS_CHECK_RES(res); + } + if (bitmask == 0x2) { + + // 010 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, not index\n", cur_pix); + + // no op, this should be taken care of when checking valid references + } + + // 011 ok - busy, referenced, not index + + if (bitmask == 0x4) { + + // 100 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, unreferenced, INDEX\n", cur_pix); + + // this should never happen, major fubar + } + + // 101 ok - busy, unreferenced, index + + if (bitmask == 0x6) { + + // 110 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" FREE, REFERENCED, INDEX\n", cur_pix); + + // no op, this should be taken care of when checking valid references + } + if (bitmask == 0x7) { + + // 111 + SPIFFS_CHECK_DBG("PA: pix "_SPIPRIpg" USED, REFERENCED, INDEX\n", cur_pix); + + // no op, this should be taken care of when checking valid references + } + } + } + } + + SPIFFS_CHECK_DBG("PA: processed "_SPIPRIpg", restart "_SPIPRIi"\n", pix_offset, restart); + // next page range + if (!restart) { + pix_offset += pages_per_scan; + } + } // while page range not reached end + return res; +} + +// Checks consistency amongst all pages and fixes irregularities +s32_t spiffs_page_consistency_check(spiffs *fs) { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 0, 0); + s32_t res = spiffs_page_consistency_check_i(fs); + if (res != SPIFFS_OK) { + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_ERROR, res, 0); + } + CHECK_CB(fs, SPIFFS_CHECK_PAGE, SPIFFS_CHECK_PROGRESS, 256, 0); + return res; +} + +//--------------------------------------- +// Object index consistency + +// searches for given object id in temporary object id index, +// returns the index or -1 +static int spiffs_object_index_search(spiffs *fs, spiffs_obj_id obj_id) { + u32_t i; + spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work; + obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG; + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id); i++) { + if ((obj_table[i] & ~SPIFFS_OBJ_ID_IX_FLAG) == obj_id) { + return i; + } + } + return -1; +} + +static s32_t spiffs_object_index_consistency_check_v(spiffs *fs, spiffs_obj_id obj_id, spiffs_block_ix cur_block, + int cur_entry, const void *user_const_p, void *user_var_p) { + (void)user_const_p; + s32_t res_c = SPIFFS_VIS_COUNTINUE; + s32_t res = SPIFFS_OK; + u32_t *log_ix = (u32_t*)user_var_p; + spiffs_obj_id *obj_table = (spiffs_obj_id *)fs->work; + + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, + (cur_block * 256)/fs->block_count, 0); + + if (obj_id != SPIFFS_OBJ_ID_FREE && obj_id != SPIFFS_OBJ_ID_DELETED && (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { + spiffs_page_header p_hdr; + spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, cur_block, cur_entry); + + // load header + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + + if (p_hdr.span_ix == 0 && + (p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET)) { + SPIFFS_CHECK_DBG("IX: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" header not fully deleted - deleting\n", + cur_pix, obj_id, p_hdr.span_ix); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_PAGE, cur_pix, obj_id); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + return res_c; + } + + if ((p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + return res_c; + } + + if (p_hdr.span_ix == 0) { + // objix header page, register objid as reachable + int r = spiffs_object_index_search(fs, obj_id); + if (r == -1) { + // not registered, do it + obj_table[*log_ix] = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + (*log_ix)++; + if (*log_ix >= SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)) { + *log_ix = 0; + } + } + } else { // span index + // objix page, see if header can be found + int r = spiffs_object_index_search(fs, obj_id); + u8_t delete = 0; + if (r == -1) { + // not in temporary index, try finding it + spiffs_page_ix objix_hdr_pix; + res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &objix_hdr_pix); + res_c = SPIFFS_VIS_COUNTINUE_RELOAD; + if (res == SPIFFS_OK) { + // found, register as reachable + obj_table[*log_ix] = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + } else if (res == SPIFFS_ERR_NOT_FOUND) { + // not found, register as unreachable + delete = 1; + obj_table[*log_ix] = obj_id | SPIFFS_OBJ_ID_IX_FLAG; + } else { + SPIFFS_CHECK_RES(res); + } + (*log_ix)++; + if (*log_ix >= SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)) { + *log_ix = 0; + } + } else { + // in temporary index, check reachable flag + if ((obj_table[r] & SPIFFS_OBJ_ID_IX_FLAG)) { + // registered as unreachable + delete = 1; + } + } + + if (delete) { + SPIFFS_CHECK_DBG("IX: FIXUP: pix "_SPIPRIpg", obj id:"_SPIPRIid" spix:"_SPIPRIsp" is orphan index - deleting\n", + cur_pix, obj_id, p_hdr.span_ix); + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_DELETE_ORPHANED_INDEX, cur_pix, obj_id); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + } + } // span index + } // valid object index id + + return res_c; +} + +// Removes orphaned and partially deleted index pages. +// Scans for index pages. When an index page is found, corresponding index header is searched for. +// If no such page exists, the index page cannot be reached as no index header exists and must be +// deleted. +s32_t spiffs_object_index_consistency_check(spiffs *fs) { + s32_t res = SPIFFS_OK; + // impl note: + // fs->work is used for a temporary object index memory, listing found object ids and + // indicating whether they can be reached or not. Acting as a fifo if object ids cannot fit. + // In the temporary object index memory, SPIFFS_OBJ_ID_IX_FLAG bit is used to indicate + // a reachable/unreachable object id. + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + u32_t obj_id_log_ix = 0; + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 0, 0); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_object_index_consistency_check_v, 0, &obj_id_log_ix, + 0, 0); + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + if (res != SPIFFS_OK) { + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_ERROR, res, 0); + } + CHECK_CB(fs, SPIFFS_CHECK_INDEX, SPIFFS_CHECK_PROGRESS, 256, 0); + return res; +} + +#endif // !SPIFFS_READ_ONLY diff --git a/libiot/spiffs/spiffs_config.h b/libiot/spiffs/spiffs_config.h new file mode 100644 index 0000000..60f48a1 --- /dev/null +++ b/libiot/spiffs/spiffs_config.h @@ -0,0 +1,360 @@ +/* + * spiffs_config.h + * + * Created on: Jul 3, 2013 + * Author: petera + */ + +#ifndef SPIFFS_CONFIG_H_ +#define SPIFFS_CONFIG_H_ + +// ----------- 8< ------------ +// Following includes are for the linux test build of spiffs +// These may/should/must be removed/altered/replaced in your target +#include +#include +#include +#include +#include +#include +// ----------- >8 ------------ + +typedef signed int s32_t; +typedef unsigned int u32_t; +typedef signed short s16_t; +typedef unsigned short u16_t; +typedef signed char s8_t; +typedef unsigned char u8_t; + +// compile time switches + +// Set generic spiffs debug output call. +#ifndef SPIFFS_DBG +#define SPIFFS_DBG(...) //printf(__VA_ARGS__) +#endif +// Set spiffs debug output call for garbage collecting. +#ifndef SPIFFS_GC_DBG +#define SPIFFS_GC_DBG(...) //printf(__VA_ARGS__) +#endif +// Set spiffs debug output call for caching. +#ifndef SPIFFS_CACHE_DBG +#define SPIFFS_CACHE_DBG(...) //printf(__VA_ARGS__) +#endif +// Set spiffs debug output call for system consistency checks. +#ifndef SPIFFS_CHECK_DBG +#define SPIFFS_CHECK_DBG(...) //printf(__VA_ARGS__) +#endif + +// Defines spiffs debug print formatters +// some general signed number +#ifndef _SPIPRIi +#define _SPIPRIi "%d" +#endif +// address +#ifndef _SPIPRIad +#define _SPIPRIad "%08x" +#endif +// block +#ifndef _SPIPRIbl +#define _SPIPRIbl "%04x" +#endif +// page +#ifndef _SPIPRIpg +#define _SPIPRIpg "%04x" +#endif +// span index +#ifndef _SPIPRIsp +#define _SPIPRIsp "%04x" +#endif +// file descriptor +#ifndef _SPIPRIfd +#define _SPIPRIfd "%d" +#endif +// file object id +#ifndef _SPIPRIid +#define _SPIPRIid "%04x" +#endif +// file flags +#ifndef _SPIPRIfl +#define _SPIPRIfl "%02x" +#endif + + +// Enable/disable API functions to determine exact number of bytes +// for filedescriptor and cache buffers. Once decided for a configuration, +// this can be disabled to reduce flash. +#ifndef SPIFFS_BUFFER_HELP +#define SPIFFS_BUFFER_HELP 0 +#endif + +// Enables/disable memory read caching of nucleus file system operations. +// If enabled, memory area must be provided for cache in SPIFFS_mount. +#ifndef SPIFFS_CACHE +#define SPIFFS_CACHE 1 +#endif +#if SPIFFS_CACHE +// Enables memory write caching for file descriptors in hydrogen +#ifndef SPIFFS_CACHE_WR +#define SPIFFS_CACHE_WR 1 +#endif + +// Enable/disable statistics on caching. Debug/test purpose only. +#ifndef SPIFFS_CACHE_STATS +#define SPIFFS_CACHE_STATS 0 +#endif +#endif + +// Always check header of each accessed page to ensure consistent state. +// If enabled it will increase number of reads, will increase flash. +#ifndef SPIFFS_PAGE_CHECK +#define SPIFFS_PAGE_CHECK 1 +#endif + +// Define maximum number of gc runs to perform to reach desired free pages. +#ifndef SPIFFS_GC_MAX_RUNS +#define SPIFFS_GC_MAX_RUNS 3 +#endif + +// Enable/disable statistics on gc. Debug/test purpose only. +#ifndef SPIFFS_GC_STATS +#define SPIFFS_GC_STATS 0 +#endif + +// Garbage collecting examines all pages in a block which and sums up +// to a block score. Deleted pages normally gives positive score and +// used pages normally gives a negative score (as these must be moved). +// To have a fair wear-leveling, the erase age is also included in score, +// whose factor normally is the most positive. +// The larger the score, the more likely it is that the block will +// picked for garbage collection. + +// Garbage collecting heuristics - weight used for deleted pages. +#ifndef SPIFFS_GC_HEUR_W_DELET +#define SPIFFS_GC_HEUR_W_DELET (5) +#endif +// Garbage collecting heuristics - weight used for used pages. +#ifndef SPIFFS_GC_HEUR_W_USED +#define SPIFFS_GC_HEUR_W_USED (-1) +#endif +// Garbage collecting heuristics - weight used for time between +// last erased and erase of this block. +#ifndef SPIFFS_GC_HEUR_W_ERASE_AGE +#define SPIFFS_GC_HEUR_W_ERASE_AGE (50) +#endif + +// Object name maximum length. Note that this length include the +// zero-termination character, meaning maximum string of characters +// can at most be SPIFFS_OBJ_NAME_LEN - 1. +#ifndef SPIFFS_OBJ_NAME_LEN +#define SPIFFS_OBJ_NAME_LEN (64) +#endif + +// Maximum length of the metadata associated with an object. +// Setting to non-zero value enables metadata-related API but also +// changes the on-disk format, so the change is not backward-compatible. +// +// Do note: the meta length must never exceed +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + 64) +// +// This is derived from following: +// logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) + +// spiffs_object_ix_header fields + at least some LUT entries) +#ifndef SPIFFS_OBJ_META_LEN +#define SPIFFS_OBJ_META_LEN (1) +#endif + +// Size of buffer allocated on stack used when copying data. +// Lower value generates more read/writes. No meaning having it bigger +// than logical page size. +#ifndef SPIFFS_COPY_BUFFER_STACK +#define SPIFFS_COPY_BUFFER_STACK (64) +#endif + +// Enable this to have an identifiable spiffs filesystem. This will look for +// a magic in all sectors to determine if this is a valid spiffs system or +// not on mount point. If not, SPIFFS_format must be called prior to mounting +// again. +#ifndef SPIFFS_USE_MAGIC +#define SPIFFS_USE_MAGIC (1) +#endif + +#if SPIFFS_USE_MAGIC +// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is +// enabled, the magic will also be dependent on the length of the filesystem. +// For example, a filesystem configured and formatted for 4 megabytes will not +// be accepted for mounting with a configuration defining the filesystem as 2 +// megabytes. +#ifndef SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_USE_MAGIC_LENGTH (1) +#endif +#endif + +// SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level +// These should be defined on a multithreaded system + +// define this to enter a mutex if you're running on a multithreaded system +#ifndef SPIFFS_LOCK +#define SPIFFS_LOCK(fs) spiffs_lock(fs) +#endif +// define this to exit a mutex if you're running on a multithreaded system +#ifndef SPIFFS_UNLOCK +#define SPIFFS_UNLOCK(fs) spiffs_unlock(fs) +#endif + +// Enable if only one spiffs instance with constant configuration will exist +// on the target. This will reduce calculations, flash and memory accesses. +// Parts of configuration must be defined below instead of at time of mount. +#ifndef SPIFFS_SINGLETON +#define SPIFFS_SINGLETON 0 +#endif + +#if SPIFFS_SINGLETON +// Instead of giving parameters in config struct, singleton build must +// give parameters in defines below. +#ifndef SPIFFS_CFG_PHYS_SZ +#define SPIFFS_CFG_PHYS_SZ(ignore) (1024*1024*2) +#endif +#ifndef SPIFFS_CFG_PHYS_ERASE_SZ +#define SPIFFS_CFG_PHYS_ERASE_SZ(ignore) (65536) +#endif +#ifndef SPIFFS_CFG_PHYS_ADDR +#define SPIFFS_CFG_PHYS_ADDR(ignore) (0) +#endif +#ifndef SPIFFS_CFG_LOG_PAGE_SZ +#define SPIFFS_CFG_LOG_PAGE_SZ(ignore) (256) +#endif +#ifndef SPIFFS_CFG_LOG_BLOCK_SZ +#define SPIFFS_CFG_LOG_BLOCK_SZ(ignore) (65536) +#endif +#endif + +// Enable this if your target needs aligned data for index tables +#ifndef SPIFFS_ALIGNED_OBJECT_INDEX_TABLES +#define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 1 +#endif + +// Enable this if you want the HAL callbacks to be called with the spiffs struct +#ifndef SPIFFS_HAL_CALLBACK_EXTRA +#define SPIFFS_HAL_CALLBACK_EXTRA 0 +#endif + +// Enable this if you want to add an integer offset to all file handles +// (spiffs_file). This is useful if running multiple instances of spiffs on +// same target, in order to recognise to what spiffs instance a file handle +// belongs. +// NB: This adds config field fh_ix_offset in the configuration struct when +// mounting, which must be defined. +#ifndef SPIFFS_FILEHDL_OFFSET +#define SPIFFS_FILEHDL_OFFSET 0 +#endif + +// Enable this to compile a read only version of spiffs. +// This will reduce binary size of spiffs. All code comprising modification +// of the file system will not be compiled. Some config will be ignored. +// HAL functions for erasing and writing to spi-flash may be null. Cache +// can be disabled for even further binary size reduction (and ram savings). +// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL. +// If the file system cannot be mounted due to aborted erase operation and +// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be +// returned. +// Might be useful for e.g. bootloaders and such. +#ifndef SPIFFS_READ_ONLY +#define SPIFFS_READ_ONLY 0 +#endif + +// Enable this to add a temporal file cache using the fd buffer. +// The effects of the cache is that SPIFFS_open will find the file faster in +// certain cases. It will make it a lot easier for spiffs to find files +// opened frequently, reducing number of readings from the spi flash for +// finding those files. +// This will grow each fd by 6 bytes. If your files are opened in patterns +// with a degree of temporal locality, the system is optimized. +// Examples can be letting spiffs serve web content, where one file is the css. +// The css is accessed for each html file that is opened, meaning it is +// accessed almost every second time a file is opened. Another example could be +// a log file that is often opened, written, and closed. +// The size of the cache is number of given file descriptors, as it piggybacks +// on the fd update mechanism. The cache lives in the closed file descriptors. +// When closed, the fd know the whereabouts of the file. Instead of forgetting +// this, the temporal cache will keep handling updates to that file even if the +// fd is closed. If the file is opened again, the location of the file is found +// directly. If all available descriptors become opened, all cache memory is +// lost. +#ifndef SPIFFS_TEMPORAL_FD_CACHE +#define SPIFFS_TEMPORAL_FD_CACHE 1 +#endif + +// Temporal file cache hit score. Each time a file is opened, all cached files +// will lose one point. If the opened file is found in cache, that entry will +// gain SPIFFS_TEMPORAL_CACHE_HIT_SCORE points. One can experiment with this +// value for the specific access patterns of the application. However, it must +// be between 1 (no gain for hitting a cached entry often) and 255. +#ifndef SPIFFS_TEMPORAL_CACHE_HIT_SCORE +#define SPIFFS_TEMPORAL_CACHE_HIT_SCORE 4 +#endif + +// Enable to be able to map object indices to memory. +// This allows for faster and more deterministic reading if cases of reading +// large files and when changing file offset by seeking around a lot. +// When mapping a file's index, the file system will be scanned for index pages +// and the info will be put in memory provided by user. When reading, the +// memory map can be looked up instead of searching for index pages on the +// medium. This way, user can trade memory against performance. +// Whole, parts of, or future parts not being written yet can be mapped. The +// memory array will be owned by spiffs and updated accordingly during garbage +// collecting or when modifying the indices. The latter is invoked by when the +// file is modified in some way. The index buffer is tied to the file +// descriptor. +#ifndef SPIFFS_IX_MAP +#define SPIFFS_IX_MAP 1 +#endif + +// Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function +// in the api. This function will visualize all filesystem using given printf +// function. +#ifndef SPIFFS_TEST_VISUALISATION +#define SPIFFS_TEST_VISUALISATION 0 +#endif +#if SPIFFS_TEST_VISUALISATION +#ifndef spiffs_printf +#define spiffs_printf(...) printf(__VA_ARGS__) +#endif +// spiffs_printf argument for a free page +#ifndef SPIFFS_TEST_VIS_FREE_STR +#define SPIFFS_TEST_VIS_FREE_STR "_" +#endif +// spiffs_printf argument for a deleted page +#ifndef SPIFFS_TEST_VIS_DELE_STR +#define SPIFFS_TEST_VIS_DELE_STR "/" +#endif +// spiffs_printf argument for an index page for given object id +#ifndef SPIFFS_TEST_VIS_INDX_STR +#define SPIFFS_TEST_VIS_INDX_STR(id) "i" +#endif +// spiffs_printf argument for a data page for given object id +#ifndef SPIFFS_TEST_VIS_DATA_STR +#define SPIFFS_TEST_VIS_DATA_STR(id) "d" +#endif +#endif + +// Types depending on configuration such as the amount of flash bytes +// given to spiffs file system in total (spiffs_file_system_size), +// the logical block size (log_block_size), and the logical page size +// (log_page_size) + +// Block index type. Make sure the size of this type can hold +// the highest number of all blocks - i.e. spiffs_file_system_size / log_block_size +typedef u16_t spiffs_block_ix; +// Page index type. Make sure the size of this type can hold +// the highest page number of all pages - i.e. spiffs_file_system_size / log_page_size +typedef u16_t spiffs_page_ix; +// Object id type - most significant bit is reserved for index flag. Make sure the +// size of this type can hold the highest object id on a full system, +// i.e. 2 + (spiffs_file_system_size / (2*log_page_size))*2 +typedef u16_t spiffs_obj_id; +// Object span index type. Make sure the size of this type can +// hold the largest possible span index on the system - +// i.e. (spiffs_file_system_size / log_page_size) - 1 +typedef u16_t spiffs_span_ix; + +#endif /* SPIFFS_CONFIG_H_ */ diff --git a/libiot/spiffs/spiffs_gc.c b/libiot/spiffs/spiffs_gc.c new file mode 100644 index 0000000..c8fb01a --- /dev/null +++ b/libiot/spiffs/spiffs_gc.c @@ -0,0 +1,606 @@ +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if !SPIFFS_READ_ONLY + +// Erases a logical block and updates the erase counter. +// If cache is enabled, all pages that might be cached in this block +// is dropped. +static s32_t spiffs_gc_erase_block( + spiffs *fs, + spiffs_block_ix bix) { + s32_t res; + + SPIFFS_GC_DBG("gc: erase block "_SPIPRIbl"\n", bix); + res = spiffs_erase_block(fs, bix); + SPIFFS_CHECK_RES(res); + +#if SPIFFS_CACHE + { + u32_t i; + for (i = 0; i < SPIFFS_PAGES_PER_BLOCK(fs); i++) { + spiffs_cache_drop_page(fs, SPIFFS_PAGE_FOR_BLOCK(fs, bix) + i); + } + } +#endif + return res; +} + +// Searches for blocks where all entries are deleted - if one is found, +// the block is erased. Compared to the non-quick gc, the quick one ensures +// that no updates are needed on existing objects on pages that are erased. +s32_t spiffs_gc_quick( + spiffs *fs, u16_t max_free_pages) { + s32_t res = SPIFFS_OK; + u32_t blocks = fs->block_count; + spiffs_block_ix cur_block = 0; + u32_t cur_block_addr = 0; + int cur_entry = 0; + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + + SPIFFS_GC_DBG("gc_quick: running\n"); +#if SPIFFS_GC_STATS + fs->stats_gc_runs++; +#endif + + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // find fully deleted blocks + // check each block + while (res == SPIFFS_OK && blocks--) { + u16_t deleted_pages_in_block = 0; + u16_t free_pages_in_block = 0; + + int obj_lookup_page = 0; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && + cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_DELETED) { + deleted_pages_in_block++; + } else if (obj_id == SPIFFS_OBJ_ID_FREE) { + // kill scan, go for next block + free_pages_in_block++; + if (free_pages_in_block > max_free_pages) { + obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs); + res = 1; // kill object lu loop + break; + } + } else { + // kill scan, go for next block + obj_lookup_page = SPIFFS_OBJ_LOOKUP_PAGES(fs); + res = 1; // kill object lu loop + break; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + if (res == 1) res = SPIFFS_OK; + + if (res == SPIFFS_OK && + deleted_pages_in_block + free_pages_in_block == SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs) && + free_pages_in_block <= max_free_pages) { + // found a fully deleted block + fs->stats_p_deleted -= deleted_pages_in_block; + res = spiffs_gc_erase_block(fs, cur_block); + return res; + } + + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + } // per block + + if (res == SPIFFS_OK) { + res = SPIFFS_ERR_NO_DELETED_BLOCKS; + } + return res; +} + +// Checks if garbage collecting is necessary. If so a candidate block is found, +// cleansed and erased +s32_t spiffs_gc_check( + spiffs *fs, + u32_t len) { + s32_t res; + s32_t free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count-2) + - fs->stats_p_allocated - fs->stats_p_deleted; + int tries = 0; + + if (fs->free_blocks > 3 && + (s32_t)len < free_pages * (s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { + return SPIFFS_OK; + } + + u32_t needed_pages = (len + SPIFFS_DATA_PAGE_SIZE(fs) - 1) / SPIFFS_DATA_PAGE_SIZE(fs); +// if (fs->free_blocks <= 2 && (s32_t)needed_pages > free_pages) { +// SPIFFS_GC_DBG("gc: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); +// return SPIFFS_ERR_FULL; +// } + if ((s32_t)needed_pages > (s32_t)(free_pages + fs->stats_p_deleted)) { + SPIFFS_GC_DBG("gc_check: full freeblk:"_SPIPRIi" needed:"_SPIPRIi" free:"_SPIPRIi" dele:"_SPIPRIi"\n", fs->free_blocks, needed_pages, free_pages, fs->stats_p_deleted); + return SPIFFS_ERR_FULL; + } + + do { + SPIFFS_GC_DBG("\ngc_check #"_SPIPRIi": run gc free_blocks:"_SPIPRIi" pfree:"_SPIPRIi" pallo:"_SPIPRIi" pdele:"_SPIPRIi" ["_SPIPRIi"] len:"_SPIPRIi" of "_SPIPRIi"\n", + tries, + fs->free_blocks, free_pages, fs->stats_p_allocated, fs->stats_p_deleted, (free_pages+fs->stats_p_allocated+fs->stats_p_deleted), + len, (u32_t)(free_pages*SPIFFS_DATA_PAGE_SIZE(fs))); + + spiffs_block_ix *cands; + int count; + spiffs_block_ix cand; + s32_t prev_free_pages = free_pages; + // if the fs is crammed, ignore block age when selecting candidate - kind of a bad state + res = spiffs_gc_find_candidate(fs, &cands, &count, free_pages <= 0); + SPIFFS_CHECK_RES(res); + if (count == 0) { + SPIFFS_GC_DBG("gc_check: no candidates, return\n"); + return (s32_t)needed_pages < free_pages ? SPIFFS_OK : SPIFFS_ERR_FULL; + } +#if SPIFFS_GC_STATS + fs->stats_gc_runs++; +#endif + cand = cands[0]; + fs->cleaning = 1; + //SPIFFS_GC_DBG("gcing: cleaning block "_SPIPRIi"\n", cand); + res = spiffs_gc_clean(fs, cand); + fs->cleaning = 0; + if (res < 0) { + SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res); + } else { + SPIFFS_GC_DBG("gc_check: cleaning block "_SPIPRIi", result "_SPIPRIi"\n", cand, res); + } + SPIFFS_CHECK_RES(res); + + res = spiffs_gc_erase_page_stats(fs, cand); + SPIFFS_CHECK_RES(res); + + res = spiffs_gc_erase_block(fs, cand); + SPIFFS_CHECK_RES(res); + + free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) + - fs->stats_p_allocated - fs->stats_p_deleted; + + if (prev_free_pages <= 0 && prev_free_pages == free_pages) { + // abort early to reduce wear, at least tried once + SPIFFS_GC_DBG("gc_check: early abort, no result on gc when fs crammed\n"); + break; + } + + } while (++tries < SPIFFS_GC_MAX_RUNS && (fs->free_blocks <= 2 || + (s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs))); + + free_pages = + (SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs)) * (fs->block_count - 2) + - fs->stats_p_allocated - fs->stats_p_deleted; + if ((s32_t)len > free_pages*(s32_t)SPIFFS_DATA_PAGE_SIZE(fs)) { + res = SPIFFS_ERR_FULL; + } + + SPIFFS_GC_DBG("gc_check: finished, "_SPIPRIi" dirty, blocks "_SPIPRIi" free, "_SPIPRIi" pages free, "_SPIPRIi" tries, res "_SPIPRIi"\n", + fs->stats_p_allocated + fs->stats_p_deleted, + fs->free_blocks, free_pages, tries, res); + + return res; +} + +// Updates page statistics for a block that is about to be erased +s32_t spiffs_gc_erase_page_stats( + spiffs *fs, + spiffs_block_ix bix) { + s32_t res = SPIFFS_OK; + int obj_lookup_page = 0; + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = 0; + u32_t dele = 0; + u32_t allo = 0; + + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + dele++; + } else { + allo++; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + SPIFFS_GC_DBG("gc_check: wipe pallo:"_SPIPRIi" pdele:"_SPIPRIi"\n", allo, dele); + fs->stats_p_allocated -= allo; + fs->stats_p_deleted -= dele; + return res; +} + +// Finds block candidates to erase +s32_t spiffs_gc_find_candidate( + spiffs *fs, + spiffs_block_ix **block_candidates, + int *candidate_count, + char fs_crammed) { + s32_t res = SPIFFS_OK; + u32_t blocks = fs->block_count; + spiffs_block_ix cur_block = 0; + u32_t cur_block_addr = 0; + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = 0; + + // using fs->work area as sorted candidate memory, (spiffs_block_ix)cand_bix/(s32_t)score + int max_candidates = MIN(fs->block_count, (SPIFFS_CFG_LOG_PAGE_SZ(fs)-8)/(sizeof(spiffs_block_ix) + sizeof(s32_t))); + *candidate_count = 0; + memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + + // divide up work area into block indices and scores + spiffs_block_ix *cand_blocks = (spiffs_block_ix *)fs->work; + s32_t *cand_scores = (s32_t *)(fs->work + max_candidates * sizeof(spiffs_block_ix)); + + // align cand_scores on s32_t boundary + cand_scores = (s32_t*)(((intptr_t)cand_scores + sizeof(intptr_t) - 1) & ~(sizeof(intptr_t) - 1)); + + *block_candidates = cand_blocks; + + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // check each block + while (res == SPIFFS_OK && blocks--) { + u16_t deleted_pages_in_block = 0; + u16_t used_pages_in_block = 0; + + int obj_lookup_page = 0; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && + cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + // when a free entry is encountered, scan logic ensures that all following entries are free also + res = 1; // kill object lu loop + break; + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + deleted_pages_in_block++; + } else { + used_pages_in_block++; + } + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + if (res == 1) res = SPIFFS_OK; + + // calculate score and insert into candidate table + // stoneage sort, but probably not so many blocks + if (res == SPIFFS_OK && deleted_pages_in_block > 0) { + // read erase count + spiffs_obj_id erase_count; + res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_ERASE_COUNT_PADDR(fs, cur_block), + sizeof(spiffs_obj_id), (u8_t *)&erase_count); + SPIFFS_CHECK_RES(res); + + spiffs_obj_id erase_age; + if (fs->max_erase_count > erase_count) { + erase_age = fs->max_erase_count - erase_count; + } else { + erase_age = SPIFFS_OBJ_ID_FREE - (erase_count - fs->max_erase_count); + } + + s32_t score = + deleted_pages_in_block * SPIFFS_GC_HEUR_W_DELET + + used_pages_in_block * SPIFFS_GC_HEUR_W_USED + + erase_age * (fs_crammed ? 0 : SPIFFS_GC_HEUR_W_ERASE_AGE); + int cand_ix = 0; + SPIFFS_GC_DBG("gc_check: bix:"_SPIPRIbl" del:"_SPIPRIi" use:"_SPIPRIi" score:"_SPIPRIi"\n", cur_block, deleted_pages_in_block, used_pages_in_block, score); + while (cand_ix < max_candidates) { + if (cand_blocks[cand_ix] == (spiffs_block_ix)-1) { + cand_blocks[cand_ix] = cur_block; + cand_scores[cand_ix] = score; + break; + } else if (cand_scores[cand_ix] < score) { + int reorder_cand_ix = max_candidates - 2; + while (reorder_cand_ix >= cand_ix) { + cand_blocks[reorder_cand_ix + 1] = cand_blocks[reorder_cand_ix]; + cand_scores[reorder_cand_ix + 1] = cand_scores[reorder_cand_ix]; + reorder_cand_ix--; + } + cand_blocks[cand_ix] = cur_block; + cand_scores[cand_ix] = score; + break; + } + cand_ix++; + } + (*candidate_count)++; + } + + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + } // per block + + return res; +} + +typedef enum { + FIND_OBJ_DATA, + MOVE_OBJ_DATA, + MOVE_OBJ_IX, + FINISHED +} spiffs_gc_clean_state; + +typedef struct { + spiffs_gc_clean_state state; + spiffs_obj_id cur_obj_id; + spiffs_span_ix cur_objix_spix; + spiffs_page_ix cur_objix_pix; + spiffs_page_ix cur_data_pix; + int stored_scan_entry_index; + u8_t obj_id_found; +} spiffs_gc; + +// Empties given block by moving all data into free pages of another block +// Strategy: +// loop: +// scan object lookup for object data pages +// for first found id, check spix and load corresponding object index page to memory +// push object scan lookup entry index +// rescan object lookup, find data pages with same id and referenced by same object index +// move data page, update object index in memory +// when reached end of lookup, store updated object index +// pop object scan lookup entry index +// repeat loop until end of object lookup +// scan object lookup again for remaining object index pages, move to new page in other block +// +s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { + s32_t res = SPIFFS_OK; + const int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + // this is the global localizer being pushed and popped + int cur_entry = 0; + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + spiffs_gc gc; // our stack frame/state + spiffs_page_ix cur_pix = 0; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + + SPIFFS_GC_DBG("gc_clean: cleaning block "_SPIPRIbl"\n", bix); + + memset(&gc, 0, sizeof(spiffs_gc)); + gc.state = FIND_OBJ_DATA; + + if (fs->free_cursor_block_ix == bix) { + // move free cursor to next block, cannot use free pages from the block we want to clean + fs->free_cursor_block_ix = (bix+1)%fs->block_count; + fs->free_cursor_obj_lu_entry = 0; + SPIFFS_GC_DBG("gc_clean: move free cursor to block "_SPIPRIbl"\n", fs->free_cursor_block_ix); + } + + while (res == SPIFFS_OK && gc.state != FINISHED) { + SPIFFS_GC_DBG("gc_clean: state = "_SPIPRIi" entry:"_SPIPRIi"\n", gc.state, cur_entry); + gc.obj_id_found = 0; // reset (to no found data page) + + // scan through lookup pages + int obj_lookup_page = cur_entry / entries_per_page; + u8_t scan = 1; + // check each object lookup page + while (scan && res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each object lookup entry + while (scan && res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + cur_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, cur_entry); + + // act upon object id depending on gc state + switch (gc.state) { + case FIND_OBJ_DATA: + // find a data page + if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && + ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0)) { + // found a data page, stop scanning and handle in switch case below + SPIFFS_GC_DBG("gc_clean: FIND_DATA state:"_SPIPRIi" - found obj id "_SPIPRIid"\n", gc.state, obj_id); + gc.obj_id_found = 1; + gc.cur_obj_id = obj_id; + gc.cur_data_pix = cur_pix; + scan = 0; + } + break; + case MOVE_OBJ_DATA: + // evacuate found data pages for corresponding object index we have in memory, + // update memory representation + if (obj_id == gc.cur_obj_id) { + spiffs_page_header p_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA found data page "_SPIPRIid":"_SPIPRIsp" @ "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix); + if (SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix) != gc.cur_objix_spix) { + SPIFFS_GC_DBG("gc_clean: MOVE_DATA no objix spix match, take in another run\n"); + } else { + spiffs_page_ix new_data_pix; + if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { + // move page + res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_data_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", gc.cur_obj_id, p_hdr.span_ix, cur_pix, new_data_pix); + SPIFFS_CHECK_RES(res); + // move wipes obj_lu, reload it + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } else { + // page is deleted but not deleted in lookup, scrap it - + // might seem unnecessary as we will erase this block, but + // we might get aborted + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_page_delete(fs, cur_pix); + SPIFFS_CHECK_RES(res); + new_data_pix = SPIFFS_OBJ_ID_FREE; + } + // update memory representation of object index page with new data page + if (gc.cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[p_hdr.span_ix] = new_data_pix; + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)] = new_data_pix; + SPIFFS_GC_DBG("gc_clean: MOVE_DATA wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, p_hdr.span_ix)); + } + } + } + break; + case MOVE_OBJ_IX: + // find and evacuate object index pages + if (obj_id != SPIFFS_OBJ_ID_DELETED && obj_id != SPIFFS_OBJ_ID_FREE && + (obj_id & SPIFFS_OBJ_ID_IX_FLAG)) { + // found an index object id + spiffs_page_header p_hdr; + spiffs_page_ix new_pix; + // load header + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + if (p_hdr.flags & SPIFFS_PH_FLAG_DELET) { + // move page + res = spiffs_page_move(fs, 0, 0, obj_id, &p_hdr, cur_pix, &new_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX move objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg" to "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix, new_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&p_hdr, + SPIFFS_EV_IX_MOV, obj_id, p_hdr.span_ix, new_pix, 0); + // move wipes obj_lu, reload it + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), + SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } else { + // page is deleted but not deleted in lookup, scrap it - + // might seem unnecessary as we will erase this block, but + // we might get aborted + SPIFFS_GC_DBG("gc_clean: MOVE_OBJIX wipe objix "_SPIPRIid":"_SPIPRIsp" page "_SPIPRIpg"\n", obj_id, p_hdr.span_ix, cur_pix); + res = spiffs_page_delete(fs, cur_pix); + if (res == SPIFFS_OK) { + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, obj_id, p_hdr.span_ix, cur_pix, 0); + } + } + SPIFFS_CHECK_RES(res); + } + break; + default: + scan = 0; + break; + } // switch gc state + cur_entry++; + } // per entry + obj_lookup_page++; // no need to check scan variable here, obj_lookup_page is set in start of loop + } // per object lookup page + if (res != SPIFFS_OK) break; + + // state finalization and switch + switch (gc.state) { + case FIND_OBJ_DATA: + if (gc.obj_id_found) { + // handle found data page - + // find out corresponding obj ix page and load it to memory + spiffs_page_header p_hdr; + spiffs_page_ix objix_pix; + gc.stored_scan_entry_index = cur_entry; // push cursor + cur_entry = 0; // restart scan from start + gc.state = MOVE_OBJ_DATA; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, cur_pix), sizeof(spiffs_page_header), (u8_t*)&p_hdr); + SPIFFS_CHECK_RES(res); + gc.cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, p_hdr.span_ix); + SPIFFS_GC_DBG("gc_clean: FIND_DATA find objix span_ix:"_SPIPRIsp"\n", gc.cur_objix_spix); + res = spiffs_obj_lu_find_id_and_span(fs, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix, 0, &objix_pix); + if (res == SPIFFS_ERR_NOT_FOUND) { + // on borked systems we might get an ERR_NOT_FOUND here - + // this is handled by simply deleting the page as it is not referenced + // from anywhere + SPIFFS_GC_DBG("gc_clean: FIND_OBJ_DATA objix not found! Wipe page "_SPIPRIpg"\n", gc.cur_data_pix); + res = spiffs_page_delete(fs, gc.cur_data_pix); + SPIFFS_CHECK_RES(res); + // then we restore states and continue scanning for data pages + cur_entry = gc.stored_scan_entry_index; // pop cursor + gc.state = FIND_OBJ_DATA; + break; // done + } + SPIFFS_CHECK_RES(res); + SPIFFS_GC_DBG("gc_clean: FIND_DATA found object index at page "_SPIPRIpg"\n", objix_pix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + // cannot allow a gc if the presumed index in fact is no index, a + // check must run or lot of data may be lost + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_spix); + gc.cur_objix_pix = objix_pix; + } else { + // no more data pages found, passed thru all block, start evacuating object indices + gc.state = MOVE_OBJ_IX; + cur_entry = 0; // restart entry scan index + } + break; + case MOVE_OBJ_DATA: { + // store modified objix (hdr) page residing in memory now that all + // data pages belonging to this object index and residing in the block + // we want to evacuate + spiffs_page_ix new_objix_pix; + gc.state = FIND_OBJ_DATA; + cur_entry = gc.stored_scan_entry_index; // pop cursor + if (gc.cur_objix_spix == 0) { + // store object index header page + res = spiffs_object_update_index_hdr(fs, 0, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, gc.cur_objix_pix, fs->work, 0, 0, 0, &new_objix_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, 0); + SPIFFS_CHECK_RES(res); + } else { + // store object index page + res = spiffs_page_move(fs, 0, fs->work, gc.cur_obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, gc.cur_objix_pix, &new_objix_pix); + SPIFFS_GC_DBG("gc_clean: MOVE_DATA store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, objix->p_hdr.span_ix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD, gc.cur_obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + } + } + break; + case MOVE_OBJ_IX: + // scanned thru all block, no more object indices found - our work here is done + gc.state = FINISHED; + break; + default: + cur_entry = 0; + break; + } // switch gc.state + SPIFFS_GC_DBG("gc_clean: state-> "_SPIPRIi"\n", gc.state); + } // while state != FINISHED + + + return res; +} + +#endif // !SPIFFS_READ_ONLY diff --git a/libiot/spiffs/spiffs_hydrogen.c b/libiot/spiffs/spiffs_hydrogen.c new file mode 100644 index 0000000..9ff3e7a --- /dev/null +++ b/libiot/spiffs/spiffs_hydrogen.c @@ -0,0 +1,1405 @@ +/* + * spiffs_hydrogen.c + * + * Created on: Jun 16, 2013 + * Author: petera + */ + +#include "spiffs.h" +#include "spiffs_nucleus.h" + +#if SPIFFS_FILEHDL_OFFSET +#define SPIFFS_FH_OFFS(fs, fh) ((fh) != 0 ? ((fh) + (fs)->cfg.fh_ix_offset) : 0) +#define SPIFFS_FH_UNOFFS(fs, fh) ((fh) != 0 ? ((fh) - (fs)->cfg.fh_ix_offset) : 0) +#else +#define SPIFFS_FH_OFFS(fs, fh) (fh) +#define SPIFFS_FH_UNOFFS(fs, fh) (fh) +#endif + +#if SPIFFS_CACHE == 1 +static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh); +#endif + +#if SPIFFS_BUFFER_HELP +u32_t SPIFFS_buffer_bytes_for_filedescs(spiffs *fs, u32_t num_descs) { + return num_descs * sizeof(spiffs_fd); +} +#if SPIFFS_CACHE +u32_t SPIFFS_buffer_bytes_for_cache(spiffs *fs, u32_t num_pages) { + return sizeof(spiffs_cache) + num_pages * (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs)); +} +#endif +#endif + +u8_t SPIFFS_mounted(spiffs *fs) { + return SPIFFS_CHECK_MOUNT(fs); +} + +s32_t SPIFFS_format(spiffs *fs) { +#if SPIFFS_READ_ONLY + (void)fs; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + if (SPIFFS_CHECK_MOUNT(fs)) { + fs->err_code = SPIFFS_ERR_MOUNTED; + return -1; + } + + s32_t res; + SPIFFS_LOCK(fs); + + spiffs_block_ix bix = 0; + while (bix < fs->block_count) { + fs->max_erase_count = 0; + res = spiffs_erase_block(fs, bix); + if (res != SPIFFS_OK) { + res = SPIFFS_ERR_ERASE_FAIL; + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + bix++; + } + + SPIFFS_UNLOCK(fs); + + return 0; +#endif // SPIFFS_READ_ONLY +} + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + +s32_t SPIFFS_probe_fs(spiffs_config *config) { + s32_t res = spiffs_probe(config); + return res; +} + +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + +s32_t SPIFFS_mount(spiffs *fs, spiffs_config *config, u8_t *work, + u8_t *fd_space, u32_t fd_space_size, + void *cache, u32_t cache_size, + spiffs_check_callback check_cb_f) { + void *user_data; + SPIFFS_LOCK(fs); + user_data = fs->user_data; + memset(fs, 0, sizeof(spiffs)); + memcpy(&fs->cfg, config, sizeof(spiffs_config)); + fs->user_data = user_data; + fs->block_count = SPIFFS_CFG_PHYS_SZ(fs) / SPIFFS_CFG_LOG_BLOCK_SZ(fs); + fs->work = &work[0]; + fs->lu_work = &work[SPIFFS_CFG_LOG_PAGE_SZ(fs)]; + memset(fd_space, 0, fd_space_size); + // align fd_space pointer to pointer size byte boundary + u8_t ptr_size = sizeof(void*); + u8_t addr_lsb = ((u8_t)(intptr_t)fd_space) & (ptr_size-1); + if (addr_lsb) { + fd_space += (ptr_size-addr_lsb); + fd_space_size -= (ptr_size-addr_lsb); + } + fs->fd_space = fd_space; + fs->fd_count = (fd_space_size/sizeof(spiffs_fd)); + + // align cache pointer to 4 byte boundary + addr_lsb = ((u8_t)(intptr_t)cache) & (ptr_size-1); + if (addr_lsb) { + u8_t *cache_8 = (u8_t *)cache; + cache_8 += (ptr_size-addr_lsb); + cache = cache_8; + cache_size -= (ptr_size-addr_lsb); + } + if (cache_size & (ptr_size-1)) { + cache_size -= (cache_size & (ptr_size-1)); + } + +#if SPIFFS_CACHE + fs->cache = cache; + fs->cache_size = (cache_size > (SPIFFS_CFG_LOG_PAGE_SZ(fs)*32)) ? SPIFFS_CFG_LOG_PAGE_SZ(fs)*32 : cache_size; + spiffs_cache_init(fs); +#endif + + s32_t res; + +#if SPIFFS_USE_MAGIC + res = SPIFFS_CHECK_MAGIC_POSSIBLE(fs) ? SPIFFS_OK : SPIFFS_ERR_MAGIC_NOT_POSSIBLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + fs->config_magic = SPIFFS_CONFIG_MAGIC; + + res = spiffs_obj_lu_scan(fs); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_DBG("page index byte len: "_SPIPRIi"\n", (u32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)); + SPIFFS_DBG("object lookup pages: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_LOOKUP_PAGES(fs)); + SPIFFS_DBG("page pages per block: "_SPIPRIi"\n", (u32_t)SPIFFS_PAGES_PER_BLOCK(fs)); + SPIFFS_DBG("page header length: "_SPIPRIi"\n", (u32_t)sizeof(spiffs_page_header)); + SPIFFS_DBG("object header index entries: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_HDR_IX_LEN(fs)); + SPIFFS_DBG("object index entries: "_SPIPRIi"\n", (u32_t)SPIFFS_OBJ_IX_LEN(fs)); + SPIFFS_DBG("available file descriptors: "_SPIPRIi"\n", (u32_t)fs->fd_count); + SPIFFS_DBG("free blocks: "_SPIPRIi"\n", (u32_t)fs->free_blocks); + + fs->check_cb_f = check_cb_f; + + fs->mounted = 1; + + SPIFFS_UNLOCK(fs); + + return 0; +} + +void SPIFFS_unmount(spiffs *fs) { + if (!SPIFFS_CHECK_CFG(fs) || !SPIFFS_CHECK_MOUNT(fs)) return; + SPIFFS_LOCK(fs); + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr != 0) { +#if SPIFFS_CACHE + (void)spiffs_fflush_cache(fs, cur_fd->file_nbr); +#endif + spiffs_fd_return(fs, cur_fd->file_nbr); + } + } + fs->mounted = 0; + + SPIFFS_UNLOCK(fs); +} + +s32_t SPIFFS_errno(spiffs *fs) { + return fs->err_code; +} + +void SPIFFS_clearerr(spiffs *fs) { + fs->err_code = SPIFFS_OK; +} + +s32_t SPIFFS_creat(spiffs *fs, const char *path, spiffs_mode mode) { +#if SPIFFS_READ_ONLY + (void)fs; (void)path; (void)mode; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + (void)mode; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + spiffs_obj_id obj_id; + s32_t res; + + res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, (const u8_t*)path); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + res = spiffs_object_create(fs, obj_id, (const u8_t*)path, 0, SPIFFS_TYPE_FILE, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + +spiffs_file SPIFFS_open(spiffs *fs, const char *path, spiffs_flags flags, spiffs_mode mode) { + (void)mode; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + spiffs_page_ix pix; + +#if SPIFFS_READ_ONLY + // not valid flags in read only mode + flags &= ~(SPIFFS_WRONLY | SPIFFS_CREAT | SPIFFS_TRUNC); +#endif // SPIFFS_READ_ONLY + + s32_t res = spiffs_fd_find_new(fs, &fd, path); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); + if ((flags & SPIFFS_O_CREAT) == 0) { + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if (res == SPIFFS_OK && + (flags & (SPIFFS_O_CREAT | SPIFFS_O_EXCL)) == (SPIFFS_O_CREAT | SPIFFS_O_EXCL)) { + // creat and excl and file exists - fail + res = SPIFFS_ERR_FILE_EXISTS; + spiffs_fd_return(fs, fd->file_nbr); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if ((flags & SPIFFS_O_CREAT) && res == SPIFFS_ERR_NOT_FOUND) { +#if !SPIFFS_READ_ONLY + spiffs_obj_id obj_id; + // no need to enter conflicting name here, already looked for it above + res = spiffs_obj_lu_find_free_obj_id(fs, &obj_id, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + res = spiffs_object_create(fs, obj_id, (const u8_t*)path, 0, SPIFFS_TYPE_FILE, &pix); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + flags &= ~SPIFFS_O_TRUNC; +#endif // !SPIFFS_READ_ONLY + } else { + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + res = spiffs_object_open_by_page(fs, pix, fd, flags, mode); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } +#endif // !SPIFFS_READ_ONLY + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return SPIFFS_FH_OFFS(fs, fd->file_nbr); +} + +spiffs_file SPIFFS_open_by_dirent(spiffs *fs, struct spiffs_dirent *e, spiffs_flags flags, spiffs_mode mode) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + + s32_t res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, e->pix, fd, flags, mode); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } +#endif // !SPIFFS_READ_ONLY + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return SPIFFS_FH_OFFS(fs, fd->file_nbr); +} + +spiffs_file SPIFFS_open_by_page(spiffs *fs, spiffs_page_ix page_ix, spiffs_flags flags, spiffs_mode mode) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + + s32_t res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (SPIFFS_IS_LOOKUP_PAGE(fs, page_ix)) { + res = SPIFFS_ERR_NOT_A_FILE; + spiffs_fd_return(fs, fd->file_nbr); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + res = spiffs_object_open_by_page(fs, page_ix, fd, flags, mode); + if (res == SPIFFS_ERR_IS_FREE || + res == SPIFFS_ERR_DELETED || + res == SPIFFS_ERR_NOT_FINALIZED || + res == SPIFFS_ERR_NOT_INDEX || + res == SPIFFS_ERR_INDEX_SPAN_MISMATCH) { + res = SPIFFS_ERR_NOT_A_FILE; + } + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if !SPIFFS_READ_ONLY + if (flags & SPIFFS_O_TRUNC) { + res = spiffs_object_truncate(fd, 0, 0); + if (res < SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } +#endif // !SPIFFS_READ_ONLY + + fd->fdoffset = 0; + + SPIFFS_UNLOCK(fs); + + return SPIFFS_FH_OFFS(fs, fd->file_nbr); +} + +static s32_t spiffs_hydro_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_RDONLY) == 0) { + res = SPIFFS_ERR_NOT_READABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if (fd->size == SPIFFS_UNDEFINED_LEN && len > 0) { + // special case for zero sized files + res = SPIFFS_ERR_END_OF_OBJECT; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + if (fd->fdoffset + len >= fd->size) { + // reading beyond file size + s32_t avail = fd->size - fd->fdoffset; + if (avail <= 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_END_OF_OBJECT); + } + res = spiffs_object_read(fd, fd->fdoffset, avail, (u8_t*)buf); + if (res == SPIFFS_ERR_END_OF_OBJECT) { + fd->fdoffset += avail; + SPIFFS_UNLOCK(fs); + return avail; + } else { + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + len = avail; + } + } else { + // reading within file size + res = spiffs_object_read(fd, fd->fdoffset, len, (u8_t*)buf); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + fd->fdoffset += len; + + SPIFFS_UNLOCK(fs); + + return len; +} + +s32_t SPIFFS_read(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { + s32_t res = spiffs_hydro_read(fs, fh, buf, len); + if (res == SPIFFS_ERR_END_OF_OBJECT) { + res = 0; + } + return res; +} + + +#if !SPIFFS_READ_ONLY +static s32_t spiffs_hydro_write(spiffs *fs, spiffs_fd *fd, void *buf, u32_t offset, s32_t len) { + (void)fs; + s32_t res = SPIFFS_OK; + s32_t remaining = len; + if (fd->size != SPIFFS_UNDEFINED_LEN && offset < fd->size) { + s32_t m_len = MIN((s32_t)(fd->size - offset), len); + res = spiffs_object_modify(fd, offset, (u8_t *)buf, m_len); + SPIFFS_CHECK_RES(res); + remaining -= m_len; + u8_t *buf_8 = (u8_t *)buf; + buf_8 += m_len; + buf = buf_8; + offset += m_len; + } + if (remaining > 0) { + res = spiffs_object_append(fd, offset, (u8_t *)buf, remaining); + SPIFFS_CHECK_RES(res); + } + return len; + +} +#endif // !SPIFFS_READ_ONLY + +s32_t SPIFFS_write(spiffs *fs, spiffs_file fh, void *buf, s32_t len) { +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; (void)buf; (void)len; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + u32_t offset; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { + res = SPIFFS_ERR_NOT_WRITABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + if ((fd->flags & SPIFFS_O_APPEND)) { + fd->fdoffset = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size; + } + + offset = fd->fdoffset; + +#if SPIFFS_CACHE_WR + if (fd->cache_page == 0) { + // see if object id is associated with cache already + fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd); + } +#endif + if (fd->flags & SPIFFS_O_APPEND) { + if (fd->size == SPIFFS_UNDEFINED_LEN) { + offset = 0; + } else { + offset = fd->size; + } +#if SPIFFS_CACHE_WR + if (fd->cache_page) { + offset = MAX(offset, fd->cache_page->offset + fd->cache_page->size); + } +#endif + } + +#if SPIFFS_CACHE_WR + if ((fd->flags & SPIFFS_O_DIRECT) == 0) { + if (len < (s32_t)SPIFFS_CFG_LOG_PAGE_SZ(fs)) { + // small write, try to cache it + u8_t alloc_cpage = 1; + if (fd->cache_page) { + // have a cached page for this fd already, check cache page boundaries + if (offset < fd->cache_page->offset || // writing before cache + offset > fd->cache_page->offset + fd->cache_page->size || // writing after cache + offset + len > fd->cache_page->offset + SPIFFS_CFG_LOG_PAGE_SZ(fs)) // writing beyond cache page + { + // boundary violation, write back cache first and allocate new + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", boundary viol, offs:"_SPIPRIi" size:"_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), + fd->cache_page->offset, fd->cache_page->size); + spiffs_cache_fd_release(fs, fd->cache_page); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else { + // writing within cache + alloc_cpage = 0; + } + } + + if (alloc_cpage) { + fd->cache_page = spiffs_cache_page_allocate_by_fd(fs, fd); + if (fd->cache_page) { + fd->cache_page->offset = offset; + fd->cache_page->size = 0; + SPIFFS_CACHE_DBG("CACHE_WR_ALLO: allocating cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id); + } + } + + if (fd->cache_page) { + u32_t offset_in_cpage = offset - fd->cache_page->offset; + SPIFFS_CACHE_DBG("CACHE_WR_WRITE: storing to cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", offs "_SPIPRIi":"_SPIPRIi" len "_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, + offset, offset_in_cpage, len); + spiffs_cache *cache = spiffs_get_cache(fs); + u8_t *cpage_data = spiffs_get_cache_page(fs, cache, fd->cache_page->ix); + memcpy(&cpage_data[offset_in_cpage], buf, len); + fd->cache_page->size = MAX(fd->cache_page->size, offset_in_cpage + len); + fd->fdoffset += len; + SPIFFS_UNLOCK(fs); + return len; + } else { + res = spiffs_hydro_write(fs, fd, buf, offset, len); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + fd->fdoffset += len; + SPIFFS_UNLOCK(fs); + return res; + } + } else { + // big write, no need to cache it - but first check if there is a cached write already + if (fd->cache_page) { + // write back cache first + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", big write, offs:"_SPIPRIi" size:"_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), + fd->cache_page->offset, fd->cache_page->size); + spiffs_cache_fd_release(fs, fd->cache_page); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + // data written below + } + } + } +#endif + + res = spiffs_hydro_write(fs, fd, buf, offset, len); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + fd->fdoffset += len; + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_lseek(spiffs *fs, spiffs_file fh, s32_t offs, int whence) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + s32_t fileSize = fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size; + + switch (whence) { + case SPIFFS_SEEK_CUR: + offs = fd->fdoffset+offs; + break; + case SPIFFS_SEEK_END: + offs = fileSize + offs; + break; + } + + if ((offs > fileSize)) { + fd->fdoffset = fileSize; + res = SPIFFS_ERR_END_OF_OBJECT; + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + spiffs_span_ix data_spix = offs / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_span_ix objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (fd->cursor_objix_spix != objix_spix) { + spiffs_page_ix pix; + res = spiffs_obj_lu_find_id_and_span( + fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, objix_spix, 0, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + fd->cursor_objix_spix = objix_spix; + fd->cursor_objix_pix = pix; + } + fd->fdoffset = offs; + + SPIFFS_UNLOCK(fs); + + return offs; +} + +s32_t SPIFFS_remove(spiffs *fs, const char *path) { +#if SPIFFS_READ_ONLY + (void)fs; (void)path; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + spiffs_page_ix pix; + s32_t res; + + res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, pix, fd, 0,0); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_truncate(fd, 0, 1); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_fremove(spiffs *fs, spiffs_file fh) { +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { + res = SPIFFS_ERR_NOT_WRITABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + +#if SPIFFS_CACHE_WR + spiffs_cache_fd_release(fs, fd->cache_page); +#endif + + res = spiffs_object_truncate(fd, 0, 1); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return 0; +#endif // SPIFFS_READ_ONLY +} + +static s32_t spiffs_stat_pix(spiffs *fs, spiffs_page_ix pix, spiffs_file fh, spiffs_stat *s) { + (void)fh; + spiffs_page_object_ix_header objix_hdr; + spiffs_obj_id obj_id; + s32_t res =_spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, fh, + SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_API_CHECK_RES(fs, res); + + u32_t obj_id_addr = SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs , pix)) + + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_obj_id); + res =_spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, fh, + obj_id_addr, sizeof(spiffs_obj_id), (u8_t *)&obj_id); + SPIFFS_API_CHECK_RES(fs, res); + + s->obj_id = obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + s->type = objix_hdr.type; + s->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size; + s->pix = pix; + strncpy((char *)s->name, (char *)objix_hdr.name, SPIFFS_OBJ_NAME_LEN); +#if SPIFFS_OBJ_META_LEN + memcpy(s->meta, objix_hdr.meta, SPIFFS_OBJ_META_LEN); +#endif + + return res; +} + +s32_t SPIFFS_stat(spiffs *fs, const char *path, spiffs_stat *s) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + s32_t res; + spiffs_page_ix pix; + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)path, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_stat_pix(fs, pix, 0, s); + + SPIFFS_UNLOCK(fs); + + return res; +} + +s32_t SPIFFS_fstat(spiffs *fs, spiffs_file fh, spiffs_stat *s) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_fd *fd; + s32_t res; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + spiffs_fflush_cache(fs, fh); +#endif + + res = spiffs_stat_pix(fs, fd->objix_hdr_pix, fh, s); + + SPIFFS_UNLOCK(fs); + + return res; +} + +// Checks if there are any cached writes for the object id associated with +// given filehandle. If so, these writes are flushed. +#if SPIFFS_CACHE == 1 +static s32_t spiffs_fflush_cache(spiffs *fs, spiffs_file fh) { + (void)fs; + (void)fh; + s32_t res = SPIFFS_OK; +#if !SPIFFS_READ_ONLY && SPIFFS_CACHE_WR + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES(fs, res); + + if ((fd->flags & SPIFFS_O_DIRECT) == 0) { + if (fd->cache_page == 0) { + // see if object id is associated with cache already + fd->cache_page = spiffs_cache_page_get_by_fd(fs, fd); + } + if (fd->cache_page) { + SPIFFS_CACHE_DBG("CACHE_WR_DUMP: dumping cache page "_SPIPRIpg" for fd "_SPIPRIfd":"_SPIPRIid", flush, offs:"_SPIPRIi" size:"_SPIPRIi"\n", + fd->cache_page->ix, fd->file_nbr, fd->obj_id, fd->cache_page->offset, fd->cache_page->size); + res = spiffs_hydro_write(fs, fd, + spiffs_get_cache_page(fs, spiffs_get_cache(fs), fd->cache_page->ix), + fd->cache_page->offset, fd->cache_page->size); + if (res < SPIFFS_OK) { + fs->err_code = res; + } + spiffs_cache_fd_release(fs, fd->cache_page); + } + } +#endif + + return res; +} +#endif + +s32_t SPIFFS_fflush(spiffs *fs, spiffs_file fh) { + (void)fh; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + s32_t res = SPIFFS_OK; +#if !SPIFFS_READ_ONLY && SPIFFS_CACHE_WR + SPIFFS_LOCK(fs); + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs,res); + SPIFFS_UNLOCK(fs); +#endif + + return res; +} + +s32_t SPIFFS_close(spiffs *fs, spiffs_file fh) { + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + + s32_t res = SPIFFS_OK; + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); +#if SPIFFS_CACHE + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + res = spiffs_fd_return(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +} + +s32_t SPIFFS_rename(spiffs *fs, const char *old_path, const char *new_path) { +#if SPIFFS_READ_ONLY + (void)fs; (void)old_path; (void)new_path; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + if (strlen(new_path) > SPIFFS_OBJ_NAME_LEN - 1 || + strlen(old_path) > SPIFFS_OBJ_NAME_LEN - 1) { + SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_NAME_TOO_LONG); + } + SPIFFS_LOCK(fs); + + spiffs_page_ix pix_old, pix_dummy; + spiffs_fd *fd; + + s32_t res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)old_path, &pix_old); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)new_path, &pix_dummy); + if (res == SPIFFS_ERR_NOT_FOUND) { + res = SPIFFS_OK; + } else if (res == SPIFFS_OK) { + res = SPIFFS_ERR_CONFLICTING_NAME; + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, pix_old, fd, 0, 0); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, (const u8_t*)new_path, + 0, 0, &pix_dummy); +#if SPIFFS_TEMPORAL_FD_CACHE + if (res == SPIFFS_OK) { + spiffs_fd_temporal_cache_rehash(fs, old_path, new_path); + } +#endif + + spiffs_fd_return(fs, fd->file_nbr); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} + +#if SPIFFS_OBJ_META_LEN +s32_t SPIFFS_update_meta(spiffs *fs, const char *name, const void *meta) { +#if SPIFFS_READ_ONLY + (void)fs; (void)name; (void)meta; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + spiffs_page_ix pix, pix_dummy; + spiffs_fd *fd; + + s32_t res = spiffs_object_find_object_index_header_by_name(fs, (const u8_t*)name, &pix); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_fd_find_new(fs, &fd, 0); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_open_by_page(fs, pix, fd, 0, 0); + if (res != SPIFFS_OK) { + spiffs_fd_return(fs, fd->file_nbr); + } + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, 0, meta, + 0, &pix_dummy); + + spiffs_fd_return(fs, fd->file_nbr); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_fupdate_meta(spiffs *fs, spiffs_file fh, const void *meta) { +#if SPIFFS_READ_ONLY + (void)fs; (void)fh; (void)meta; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + s32_t res; + spiffs_fd *fd; + spiffs_page_ix pix_dummy; + + fh = SPIFFS_FH_UNOFFS(fs, fh); + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if ((fd->flags & SPIFFS_O_WRONLY) == 0) { + res = SPIFFS_ERR_NOT_WRITABLE; + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, fd->objix_hdr_pix, 0, 0, meta, + 0, &pix_dummy); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + + return res; +#endif // SPIFFS_READ_ONLY +} +#endif // SPIFFS_OBJ_META_LEN + +spiffs_DIR *SPIFFS_opendir(spiffs *fs, const char *name, spiffs_DIR *d) { + (void)name; + + if (!SPIFFS_CHECK_CFG((fs))) { + (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; + return 0; + } + + if (!SPIFFS_CHECK_MOUNT(fs)) { + fs->err_code = SPIFFS_ERR_NOT_MOUNTED; + return 0; + } + + d->fs = fs; + d->block = 0; + d->entry = 0; + return d; +} + +static s32_t spiffs_read_dir_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)user_const_p; + s32_t res; + spiffs_page_object_ix_header objix_hdr; + if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED || + (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) { + return SPIFFS_VIS_COUNTINUE; + } + + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + if (res != SPIFFS_OK) return res; + if ((obj_id & SPIFFS_OBJ_ID_IX_FLAG) && + objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + struct spiffs_dirent *e = (struct spiffs_dirent*)user_var_p; + e->obj_id = obj_id; + strcpy((char *)e->name, (char *)objix_hdr.name); + e->type = objix_hdr.type; + e->size = objix_hdr.size == SPIFFS_UNDEFINED_LEN ? 0 : objix_hdr.size; + e->pix = pix; +#if SPIFFS_OBJ_META_LEN + memcpy(e->meta, objix_hdr.meta, SPIFFS_OBJ_META_LEN); +#endif + return SPIFFS_OK; + } + return SPIFFS_VIS_COUNTINUE; +} + +struct spiffs_dirent *SPIFFS_readdir(spiffs_DIR *d, struct spiffs_dirent *e) { + if (!SPIFFS_CHECK_MOUNT(d->fs)) { + d->fs->err_code = SPIFFS_ERR_NOT_MOUNTED; + return 0; + } + SPIFFS_LOCK(d->fs); + + spiffs_block_ix bix; + int entry; + s32_t res; + struct spiffs_dirent *ret = 0; + + res = spiffs_obj_lu_find_entry_visitor(d->fs, + d->block, + d->entry, + SPIFFS_VIS_NO_WRAP, + 0, + spiffs_read_dir_v, + 0, + e, + &bix, + &entry); + if (res == SPIFFS_OK) { + d->block = bix; + d->entry = entry + 1; + e->obj_id &= ~SPIFFS_OBJ_ID_IX_FLAG; + ret = e; + } else { + d->fs->err_code = res; + } + SPIFFS_UNLOCK(d->fs); + return ret; +} + +s32_t SPIFFS_closedir(spiffs_DIR *d) { + SPIFFS_API_CHECK_CFG(d->fs); + SPIFFS_API_CHECK_MOUNT(d->fs); + return 0; +} + +s32_t SPIFFS_check(spiffs *fs) { +#if SPIFFS_READ_ONLY + (void)fs; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + res = spiffs_lookup_consistency_check(fs, 0); + + res = spiffs_object_index_consistency_check(fs); + + res = spiffs_page_consistency_check(fs); + + res = spiffs_obj_lu_scan(fs); + + SPIFFS_UNLOCK(fs); + return res; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_info(spiffs *fs, u32_t *total, u32_t *used) { + s32_t res = SPIFFS_OK; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + u32_t pages_per_block = SPIFFS_PAGES_PER_BLOCK(fs); + u32_t blocks = fs->block_count; + u32_t obj_lu_pages = SPIFFS_OBJ_LOOKUP_PAGES(fs); + u32_t data_page_size = SPIFFS_DATA_PAGE_SIZE(fs); + u32_t total_data_pages = (blocks - 2) * (pages_per_block - obj_lu_pages) + 1; // -2 for spare blocks, +1 for emergency page + + if (total) { + *total = total_data_pages * data_page_size; + } + + if (used) { + *used = fs->stats_p_allocated * data_page_size; + } + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_gc_quick(spiffs *fs, u16_t max_free_pages) { +#if SPIFFS_READ_ONLY + (void)fs; (void)max_free_pages; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + res = spiffs_gc_quick(fs, max_free_pages); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + + +s32_t SPIFFS_gc(spiffs *fs, u32_t size) { +#if SPIFFS_READ_ONLY + (void)fs; (void)size; + return SPIFFS_ERR_RO_NOT_IMPL; +#else + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + res = spiffs_gc_check(fs, size); + + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + SPIFFS_UNLOCK(fs); + return 0; +#endif // SPIFFS_READ_ONLY +} + +s32_t SPIFFS_eof(spiffs *fs, spiffs_file fh) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + res = (fd->fdoffset >= (fd->size == SPIFFS_UNDEFINED_LEN ? 0 : fd->size)); + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_tell(spiffs *fs, spiffs_file fh) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + +#if SPIFFS_CACHE_WR + res = spiffs_fflush_cache(fs, fh); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); +#endif + + res = fd->fdoffset; + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_set_file_callback_func(spiffs *fs, spiffs_file_callback cb_func) { + SPIFFS_LOCK(fs); + fs->file_cb_f = cb_func; + SPIFFS_UNLOCK(fs); + return 0; +} + +#if SPIFFS_IX_MAP + +s32_t SPIFFS_ix_map(spiffs *fs, spiffs_file fh, spiffs_ix_map *map, + u32_t offset, u32_t len, spiffs_page_ix *map_buf) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_MAPPED); + } + + map->map_buf = map_buf; + map->offset = offset; + // nb: spix range includes last + map->start_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + map->end_spix = (offset + len) / SPIFFS_DATA_PAGE_SIZE(fs); + memset(map_buf, 0, sizeof(spiffs_page_ix) * (map->end_spix - map->start_spix + 1)); + fd->ix_map = map; + + // scan for pixes + res = spiffs_populate_ix_map(fs, fd, 0, map->end_spix - map->start_spix + 1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_ix_unmap(spiffs *fs, spiffs_file fh) { + s32_t res; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map == 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_UNMAPPED); + } + + fd->ix_map = 0; + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_ix_remap(spiffs *fs, spiffs_file fh, u32_t offset) { + s32_t res = SPIFFS_OK; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + fh = SPIFFS_FH_UNOFFS(fs, fh); + + spiffs_fd *fd; + res = spiffs_fd_get(fs, fh, &fd); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + + if (fd->ix_map == 0) { + SPIFFS_API_CHECK_RES_UNLOCK(fs, SPIFFS_ERR_IX_MAP_UNMAPPED); + } + + spiffs_ix_map *map = fd->ix_map; + + s32_t spix_diff = offset / SPIFFS_DATA_PAGE_SIZE(fs) - map->start_spix; + map->offset = offset; + + // move existing pixes if within map offs + if (spix_diff != 0) { + // move vector + int i; + const s32_t vec_len = map->end_spix - map->start_spix + 1; // spix range includes last + map->start_spix += spix_diff; + map->end_spix += spix_diff; + if (spix_diff >= vec_len) { + // moving beyond range + memset(&map->map_buf, 0, vec_len * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, 0, vec_len-1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else if (spix_diff > 0) { + // diff positive + for (i = 0; i < vec_len - spix_diff; i++) { + map->map_buf[i] = map->map_buf[i + spix_diff]; + } + // memset is non-inclusive + memset(&map->map_buf[vec_len - spix_diff], 0, spix_diff * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, vec_len - spix_diff, vec_len-1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } else { + // diff negative + for (i = vec_len - 1; i >= -spix_diff; i--) { + map->map_buf[i] = map->map_buf[i + spix_diff]; + } + // memset is non-inclusive + memset(&map->map_buf[0], 0, -spix_diff * sizeof(spiffs_page_ix)); + // populate_ix_map is inclusive + res = spiffs_populate_ix_map(fs, fd, 0, -spix_diff - 1); + SPIFFS_API_CHECK_RES_UNLOCK(fs, res); + } + + } + + SPIFFS_UNLOCK(fs); + return res; +} + +s32_t SPIFFS_bytes_to_ix_map_entries(spiffs *fs, u32_t bytes) { + SPIFFS_API_CHECK_CFG(fs); + // always add one extra page, the offset might change to the middle of a page + return (bytes + SPIFFS_DATA_PAGE_SIZE(fs) ) / SPIFFS_DATA_PAGE_SIZE(fs); +} + +s32_t SPIFFS_ix_map_entries_to_bytes(spiffs *fs, u32_t map_page_ix_entries) { + SPIFFS_API_CHECK_CFG(fs); + return map_page_ix_entries * SPIFFS_DATA_PAGE_SIZE(fs); +} + +#endif // SPIFFS_IX_MAP + +#if SPIFFS_TEST_VISUALISATION +s32_t SPIFFS_vis(spiffs *fs) { + s32_t res = SPIFFS_OK; + SPIFFS_API_CHECK_CFG(fs); + SPIFFS_API_CHECK_MOUNT(fs); + SPIFFS_LOCK(fs); + + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + spiffs_block_ix bix = 0; + + while (bix < fs->block_count) { + // check each object lookup page + int obj_lookup_page = 0; + int cur_entry = 0; + + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, bix * SPIFFS_CFG_LOG_BLOCK_SZ(fs) + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && cur_entry < (int)(SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs))) { + spiffs_obj_id obj_id = obj_lu_buf[cur_entry-entry_offset]; + if (cur_entry == 0) { + spiffs_printf(_SPIPRIbl" ", bix); + } else if ((cur_entry & 0x3f) == 0) { + spiffs_printf(" "); + } + if (obj_id == SPIFFS_OBJ_ID_FREE) { + spiffs_printf(SPIFFS_TEST_VIS_FREE_STR); + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + spiffs_printf(SPIFFS_TEST_VIS_DELE_STR); + } else if (obj_id & SPIFFS_OBJ_ID_IX_FLAG){ + spiffs_printf(SPIFFS_TEST_VIS_INDX_STR(obj_id)); + } else { + spiffs_printf(SPIFFS_TEST_VIS_DATA_STR(obj_id)); + } + cur_entry++; + if ((cur_entry & 0x3f) == 0) { + spiffs_printf("\n"); + } + } // per entry + obj_lookup_page++; + } // per object lookup page + + spiffs_obj_id erase_count; + res = _spiffs_rd(fs, SPIFFS_OP_C_READ | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_ERASE_COUNT_PADDR(fs, bix), + sizeof(spiffs_obj_id), (u8_t *)&erase_count); + SPIFFS_CHECK_RES(res); + + if (erase_count != (spiffs_obj_id)-1) { + spiffs_printf("\tera_cnt: "_SPIPRIi"\n", erase_count); + } else { + spiffs_printf("\tera_cnt: N/A\n"); + } + + bix++; + } // per block + + spiffs_printf("era_cnt_max: "_SPIPRIi"\n", fs->max_erase_count); + spiffs_printf("last_errno: "_SPIPRIi"\n", fs->err_code); + spiffs_printf("blocks: "_SPIPRIi"\n", fs->block_count); + spiffs_printf("free_blocks: "_SPIPRIi"\n", fs->free_blocks); + spiffs_printf("page_alloc: "_SPIPRIi"\n", fs->stats_p_allocated); + spiffs_printf("page_delet: "_SPIPRIi"\n", fs->stats_p_deleted); + SPIFFS_UNLOCK(fs); + u32_t total, used; + SPIFFS_info(fs, &total, &used); + spiffs_printf("used: "_SPIPRIi" of "_SPIPRIi"\n", used, total); + return res; +} +#endif diff --git a/libiot/spiffs/spiffs_nucleus.c b/libiot/spiffs/spiffs_nucleus.c new file mode 100644 index 0000000..44ba711 --- /dev/null +++ b/libiot/spiffs/spiffs_nucleus.c @@ -0,0 +1,2327 @@ +#include "spiffs.h" +#include "spiffs_nucleus.h" + +static s32_t spiffs_page_data_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) { + s32_t res = SPIFFS_OK; + if (pix == (spiffs_page_ix)-1) { + // referring to page 0xffff...., bad object index + return SPIFFS_ERR_INDEX_REF_FREE; + } + if (pix % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + // referring to an object lookup page, bad object index + return SPIFFS_ERR_INDEX_REF_LU; + } + if (pix > SPIFFS_MAX_PAGES(fs)) { + // referring to a bad page + return SPIFFS_ERR_INDEX_REF_INVALID; + } +#if SPIFFS_PAGE_CHECK + spiffs_page_header ph; + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, pix), + sizeof(spiffs_page_header), + (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_DATA(ph, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, spix); +#endif + return res; +} + +#if !SPIFFS_READ_ONLY +static s32_t spiffs_page_index_check(spiffs *fs, spiffs_fd *fd, spiffs_page_ix pix, spiffs_span_ix spix) { + s32_t res = SPIFFS_OK; + if (pix == (spiffs_page_ix)-1) { + // referring to page 0xffff...., bad object index + return SPIFFS_ERR_INDEX_FREE; + } + if (pix % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + // referring to an object lookup page, bad object index + return SPIFFS_ERR_INDEX_LU; + } + if (pix > SPIFFS_MAX_PAGES(fs)) { + // referring to a bad page + return SPIFFS_ERR_INDEX_INVALID; + } +#if SPIFFS_PAGE_CHECK + spiffs_page_header ph; + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, pix), + sizeof(spiffs_page_header), + (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(ph, fd->obj_id, spix); +#endif + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_CACHE + +s32_t spiffs_phys_rd( + spiffs *fs, + u32_t addr, + u32_t len, + u8_t *dst) { + return SPIFFS_HAL_READ(fs, addr, len, dst); +} + +s32_t spiffs_phys_wr( + spiffs *fs, + u32_t addr, + u32_t len, + u8_t *src) { + return SPIFFS_HAL_WRITE(fs, addr, len, src); +} + +#endif + +#if !SPIFFS_READ_ONLY +s32_t spiffs_phys_cpy( + spiffs *fs, + spiffs_file fh, + u32_t dst, + u32_t src, + u32_t len) { + (void)fh; + s32_t res; + u8_t b[SPIFFS_COPY_BUFFER_STACK]; + while (len > 0) { + u32_t chunk_size = MIN(SPIFFS_COPY_BUFFER_STACK, len); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVS, fh, src, chunk_size, b); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_MOVD, fh, dst, chunk_size, b); + SPIFFS_CHECK_RES(res); + len -= chunk_size; + src += chunk_size; + dst += chunk_size; + } + return SPIFFS_OK; +} +#endif // !SPIFFS_READ_ONLY + +// Find object lookup entry containing given id with visitor. +// Iterate over object lookup pages in each block until a given object id entry is found. +// When found, the visitor function is called with block index, entry index and user data. +// If visitor returns SPIFFS_VIS_CONTINUE, the search goes on. Otherwise, the search will be +// ended and visitor's return code is returned to caller. +// If no visitor is given (0) the search returns on first entry with matching object id. +// If no match is found in all look up, SPIFFS_VIS_END is returned. +// @param fs the file system +// @param starting_block the starting block to start search in +// @param starting_lu_entry the look up index entry to start search in +// @param flags ored combination of SPIFFS_VIS_CHECK_ID, SPIFFS_VIS_CHECK_PH, +// SPIFFS_VIS_NO_WRAP +// @param obj_id argument object id +// @param v visitor callback function +// @param user_const_p any const pointer, passed to the callback visitor function +// @param user_var_p any pointer, passed to the callback visitor function +// @param block_ix reported block index where match was found +// @param lu_entry reported look up index where match was found +s32_t spiffs_obj_lu_find_entry_visitor( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + u8_t flags, + spiffs_obj_id obj_id, + spiffs_visitor_f v, + const void *user_const_p, + void *user_var_p, + spiffs_block_ix *block_ix, + int *lu_entry) { + s32_t res = SPIFFS_OK; + s32_t entry_count = fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs); + spiffs_block_ix cur_block = starting_block; + u32_t cur_block_addr = starting_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs); + + spiffs_obj_id *obj_lu_buf = (spiffs_obj_id *)fs->lu_work; + int cur_entry = starting_lu_entry; + int entries_per_page = (SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(spiffs_obj_id)); + + // wrap initial + if (cur_entry > (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) - 1) { + cur_entry = 0; + cur_block++; + cur_block_addr = cur_block * SPIFFS_CFG_LOG_BLOCK_SZ(fs); + if (cur_block >= fs->block_count) { + if (flags & SPIFFS_VIS_NO_WRAP) { + return SPIFFS_VIS_END; + } else { + // block wrap + cur_block = 0; + cur_block_addr = 0; + } + } + } + + // check each block + while (res == SPIFFS_OK && entry_count > 0) { + int obj_lookup_page = cur_entry / entries_per_page; + // check each object lookup page + while (res == SPIFFS_OK && obj_lookup_page < (int)SPIFFS_OBJ_LOOKUP_PAGES(fs)) { + int entry_offset = obj_lookup_page * entries_per_page; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + // check each entry + while (res == SPIFFS_OK && + cur_entry - entry_offset < entries_per_page && // for non-last obj lookup pages + cur_entry < (int)SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) // for last obj lookup page + { + if ((flags & SPIFFS_VIS_CHECK_ID) == 0 || obj_lu_buf[cur_entry-entry_offset] == obj_id) { + if (block_ix) *block_ix = cur_block; + if (lu_entry) *lu_entry = cur_entry; + if (v) { + res = v( + fs, + (flags & SPIFFS_VIS_CHECK_PH) ? obj_id : obj_lu_buf[cur_entry-entry_offset], + cur_block, + cur_entry, + user_const_p, + user_var_p); + if (res == SPIFFS_VIS_COUNTINUE || res == SPIFFS_VIS_COUNTINUE_RELOAD) { + if (res == SPIFFS_VIS_COUNTINUE_RELOAD) { + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, cur_block_addr + SPIFFS_PAGE_TO_PADDR(fs, obj_lookup_page), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->lu_work); + SPIFFS_CHECK_RES(res); + } + res = SPIFFS_OK; + cur_entry++; + entry_count--; + continue; + } else { + return res; + } + } else { + return SPIFFS_OK; + } + } + entry_count--; + cur_entry++; + } // per entry + obj_lookup_page++; + } // per object lookup page + cur_entry = 0; + cur_block++; + cur_block_addr += SPIFFS_CFG_LOG_BLOCK_SZ(fs); + if (cur_block >= fs->block_count) { + if (flags & SPIFFS_VIS_NO_WRAP) { + return SPIFFS_VIS_END; + } else { + // block wrap + cur_block = 0; + cur_block_addr = 0; + } + } + } // per block + + SPIFFS_CHECK_RES(res); + + return SPIFFS_VIS_END; +} + +#if !SPIFFS_READ_ONLY +s32_t spiffs_erase_block( + spiffs *fs, + spiffs_block_ix bix) { + s32_t res; + u32_t addr = SPIFFS_BLOCK_TO_PADDR(fs, bix); + s32_t size = SPIFFS_CFG_LOG_BLOCK_SZ(fs); + + // here we ignore res, just try erasing the block + while (size > 0) { + SPIFFS_DBG("erase "_SPIPRIad":"_SPIPRIi"\n", addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + SPIFFS_HAL_ERASE(fs, addr, SPIFFS_CFG_PHYS_ERASE_SZ(fs)); + + addr += SPIFFS_CFG_PHYS_ERASE_SZ(fs); + size -= SPIFFS_CFG_PHYS_ERASE_SZ(fs); + } + fs->free_blocks++; + + // register erase count for this block + res = _spiffs_wr(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_ERASE_COUNT_PADDR(fs, bix), + sizeof(spiffs_obj_id), (u8_t *)&fs->max_erase_count); + SPIFFS_CHECK_RES(res); + +#if SPIFFS_USE_MAGIC + // finally, write magic + spiffs_obj_id magic = SPIFFS_MAGIC(fs, bix); + res = _spiffs_wr(fs, SPIFFS_OP_C_WRTHRU | SPIFFS_OP_T_OBJ_LU2, 0, + SPIFFS_MAGIC_PADDR(fs, bix), + sizeof(spiffs_obj_id), (u8_t *)&magic); + SPIFFS_CHECK_RES(res); +#endif + + fs->max_erase_count++; + if (fs->max_erase_count == SPIFFS_OBJ_ID_IX_FLAG) { + fs->max_erase_count = 0; + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 +s32_t spiffs_probe( + spiffs_config *cfg) { + s32_t res; + u32_t paddr; + spiffs dummy_fs; // create a dummy fs struct just to be able to use macros + memcpy(&dummy_fs.cfg, cfg, sizeof(spiffs_config)); + dummy_fs.block_count = 0; + + // Read three magics, as one block may be in an aborted erase state. + // At least two of these must contain magic and be in decreasing order. + spiffs_obj_id magic[3]; + spiffs_obj_id bix_count[3]; + + spiffs_block_ix bix; + for (bix = 0; bix < 3; bix++) { + paddr = SPIFFS_MAGIC_PADDR(&dummy_fs, bix); +#if SPIFFS_HAL_CALLBACK_EXTRA + // not any proper fs to report here, so callback with null + // (cross fingers that no-one gets angry) + res = cfg->hal_read_f((void *)0, paddr, sizeof(spiffs_obj_id), (u8_t *)&magic[bix]); +#else + res = cfg->hal_read_f(paddr, sizeof(spiffs_obj_id), (u8_t *)&magic[bix]); +#endif + bix_count[bix] = magic[bix] ^ SPIFFS_MAGIC(&dummy_fs, 0); + SPIFFS_CHECK_RES(res); + } + + // check that we have sane number of blocks + if (bix_count[0] < 3) return SPIFFS_ERR_PROBE_TOO_FEW_BLOCKS; + // check that the order is correct, take aborted erases in calculation + // first block aborted erase + if (magic[0] == (spiffs_obj_id)(-1) && bix_count[1] - bix_count[2] == 1) { + return (bix_count[1]+1) * cfg->log_block_size; + } + // second block aborted erase + if (magic[1] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[2] == 2) { + return bix_count[0] * cfg->log_block_size; + } + // third block aborted erase + if (magic[2] == (spiffs_obj_id)(-1) && bix_count[0] - bix_count[1] == 1) { + return bix_count[0] * cfg->log_block_size; + } + // no block has aborted erase + if (bix_count[0] - bix_count[1] == 1 && bix_count[1] - bix_count[2] == 1) { + return bix_count[0] * cfg->log_block_size; + } + + return SPIFFS_ERR_PROBE_NOT_A_FS; +} +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH && SPIFFS_SINGLETON==0 + + +static s32_t spiffs_obj_lu_scan_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)bix; + (void)user_const_p; + (void)user_var_p; + if (obj_id == SPIFFS_OBJ_ID_FREE) { + if (ix_entry == 0) { + fs->free_blocks++; + // todo optimize further, return SPIFFS_NEXT_BLOCK + } + } else if (obj_id == SPIFFS_OBJ_ID_DELETED) { + fs->stats_p_deleted++; + } else { + fs->stats_p_allocated++; + } + + return SPIFFS_VIS_COUNTINUE; +} + + +// Scans thru all obj lu and counts free, deleted and used pages +// Find the maximum block erase count +// Checks magic if enabled +s32_t spiffs_obj_lu_scan( + spiffs *fs) { + s32_t res; + spiffs_block_ix bix; + int entry; +#if SPIFFS_USE_MAGIC + spiffs_block_ix unerased_bix = (spiffs_block_ix)-1; +#endif + + // find out erase count + // if enabled, check magic + bix = 0; + spiffs_obj_id erase_count_final; + spiffs_obj_id erase_count_min = SPIFFS_OBJ_ID_FREE; + spiffs_obj_id erase_count_max = 0; + while (bix < fs->block_count) { +#if SPIFFS_USE_MAGIC + spiffs_obj_id magic; + res = _spiffs_rd(fs, + SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_MAGIC_PADDR(fs, bix) , + sizeof(spiffs_obj_id), (u8_t *)&magic); + + SPIFFS_CHECK_RES(res); + if (magic != SPIFFS_MAGIC(fs, bix)) { + if (unerased_bix == (spiffs_block_ix)-1) { + // allow one unerased block as it might be powered down during an erase + unerased_bix = bix; + } else { + // more than one unerased block, bail out + SPIFFS_CHECK_RES(SPIFFS_ERR_NOT_A_FS); + } + } +#endif + spiffs_obj_id erase_count; + res = _spiffs_rd(fs, + SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_ERASE_COUNT_PADDR(fs, bix) , + sizeof(spiffs_obj_id), (u8_t *)&erase_count); + SPIFFS_CHECK_RES(res); + if (erase_count != SPIFFS_OBJ_ID_FREE) { + erase_count_min = MIN(erase_count_min, erase_count); + erase_count_max = MAX(erase_count_max, erase_count); + } + bix++; + } + + if (erase_count_min == 0 && erase_count_max == SPIFFS_OBJ_ID_FREE) { + // clean system, set counter to zero + erase_count_final = 0; + } else if (erase_count_max - erase_count_min > (SPIFFS_OBJ_ID_FREE)/2) { + // wrap, take min + erase_count_final = erase_count_min+1; + } else { + erase_count_final = erase_count_max+1; + } + + fs->max_erase_count = erase_count_final; + +#if SPIFFS_USE_MAGIC + if (unerased_bix != (spiffs_block_ix)-1) { + // found one unerased block, remedy + SPIFFS_DBG("mount: erase block "_SPIPRIbl"\n", bix); +#if SPIFFS_READ_ONLY + res = SPIFFS_ERR_RO_ABORTED_OPERATION; +#else + res = spiffs_erase_block(fs, unerased_bix); +#endif // SPIFFS_READ_ONLY + SPIFFS_CHECK_RES(res); + } +#endif + + // count blocks + + fs->free_blocks = 0; + fs->stats_p_allocated = 0; + fs->stats_p_deleted = 0; + + res = spiffs_obj_lu_find_entry_visitor(fs, + 0, + 0, + 0, + 0, + spiffs_obj_lu_scan_v, + 0, + 0, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + + SPIFFS_CHECK_RES(res); + + return res; +} + +#if !SPIFFS_READ_ONLY +// Find free object lookup entry +// Iterate over object lookup pages in each block until a free object id entry is found +s32_t spiffs_obj_lu_find_free( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_block_ix *block_ix, + int *lu_entry) { + s32_t res; + if (!fs->cleaning && fs->free_blocks < 2) { + res = spiffs_gc_quick(fs, 0); + if (res == SPIFFS_ERR_NO_DELETED_BLOCKS) { + res = SPIFFS_OK; + } + SPIFFS_CHECK_RES(res); + if (fs->free_blocks < 2) { + return SPIFFS_ERR_FULL; + } + } + res = spiffs_obj_lu_find_id(fs, starting_block, starting_lu_entry, + SPIFFS_OBJ_ID_FREE, block_ix, lu_entry); + if (res == SPIFFS_OK) { + fs->free_cursor_block_ix = *block_ix; + fs->free_cursor_obj_lu_entry = (*lu_entry) + 1; + if (*lu_entry == 0) { + fs->free_blocks--; + } + } + if (res == SPIFFS_ERR_FULL) { + SPIFFS_DBG("fs full\n"); + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +// Find object lookup entry containing given id +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_obj_id obj_id, + spiffs_block_ix *block_ix, + int *lu_entry) { + s32_t res = spiffs_obj_lu_find_entry_visitor( + fs, starting_block, starting_lu_entry, SPIFFS_VIS_CHECK_ID, obj_id, 0, 0, 0, block_ix, lu_entry); + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + return res; +} + + +static s32_t spiffs_obj_lu_find_id_and_span_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + s32_t res; + spiffs_page_header ph; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + res = _spiffs_rd(fs, 0, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_header), (u8_t *)&ph); + SPIFFS_CHECK_RES(res); + if (ph.obj_id == obj_id && + ph.span_ix == *((spiffs_span_ix*)user_var_p) && + (ph.flags & (SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED)) == SPIFFS_PH_FLAG_DELET && + !((obj_id & SPIFFS_OBJ_ID_IX_FLAG) && (ph.flags & SPIFFS_PH_FLAG_IXDELE) == 0 && ph.span_ix == 0) && + (user_const_p == 0 || *((const spiffs_page_ix*)user_const_p) != pix)) { + return SPIFFS_OK; + } else { + return SPIFFS_VIS_COUNTINUE; + } +} + +// Find object lookup entry containing given id and span index +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id_and_span( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + SPIFFS_VIS_CHECK_ID, + obj_id, + spiffs_obj_lu_find_id_and_span_v, + exclusion_pix ? &exclusion_pix : 0, + &spix, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +// Find object lookup entry containing given id and span index in page headers only +// Iterate over object lookup pages in each block until a given object id entry is found +s32_t spiffs_obj_lu_find_id_and_span_by_phdr( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + SPIFFS_VIS_CHECK_PH, + obj_id, + spiffs_obj_lu_find_id_and_span_v, + exclusion_pix ? &exclusion_pix : 0, + &spix, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +#if SPIFFS_IX_MAP + +// update index map of given fd with given object index data +static void spiffs_update_ix_map(spiffs *fs, + spiffs_fd *fd, spiffs_span_ix objix_spix, spiffs_page_object_ix *objix) { +#if SPIFFS_SINGLETON + (void)fs; +#endif + spiffs_ix_map *map = fd->ix_map; + spiffs_span_ix map_objix_start_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix); + spiffs_span_ix map_objix_end_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->end_spix); + + // check if updated ix is within map range + if (objix_spix < map_objix_start_spix || objix_spix > map_objix_end_spix) { + return; + } + + // update memory mapped page index buffer to new pages + + // get range of updated object index map data span indices + spiffs_span_ix objix_data_spix_start = + SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, objix_spix); + spiffs_span_ix objix_data_spix_end = objix_data_spix_start + + (objix_spix == 0 ? SPIFFS_OBJ_HDR_IX_LEN(fs) : SPIFFS_OBJ_IX_LEN(fs)); + + // calc union of object index range and index map range array + spiffs_span_ix map_spix = MAX(map->start_spix, objix_data_spix_start); + spiffs_span_ix map_spix_end = MIN(map->end_spix + 1, objix_data_spix_end); + + while (map_spix < map_spix_end) { + spiffs_page_ix objix_data_pix; + if (objix_spix == 0) { + // get data page from object index header page + objix_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix_header)))[map_spix]; + } else { + // get data page from object index page + objix_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, map_spix)]; + } + + if (objix_data_pix == (spiffs_page_ix)-1) { + // reached end of object, abort + break; + } + + map->map_buf[map_spix - map->start_spix] = objix_data_pix; + SPIFFS_DBG("map "_SPIPRIid":"_SPIPRIsp" ("_SPIPRIsp"--"_SPIPRIsp") objix.spix:"_SPIPRIsp" to pix "_SPIPRIpg"\n", + fd->obj_id, map_spix - map->start_spix, + map->start_spix, map->end_spix, + objix->p_hdr.span_ix, + objix_data_pix); + + map_spix++; + } +} + +typedef struct { + spiffs_fd *fd; + u32_t remaining_objix_pages_to_visit; + spiffs_span_ix map_objix_start_spix; + spiffs_span_ix map_objix_end_spix; +} spiffs_ix_map_populate_state; + +static s32_t spiffs_populate_ix_map_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)user_const_p; + s32_t res; + spiffs_ix_map_populate_state *state = (spiffs_ix_map_populate_state *)user_var_p; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + + // load header to check it + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix), (u8_t *)objix); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, obj_id, objix->p_hdr.span_ix); + + // check if hdr is ok, and if objix range overlap with ix map range + if ((objix->p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE) && + objix->p_hdr.span_ix >= state->map_objix_start_spix && + objix->p_hdr.span_ix <= state->map_objix_end_spix) { + // ok, load rest of object index + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix) + sizeof(spiffs_page_object_ix), + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix), + (u8_t *)objix + sizeof(spiffs_page_object_ix)); + SPIFFS_CHECK_RES(res); + + spiffs_update_ix_map(fs, state->fd, objix->p_hdr.span_ix, objix); + + state->remaining_objix_pages_to_visit--; + SPIFFS_DBG("map "_SPIPRIid" ("_SPIPRIsp"--"_SPIPRIsp") remaining objix pages "_SPIPRIi"\n", + state->fd->obj_id, + state->fd->ix_map->start_spix, state->fd->ix_map->end_spix, + state->remaining_objix_pages_to_visit); + } + + if (res == SPIFFS_OK) { + res = state->remaining_objix_pages_to_visit ? SPIFFS_VIS_COUNTINUE : SPIFFS_VIS_END; + } + return res; +} + +// populates index map, from vector entry start to vector entry end, inclusive +s32_t spiffs_populate_ix_map(spiffs *fs, spiffs_fd *fd, u32_t vec_entry_start, u32_t vec_entry_end) { + s32_t res; + spiffs_ix_map *map = fd->ix_map; + spiffs_ix_map_populate_state state; + vec_entry_start = MIN((map->end_spix - map->start_spix + 1) - 1, (s32_t)vec_entry_start); + vec_entry_end = MAX((map->end_spix - map->start_spix + 1) - 1, (s32_t)vec_entry_end); + if (vec_entry_start > vec_entry_end) { + return SPIFFS_ERR_IX_MAP_BAD_RANGE; + } + state.map_objix_start_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix + vec_entry_start); + state.map_objix_end_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, map->start_spix + vec_entry_end); + state.remaining_objix_pages_to_visit = + state.map_objix_end_spix - state.map_objix_start_spix + 1; + state.fd = fd; + + res = spiffs_obj_lu_find_entry_visitor( + fs, + SPIFFS_BLOCK_FOR_PAGE(fs, fd->objix_hdr_pix), + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, fd->objix_hdr_pix), + SPIFFS_VIS_CHECK_ID, + fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, + spiffs_populate_ix_map_v, + 0, + &state, + 0, + 0); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_OK; + } + + return res; +} + +#endif + + +#if !SPIFFS_READ_ONLY +// Allocates a free defined page with given obj_id +// Occupies object lookup entry and page +// data may be NULL; where only page header is stored, len and page_offs is ignored +s32_t spiffs_page_allocate_data( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_page_header *ph, + u8_t *data, + u32_t len, + u32_t page_offs, + u8_t finalize, + spiffs_page_ix *pix) { + s32_t res = SPIFFS_OK; + spiffs_block_ix bix; + int entry; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + + // occupy page in object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + // write page header + ph->flags &= ~SPIFFS_PH_FLAG_USED; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_header), (u8_t*)ph); + SPIFFS_CHECK_RES(res); + + // write page data + if (data) { + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0,SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + sizeof(spiffs_page_header) + page_offs, len, data); + SPIFFS_CHECK_RES(res); + } + + // finalize header if necessary + if (finalize && (ph->flags & SPIFFS_PH_FLAG_FINAL)) { + ph->flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&ph->flags); + SPIFFS_CHECK_RES(res); + } + + // return written page + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Moves a page from src to a free page and finalizes it. Updates page index. Page data is given in param page. +// If page data is null, provided header is used for metainfo and page data is physically copied. +s32_t spiffs_page_move( + spiffs *fs, + spiffs_file fh, + u8_t *page_data, + spiffs_obj_id obj_id, + spiffs_page_header *page_hdr, + spiffs_page_ix src_pix, + spiffs_page_ix *dst_pix) { + s32_t res; + u8_t was_final = 0; + spiffs_page_header *p_hdr; + spiffs_block_ix bix; + int entry; + spiffs_page_ix free_pix; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + free_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + + if (dst_pix) *dst_pix = free_pix; + + p_hdr = page_data ? (spiffs_page_header *)page_data : page_hdr; + if (page_data) { + // got page data + was_final = (p_hdr->flags & SPIFFS_PH_FLAG_FINAL) == 0; + // write unfinalized page + p_hdr->flags |= SPIFFS_PH_FLAG_FINAL; + p_hdr->flags &= ~SPIFFS_PH_FLAG_USED; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), page_data); + } else { + // copy page data + res = spiffs_phys_cpy(fs, fh, SPIFFS_PAGE_TO_PADDR(fs, free_pix), SPIFFS_PAGE_TO_PADDR(fs, src_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs)); + } + SPIFFS_CHECK_RES(res); + + // mark entry in destination object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, free_pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, free_pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + if (was_final) { + // mark finalized in destination page + p_hdr->flags &= ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_USED); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fh, + SPIFFS_PAGE_TO_PADDR(fs, free_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr->flags); + SPIFFS_CHECK_RES(res); + } + // mark source deleted + res = spiffs_page_delete(fs, src_pix); + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Deletes a page and removes it from object lookup. +s32_t spiffs_page_delete( + spiffs *fs, + spiffs_page_ix pix) { + s32_t res; + spiffs_page_header hdr; + hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_USED); + // mark deleted entry in source object lookup + spiffs_obj_id d_obj_id = SPIFFS_OBJ_ID_DELETED; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_DELE, + 0, + SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs, pix)) + SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix) * sizeof(spiffs_page_ix), + sizeof(spiffs_obj_id), + (u8_t *)&d_obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_deleted++; + fs->stats_p_allocated--; + + // mark deleted in source page + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_DELE, + 0, + SPIFFS_PAGE_TO_PADDR(fs, pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&hdr.flags); + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Create an object index header page with empty index and undefined length +s32_t spiffs_object_create( + spiffs *fs, + spiffs_obj_id obj_id, + const u8_t name[], + const u8_t meta[], + spiffs_obj_type type, + spiffs_page_ix *objix_hdr_pix) { + s32_t res = SPIFFS_OK; + spiffs_block_ix bix; + spiffs_page_object_ix_header oix_hdr; + int entry; + + res = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_CHECK_RES(res); + + obj_id |= SPIFFS_OBJ_ID_IX_FLAG; + + // find free entry + res = spiffs_obj_lu_find_free(fs, fs->free_cursor_block_ix, fs->free_cursor_obj_lu_entry, &bix, &entry); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("create: found free page @ "_SPIPRIpg" bix:"_SPIPRIbl" entry:"_SPIPRIsp"\n", (spiffs_page_ix)SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), bix, entry); + + // occupy page in object lookup + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_UPDT, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t*)&obj_id); + SPIFFS_CHECK_RES(res); + + fs->stats_p_allocated++; + + // write empty object index page + oix_hdr.p_hdr.obj_id = obj_id; + oix_hdr.p_hdr.span_ix = 0; + oix_hdr.p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_USED); + oix_hdr.type = type; + oix_hdr.size = SPIFFS_UNDEFINED_LEN; // keep ones so we can update later without wasting this page + strncpy((char*)oix_hdr.name, (const char*)name, SPIFFS_OBJ_NAME_LEN); +#if SPIFFS_OBJ_META_LEN + if (meta) { + memcpy(oix_hdr.meta, meta, SPIFFS_OBJ_META_LEN); + } else { + memset(oix_hdr.meta, 0xff, SPIFFS_OBJ_META_LEN); + } +#else + (void) meta; +#endif + + // update page + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, entry), sizeof(spiffs_page_object_ix_header), (u8_t*)&oix_hdr); + + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)&oix_hdr, + SPIFFS_EV_IX_NEW, obj_id, 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry), SPIFFS_UNDEFINED_LEN); + + if (objix_hdr_pix) { + *objix_hdr_pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// update object index header with any combination of name/size/index +// new_objix_hdr_data may be null, if so the object index header page is loaded +// name may be null, if so name is not changed +// size may be null, if so size is not changed +s32_t spiffs_object_update_index_hdr( + spiffs *fs, + spiffs_fd *fd, + spiffs_obj_id obj_id, + spiffs_page_ix objix_hdr_pix, + u8_t *new_objix_hdr_data, + const u8_t name[], + const u8_t meta[], + u32_t size, + spiffs_page_ix *new_pix) { + s32_t res = SPIFFS_OK; + spiffs_page_object_ix_header *objix_hdr; + spiffs_page_ix new_objix_hdr_pix; + + obj_id |= SPIFFS_OBJ_ID_IX_FLAG; + + if (new_objix_hdr_data) { + // object index header page already given to us, no need to load it + objix_hdr = (spiffs_page_object_ix_header *)new_objix_hdr_data; + } else { + // read object index header page + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_hdr_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + objix_hdr = (spiffs_page_object_ix_header *)fs->work; + } + + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, obj_id, 0); + + // change name + if (name) { + strncpy((char*)objix_hdr->name, (const char*)name, SPIFFS_OBJ_NAME_LEN); + } +#if SPIFFS_OBJ_META_LEN + if (meta) { + memcpy(objix_hdr->meta, meta, SPIFFS_OBJ_META_LEN); + } +#else + (void) meta; +#endif + if (size) { + objix_hdr->size = size; + } + + // move and update page + res = spiffs_page_move(fs, fd == 0 ? 0 : fd->file_nbr, (u8_t*)objix_hdr, obj_id, 0, objix_hdr_pix, &new_objix_hdr_pix); + + if (res == SPIFFS_OK) { + if (new_pix) { + *new_pix = new_objix_hdr_pix; + } + // callback on object index update + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix_hdr, + new_objix_hdr_data ? SPIFFS_EV_IX_UPD : SPIFFS_EV_IX_UPD_HDR, + obj_id, objix_hdr->p_hdr.span_ix, new_objix_hdr_pix, objix_hdr->size); + if (fd) fd->objix_hdr_pix = new_objix_hdr_pix; // if this is not in the registered cluster + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +void spiffs_cb_object_event( + spiffs *fs, + spiffs_page_object_ix *objix, + int ev, + spiffs_obj_id obj_id_raw, + spiffs_span_ix spix, + spiffs_page_ix new_pix, + u32_t new_size) { +#if SPIFFS_IX_MAP == 0 + (void)objix; +#endif + // update index caches in all file descriptors + spiffs_obj_id obj_id = obj_id_raw & ~SPIFFS_OBJ_ID_IX_FLAG; + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; +#if SPIFFS_TEMPORAL_FD_CACHE + if (cur_fd->score == 0 || (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; +#else + if (cur_fd->file_nbr == 0 || (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; +#endif + if (spix == 0) { + if (ev != SPIFFS_EV_IX_DEL) { + SPIFFS_DBG(" callback: setting fd "_SPIPRIfd":"_SPIPRIid" objix_hdr_pix to "_SPIPRIpg", size:"_SPIPRIi"\n", cur_fd->file_nbr, cur_fd->obj_id, new_pix, new_size); + cur_fd->objix_hdr_pix = new_pix; + if (new_size != 0) { + cur_fd->size = new_size; + } + } else { + cur_fd->file_nbr = 0; + cur_fd->obj_id = SPIFFS_OBJ_ID_DELETED; + } + } + if (cur_fd->cursor_objix_spix == spix) { + if (ev != SPIFFS_EV_IX_DEL) { + SPIFFS_DBG(" callback: setting fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp" objix_pix to "_SPIPRIpg"\n", cur_fd->file_nbr, cur_fd->obj_id, spix, new_pix); + cur_fd->cursor_objix_pix = new_pix; + } else { + cur_fd->cursor_objix_pix = 0; + } + } + } + +#if SPIFFS_IX_MAP + + // update index maps + if (ev == SPIFFS_EV_IX_UPD || ev == SPIFFS_EV_IX_NEW) { + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + // check fd opened, having ix map, match obj id + if (cur_fd->file_nbr == 0 || + cur_fd->ix_map == 0 || + (cur_fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG) != obj_id) continue; + SPIFFS_DBG(" callback: map ix update fd "_SPIPRIfd":"_SPIPRIid" span:"_SPIPRIsp"\n", cur_fd->file_nbr, cur_fd->obj_id, spix); + spiffs_update_ix_map(fs, cur_fd, spix, objix); + } + } + +#endif + + // callback to user if object index header + if (fs->file_cb_f && spix == 0 && (obj_id_raw & SPIFFS_OBJ_ID_IX_FLAG)) { + spiffs_fileop_type op; + if (ev == SPIFFS_EV_IX_NEW) { + op = SPIFFS_CB_CREATED; + } else if (ev == SPIFFS_EV_IX_UPD || + ev == SPIFFS_EV_IX_MOV || + ev == SPIFFS_EV_IX_UPD_HDR) { + op = SPIFFS_CB_UPDATED; + } else if (ev == SPIFFS_EV_IX_DEL) { + op = SPIFFS_CB_DELETED; + } else { + SPIFFS_DBG(" callback: WARNING unknown callback event "_SPIPRIi"\n", ev); + return; // bail out + } + fs->file_cb_f(fs, op, obj_id, new_pix); + } +} + +// Open object by id +s32_t spiffs_object_open_by_id( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_fd *fd, + spiffs_flags flags, + spiffs_mode mode) { + s32_t res = SPIFFS_OK; + spiffs_page_ix pix; + + res = spiffs_obj_lu_find_id_and_span(fs, obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix); + SPIFFS_CHECK_RES(res); + + res = spiffs_object_open_by_page(fs, pix, fd, flags, mode); + + return res; +} + +// Open object by page index +s32_t spiffs_object_open_by_page( + spiffs *fs, + spiffs_page_ix pix, + spiffs_fd *fd, + spiffs_flags flags, + spiffs_mode mode) { + (void)mode; + s32_t res = SPIFFS_OK; + spiffs_page_object_ix_header oix_hdr; + spiffs_obj_id obj_id; + + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&oix_hdr); + SPIFFS_CHECK_RES(res); + + spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(fs, pix); + int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, pix); + + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU | SPIFFS_OP_C_READ, + 0, SPIFFS_BLOCK_TO_PADDR(fs, bix) + entry * sizeof(spiffs_obj_id), sizeof(spiffs_obj_id), (u8_t *)&obj_id); + + fd->fs = fs; + fd->objix_hdr_pix = pix; + fd->size = oix_hdr.size; + fd->offset = 0; + fd->cursor_objix_pix = pix; + fd->cursor_objix_spix = 0; + fd->obj_id = obj_id; + fd->flags = flags; + + SPIFFS_VALIDATE_OBJIX(oix_hdr.p_hdr, fd->obj_id, 0); + + SPIFFS_DBG("open: fd "_SPIPRIfd" is obj id "_SPIPRIid"\n", fd->file_nbr, fd->obj_id); + + return res; +} + +#if !SPIFFS_READ_ONLY +// Append to object +// keep current object index (header) page in fs->work buffer +s32_t spiffs_object_append(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { + spiffs *fs = fd->fs; + s32_t res = SPIFFS_OK; + u32_t written = 0; + + SPIFFS_DBG("append: "_SPIPRIi" bytes @ offs "_SPIPRIi" of size "_SPIPRIi"\n", len, offset, fd->size); + + if (offset > fd->size) { + SPIFFS_DBG("append: offset reversed to size\n"); + offset = fd->size; + } + + res = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs)); // add an extra page of data worth for meta + if (res != SPIFFS_OK) { + SPIFFS_DBG("append: gc check fail "_SPIPRIi"\n", res); + } + SPIFFS_CHECK_RES(res); + + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_header p_hdr; + + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix; + spiffs_page_ix new_objix_hdr_page; + + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_page_ix data_page; + u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs); + + // write all data + while (res == SPIFFS_OK && written < len) { + // calculate object index page span index + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // handle storing and loading of object indices + if (cur_objix_spix != prev_objix_spix) { + // new object index page + // within this clause we return directly if something fails, object index mess-up + if (written > 0) { + // store previous object index page, unless first pass + SPIFFS_DBG("append: "_SPIPRIid" store objix "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + cur_objix_pix, prev_objix_spix, written); + if (prev_objix_spix == 0) { + // this is an update to object index header page + objix_hdr->size = offset+written; + if (offset == 0) { + // was an empty object, update same page (size was 0xffffffff) + res = spiffs_page_index_check(fs, fd, cur_objix_pix, 0); + SPIFFS_CHECK_RES(res); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + } else { + // was a nonempty object, update to new page + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("append: "_SPIPRIid" store new objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + new_objix_hdr_page, 0, written); + } + } else { + // this is an update to an object index page + res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD,fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); + // update length in object index header page + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_CHECK_RES(res); + SPIFFS_DBG("append: "_SPIPRIid" store new size I "_SPIPRIi" in objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + offset+written, new_objix_hdr_page, 0, written); + } + fd->size = offset+written; + fd->offset = offset+written; + } + + // create or load new object index page + if (cur_objix_spix == 0) { + // load object index header page, must always exist + SPIFFS_DBG("append: "_SPIPRIid" load objixhdr page "_SPIPRIpg":"_SPIPRIsp"\n", fd->obj_id, cur_objix_pix, cur_objix_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + } else { + spiffs_span_ix len_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, (fd->size-1)/SPIFFS_DATA_PAGE_SIZE(fs)); + // on subsequent passes, create a new object index page + if (written > 0 || cur_objix_spix > len_objix_spix) { + p_hdr.obj_id = fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = cur_objix_spix; + p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_INDEX); + res = spiffs_page_allocate_data(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, 0, 0, 0, 1, &cur_objix_pix); + SPIFFS_CHECK_RES(res); + // quick "load" of new object index page + memset(fs->work, 0xff, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + memcpy(fs->work, &p_hdr, sizeof(spiffs_page_header)); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_NEW, fd->obj_id, cur_objix_spix, cur_objix_pix, 0); + SPIFFS_DBG("append: "_SPIPRIid" create objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id + , cur_objix_pix, cur_objix_spix, written); + } else { + // on first pass, we load existing object index page + spiffs_page_ix pix; + SPIFFS_DBG("append: "_SPIPRIid" find objix span_ix:"_SPIPRIsp"\n", fd->obj_id, cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &pix); + SPIFFS_CHECK_RES(res); + } + SPIFFS_DBG("append: "_SPIPRIid" found object index at page "_SPIPRIpg" [fd size "_SPIPRIi"]\n", fd->obj_id, pix, fd->size); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + cur_objix_pix = pix; + } + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = offset+written; + fd->size = offset+written; + } + prev_objix_spix = cur_objix_spix; + } + + // write data + u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs); + if (page_offs == 0) { + // at beginning of a page, allocate and write a new page of data + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL); // finalize immediately + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, &data[written], to_write, page_offs, 1, &data_page); + SPIFFS_DBG("append: "_SPIPRIid" store new data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", fd->obj_id, + data_page, data_spix, page_offs, to_write, written); + } else { + // append to existing page, fill out free data in existing page + if (cur_objix_spix == 0) { + // get data page from object index header page + data_page = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + data_page = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } + + res = spiffs_page_data_check(fs, fd, data_page, data_spix); + SPIFFS_CHECK_RES(res); + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, data_page) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]); + SPIFFS_DBG("append: "_SPIPRIid" store to existing data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", fd->obj_id + , data_page, data_spix, page_offs, to_write, written); + } + + if (res != SPIFFS_OK) break; + + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_page; + SPIFFS_DBG("append: "_SPIPRIid" wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", fd->obj_id + , data_page, data_spix); + objix_hdr->size = offset+written; + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_page; + SPIFFS_DBG("append: "_SPIPRIid" wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", fd->obj_id + , data_page, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + + // update internals + page_offs = 0; + data_spix++; + written += to_write; + } // while all data + + fd->size = offset+written; + fd->offset = offset+written; + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + // finalize updated object indices + s32_t res2 = SPIFFS_OK; + if (cur_objix_spix != 0) { + // wrote beyond object index header page + // write last modified object index page, unless object header index page + SPIFFS_DBG("append: "_SPIPRIid" store objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id, + cur_objix_pix, cur_objix_spix, written); + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res2); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, cur_objix_pix, 0); + + // update size in object header index page + res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_DBG("append: "_SPIPRIid" store new size II "_SPIPRIi" in objix_hdr, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi", res "_SPIPRIi"\n", fd->obj_id + , offset+written, new_objix_hdr_page, 0, written, res2); + SPIFFS_CHECK_RES(res2); + } else { + // wrote within object index header page + if (offset == 0) { + // wrote to empty object - simply update size and write whole page + objix_hdr->size = offset+written; + SPIFFS_DBG("append: "_SPIPRIid" store fresh objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id + , cur_objix_pix, cur_objix_spix, written); + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res2); + // callback on object index update + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)fs->work, + SPIFFS_EV_IX_UPD_HDR, fd->obj_id, objix_hdr->p_hdr.span_ix, cur_objix_pix, objix_hdr->size); + } else { + // modifying object index header page, update size and make new copy + res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, offset+written, &new_objix_hdr_page); + SPIFFS_DBG("append: "_SPIPRIid" store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", fd->obj_id + , new_objix_hdr_page, 0, written); + SPIFFS_CHECK_RES(res2); + } + } + + return res; +} // spiffs_object_append +#endif // !SPIFFS_READ_ONLY + +#if !SPIFFS_READ_ONLY +// Modify object +// keep current object index (header) page in fs->work buffer +s32_t spiffs_object_modify(spiffs_fd *fd, u32_t offset, u8_t *data, u32_t len) { + spiffs *fs = fd->fs; + s32_t res = SPIFFS_OK; + u32_t written = 0; + + res = spiffs_gc_check(fs, len + SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_CHECK_RES(res); + + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_header p_hdr; + + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_ix cur_objix_pix = fd->objix_hdr_pix; + spiffs_page_ix new_objix_hdr_pix; + + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + spiffs_page_ix data_pix; + u32_t page_offs = offset % SPIFFS_DATA_PAGE_SIZE(fs); + + + // write all data + while (res == SPIFFS_OK && written < len) { + // calculate object index page span index + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // handle storing and loading of object indices + if (cur_objix_spix != prev_objix_spix) { + // new object index page + // within this clause we return directly if something fails, object index mess-up + if (written > 0) { + // store previous object index (header) page, unless first pass + if (prev_objix_spix == 0) { + // store previous object index header page + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, 0, &new_objix_hdr_pix); + SPIFFS_DBG("modify: store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_hdr_pix, 0, written); + SPIFFS_CHECK_RES(res); + } else { + // store new version of previous object index page + spiffs_page_ix new_objix_pix; + + res = spiffs_page_index_check(fs, fd, cur_objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, fd->obj_id, 0, cur_objix_pix, &new_objix_pix); + SPIFFS_DBG("modify: store previous modified objix page, "_SPIPRIid":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_pix, objix->p_hdr.span_ix, written); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + } + } + + // load next object index page + if (cur_objix_spix == 0) { + // load object index header page, must exist + SPIFFS_DBG("modify: load objixhdr page "_SPIPRIpg":"_SPIPRIsp"\n", cur_objix_pix, cur_objix_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, cur_objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + } else { + // load existing object index page on first pass + spiffs_page_ix pix; + SPIFFS_DBG("modify: find objix span_ix:"_SPIPRIsp"\n", cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &pix); + SPIFFS_CHECK_RES(res); + } + SPIFFS_DBG("modify: found object index at page "_SPIPRIpg"\n", pix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + cur_objix_pix = pix; + } + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = offset+written; + prev_objix_spix = cur_objix_spix; + } + + // write partial data + u32_t to_write = MIN(len-written, SPIFFS_DATA_PAGE_SIZE(fs) - page_offs); + spiffs_page_ix orig_data_pix; + if (cur_objix_spix == 0) { + // get data page from object index header page + orig_data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + orig_data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } + + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff; + if (page_offs == 0 && to_write == SPIFFS_DATA_PAGE_SIZE(fs)) { + // a full page, allocate and write a new page of data + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, &data[written], to_write, page_offs, 1, &data_pix); + SPIFFS_DBG("modify: store new data page, "_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", data_pix, data_spix, page_offs, to_write, written); + } else { + // write to existing page, allocate new and copy unmodified data + + res = spiffs_page_data_check(fs, fd, orig_data_pix, data_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, 0, 0, 0, 0, &data_pix); + if (res != SPIFFS_OK) break; + + // copy unmodified data + if (page_offs > 0) { + // before modification + res = spiffs_phys_cpy(fs, fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header), + page_offs); + if (res != SPIFFS_OK) break; + } + if (page_offs + to_write < SPIFFS_DATA_PAGE_SIZE(fs)) { + // after modification + res = spiffs_phys_cpy(fs, fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs + to_write, + SPIFFS_PAGE_TO_PADDR(fs, orig_data_pix) + sizeof(spiffs_page_header) + page_offs + to_write, + SPIFFS_DATA_PAGE_SIZE(fs) - (page_offs + to_write)); + if (res != SPIFFS_OK) break; + } + + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + page_offs, to_write, &data[written]); + if (res != SPIFFS_OK) break; + p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr.flags); + if (res != SPIFFS_OK) break; + + SPIFFS_DBG("modify: store to existing data page, src:"_SPIPRIpg", dst:"_SPIPRIpg":"_SPIPRIsp" offset:"_SPIPRIi", len "_SPIPRIi", written "_SPIPRIi"\n", orig_data_pix, data_pix, data_spix, page_offs, to_write, written); + } + + // delete original data page + res = spiffs_page_delete(fs, orig_data_pix); + if (res != SPIFFS_OK) break; + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = data_pix; + SPIFFS_DBG("modify: wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", data_pix, data_spix); + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = data_pix; + SPIFFS_DBG("modify: wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + + // update internals + page_offs = 0; + data_spix++; + written += to_write; + } // while all data + + fd->offset = offset+written; + fd->cursor_objix_pix = cur_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + // finalize updated object indices + s32_t res2 = SPIFFS_OK; + if (cur_objix_spix != 0) { + // wrote beyond object index header page + // write last modified object index page + // move and update page + spiffs_page_ix new_objix_pix; + + res2 = spiffs_page_index_check(fs, fd, cur_objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res2); + + res2 = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix, fd->obj_id, 0, cur_objix_pix, &new_objix_pix); + SPIFFS_DBG("modify: store modified objix page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_pix, cur_objix_spix, written); + fd->cursor_objix_pix = new_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + SPIFFS_CHECK_RES(res2); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + + } else { + // wrote within object index header page + res2 = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, fs->work, 0, 0, 0, &new_objix_hdr_pix); + SPIFFS_DBG("modify: store modified objix_hdr page, "_SPIPRIpg":"_SPIPRIsp", written "_SPIPRIi"\n", new_objix_hdr_pix, 0, written); + SPIFFS_CHECK_RES(res2); + } + + return res; +} // spiffs_object_modify +#endif // !SPIFFS_READ_ONLY + +static s32_t spiffs_object_find_object_index_header_by_name_v( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_block_ix bix, + int ix_entry, + const void *user_const_p, + void *user_var_p) { + (void)user_var_p; + s32_t res; + spiffs_page_object_ix_header objix_hdr; + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + if (obj_id == SPIFFS_OBJ_ID_FREE || obj_id == SPIFFS_OBJ_ID_DELETED || + (obj_id & SPIFFS_OBJ_ID_IX_FLAG) == 0) { + return SPIFFS_VIS_COUNTINUE; + } + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_CHECK_RES(res); + if (objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + if (strcmp((const char*)user_const_p, (char*)objix_hdr.name) == 0) { + return SPIFFS_OK; + } + } + + return SPIFFS_VIS_COUNTINUE; +} + +// Finds object index header page by name +s32_t spiffs_object_find_object_index_header_by_name( + spiffs *fs, + const u8_t name[SPIFFS_OBJ_NAME_LEN], + spiffs_page_ix *pix) { + s32_t res; + spiffs_block_ix bix; + int entry; + + res = spiffs_obj_lu_find_entry_visitor(fs, + fs->cursor_block_ix, + fs->cursor_obj_lu_entry, + 0, + 0, + spiffs_object_find_object_index_header_by_name_v, + name, + 0, + &bix, + &entry); + + if (res == SPIFFS_VIS_END) { + res = SPIFFS_ERR_NOT_FOUND; + } + SPIFFS_CHECK_RES(res); + + if (pix) { + *pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, entry); + } + + fs->cursor_block_ix = bix; + fs->cursor_obj_lu_entry = entry; + + return res; +} + +#if !SPIFFS_READ_ONLY +// Truncates object to new size. If new size is null, object may be removed totally +s32_t spiffs_object_truncate( + spiffs_fd *fd, + u32_t new_size, + u8_t remove_full) { + s32_t res = SPIFFS_OK; + spiffs *fs = fd->fs; + + if ((fd->size == SPIFFS_UNDEFINED_LEN || fd->size == 0) && !remove_full) { + // no op + return res; + } + + // need 2 pages if not removing: object index page + possibly chopped data page + if (remove_full == 0) { + res = spiffs_gc_check(fs, SPIFFS_DATA_PAGE_SIZE(fs) * 2); + SPIFFS_CHECK_RES(res); + } + + spiffs_page_ix objix_pix = fd->objix_hdr_pix; + spiffs_span_ix data_spix = (fd->size > 0 ? fd->size-1 : 0) / SPIFFS_DATA_PAGE_SIZE(fs); + u32_t cur_size = fd->size == (u32_t)SPIFFS_UNDEFINED_LEN ? 0 : fd->size ; + spiffs_span_ix cur_objix_spix = 0; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + spiffs_page_ix data_pix; + spiffs_page_ix new_objix_hdr_pix; + + // before truncating, check if object is to be fully removed and mark this + if (remove_full && new_size == 0) { + u8_t flags = ~( SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE); + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_UPDT, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, fd->objix_hdr_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&flags); + SPIFFS_CHECK_RES(res); + } + + // delete from end of object until desired len is reached + while (cur_size > new_size) { + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + + // put object index for current data span index in work buffer + if (prev_objix_spix != cur_objix_spix) { + if (prev_objix_spix != (spiffs_span_ix)-1) { + // remove previous object index page + SPIFFS_DBG("truncate: delete objix page "_SPIPRIpg":"_SPIPRIsp"\n", objix_pix, prev_objix_spix); + + res = spiffs_page_index_check(fs, fd, objix_pix, prev_objix_spix); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_delete(fs, objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, fd->obj_id, objix->p_hdr.span_ix, objix_pix, 0); + if (prev_objix_spix > 0) { + // Update object index header page, unless we totally want to remove the file. + // If fully removing, we're not keeping consistency as good as when storing the header between chunks, + // would we be aborted. But when removing full files, a crammed system may otherwise + // report ERR_FULL a la windows. We cannot have that. + // Hence, take the risk - if aborted, a file check would free the lost pages and mend things + // as the file is marked as fully deleted in the beginning. + if (remove_full == 0) { + SPIFFS_DBG("truncate: update objix hdr page "_SPIPRIpg":"_SPIPRIsp" to size "_SPIPRIi"\n", fd->objix_hdr_pix, prev_objix_spix, cur_size); + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + fd->size = cur_size; + } + } + // load current object index (header) page + if (cur_objix_spix == 0) { + objix_pix = fd->objix_hdr_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &objix_pix); + SPIFFS_CHECK_RES(res); + } + + SPIFFS_DBG("truncate: load objix page "_SPIPRIpg":"_SPIPRIsp" for data spix:"_SPIPRIsp"\n", objix_pix, cur_objix_spix, data_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix_hdr->p_hdr, fd->obj_id, cur_objix_spix); + fd->cursor_objix_pix = objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = cur_size; + + prev_objix_spix = cur_objix_spix; + } + + if (cur_objix_spix == 0) { + // get data page from object index header page + data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = SPIFFS_OBJ_ID_FREE; + } else { + // get data page from object index page + data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = SPIFFS_OBJ_ID_FREE; + } + + SPIFFS_DBG("truncate: got data pix "_SPIPRIpg"\n", data_pix); + + if (new_size == 0 || remove_full || cur_size - new_size >= SPIFFS_DATA_PAGE_SIZE(fs)) { + // delete full data page + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + if (res != SPIFFS_ERR_DELETED && res != SPIFFS_OK && res != SPIFFS_ERR_INDEX_REF_FREE) { + SPIFFS_DBG("truncate: err validating data pix "_SPIPRIi"\n", res); + break; + } + + if (res == SPIFFS_OK) { + res = spiffs_page_delete(fs, data_pix); + if (res != SPIFFS_OK) { + SPIFFS_DBG("truncate: err deleting data pix "_SPIPRIi"\n", res); + break; + } + } else if (res == SPIFFS_ERR_DELETED || res == SPIFFS_ERR_INDEX_REF_FREE) { + res = SPIFFS_OK; + } + + // update current size + if (cur_size % SPIFFS_DATA_PAGE_SIZE(fs) == 0) { + cur_size -= SPIFFS_DATA_PAGE_SIZE(fs); + } else { + cur_size -= cur_size % SPIFFS_DATA_PAGE_SIZE(fs); + } + fd->size = cur_size; + fd->offset = cur_size; + SPIFFS_DBG("truncate: delete data page "_SPIPRIpg" for data spix:"_SPIPRIsp", cur_size:"_SPIPRIi"\n", data_pix, data_spix, cur_size); + } else { + // delete last page, partially + spiffs_page_header p_hdr; + spiffs_page_ix new_data_pix; + u32_t bytes_to_remove = SPIFFS_DATA_PAGE_SIZE(fs) - (new_size % SPIFFS_DATA_PAGE_SIZE(fs)); + SPIFFS_DBG("truncate: delete "_SPIPRIi" bytes from data page "_SPIPRIpg" for data spix:"_SPIPRIsp", cur_size:"_SPIPRIi"\n", bytes_to_remove, data_pix, data_spix, cur_size); + + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + if (res != SPIFFS_OK) break; + + p_hdr.obj_id = fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG; + p_hdr.span_ix = data_spix; + p_hdr.flags = 0xff; + // allocate new page and copy unmodified data + res = spiffs_page_allocate_data(fs, fd->obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, + &p_hdr, 0, 0, 0, 0, &new_data_pix); + if (res != SPIFFS_OK) break; + res = spiffs_phys_cpy(fs, 0, + SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + sizeof(spiffs_page_header), + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header), + SPIFFS_DATA_PAGE_SIZE(fs) - bytes_to_remove); + if (res != SPIFFS_OK) break; + // delete original data page + res = spiffs_page_delete(fs, data_pix); + if (res != SPIFFS_OK) break; + p_hdr.flags &= ~SPIFFS_PH_FLAG_FINAL; + res = _spiffs_wr(fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_UPDT, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, new_data_pix) + offsetof(spiffs_page_header, flags), + sizeof(u8_t), + (u8_t *)&p_hdr.flags); + if (res != SPIFFS_OK) break; + + // update memory representation of object index page with new data page + if (cur_objix_spix == 0) { + // update object index header page + ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix] = new_data_pix; + SPIFFS_DBG("truncate: wrote page "_SPIPRIpg" to objix_hdr entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } else { + // update object index page + ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)] = new_data_pix; + SPIFFS_DBG("truncate: wrote page "_SPIPRIpg" to objix entry "_SPIPRIsp" in mem\n", new_data_pix, (spiffs_span_ix)SPIFFS_OBJ_IX_ENTRY(fs, data_spix)); + } + cur_size = new_size; + fd->size = new_size; + fd->offset = cur_size; + break; + } + data_spix--; + } // while all data + + // update object indices + if (cur_objix_spix == 0) { + // update object index header page + if (cur_size == 0) { + if (remove_full) { + // remove object altogether + SPIFFS_DBG("truncate: remove object index header page "_SPIPRIpg"\n", objix_pix); + + res = spiffs_page_index_check(fs, fd, objix_pix, 0); + SPIFFS_CHECK_RES(res); + + res = spiffs_page_delete(fs, objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)0, + SPIFFS_EV_IX_DEL, fd->obj_id, 0, objix_pix, 0); + } else { + // make uninitialized object + SPIFFS_DBG("truncate: reset objix_hdr page "_SPIPRIpg"\n", objix_pix); + memset(fs->work + sizeof(spiffs_page_object_ix_header), 0xff, + SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header)); + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + objix_pix, fs->work, 0, 0, SPIFFS_UNDEFINED_LEN, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + } else { + // update object index header page + SPIFFS_DBG("truncate: update object index header page with indices and size\n"); + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + objix_pix, fs->work, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + } else { + // update both current object index page and object index header page + spiffs_page_ix new_objix_pix; + + res = spiffs_page_index_check(fs, fd, objix_pix, cur_objix_spix); + SPIFFS_CHECK_RES(res); + + // move and update object index page + res = spiffs_page_move(fs, fd->file_nbr, (u8_t*)objix_hdr, fd->obj_id, 0, objix_pix, &new_objix_pix); + SPIFFS_CHECK_RES(res); + spiffs_cb_object_event(fs, (spiffs_page_object_ix *)objix_hdr, + SPIFFS_EV_IX_UPD, fd->obj_id, objix->p_hdr.span_ix, new_objix_pix, 0); + SPIFFS_DBG("truncate: store modified objix page, "_SPIPRIpg":"_SPIPRIsp"\n", new_objix_pix, cur_objix_spix); + fd->cursor_objix_pix = new_objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + fd->offset = cur_size; + // update object index header page with new size + res = spiffs_object_update_index_hdr(fs, fd, fd->obj_id, + fd->objix_hdr_pix, 0, 0, 0, cur_size, &new_objix_hdr_pix); + SPIFFS_CHECK_RES(res); + } + fd->size = cur_size; + + return res; +} // spiffs_object_truncate +#endif // !SPIFFS_READ_ONLY + +s32_t spiffs_object_read( + spiffs_fd *fd, + u32_t offset, + u32_t len, + u8_t *dst) { + s32_t res = SPIFFS_OK; + spiffs *fs = fd->fs; + spiffs_page_ix objix_pix; + spiffs_page_ix data_pix; + spiffs_span_ix data_spix = offset / SPIFFS_DATA_PAGE_SIZE(fs); + u32_t cur_offset = offset; + spiffs_span_ix cur_objix_spix; + spiffs_span_ix prev_objix_spix = (spiffs_span_ix)-1; + spiffs_page_object_ix_header *objix_hdr = (spiffs_page_object_ix_header *)fs->work; + spiffs_page_object_ix *objix = (spiffs_page_object_ix *)fs->work; + + while (cur_offset < offset + len) { +#if SPIFFS_IX_MAP + // check if we have a memory, index map and if so, if we're within index map's range + // and if so, if the entry is populated + if (fd->ix_map && data_spix >= fd->ix_map->start_spix && data_spix <= fd->ix_map->end_spix + && fd->ix_map->map_buf[data_spix - fd->ix_map->start_spix]) { + data_pix = fd->ix_map->map_buf[data_spix - fd->ix_map->start_spix]; + } else { +#endif + cur_objix_spix = SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, data_spix); + if (prev_objix_spix != cur_objix_spix) { + // load current object index (header) page + if (cur_objix_spix == 0) { + objix_pix = fd->objix_hdr_pix; + } else { + SPIFFS_DBG("read: find objix "_SPIPRIid":"_SPIPRIsp"\n", fd->obj_id, cur_objix_spix); + if (fd->cursor_objix_spix == cur_objix_spix) { + objix_pix = fd->cursor_objix_pix; + } else { + res = spiffs_obj_lu_find_id_and_span(fs, fd->obj_id | SPIFFS_OBJ_ID_IX_FLAG, cur_objix_spix, 0, &objix_pix); + SPIFFS_CHECK_RES(res); + } + } + SPIFFS_DBG("read: load objix page "_SPIPRIpg":"_SPIPRIsp" for data spix:"_SPIPRIsp"\n", objix_pix, cur_objix_spix, data_spix); + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_IX | SPIFFS_OP_C_READ, + fd->file_nbr, SPIFFS_PAGE_TO_PADDR(fs, objix_pix), SPIFFS_CFG_LOG_PAGE_SZ(fs), fs->work); + SPIFFS_CHECK_RES(res); + SPIFFS_VALIDATE_OBJIX(objix->p_hdr, fd->obj_id, cur_objix_spix); + + fd->offset = cur_offset; + fd->cursor_objix_pix = objix_pix; + fd->cursor_objix_spix = cur_objix_spix; + + prev_objix_spix = cur_objix_spix; + } + + if (cur_objix_spix == 0) { + // get data page from object index header page + data_pix = ((spiffs_page_ix*)((u8_t *)objix_hdr + sizeof(spiffs_page_object_ix_header)))[data_spix]; + } else { + // get data page from object index page + data_pix = ((spiffs_page_ix*)((u8_t *)objix + sizeof(spiffs_page_object_ix)))[SPIFFS_OBJ_IX_ENTRY(fs, data_spix)]; + } +#if SPIFFS_IX_MAP + } +#endif + // all remaining data + u32_t len_to_read = offset + len - cur_offset; + // remaining data in page + len_to_read = MIN(len_to_read, SPIFFS_DATA_PAGE_SIZE(fs) - (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs))); + // remaining data in file + len_to_read = MIN(len_to_read, fd->size); + SPIFFS_DBG("read: offset:"_SPIPRIi" rd:"_SPIPRIi" data spix:"_SPIPRIsp" is data_pix:"_SPIPRIpg" addr:"_SPIPRIad"\n", cur_offset, len_to_read, data_spix, data_pix, + (u32_t)(SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)))); + if (len_to_read <= 0) { + res = SPIFFS_ERR_END_OF_OBJECT; + break; + } + res = spiffs_page_data_check(fs, fd, data_pix, data_spix); + SPIFFS_CHECK_RES(res); + res = _spiffs_rd( + fs, SPIFFS_OP_T_OBJ_DA | SPIFFS_OP_C_READ, + fd->file_nbr, + SPIFFS_PAGE_TO_PADDR(fs, data_pix) + sizeof(spiffs_page_header) + (cur_offset % SPIFFS_DATA_PAGE_SIZE(fs)), + len_to_read, + dst); + SPIFFS_CHECK_RES(res); + dst += len_to_read; + cur_offset += len_to_read; + fd->offset = cur_offset; + data_spix++; + } + + return res; +} + +#if !SPIFFS_READ_ONLY +typedef struct { + spiffs_obj_id min_obj_id; + spiffs_obj_id max_obj_id; + u32_t compaction; + const u8_t *conflicting_name; +} spiffs_free_obj_id_state; + +static s32_t spiffs_obj_lu_find_free_obj_id_bitmap_v(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, + const void *user_const_p, void *user_var_p) { + if (id != SPIFFS_OBJ_ID_FREE && id != SPIFFS_OBJ_ID_DELETED) { + spiffs_obj_id min_obj_id = *((spiffs_obj_id*)user_var_p); + const u8_t *conflicting_name = (const u8_t*)user_const_p; + + // if conflicting name parameter is given, also check if this name is found in object index hdrs + if (conflicting_name && (id & SPIFFS_OBJ_ID_IX_FLAG)) { + spiffs_page_ix pix = SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, bix, ix_entry); + int res; + spiffs_page_object_ix_header objix_hdr; + res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ, + 0, SPIFFS_PAGE_TO_PADDR(fs, pix), sizeof(spiffs_page_object_ix_header), (u8_t *)&objix_hdr); + SPIFFS_CHECK_RES(res); + if (objix_hdr.p_hdr.span_ix == 0 && + (objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_IXDELE)) == + (SPIFFS_PH_FLAG_DELET | SPIFFS_PH_FLAG_IXDELE)) { + if (strcmp((const char*)user_const_p, (char*)objix_hdr.name) == 0) { + return SPIFFS_ERR_CONFLICTING_NAME; + } + } + } + + id &= ~SPIFFS_OBJ_ID_IX_FLAG; + u32_t bit_ix = (id-min_obj_id) & 7; + int byte_ix = (id-min_obj_id) >> 3; + if (byte_ix >= 0 && (u32_t)byte_ix < SPIFFS_CFG_LOG_PAGE_SZ(fs)) { + fs->work[byte_ix] |= (1<conflicting_name && strcmp((const char *)state->conflicting_name, (char *)objix_hdr.name) == 0) { + return SPIFFS_ERR_CONFLICTING_NAME; + } + + id &= ~SPIFFS_OBJ_ID_IX_FLAG; + if (id >= state->min_obj_id && id <= state->max_obj_id) { + u8_t *map = (u8_t *)fs->work; + int ix = (id - state->min_obj_id) / state->compaction; + //SPIFFS_DBG("free_obj_id: add ix "_SPIPRIi" for id "_SPIPRIid" min"_SPIPRIid" max"_SPIPRIid" comp:"_SPIPRIi"\n", ix, id, state->min_obj_id, state->max_obj_id, state->compaction); + map[ix]++; + } + } + } + return SPIFFS_VIS_COUNTINUE; +} + +// Scans thru all object lookup for object index header pages. If total possible number of +// object ids cannot fit into a work buffer, these are grouped. When a group containing free +// object ids is found, the object lu is again scanned for object ids within group and bitmasked. +// Finally, the bitmask is searched for a free id +s32_t spiffs_obj_lu_find_free_obj_id(spiffs *fs, spiffs_obj_id *obj_id, const u8_t *conflicting_name) { + s32_t res = SPIFFS_OK; + u32_t max_objects = (fs->block_count * SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs)) / 2; + spiffs_free_obj_id_state state; + spiffs_obj_id free_obj_id = SPIFFS_OBJ_ID_FREE; + state.min_obj_id = 1; + state.max_obj_id = max_objects + 1; + if (state.max_obj_id & SPIFFS_OBJ_ID_IX_FLAG) { + state.max_obj_id = ((spiffs_obj_id)-1) & ~SPIFFS_OBJ_ID_IX_FLAG; + } + state.compaction = 0; + state.conflicting_name = conflicting_name; + while (res == SPIFFS_OK && free_obj_id == SPIFFS_OBJ_ID_FREE) { + if (state.max_obj_id - state.min_obj_id <= (spiffs_obj_id)SPIFFS_CFG_LOG_PAGE_SZ(fs)*8) { + // possible to represent in bitmap + u32_t i, j; + SPIFFS_DBG("free_obj_id: BITM min:"_SPIPRIid" max:"_SPIPRIid"\n", state.min_obj_id, state.max_obj_id); + + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_bitmap_v, + conflicting_name, &state.min_obj_id, 0, 0); + if (res == SPIFFS_VIS_END) res = SPIFFS_OK; + SPIFFS_CHECK_RES(res); + // traverse bitmask until found free obj_id + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs); i++) { + u8_t mask = fs->work[i]; + if (mask == 0xff) { + continue; + } + for (j = 0; j < 8; j++) { + if ((mask & (1<work; + u8_t min_count = 0xff; + + for (i = 0; i < SPIFFS_CFG_LOG_PAGE_SZ(fs)/sizeof(u8_t); i++) { + if (map[i] < min_count) { + min_count = map[i]; + min_i = i; + if (min_count == 0) { + break; + } + } + } + + if (min_count == state.compaction) { + // there are no free objids! + SPIFFS_DBG("free_obj_id: compacted table is full\n"); + return SPIFFS_ERR_FULL; + } + + SPIFFS_DBG("free_obj_id: COMP select index:"_SPIPRIi" min_count:"_SPIPRIi" min:"_SPIPRIid" max:"_SPIPRIid" compact:"_SPIPRIi"\n", min_i, min_count, state.min_obj_id, state.max_obj_id, state.compaction); + + if (min_count == 0) { + // no id in this range, skip compacting and use directly + *obj_id = min_i * state.compaction + state.min_obj_id; + return SPIFFS_OK; + } else { + SPIFFS_DBG("free_obj_id: COMP SEL chunk:"_SPIPRIi" min:"_SPIPRIid" -> "_SPIPRIid"\n", state.compaction, state.min_obj_id, state.min_obj_id + min_i * state.compaction); + state.min_obj_id += min_i * state.compaction; + state.max_obj_id = state.min_obj_id + state.compaction; + // decrease compaction + } + if ((state.max_obj_id - state.min_obj_id <= (spiffs_obj_id)SPIFFS_CFG_LOG_PAGE_SZ(fs)*8)) { + // no need for compacting, use bitmap + continue; + } + } + // in a work memory of log_page_size bytes, we may fit in log_page_size ids + // todo what if compaction is > 255 - then we cannot fit it in a byte + state.compaction = (state.max_obj_id-state.min_obj_id) / ((SPIFFS_CFG_LOG_PAGE_SZ(fs) / sizeof(u8_t))); + SPIFFS_DBG("free_obj_id: COMP min:"_SPIPRIid" max:"_SPIPRIid" compact:"_SPIPRIi"\n", state.min_obj_id, state.max_obj_id, state.compaction); + + memset(fs->work, 0, SPIFFS_CFG_LOG_PAGE_SZ(fs)); + res = spiffs_obj_lu_find_entry_visitor(fs, 0, 0, 0, 0, spiffs_obj_lu_find_free_obj_id_compact_v, &state, 0, 0, 0); + if (res == SPIFFS_VIS_END) res = SPIFFS_OK; + SPIFFS_CHECK_RES(res); + state.conflicting_name = 0; // searched for conflicting name once, no need to do it again + } + } + + return res; +} +#endif // !SPIFFS_READ_ONLY + +#if SPIFFS_TEMPORAL_FD_CACHE +// djb2 hash +static u32_t spiffs_hash(spiffs *fs, const u8_t *name) { + (void)fs; + u32_t hash = 5381; + u8_t c; + int i = 0; + while ((c = name[i++]) && i < SPIFFS_OBJ_NAME_LEN) { + hash = (hash * 33) ^ c; + } + return hash; +} +#endif + +s32_t spiffs_fd_find_new(spiffs *fs, spiffs_fd **fd, const char *name) { +#if SPIFFS_TEMPORAL_FD_CACHE + u32_t i; + u16_t min_score = 0xffff; + u32_t cand_ix = (u32_t)-1; + u32_t name_hash = name ? spiffs_hash(fs, (const u8_t *)name) : 0; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + + if (name) { + // first, decrease score of all closed descriptors + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + if (cur_fd->score > 1) { // score == 0 indicates never used fd + cur_fd->score--; + } + } + } + } + + // find the free fd with least score + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + if (name && cur_fd->name_hash == name_hash) { + cand_ix = i; + break; + } + if (cur_fd->score < min_score) { + min_score = cur_fd->score; + cand_ix = i; + } + } + } + + if (cand_ix != (u32_t)-1) { + spiffs_fd *cur_fd = &fds[cand_ix]; + if (name) { + if (cur_fd->name_hash == name_hash && cur_fd->score > 0) { + // opened an fd with same name hash, assume same file + // set search point to saved obj index page and hope we have a correct match directly + // when start searching - if not, we will just keep searching until it is found + fs->cursor_block_ix = SPIFFS_BLOCK_FOR_PAGE(fs, cur_fd->objix_hdr_pix); + fs->cursor_obj_lu_entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, cur_fd->objix_hdr_pix); + // update score + if (cur_fd->score < 0xffff-SPIFFS_TEMPORAL_CACHE_HIT_SCORE) { + cur_fd->score += SPIFFS_TEMPORAL_CACHE_HIT_SCORE; + } else { + cur_fd->score = 0xffff; + } + } else { + // no hash hit, restore this fd to initial state + cur_fd->score = SPIFFS_TEMPORAL_CACHE_HIT_SCORE; + cur_fd->name_hash = name_hash; + } + } + cur_fd->file_nbr = cand_ix+1; + *fd = cur_fd; + return SPIFFS_OK; + } else { + return SPIFFS_ERR_OUT_OF_FILE_DESCS; + } +#else + (void)name; + u32_t i; + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->file_nbr == 0) { + cur_fd->file_nbr = i+1; + *fd = cur_fd; + return SPIFFS_OK; + } + } + return SPIFFS_ERR_OUT_OF_FILE_DESCS; +#endif +} + +s32_t spiffs_fd_return(spiffs *fs, spiffs_file f) { + if (f <= 0 || f > (s16_t)fs->fd_count) { + return SPIFFS_ERR_BAD_DESCRIPTOR; + } + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + spiffs_fd *fd = &fds[f-1]; + if (fd->file_nbr == 0) { + return SPIFFS_ERR_FILE_CLOSED; + } + fd->file_nbr = 0; +#if SPIFFS_IX_MAP + fd->ix_map = 0; +#endif + return SPIFFS_OK; +} + +s32_t spiffs_fd_get(spiffs *fs, spiffs_file f, spiffs_fd **fd) { + if (f <= 0 || f > (s16_t)fs->fd_count) { + return SPIFFS_ERR_BAD_DESCRIPTOR; + } + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + *fd = &fds[f-1]; + if ((*fd)->file_nbr == 0) { + return SPIFFS_ERR_FILE_CLOSED; + } + return SPIFFS_OK; +} + +#if SPIFFS_TEMPORAL_FD_CACHE +void spiffs_fd_temporal_cache_rehash( + spiffs *fs, + const char *old_path, + const char *new_path) { + u32_t i; + u32_t old_hash = spiffs_hash(fs, (const u8_t *)old_path); + u32_t new_hash = spiffs_hash(fs, (const u8_t *)new_path); + spiffs_fd *fds = (spiffs_fd *)fs->fd_space; + for (i = 0; i < fs->fd_count; i++) { + spiffs_fd *cur_fd = &fds[i]; + if (cur_fd->score > 0 && cur_fd->name_hash == old_hash) { + cur_fd->name_hash = new_hash; + } + } +} +#endif diff --git a/libiot/spiffs/spiffs_nucleus.h b/libiot/spiffs/spiffs_nucleus.h new file mode 100644 index 0000000..7d676ee --- /dev/null +++ b/libiot/spiffs/spiffs_nucleus.h @@ -0,0 +1,797 @@ +/* + * spiffs_nucleus.h + * + * Created on: Jun 15, 2013 + * Author: petera + */ + +/* SPIFFS layout + * + * spiffs is designed for following spi flash characteristics: + * - only big areas of data (blocks) can be erased + * - erasing resets all bits in a block to ones + * - writing pulls ones to zeroes + * - zeroes cannot be pulled to ones, without erase + * - wear leveling + * + * spiffs is also meant to be run on embedded, memory constraint devices. + * + * Entire area is divided in blocks. Entire area is also divided in pages. + * Each block contains same number of pages. A page cannot be erased, but a + * block can be erased. + * + * Entire area must be block_size * x + * page_size must be block_size / (2^y) where y > 2 + * + * ex: area = 1024*1024 bytes, block size = 65536 bytes, page size = 256 bytes + * + * BLOCK 0 PAGE 0 object lookup 1 + * PAGE 1 object lookup 2 + * ... + * PAGE n-1 object lookup n + * PAGE n object data 1 + * PAGE n+1 object data 2 + * ... + * PAGE n+m-1 object data m + * + * BLOCK 1 PAGE n+m object lookup 1 + * PAGE n+m+1 object lookup 2 + * ... + * PAGE 2n+m-1 object lookup n + * PAGE 2n+m object data 1 + * PAGE 2n+m object data 2 + * ... + * PAGE 2n+2m-1 object data m + * ... + * + * n is number of object lookup pages, which is number of pages needed to index all pages + * in a block by object id + * : block_size / page_size * sizeof(obj_id) / page_size + * m is number data pages, which is number of pages in block minus number of lookup pages + * : block_size / page_size - block_size / page_size * sizeof(obj_id) / page_size + * thus, n+m is total number of pages in a block + * : block_size / page_size + * + * ex: n = 65536/256*2/256 = 2, m = 65536/256 - 2 = 254 => n+m = 65536/256 = 256 + * + * Object lookup pages contain object id entries. Each entry represent the corresponding + * data page. + * Assuming a 16 bit object id, an object id being 0xffff represents a free page. + * An object id being 0x0000 represents a deleted page. + * + * ex: page 0 : lookup : 0008 0001 0aaa ffff ffff ffff ffff ffff .. + * page 1 : lookup : ffff ffff ffff ffff ffff ffff ffff ffff .. + * page 2 : data : data for object id 0008 + * page 3 : data : data for object id 0001 + * page 4 : data : data for object id 0aaa + * ... + * + * + * Object data pages can be either object index pages or object content. + * All object data pages contains a data page header, containing object id and span index. + * The span index denotes the object page ordering amongst data pages with same object id. + * This applies to both object index pages (when index spans more than one page of entries), + * and object data pages. + * An object index page contains page entries pointing to object content page. The entry index + * in a object index page correlates to the span index in the actual object data page. + * The first object index page (span index 0) is called object index header page, and also + * contains object flags (directory/file), size, object name etc. + * + * ex: + * BLOCK 1 + * PAGE 256: objectl lookup page 1 + * [*123] [ 123] [ 123] [ 123] + * [ 123] [*123] [ 123] [ 123] + * [free] [free] [free] [free] ... + * PAGE 257: objectl lookup page 2 + * [free] [free] [free] [free] ... + * PAGE 258: object index page (header) + * obj.id:0123 span.ix:0000 flags:INDEX + * size:1600 name:ex.txt type:file + * [259] [260] [261] [262] + * PAGE 259: object data page + * obj.id:0123 span.ix:0000 flags:DATA + * PAGE 260: object data page + * obj.id:0123 span.ix:0001 flags:DATA + * PAGE 261: object data page + * obj.id:0123 span.ix:0002 flags:DATA + * PAGE 262: object data page + * obj.id:0123 span.ix:0003 flags:DATA + * PAGE 263: object index page + * obj.id:0123 span.ix:0001 flags:INDEX + * [264] [265] [fre] [fre] + * [fre] [fre] [fre] [fre] + * PAGE 264: object data page + * obj.id:0123 span.ix:0004 flags:DATA + * PAGE 265: object data page + * obj.id:0123 span.ix:0005 flags:DATA + * + */ +#ifndef SPIFFS_NUCLEUS_H_ +#define SPIFFS_NUCLEUS_H_ + +#define _SPIFFS_ERR_CHECK_FIRST (SPIFFS_ERR_INTERNAL - 1) +#define SPIFFS_ERR_CHECK_OBJ_ID_MISM (SPIFFS_ERR_INTERNAL - 1) +#define SPIFFS_ERR_CHECK_SPIX_MISM (SPIFFS_ERR_INTERNAL - 2) +#define SPIFFS_ERR_CHECK_FLAGS_BAD (SPIFFS_ERR_INTERNAL - 3) +#define _SPIFFS_ERR_CHECK_LAST (SPIFFS_ERR_INTERNAL - 4) + +// visitor result, continue searching +#define SPIFFS_VIS_COUNTINUE (SPIFFS_ERR_INTERNAL - 20) +// visitor result, continue searching after reloading lu buffer +#define SPIFFS_VIS_COUNTINUE_RELOAD (SPIFFS_ERR_INTERNAL - 21) +// visitor result, stop searching +#define SPIFFS_VIS_END (SPIFFS_ERR_INTERNAL - 22) + +// updating an object index contents +#define SPIFFS_EV_IX_UPD (0) +// creating a new object index +#define SPIFFS_EV_IX_NEW (1) +// deleting an object index +#define SPIFFS_EV_IX_DEL (2) +// moving an object index without updating contents +#define SPIFFS_EV_IX_MOV (3) +// updating an object index header data only, not the table itself +#define SPIFFS_EV_IX_UPD_HDR (4) + +#define SPIFFS_OBJ_ID_IX_FLAG ((spiffs_obj_id)(1<<(8*sizeof(spiffs_obj_id)-1))) + +#define SPIFFS_UNDEFINED_LEN (u32_t)(-1) + +#define SPIFFS_OBJ_ID_DELETED ((spiffs_obj_id)0) +#define SPIFFS_OBJ_ID_FREE ((spiffs_obj_id)-1) + +#if SPIFFS_USE_MAGIC +#if !SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_MAGIC(fs, bix) \ + ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs))) +#else // SPIFFS_USE_MAGIC_LENGTH +#define SPIFFS_MAGIC(fs, bix) \ + ((spiffs_obj_id)(0x20140529 ^ SPIFFS_CFG_LOG_PAGE_SZ(fs) ^ ((fs)->block_count - (bix)))) +#endif // SPIFFS_USE_MAGIC_LENGTH +#endif // SPIFFS_USE_MAGIC + +#define SPIFFS_CONFIG_MAGIC (0x20090315) + +#if SPIFFS_SINGLETON == 0 +#define SPIFFS_CFG_LOG_PAGE_SZ(fs) \ + ((fs)->cfg.log_page_size) +#define SPIFFS_CFG_LOG_BLOCK_SZ(fs) \ + ((fs)->cfg.log_block_size) +#define SPIFFS_CFG_PHYS_SZ(fs) \ + ((fs)->cfg.phys_size) +#define SPIFFS_CFG_PHYS_ERASE_SZ(fs) \ + ((fs)->cfg.phys_erase_block) +#define SPIFFS_CFG_PHYS_ADDR(fs) \ + ((fs)->cfg.phys_addr) +#endif + +// total number of pages +#define SPIFFS_MAX_PAGES(fs) \ + ( SPIFFS_CFG_PHYS_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// total number of pages per block, including object lookup pages +#define SPIFFS_PAGES_PER_BLOCK(fs) \ + ( SPIFFS_CFG_LOG_BLOCK_SZ(fs)/SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// number of object lookup pages per block +#define SPIFFS_OBJ_LOOKUP_PAGES(fs) \ + (MAX(1, (SPIFFS_PAGES_PER_BLOCK(fs) * sizeof(spiffs_obj_id)) / SPIFFS_CFG_LOG_PAGE_SZ(fs)) ) +// checks if page index belongs to object lookup +#define SPIFFS_IS_LOOKUP_PAGE(fs,pix) \ + (((pix) % SPIFFS_PAGES_PER_BLOCK(fs)) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) +// number of object lookup entries in all object lookup pages +#define SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) \ + (SPIFFS_PAGES_PER_BLOCK(fs)-SPIFFS_OBJ_LOOKUP_PAGES(fs)) +// converts a block to physical address +#define SPIFFS_BLOCK_TO_PADDR(fs, block) \ + ( SPIFFS_CFG_PHYS_ADDR(fs) + (block)* SPIFFS_CFG_LOG_BLOCK_SZ(fs) ) +// converts a object lookup entry to page index +#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PIX(fs, block, entry) \ + ((block)*SPIFFS_PAGES_PER_BLOCK(fs) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry)) +// converts a object lookup entry to physical address of corresponding page +#define SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, block, entry) \ + (SPIFFS_BLOCK_TO_PADDR(fs, block) + (SPIFFS_OBJ_LOOKUP_PAGES(fs) + entry) * SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// converts a page to physical address +#define SPIFFS_PAGE_TO_PADDR(fs, page) \ + ( SPIFFS_CFG_PHYS_ADDR(fs) + (page) * SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// converts a physical address to page +#define SPIFFS_PADDR_TO_PAGE(fs, addr) \ + ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) / SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// gives index in page for a physical address +#define SPIFFS_PADDR_TO_PAGE_OFFSET(fs, addr) \ + ( ((addr) - SPIFFS_CFG_PHYS_ADDR(fs)) % SPIFFS_CFG_LOG_PAGE_SZ(fs) ) +// returns containing block for given page +#define SPIFFS_BLOCK_FOR_PAGE(fs, page) \ + ( (page) / SPIFFS_PAGES_PER_BLOCK(fs) ) +// returns starting page for block +#define SPIFFS_PAGE_FOR_BLOCK(fs, block) \ + ( (block) * SPIFFS_PAGES_PER_BLOCK(fs) ) +// converts page to entry in object lookup page +#define SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, page) \ + ( (page) % SPIFFS_PAGES_PER_BLOCK(fs) - SPIFFS_OBJ_LOOKUP_PAGES(fs) ) +// returns data size in a data page +#define SPIFFS_DATA_PAGE_SIZE(fs) \ + ( SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_header) ) +// returns physical address for block's erase count, +// always in the physical last entry of the last object lookup page +#define SPIFFS_ERASE_COUNT_PADDR(fs, bix) \ + ( SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_OBJ_LOOKUP_PAGES(fs) * SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_obj_id) ) +// returns physical address for block's magic, +// always in the physical second last entry of the last object lookup page +#define SPIFFS_MAGIC_PADDR(fs, bix) \ + ( SPIFFS_BLOCK_TO_PADDR(fs, bix) + SPIFFS_OBJ_LOOKUP_PAGES(fs) * SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_obj_id)*2 ) +// checks if there is any room for magic in the object luts +#define SPIFFS_CHECK_MAGIC_POSSIBLE(fs) \ + ( (SPIFFS_OBJ_LOOKUP_MAX_ENTRIES(fs) % (SPIFFS_CFG_LOG_PAGE_SZ(fs)/sizeof(spiffs_obj_id))) * sizeof(spiffs_obj_id) \ + <= (SPIFFS_CFG_LOG_PAGE_SZ(fs)-sizeof(spiffs_obj_id)*2) ) + +// define helpers object + +// entries in an object header page index +#define SPIFFS_OBJ_HDR_IX_LEN(fs) \ + ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix_header))/sizeof(spiffs_page_ix)) +// entries in an object page index +#define SPIFFS_OBJ_IX_LEN(fs) \ + ((SPIFFS_CFG_LOG_PAGE_SZ(fs) - sizeof(spiffs_page_object_ix))/sizeof(spiffs_page_ix)) +// object index entry for given data span index +#define SPIFFS_OBJ_IX_ENTRY(fs, spix) \ + ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? (spix) : (((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))%SPIFFS_OBJ_IX_LEN(fs))) +// object index span index number for given data span index or entry +#define SPIFFS_OBJ_IX_ENTRY_SPAN_IX(fs, spix) \ + ((spix) < SPIFFS_OBJ_HDR_IX_LEN(fs) ? 0 : (1+((spix)-SPIFFS_OBJ_HDR_IX_LEN(fs))/SPIFFS_OBJ_IX_LEN(fs))) +// get data span index for object index span index +#define SPIFFS_DATA_SPAN_IX_FOR_OBJ_IX_SPAN_IX(fs, spix) \ + ( (spix) == 0 ? 0 : (SPIFFS_OBJ_HDR_IX_LEN(fs) + (((spix)-1) * SPIFFS_OBJ_IX_LEN(fs))) ) + +#define SPIFFS_OP_T_OBJ_LU (0<<0) +#define SPIFFS_OP_T_OBJ_LU2 (1<<0) +#define SPIFFS_OP_T_OBJ_IX (2<<0) +#define SPIFFS_OP_T_OBJ_DA (3<<0) +#define SPIFFS_OP_C_DELE (0<<2) +#define SPIFFS_OP_C_UPDT (1<<2) +#define SPIFFS_OP_C_MOVS (2<<2) +#define SPIFFS_OP_C_MOVD (3<<2) +#define SPIFFS_OP_C_FLSH (4<<2) +#define SPIFFS_OP_C_READ (5<<2) +#define SPIFFS_OP_C_WRTHRU (6<<2) + +#define SPIFFS_OP_TYPE_MASK (3<<0) +#define SPIFFS_OP_COM_MASK (7<<2) + + +// if 0, this page is written to, else clean +#define SPIFFS_PH_FLAG_USED (1<<0) +// if 0, writing is finalized, else under modification +#define SPIFFS_PH_FLAG_FINAL (1<<1) +// if 0, this is an index page, else a data page +#define SPIFFS_PH_FLAG_INDEX (1<<2) +// if 0, page is deleted, else valid +#define SPIFFS_PH_FLAG_DELET (1<<7) +// if 0, this index header is being deleted +#define SPIFFS_PH_FLAG_IXDELE (1<<6) + + +#define SPIFFS_CHECK_MOUNT(fs) \ + ((fs)->mounted != 0) + +#define SPIFFS_CHECK_CFG(fs) \ + ((fs)->config_magic == SPIFFS_CONFIG_MAGIC) + +#define SPIFFS_CHECK_RES(res) \ + do { \ + if ((res) < SPIFFS_OK) return (res); \ + } while (0); + +#define SPIFFS_API_CHECK_MOUNT(fs) \ + if (!SPIFFS_CHECK_MOUNT((fs))) { \ + (fs)->err_code = SPIFFS_ERR_NOT_MOUNTED; \ + return SPIFFS_ERR_NOT_MOUNTED; \ + } + +#define SPIFFS_API_CHECK_CFG(fs) \ + if (!SPIFFS_CHECK_CFG((fs))) { \ + (fs)->err_code = SPIFFS_ERR_NOT_CONFIGURED; \ + return SPIFFS_ERR_NOT_CONFIGURED; \ + } + +#define SPIFFS_API_CHECK_RES(fs, res) \ + if ((res) < SPIFFS_OK) { \ + (fs)->err_code = (res); \ + return (res); \ + } + +#define SPIFFS_API_CHECK_RES_UNLOCK(fs, res) \ + if ((res) < SPIFFS_OK) { \ + (fs)->err_code = (res); \ + SPIFFS_UNLOCK(fs); \ + return (res); \ + } + +#define SPIFFS_VALIDATE_OBJIX(ph, objid, spix) \ + if (((ph).flags & SPIFFS_PH_FLAG_USED) != 0) return SPIFFS_ERR_IS_FREE; \ + if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \ + if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \ + if (((ph).flags & SPIFFS_PH_FLAG_INDEX) != 0) return SPIFFS_ERR_NOT_INDEX; \ + if (((objid) & SPIFFS_OBJ_ID_IX_FLAG) == 0) return SPIFFS_ERR_NOT_INDEX; \ + if ((ph).span_ix != (spix)) return SPIFFS_ERR_INDEX_SPAN_MISMATCH; + //if ((spix) == 0 && ((ph).flags & SPIFFS_PH_FLAG_IXDELE) == 0) return SPIFFS_ERR_DELETED; + +#define SPIFFS_VALIDATE_DATA(ph, objid, spix) \ + if (((ph).flags & SPIFFS_PH_FLAG_USED) != 0) return SPIFFS_ERR_IS_FREE; \ + if (((ph).flags & SPIFFS_PH_FLAG_DELET) == 0) return SPIFFS_ERR_DELETED; \ + if (((ph).flags & SPIFFS_PH_FLAG_FINAL) != 0) return SPIFFS_ERR_NOT_FINALIZED; \ + if (((ph).flags & SPIFFS_PH_FLAG_INDEX) == 0) return SPIFFS_ERR_IS_INDEX; \ + if ((objid) & SPIFFS_OBJ_ID_IX_FLAG) return SPIFFS_ERR_IS_INDEX; \ + if ((ph).span_ix != (spix)) return SPIFFS_ERR_DATA_SPAN_MISMATCH; + + +// check id, only visit matching objec ids +#define SPIFFS_VIS_CHECK_ID (1<<0) +// report argument object id to visitor - else object lookup id is reported +#define SPIFFS_VIS_CHECK_PH (1<<1) +// stop searching at end of all look up pages +#define SPIFFS_VIS_NO_WRAP (1<<2) + +#if SPIFFS_HAL_CALLBACK_EXTRA + +#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \ + (_fs)->cfg.hal_write_f((_fs), (_paddr), (_len), (_src)) +#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \ + (_fs)->cfg.hal_read_f((_fs), (_paddr), (_len), (_dst)) +#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \ + (_fs)->cfg.hal_erase_f((_fs), (_paddr), (_len)) + +#else // SPIFFS_HAL_CALLBACK_EXTRA + +#define SPIFFS_HAL_WRITE(_fs, _paddr, _len, _src) \ + (_fs)->cfg.hal_write_f((_paddr), (_len), (_src)) +#define SPIFFS_HAL_READ(_fs, _paddr, _len, _dst) \ + (_fs)->cfg.hal_read_f((_paddr), (_len), (_dst)) +#define SPIFFS_HAL_ERASE(_fs, _paddr, _len) \ + (_fs)->cfg.hal_erase_f((_paddr), (_len)) + +#endif // SPIFFS_HAL_CALLBACK_EXTRA + +#if SPIFFS_CACHE + +#define SPIFFS_CACHE_FLAG_DIRTY (1<<0) +#define SPIFFS_CACHE_FLAG_WRTHRU (1<<1) +#define SPIFFS_CACHE_FLAG_OBJLU (1<<2) +#define SPIFFS_CACHE_FLAG_OBJIX (1<<3) +#define SPIFFS_CACHE_FLAG_DATA (1<<4) +#define SPIFFS_CACHE_FLAG_TYPE_WR (1<<7) + +#define SPIFFS_CACHE_PAGE_SIZE(fs) \ + (sizeof(spiffs_cache_page) + SPIFFS_CFG_LOG_PAGE_SZ(fs)) + +#define spiffs_get_cache(fs) \ + ((spiffs_cache *)((fs)->cache)) + +#define spiffs_get_cache_page_hdr(fs, c, ix) \ + ((spiffs_cache_page *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)]))) + +#define spiffs_get_cache_page(fs, c, ix) \ + ((u8_t *)(&((c)->cpages[(ix) * SPIFFS_CACHE_PAGE_SIZE(fs)])) + sizeof(spiffs_cache_page)) + +// cache page struct +typedef struct { + // cache flags + u8_t flags; + // cache page index + u8_t ix; + // last access of this cache page + u32_t last_access; + union { + // type read cache + struct { + // read cache page index + spiffs_page_ix pix; + }; +#if SPIFFS_CACHE_WR + // type write cache + struct { + // write cache + spiffs_obj_id obj_id; + // offset in cache page + u32_t offset; + // size of cache page + u16_t size; + }; +#endif + }; +} spiffs_cache_page; + +// cache struct +typedef struct { + u8_t cpage_count; + u32_t last_access; + u32_t cpage_use_map; + u32_t cpage_use_mask; + u8_t *cpages; +} spiffs_cache; + +#endif + + +// spiffs nucleus file descriptor +typedef struct { + // the filesystem of this descriptor + spiffs *fs; + // number of file descriptor - if 0, the file descriptor is closed + spiffs_file file_nbr; + // object id - if SPIFFS_OBJ_ID_ERASED, the file was deleted + spiffs_obj_id obj_id; + // size of the file + u32_t size; + // cached object index header page index + spiffs_page_ix objix_hdr_pix; + // cached offset object index page index + spiffs_page_ix cursor_objix_pix; + // cached offset object index span index + spiffs_span_ix cursor_objix_spix; + // current absolute offset + u32_t offset; + // current file descriptor offset + u32_t fdoffset; + // fd flags + spiffs_flags flags; +#if SPIFFS_CACHE_WR + spiffs_cache_page *cache_page; +#endif +#if SPIFFS_TEMPORAL_FD_CACHE + // djb2 hash of filename + u32_t name_hash; + // hit score (score == 0 indicates never used fd) + u16_t score; +#endif +#if SPIFFS_IX_MAP + // spiffs index map, if 0 it means unmapped + spiffs_ix_map *ix_map; +#endif +} spiffs_fd; + + +// object structs + +// page header, part of each page except object lookup pages +// NB: this is always aligned when the data page is an object index, +// as in this case struct spiffs_page_object_ix is used +typedef struct __attribute(( packed )) { + // object id + spiffs_obj_id obj_id; + // object span index + spiffs_span_ix span_ix; + // flags + u8_t flags; +} spiffs_page_header; + +// object index header page header +typedef struct __attribute(( packed )) +#if SPIFFS_ALIGNED_OBJECT_INDEX_TABLES + __attribute(( aligned(sizeof(spiffs_page_ix)) )) +#endif +{ + // common page header + spiffs_page_header p_hdr; + // alignment + u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))]; + // size of object + u32_t size; + // type of object + spiffs_obj_type type; + // name of object + u8_t name[SPIFFS_OBJ_NAME_LEN]; +#if SPIFFS_OBJ_META_LEN + // metadata. not interpreted by SPIFFS in any way. + u8_t meta[SPIFFS_OBJ_META_LEN]; +#endif +} spiffs_page_object_ix_header; + +// object index page header +typedef struct __attribute(( packed )) { + spiffs_page_header p_hdr; + u8_t _align[4 - ((sizeof(spiffs_page_header)&3)==0 ? 4 : (sizeof(spiffs_page_header)&3))]; +} spiffs_page_object_ix; + +// callback func for object lookup visitor +typedef s32_t (*spiffs_visitor_f)(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry, + const void *user_const_p, void *user_var_p); + + +#if SPIFFS_CACHE +#define _spiffs_rd(fs, op, fh, addr, len, dst) \ + spiffs_phys_rd((fs), (op), (fh), (addr), (len), (dst)) +#define _spiffs_wr(fs, op, fh, addr, len, src) \ + spiffs_phys_wr((fs), (op), (fh), (addr), (len), (src)) +#else +#define _spiffs_rd(fs, op, fh, addr, len, dst) \ + spiffs_phys_rd((fs), (addr), (len), (dst)) +#define _spiffs_wr(fs, op, fh, addr, len, src) \ + spiffs_phys_wr((fs), (addr), (len), (src)) +#endif + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +// --------------- + +s32_t spiffs_phys_rd( + spiffs *fs, +#if SPIFFS_CACHE + u8_t op, + spiffs_file fh, +#endif + u32_t addr, + u32_t len, + u8_t *dst); + +s32_t spiffs_phys_wr( + spiffs *fs, +#if SPIFFS_CACHE + u8_t op, + spiffs_file fh, +#endif + u32_t addr, + u32_t len, + u8_t *src); + +s32_t spiffs_phys_cpy( + spiffs *fs, + spiffs_file fh, + u32_t dst, + u32_t src, + u32_t len); + +s32_t spiffs_phys_count_free_blocks( + spiffs *fs); + +s32_t spiffs_obj_lu_find_entry_visitor( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + u8_t flags, + spiffs_obj_id obj_id, + spiffs_visitor_f v, + const void *user_const_p, + void *user_var_p, + spiffs_block_ix *block_ix, + int *lu_entry); + +s32_t spiffs_erase_block( + spiffs *fs, + spiffs_block_ix bix); + +#if SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH +s32_t spiffs_probe( + spiffs_config *cfg); +#endif // SPIFFS_USE_MAGIC && SPIFFS_USE_MAGIC_LENGTH + +// --------------- + +s32_t spiffs_obj_lu_scan( + spiffs *fs); + +s32_t spiffs_obj_lu_find_free_obj_id( + spiffs *fs, + spiffs_obj_id *obj_id, + const u8_t *conflicting_name); + +s32_t spiffs_obj_lu_find_free( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_block_ix *block_ix, + int *lu_entry); + +s32_t spiffs_obj_lu_find_id( + spiffs *fs, + spiffs_block_ix starting_block, + int starting_lu_entry, + spiffs_obj_id obj_id, + spiffs_block_ix *block_ix, + int *lu_entry); + +s32_t spiffs_obj_lu_find_id_and_span( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix); + +s32_t spiffs_obj_lu_find_id_and_span_by_phdr( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix exclusion_pix, + spiffs_page_ix *pix); + +// --------------- + +s32_t spiffs_page_allocate_data( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_page_header *ph, + u8_t *data, + u32_t len, + u32_t page_offs, + u8_t finalize, + spiffs_page_ix *pix); + +s32_t spiffs_page_move( + spiffs *fs, + spiffs_file fh, + u8_t *page_data, + spiffs_obj_id obj_id, + spiffs_page_header *page_hdr, + spiffs_page_ix src_pix, + spiffs_page_ix *dst_pix); + +s32_t spiffs_page_delete( + spiffs *fs, + spiffs_page_ix pix); + +// --------------- + +s32_t spiffs_object_create( + spiffs *fs, + spiffs_obj_id obj_id, + const u8_t name[], + const u8_t meta[], + spiffs_obj_type type, + spiffs_page_ix *objix_hdr_pix); + +s32_t spiffs_object_update_index_hdr( + spiffs *fs, + spiffs_fd *fd, + spiffs_obj_id obj_id, + spiffs_page_ix objix_hdr_pix, + u8_t *new_objix_hdr_data, + const u8_t name[], + const u8_t meta[], + u32_t size, + spiffs_page_ix *new_pix); + +#if SPIFFS_IX_MAP + +s32_t spiffs_populate_ix_map( + spiffs *fs, + spiffs_fd *fd, + u32_t vec_entry_start, + u32_t vec_entry_end); + +#endif + +void spiffs_cb_object_event( + spiffs *fs, + spiffs_page_object_ix *objix, + int ev, + spiffs_obj_id obj_id, + spiffs_span_ix spix, + spiffs_page_ix new_pix, + u32_t new_size); + +s32_t spiffs_object_open_by_id( + spiffs *fs, + spiffs_obj_id obj_id, + spiffs_fd *f, + spiffs_flags flags, + spiffs_mode mode); + +s32_t spiffs_object_open_by_page( + spiffs *fs, + spiffs_page_ix pix, + spiffs_fd *f, + spiffs_flags flags, + spiffs_mode mode); + +s32_t spiffs_object_append( + spiffs_fd *fd, + u32_t offset, + u8_t *data, + u32_t len); + +s32_t spiffs_object_modify( + spiffs_fd *fd, + u32_t offset, + u8_t *data, + u32_t len); + +s32_t spiffs_object_read( + spiffs_fd *fd, + u32_t offset, + u32_t len, + u8_t *dst); + +s32_t spiffs_object_truncate( + spiffs_fd *fd, + u32_t new_len, + u8_t remove_object); + +s32_t spiffs_object_find_object_index_header_by_name( + spiffs *fs, + const u8_t name[SPIFFS_OBJ_NAME_LEN], + spiffs_page_ix *pix); + +// --------------- + +s32_t spiffs_gc_check( + spiffs *fs, + u32_t len); + +s32_t spiffs_gc_erase_page_stats( + spiffs *fs, + spiffs_block_ix bix); + +s32_t spiffs_gc_find_candidate( + spiffs *fs, + spiffs_block_ix **block_candidate, + int *candidate_count, + char fs_crammed); + +s32_t spiffs_gc_clean( + spiffs *fs, + spiffs_block_ix bix); + +s32_t spiffs_gc_quick( + spiffs *fs, u16_t max_free_pages); + +// --------------- + +s32_t spiffs_fd_find_new( + spiffs *fs, + spiffs_fd **fd, + const char *name); + +s32_t spiffs_fd_return( + spiffs *fs, + spiffs_file f); + +s32_t spiffs_fd_get( + spiffs *fs, + spiffs_file f, + spiffs_fd **fd); + +#if SPIFFS_TEMPORAL_FD_CACHE +void spiffs_fd_temporal_cache_rehash( + spiffs *fs, + const char *old_path, + const char *new_path); +#endif + +#if SPIFFS_CACHE +void spiffs_cache_init( + spiffs *fs); + +void spiffs_cache_drop_page( + spiffs *fs, + spiffs_page_ix pix); + +#if SPIFFS_CACHE_WR +spiffs_cache_page *spiffs_cache_page_allocate_by_fd( + spiffs *fs, + spiffs_fd *fd); + +void spiffs_cache_fd_release( + spiffs *fs, + spiffs_cache_page *cp); + +spiffs_cache_page *spiffs_cache_page_get_by_fd( + spiffs *fs, + spiffs_fd *fd); +#endif +#endif + +s32_t spiffs_lookup_consistency_check( + spiffs *fs, + u8_t check_all_objects); + +s32_t spiffs_page_consistency_check( + spiffs *fs); + +s32_t spiffs_object_index_consistency_check( + spiffs *fs); + +#endif /* SPIFFS_NUCLEUS_H_ */ diff --git a/libiot/sys/console.c b/libiot/sys/console.c new file mode 100644 index 0000000..0c0c293 --- /dev/null +++ b/libiot/sys/console.c @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS console utility functions + * + */ + +#include "sdkconfig.h" + +#if CONFIG_LUA_RTOS_USE_CONSOLE + +#include +#include +#include + +#include +#include +#include + +void console_clear() { + printf("\033[2J\033[1;1H"); +} + +void console_hide_cursor() { + printf("\033[25h"); +} + +void console_show_cursor() { + printf("\033[25l"); +} + +void console_size(int *rows, int *cols) { + struct timeval start; // Start time + struct timeval now; // Current time + int msectimeout = 100; + char buf[6]; + char *cbuf; + char c; + + // Save cursor position + printf("\033[s"); + + // Set cursor out of the screen + printf("\033[999;999H"); + + // Get cursor position + printf("\033[6n"); + + // Return to saved cursor position + printf("\033[u"); + + int flags = fcntl(fileno(stdin), F_GETFL, 0); + fcntl(fileno(stdin), F_SETFL, flags | O_NONBLOCK); + + // Skip scape sequence + gettimeofday(&start, NULL); + for(;;) { + if (read(fileno(stdin), &c, 1) == 1) { + if (c == '\033') { + break; + } + } + + gettimeofday(&now, NULL); + if ((now.tv_sec - start.tv_sec) * 1000 - (((now.tv_usec - start.tv_usec) + 500) / 1000) >= msectimeout) { + break; + } + } + + gettimeofday(&start, NULL); + for(;;) { + if (read(fileno(stdin), &c, 1) == 1) { + if (c == '[') { + break; + } + } + + gettimeofday(&now, NULL); + if ((now.tv_sec - start.tv_sec) * 1000 - (((now.tv_usec - start.tv_usec) + 500) / 1000) >= msectimeout) { + break; + } + } + + // Read rows + c = '\0'; + cbuf = buf; + + gettimeofday(&start, NULL); + for(;;) { + if (read(fileno(stdin), &c, 1) == 1) { + if (c == ';') { + break; + } + *cbuf++ = c; + } + + gettimeofday(&now, NULL); + if ((now.tv_sec - start.tv_sec) * 1000 - (((now.tv_usec - start.tv_usec) + 500) / 1000) >= msectimeout) { + break; + } + } + + *cbuf = '\0'; + + if (*buf != '\0') { + *rows = atoi(buf); + } + + // Read cols + c = '\0'; + cbuf = buf; + + gettimeofday(&start, NULL); + for(;;) { + if (read(fileno(stdin), &c, 1) == 1) { + if (c == 'R') { + break; + } + *cbuf++ = c; + } + + gettimeofday(&now, NULL); + if ((now.tv_sec - start.tv_sec) * 1000 - (((now.tv_usec - start.tv_usec) + 500) / 1000) >= msectimeout) { + break; + } + } + + fcntl(fileno(stdin), F_SETFL, flags); + + *cbuf = '\0'; + + if (*buf != '\0') { + *cols = atoi(buf); + } +} + +void console_gotoxy(int col, int line) { + printf("\033[%d;%dH", line + 1, col + 1); +} + +void console_statusline(const char *text1, const char *text2) { + int rows = 0; + int cols = 0; + + console_size(&rows, &cols); + + console_gotoxy(0, rows); + printf("\033[1m\033[7m%s%s\033[K\033[0m", text1, text2); +} + +void console_clearstatusline() { + int rows = 0; + int cols = 0; + + console_size(&rows, &cols); + + console_gotoxy(0, rows); + printf("\033[K\033[0m"); +} + +void console_erase_eol() { + printf("\033[K"); +} + +void console_erase_sol() { + printf("\033[1K"); +} + +void console_erase_l() { + printf("\033[2K"); +} + +#endif diff --git a/libiot/sys/console.h b/libiot/sys/console.h new file mode 100644 index 0000000..7348890 --- /dev/null +++ b/libiot/sys/console.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS console utility functions + * + */ + +#ifndef CONSOLE_H +#define CONSOLE_H + +void console_clear(); +void console_size(int *rows, int *cols); +void console_gotoxy(int col, int line); +void console_statusline(const char *text1, const char *text2); +void console_clearstatusline(); +void console_erase_eol(); +void console_erase_sol(); +void console_erase_l(); +void console_hide_cursor(); +void console_show_cursor(); + +#endif /* CONSOLE_H */ + diff --git a/libiot/sys/history.c b/libiot/sys/history.c new file mode 100644 index 0000000..e01e767 --- /dev/null +++ b/libiot/sys/history.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS, shell history functions + * + */ + +#include "history.h" + +#include +#include +#include + +#include +#include +#include +#include + +static void history_add_internal(const char *buf) { + // Get the history file name + char fname[PATH_MAX + 1]; + if (!mount_history_file(fname, sizeof(fname))) { + return; + } + + int retries = 0; + +again: { + // Open the history file + FILE *fp = fopen(fname,"a"); + if (!fp) { + // File not found, try to create it, and open again + mkfile(fname); + fp = fopen(fname,"a"); + if (!fp) { + return; + } + } + + // Get file size + long size; + + if ((size = ftell(fp)) < 0) { + fclose(fp); + return; + } + + // Write to history file + int len = strlen(buf); + + int no_space = 0; + + if (fwrite(buf, 1, len, fp) != len) { + no_space = (errno == ENOSPC); + } + + if (!no_space) { + if (fflush(fp) == EOF) { + no_space = (errno == ENOSPC); + } + } + + if (no_space) { + // Truncate file to the original size + if (ftruncate(fileno(fp), size) < 0) { + fclose(fp); + return; + } + + if (retries == 0) { + // Tail file + fclose(fp); + file_tails(fname, fp->_blksize); + + retries++; + goto again; + } + } + + fclose(fp); + } +} + +void history_add(const char *line) { + history_add_internal("\n"); + history_add_internal(line); +} + +int history_get(int index, int up, char *buf, int buflen) { + // Get the history file name + char fname[PATH_MAX + 1]; + if (!mount_history_file(fname, sizeof(fname))) { + return -2; + } + + // Open history file + FILE *fp = fopen(fname,"r"); + if (!fp) { + // File not found, try to create it, and open again + mkfile(fname); + fp = fopen(fname,"r"); + if (!fp) { + return -2; + } + } + + int pos, len, new_pos; + char c; + + if (index == -1) { + fseek(fp, 0, SEEK_END); + pos = ftell(fp); + } else { + pos = index; + } + + if ((pos == 0) && (up)) { + fclose(fp); + return -3; + } + + new_pos = pos; + + while ((pos >= 0) && !feof(fp)) { + if (up) + fseek(fp, --pos, SEEK_SET); + else + fseek(fp, ++pos, SEEK_SET); + + c = fgetc(fp); + if (c == '\n') { + break; + } + } + + new_pos = pos; + + if ((pos >= 0) && !feof(fp)) { + // Get line + fgets(buf, buflen, fp); + len = strlen(buf); + + // Strip \r \n chars at the end, if present + while ((buf[len - 1] == '\r') || (buf[len - 1] == '\n')) { + len--; + } + + buf[len] = '\0'; + } else { + if (feof(fp)) + new_pos = -1; + else + new_pos = 0; + + buf[0] = 0; + } + + fclose(fp); + + return new_pos; +} diff --git a/libiot/sys/history.h b/libiot/sys/history.h new file mode 100644 index 0000000..56a22fd --- /dev/null +++ b/libiot/sys/history.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS, shell history functions + * + */ +#ifndef _HISTORY_H_ +#define _HISTORY_H_ + +/** + * @brief Add a line to the history file. If the is not left space on the device + * the history file is tailed from the beginning. + * + * @param buf The line to add. + */ +void history_add(const char *line); +int history_get(int index, int up, char *buf, int buflen); + +#endif /* _HISTORY_H_ */ diff --git a/libiot/sys/list.c b/libiot/sys/list.c new file mode 100644 index 0000000..ef965c8 --- /dev/null +++ b/libiot/sys/list.c @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS list data structure + * + */ + +//#include "esp_attr.h" + +#include +#include +#include + +#include + +#include "list.h" +#include "mutex.h" + +void lstinit(struct list *list, int first_index, uint8_t flags) { + // Create the mutex + mtx_init(&list->mutex, NULL, NULL, 0); + + mtx_lock(&list->mutex); + + list->indexes = 0; + list->free = NULL; + list->index = NULL; + list->last = NULL; + list->first_index = first_index; + list->flags = flags; + list->init = 1; + + mtx_unlock(&list->mutex); +} + +int lstadd(struct list *list, void *item, long *item_index) { + struct lstindex *index = NULL; + struct lstindex *indexa = NULL; + int grow = 0; + + mtx_lock(&list->mutex); + + if (list->flags & LIST_DEFAULT) { + // Get an index + if (list->free) { + // Get first free element + index = list->free; + list->free = index->next; + } else { + // Must grow index array + grow = 1; + } + + if (grow) { + // Increment index count + list->indexes++; + + // Create a new index array for allocate new index + indexa = (struct lstindex *)malloc(sizeof(struct lstindex) * list->indexes); + if (!indexa) { + mtx_unlock(&list->mutex); + return ENOMEM; + } + + if (list->index) { + // Copy current index array to new created + bcopy(list->index, indexa, sizeof(struct lstindex) * (list->indexes - 1)); + + // Free current index array + free(list->index); + } + + // Store new index array + list->index = indexa; + + // Current index + index = list->index + list->indexes - 1; + + // Initialize new index + index->index = list->indexes - 1; + } + + index->next = NULL; + index->item = item; + index->deleted = 0; + + // Return index + if (item_index) { + *item_index = index->index + list->first_index; + } + } else if (list->flags & LIST_NOT_INDEXED) { + // Create a new element + index = (struct lstindex *)calloc(1, sizeof(struct lstindex)); + if (!index) { + return ENOMEM; + } + + index->item = item; + + if ((list->index == NULL) && (list->last == NULL)) { + // First element + list->index = index; + } else { + // Almost there is one element in list + assert(list->last != NULL); + + list->last->next = index; + index->previous = list->last; + } + + list->last = index; + + if (item_index) { + *item_index = (long)item; + } + } + + mtx_unlock(&list->mutex); + + return 0; +} + +int lstget(struct list *list, long index, void **item) { + struct lstindex *cindex = NULL; + long iindex; + + mtx_lock(&list->mutex); + + if (list->flags & LIST_DEFAULT) { + if (!list->indexes) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + // Check index + if (index < list->first_index) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + // Get new internal index + iindex = index - list->first_index; + + // Test for a valid index + if (iindex > list->indexes) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + cindex = list->index + iindex; + + if (cindex->deleted) { + mtx_unlock(&list->mutex); + return EINVAL; + } + } else if (list->flags & LIST_NOT_INDEXED) { + cindex = list->index; + + while (cindex) { + if (cindex->item == (void *)index) { + *item = cindex->item; + break; + } + + cindex = cindex->next; + } + + if (!cindex) { + mtx_unlock(&list->mutex); + return EINVAL; + } + } + + *item = cindex->item; + + mtx_unlock(&list->mutex); + + return 0; +} + +int lstremovec(struct list *list, long index, int destroy, bool compact) { + struct lstindex *cindex = NULL; + long iindex; + + mtx_lock(&list->mutex); + + if (list->flags & LIST_DEFAULT) { + // Check index + if (index < list->first_index) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + // Get new internal index + iindex = index - list->first_index; + + // Test for a valid index + if ((iindex < 0) || (iindex > list->indexes)) { + mtx_unlock(&list->mutex); + return EINVAL; + } + + cindex = &list->index[iindex]; + + if (destroy) { + free(cindex->item); + } + + if (compact) { + bcopy(&list->index[iindex+1], &list->index[iindex], sizeof(struct lstindex) * (list->indexes - iindex - 1)); + iindex = list->indexes-1; + cindex = &list->index[iindex]; + } + + cindex->next = list->free; + cindex->deleted = 1; + list->free = cindex; + } else if (list->flags & LIST_NOT_INDEXED) { + cindex = list->index; + + while (cindex) { + if (cindex->item == (void *)index) { + if (cindex->next) { + cindex->next->previous = cindex->previous; + } else { + list->last = cindex->previous; + } + + if (cindex->previous) { + cindex->previous->next = cindex->next; + } else { + list->index = cindex->next; + } + + if (destroy) { + free(cindex->item); + } + + free(cindex); + + break; + } + + cindex = cindex->next; + } + + } + + mtx_unlock(&list->mutex); + + return 0; +} + +int lstremove(struct list *list, long index, int destroy) { + return lstremovec(list, index, destroy, false); +} + +int lstfirst(struct list *list) { + long index; + long res = -1; + + mtx_lock(&list->mutex); + + if (list->flags & LIST_DEFAULT) { + for(index=0;index < list->indexes;index++) { + if (!list->index[index].deleted) { + res = index + list->first_index; + break; + } + } + } else if (list->flags & LIST_NOT_INDEXED) { + if ((list->index != NULL) && (list->last != NULL)) { + res = (long)list->index; + } + } + + mtx_unlock(&list->mutex); + + return res; +} + +int lstlast(struct list *list) { + long index; + long res = -1; + + mtx_lock(&list->mutex); + + if (list->flags & LIST_DEFAULT) { + for(index = list->indexes - 1;index >= 0;index--) { + if (!list->index[index].deleted) { + res = index + list->first_index; + break; + } + } + } else if (list->flags & LIST_NOT_INDEXED) { + if ((list->index != NULL) && (list->last != NULL)) { + res = (long)list->last; + } + } + + mtx_unlock(&list->mutex); + + return res; +} + +int lstnext(struct list *list, long index) { + long res = -1; + long iindex; + + mtx_lock(&list->mutex); + + if (list->flags & LIST_DEFAULT) { + // Check index + if (index < list->first_index) { + mtx_unlock(&list->mutex); + return -1; + } + + // Get new internal index + iindex = index - list->first_index + 1; + + // Get next non deleted item on list + for(;iindex < list->indexes;iindex++) { + if (!list->index[iindex].deleted) { + res = iindex + list->first_index; + break; + } + } + } else if (list->flags & LIST_NOT_INDEXED) { + struct lstindex *cindex = NULL; + + cindex = list->index; + + while (cindex) { + if (cindex->item == (void *)index) { + if (cindex->next) { + res = (long)cindex->next; + } + + break; + } + + cindex = cindex->next; + } + } + + mtx_unlock(&list->mutex); + + return res; +} + +void lstdestroy(struct list *list, int items) { + long index; + + if (!list->init) return; + + mtx_lock(&list->mutex); + + if (list->flags & LIST_DEFAULT) { + if (items) { + for(index=0;index < list->indexes;index++) { + if (!list->index[index].deleted) { + free(list->index[index].item); + } + } + } + + if (0 != list->index) { + free(list->index); + list->index = 0; + } + + } else if (list->flags & LIST_NOT_INDEXED) { + struct lstindex *cindex = NULL; + struct lstindex *pindex = NULL; + + cindex = list->index; + + while (cindex) { + pindex = cindex; + cindex = cindex->next; + + free(pindex); + } + } + + list->init = 0; + + mtx_unlock(&list->mutex); + mtx_destroy(&list->mutex); +} diff --git a/libiot/sys/list.h b/libiot/sys/list.h new file mode 100644 index 0000000..ea397e0 --- /dev/null +++ b/libiot/sys/list.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS driver common functions + * + */ + +#ifndef _LIST_H +#define _LIST_H + +#include +#include +#include "mutex.h" + +struct list { + struct mtx mutex; + struct lstindex *index; + struct lstindex *free; + struct lstindex *last; + uint8_t indexes; + uint8_t first_index; + uint8_t flags; + uint8_t init; +}; + +struct lstindex { + void *item; + uint8_t index; + uint8_t deleted; + struct lstindex *next; + struct lstindex *previous; +}; + +#define LIST_DEFAULT (1 << 0) +#define LIST_NOT_INDEXED (1 << 1) + +void lstinit(struct list *list, int first_index, uint8_t flags); +int lstadd(struct list *list, void *item, long *item_index); +int lstget(struct list *list, long index, void **item); +int lstremove(struct list *list, long index, int destroy); +int lstremovec(struct list *list, long index, int destroy, bool compact); +int lstfirst(struct list *list); +int lstlast(struct list *list); +int lstnext(struct list *list, long index); +void lstdestroy(struct list *list, int items); + +#endif /* _LIST_H */ diff --git a/libiot/sys/mkfile b/libiot/sys/mkfile new file mode 100644 index 0000000..6eafd7a --- /dev/null +++ b/libiot/sys/mkfile @@ -0,0 +1,5 @@ +OFILES=\ + $OFILES\ + sys/panic.$O\ + sys/mutex.$O\ + sys/list.$O\ diff --git a/libiot/sys/mutex.c b/libiot/sys/mutex.c new file mode 100644 index 0000000..6c63fe3 --- /dev/null +++ b/libiot/sys/mutex.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS mutex api implementation over FreeRTOS + * + */ + +//#include "sdkconfig.h" +#include +#include "../freertos/adds.h" + +//#include "esp_attr.h" + +#include "mutex.h" +#include "panic.h" + +int mtx_inited(struct mtx *mutex) { + return (mutex->lock != 0); +} + +void mtx_init(struct mtx *mutex, const char *name, const char *type, int opts) { + mutex->opts = opts; + + if (mutex->opts == MTX_DEF) { + mutex->lock = xSemaphoreCreateBinary(); + } else if (opts == MTX_RECURSE) { + mutex->lock = xSemaphoreCreateRecursiveMutex(); + } else { + return; + } + + if (mutex->lock) { +#if 0 //{} + if (xPortInIsrContext()) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR( mutex->lock, &xHigherPriorityTaskWoken); + portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); + } else { +#endif + if (mutex->opts == MTX_DEF) { + xSemaphoreGive( mutex->lock ); + } +//{} } + } +} + +void mtx_lock(struct mtx *mutex) { +#if 0 //{} + if (xPortInIsrContext()) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreTakeFromISR( mutex->lock, &xHigherPriorityTaskWoken ); + portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); + } else { +#endif + if (mutex->opts == MTX_DEF) { + xSemaphoreTake( mutex->lock, portMAX_DELAY ); + } else if (mutex->opts == MTX_RECURSE) { + xSemaphoreTakeRecursive( mutex->lock, portMAX_DELAY ); + } +//{} } +} + +int mtx_trylock(struct mtx *mutex) { + if (mutex->opts == MTX_DEF) { + if (xSemaphoreTake( mutex->lock, 0 ) == pdTRUE) { + return 1; + } else { + return 0; + } + } else if (mutex->opts == MTX_RECURSE) { + if (xSemaphoreTakeRecursive( mutex->lock, 0 ) == pdTRUE) { + return 1; + } else { + return 0; + } + } + + return 0; +} + +void mtx_unlock(struct mtx *mutex) { +#if 0 //{} + if (xPortInIsrContext()) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR( mutex->lock, &xHigherPriorityTaskWoken ); + portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); + } else { +#endif + if (mutex->opts == MTX_DEF) { + xSemaphoreGive( mutex->lock ); + } else if (mutex->opts == MTX_RECURSE) { + xSemaphoreGiveRecursive( mutex->lock ); + } +//{} } +} + +void mtx_destroy(struct mtx *mutex) { + if (!mutex->lock) return; + + if (mutex->opts == MTX_DEF) { +#if 0 //{} + if (xPortInIsrContext()) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xSemaphoreGiveFromISR( mutex->lock, &xHigherPriorityTaskWoken ); + portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); + } else { +#endif + xSemaphoreGive( mutex->lock ); +//{} } + + vSemaphoreDelete( mutex->lock ); + } else if (mutex->opts == MTX_RECURSE) { + vSemaphoreDelete( mutex->lock ); + } + + mutex->lock = 0; +} diff --git a/libiot/sys/mutex.h b/libiot/sys/mutex.h new file mode 100644 index 0000000..c2084e9 --- /dev/null +++ b/libiot/sys/mutex.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS mutex api implementation over FreeRTOS + * + */ + +#ifndef _MUTEX_H +#define _MUTEX_H + +//#include "sdkconfig.h" + +#include +#include + +struct mtx { + SemaphoreHandle_t lock; + int opts; +}; + +#define MTX_DEF 0 +#define MTX_RECURSE 1 + +int mtx_inited(struct mtx *mutex); +void mtx_init(struct mtx *mutex, const char *name, const char *type, int opts); +void mtx_lock(struct mtx *mutex); +int mtx_trylock(struct mtx *mutex); +void mtx_unlock(struct mtx *mutex); +void mtx_destroy(struct mtx *mutex); + +#endif /* _MUTEX_H */ diff --git a/libiot/sys/panic.c b/libiot/sys/panic.c new file mode 100644 index 0000000..a314455 --- /dev/null +++ b/libiot/sys/panic.c @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS panic + * + */ + +#include + +void panic(char *str) { + printf("%s\n", str); + for(;;) { + } +} diff --git a/libiot/sys/panic.h b/libiot/sys/panic.h new file mode 100644 index 0000000..8791dd1 --- /dev/null +++ b/libiot/sys/panic.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS panic + * + */ + +#ifndef _SYS_PANIC_H_ +#define _SYS_PANIC_H_ + +void panic(char *str); + +#endif /* !_SYS_PANIC_H_ */ diff --git a/libiot/vfs/fat.c b/libiot/vfs/fat.c new file mode 100644 index 0000000..d612277 --- /dev/null +++ b/libiot/vfs/fat.c @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS fat vfs + * + */ + +#include "sdkconfig.h" + +#if (CONFIG_SD_CARD_MMC || CONFIG_SD_CARD_SPI) && CONFIG_LUA_RTOS_USE_FAT + +#include + +#include "esp_vfs.h" + +#include "esp_err.h" +#include "esp_log.h" +#include "esp_vfs_fat.h" +#include "driver/sdmmc_host.h" +#include "driver/sdspi_host.h" +#include "sdmmc_cmd.h" +#include "driver/sdmmc_defs.h" + +#include +#include +#include + +#include +#include +#include + +#include + +int vfs_fat_mount(const char *target) { +#if CONFIG_SD_CARD_SPI + spi_bus_t *spi_bus = get_spi_info(); + + #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + // Lock resources + if (spi_lock_bus_resources(CONFIG_LUA_RTOS_SD_SPI, DRIVER_ALL_FLAGS)) { + return -1; + } + + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_SD_CS, DRIVER_ALL_FLAGS, "SD Card - CS")) { + return -1; + } + #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + + sdmmc_host_t host = SDSPI_HOST_DEFAULT(); + sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT(); + + #if (CONFIG_LUA_RTOS_SD_SPI == 2) + host.slot = HSPI_HOST; + + slot_config.gpio_miso = CONFIG_LUA_RTOS_SPI2_MISO; + slot_config.gpio_mosi = CONFIG_LUA_RTOS_SPI2_MOSI; + slot_config.gpio_sck = CONFIG_LUA_RTOS_SPI2_CLK; + slot_config.gpio_cs = CONFIG_LUA_RTOS_SD_CS; + #endif // (CONFIG_LUA_RTOS_SD_SPI == 2) + + #if (CONFIG_LUA_RTOS_SD_SPI == 3) + host.slot = VSPI_HOST; + + slot_config.gpio_miso = CONFIG_LUA_RTOS_SPI3_MISO; + slot_config.gpio_mosi = CONFIG_LUA_RTOS_SPI3_MOSI; + slot_config.gpio_sck = CONFIG_LUA_RTOS_SPI3_CLK; + slot_config.gpio_cs = CONFIG_LUA_RTOS_SD_CS; + #endif // (CONFIG_LUA_RTOS_SD_SPI == 3) + + spi_bus[spi_idx(CONFIG_LUA_RTOS_SD_SPI)].setup |= SPI_DMA_SETUP; +#endif // CONFIG_SD_CARD_SPI + +#if CONFIG_SD_CARD_MMC + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + + slot_config.gpio_cd = CONFIG_LUA_RTOS_MCC_CD; + slot_config.gpio_wp = CONFIG_LUA_RTOS_MCC_WP; + + #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + // Lock resources + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 15, DRIVER_ALL_FLAGS, "SD Card - CMD")) { + return -1; + } + + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 14, DRIVER_ALL_FLAGS, "SD Card - CLK")) { + return -1; + } + + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 2, DRIVER_ALL_FLAGS, "SD Card - DAT0")) { + return -1; + } + #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + + gpio_pin_pullup(2); + gpio_pin_pullup(14); + gpio_pin_pullup(15); + + #if CONFIG_LUA_RTOS_MCC_1_LINE + host.flags = SDMMC_HOST_FLAG_1BIT; + slot_config.width = 1; + #endif // CONFIG_LUA_RTOS_MCC_1_LINE + + #if CONFIG_LUA_RTOS_MCC_4_LINE + host.flags = SDMMC_HOST_FLAG_4BIT; + slot_config.width = 4; + + gpio_pin_pullup(4); + gpio_pin_pullup(12); + gpio_pin_pullup(13); + + #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 4, DRIVER_ALL_FLAGS, "SD Card - DAT1")) { + return -1; + } + + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 12, DRIVER_ALL_FLAGS, "SD Card - DAT2")) { + return -1; + } + + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 13, DRIVER_ALL_FLAGS, "SD Card - DAT3")) { + return -1; + } + #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + #endif // CONFIG_LUA_RTOS_MCC_4_LINE + + #if CONFIG_LUA_RTOS_MCC_CD != -1 + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_CD, DRIVER_ALL_FLAGS, "SD Card - CD")) { + return -1; + } + #endif // CONFIG_LUA_RTOS_MCC_CD + + #if CONFIG_LUA_RTOS_MCC_WP != -1 + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_WP, DRIVER_ALL_FLAGS, "SD Card - WP")) { + return -1; + } + #endif // CONFIG_LUA_RTOS_MCC_WP +#endif // CONFIG_SD_CARD_MMC + +#if CONFIG_LUA_RTOS_SD_CONNECTED_TO_POWER_BUS + pwbus_on(); +#endif + + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 5 + }; + + #if CONFIG_SD_CARD_SPI + syslog(LOG_INFO, "sd is at spi%d, cs=%s%d", + CONFIG_LUA_RTOS_SD_SPI, + gpio_portname(CONFIG_LUA_RTOS_SD_CS), gpio_name(CONFIG_LUA_RTOS_SD_CS) + ); + #endif + + #if CONFIG_SD_CARD_MMC + syslog(LOG_INFO, "sd is at mmc0"); + #endif + + sdmmc_card_t* card; + esp_err_t ret = esp_vfs_fat_sdmmc_mount("/fat", &host, &slot_config, &mount_config, &card); + if (ret != ESP_OK) { + syslog(LOG_INFO, "fat can't mounted (error %d)", ret); + vfs_fat_umount(target); + return -1; + } + + syslog(LOG_INFO, "sd name %s", card->cid.name); + syslog(LOG_INFO, "sd type %s", (card->ocr & SD_OCR_SDHC_CAP)?"SDHC/SDXC":"SDSC"); + syslog(LOG_INFO, "sd working at %s", (card->csd.tr_speed > 25000000)?"high speed":"default speed"); + syslog(LOG_INFO, "sd size %.2f GB", + ((((double)card->csd.capacity) * ((double)card->csd.sector_size)) / 1073741824.0) + ); + + syslog(LOG_INFO, "sd CSD ver=%d, sector_size=%d, capacity=%d read_bl_len=%d", + card->csd.csd_ver, + card->csd.sector_size, card->csd.capacity, card->csd.read_block_len + ); + + syslog(LOG_INFO, "sd SCR sd_spec=%d, bus_width=%d", card->scr.sd_spec, card->scr.bus_width); + + syslog(LOG_INFO, "fat mounted on %s", target); + + return 0; +} + +int vfs_fat_umount(const char *target) { + // Unmount + esp_vfs_fat_sdmmc_unmount(); + +#if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + #if CONFIG_SD_CARD_SPI + spi_bus_t *spi_bus = get_spi_info(); + + // Unlock resources + spi_unlock_bus_resources(CONFIG_LUA_RTOS_SD_SPI); + driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_SD_CS); + spi_bus[spi_idx(CONFIG_LUA_RTOS_SD_SPI)].setup &= ~SPI_DMA_SETUP; + #endif // CONFIG_SD_CARD_SPI + + #if CONFIG_SD_CARD_MMC + // Unlock resources + driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 15); + driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 14); + driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 2); + + #if CONFIG_LUA_RTOS_MCC_4_LINE + driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 14); + driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 12); + driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 13); + #endif + + #if CONFIG_LUA_RTOS_MCC_CD != -1 + driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_CD); + #endif + + #if CONFIG_LUA_RTOS_MCC_WP != -1 + driver_unlock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_WP); + #endif + #endif // CONFIG_SD_CARD_MMC +#endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + +#if CONFIG_LUA_RTOS_SD_CONNECTED_TO_POWER_BUS + pwbus_off(); +#endif + + syslog(LOG_INFO, "fat unmounted"); + + return 0; +} + +int vfs_fat_format(const char *target) { + vfs_fat_umount(target); + +#if CONFIG_SD_CARD_SPI + spi_bus_t *spi_bus = get_spi_info(); + + #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + // Lock resources + if (spi_lock_bus_resources(CONFIG_LUA_RTOS_SD_SPI, DRIVER_ALL_FLAGS)) { + return -1; + } + + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_SD_CS, DRIVER_ALL_FLAGS, "SD Card - CS")) { + return -1; + } + #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + + sdmmc_host_t host = SDSPI_HOST_DEFAULT(); + sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT(); + + #if (CONFIG_LUA_RTOS_SD_SPI == 2) + host.slot = HSPI_HOST; + + slot_config.gpio_miso = CONFIG_LUA_RTOS_SPI2_MISO; + slot_config.gpio_mosi = CONFIG_LUA_RTOS_SPI2_MOSI; + slot_config.gpio_sck = CONFIG_LUA_RTOS_SPI2_CLK; + slot_config.gpio_cs = CONFIG_LUA_RTOS_SD_CS; + #endif // (CONFIG_LUA_RTOS_SD_SPI == 2) + + #if (CONFIG_LUA_RTOS_SD_SPI == 3) + host.slot = VSPI_HOST; + + slot_config.gpio_miso = CONFIG_LUA_RTOS_SPI3_MISO; + slot_config.gpio_mosi = CONFIG_LUA_RTOS_SPI3_MOSI; + slot_config.gpio_sck = CONFIG_LUA_RTOS_SPI3_CLK; + slot_config.gpio_cs = CONFIG_LUA_RTOS_SD_CS; + #endif // (CONFIG_LUA_RTOS_SD_SPI == 3) + + spi_bus[spi_idx(CONFIG_LUA_RTOS_SD_SPI)].setup |= SPI_DMA_SETUP; +#endif // CONFIG_SD_CARD_SPI + +#if CONFIG_SD_CARD_MMC + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + + slot_config.gpio_cd = CONFIG_LUA_RTOS_MCC_CD; + slot_config.gpio_wp = CONFIG_LUA_RTOS_MCC_WP; + + #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + // Lock resources + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 15, DRIVER_ALL_FLAGS, "SD Card - CMD")) { + return -1; + } + + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 14, DRIVER_ALL_FLAGS, "SD Card - CLK")) { + return -1; + } + + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 2, DRIVER_ALL_FLAGS, "SD Card - DAT0")) { + return -1; + } + #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + + gpio_pin_pullup(2); + gpio_pin_pullup(14); + gpio_pin_pullup(15); + + #if CONFIG_LUA_RTOS_MCC_1_LINE + host.flags = SDMMC_HOST_FLAG_1BIT; + slot_config.width = 1; + #endif // CONFIG_LUA_RTOS_MCC_1_LINE + + #if CONFIG_LUA_RTOS_MCC_4_LINE + host.flags = SDMMC_HOST_FLAG_4BIT; + slot_config.width = 4; + + gpio_pin_pullup(4); + gpio_pin_pullup(12); + gpio_pin_pullup(13); + + #if CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 4, DRIVER_ALL_FLAGS, "SD Card - DAT1")) { + return -1; + } + + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 12, DRIVER_ALL_FLAGS, "SD Card - DAT2")) { + return -1; + } + + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, 13, DRIVER_ALL_FLAGS, "SD Card - DAT3")) { + return -1; + } + #endif // CONFIG_LUA_RTOS_USE_HARDWARE_LOCKS + #endif // CONFIG_LUA_RTOS_MCC_4_LINE + + #if CONFIG_LUA_RTOS_MCC_CD != -1 + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_CD, DRIVER_ALL_FLAGS, "SD Card - CD")) { + return -1; + } + #endif // CONFIG_LUA_RTOS_MCC_CD + + #if CONFIG_LUA_RTOS_MCC_WP != -1 + if (driver_lock(SYSTEM_DRIVER, 0, GPIO_DRIVER, CONFIG_LUA_RTOS_MCC_WP, DRIVER_ALL_FLAGS, "SD Card - WP")) { + return -1; + } + #endif // CONFIG_LUA_RTOS_MCC_WP +#endif // CONFIG_SD_CARD_MMC + + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 5 + }; + + #if CONFIG_SD_CARD_SPI + syslog(LOG_INFO, "sd is at spi%d, cs=%s%d", + CONFIG_LUA_RTOS_SD_SPI, + gpio_portname(CONFIG_LUA_RTOS_SD_CS), gpio_name(CONFIG_LUA_RTOS_SD_CS) + ); + #endif + + #if CONFIG_SD_CARD_MMC + syslog(LOG_INFO, "sd is at mmc0"); + #endif + + sdmmc_card_t* card; + esp_err_t ret = esp_vfs_fat_sdmmc_format("/fat", &host, &slot_config, &mount_config, &card); + if (ret != ESP_OK) { + syslog(LOG_INFO, "fat can't mounted (error %d)", ret); + vfs_fat_umount(target); + return -1; + } + + syslog(LOG_INFO, "sd name %s", card->cid.name); + syslog(LOG_INFO, "sd type %s", (card->ocr & SD_OCR_SDHC_CAP)?"SDHC/SDXC":"SDSC"); + syslog(LOG_INFO, "sd working at %s", (card->csd.tr_speed > 25000000)?"high speed":"default speed"); + syslog(LOG_INFO, "sd size %.2f GB", + ((((double)card->csd.capacity) * ((double)card->csd.sector_size)) / 1073741824.0) + ); + + syslog(LOG_INFO, "sd CSD ver=%d, sector_size=%d, capacity=%d read_bl_len=%d", + card->csd.csd_ver, + card->csd.sector_size, card->csd.capacity, card->csd.read_block_len + ); + + syslog(LOG_INFO, "sd SCR sd_spec=%d, bus_width=%d", card->scr.sd_spec, card->scr.bus_width); + + syslog(LOG_INFO, "fat mounted on %s", target); + + vfs_fat_umount(target); + vfs_fat_mount(target); + + return 0; +} + +int vfs_fat_fsstat(const char *target, u32_t *total, u32_t *used) { + DWORD nclst = 0; + FATFS* fs = NULL; + FRESULT err = f_getfree (target, &nclst, &fs); + + if (err != FR_OK) { + syslog(LOG_ERR, "fat get fs info of '%s' (%i)", target, err); + return -1; + } + + //fs->free_clst and nclst both are equal and give the number of free clusters + //fs->csize = Cluster size [sectors] + //fs->ssize = Sector size (512, 1024, 2048 or 4096) + //fs->fsize = Size of an FAT [sectors] + //fs->n_fatent = nclst + 2 + + DWORD tclst = fs->n_fatent - 2; + + if (total) { + *total = tclst * fs->csize * fs->ssize; + } + + if (used) { + *used = (tclst - nclst) * fs->csize * fs->ssize; + } + + return 0; +} + +#endif diff --git a/libiot/vfs/include/esp_vfs.h b/libiot/vfs/include/esp_vfs.h new file mode 100644 index 0000000..a0762d3 --- /dev/null +++ b/libiot/vfs/include/esp_vfs.h @@ -0,0 +1,424 @@ +// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_VFS_H__ +#define __ESP_VFS_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include <../platform_include/sys/termios.h> +//#include +#include +#include +//#include "sdkconfig.h" + +#include "lwip/sockets.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _SYS_TYPES_FD_SET +#error "VFS should be used with FD_SETSIZE and FD_SET from sys/types.h" +#endif + +/** + * Maximum number of (global) file descriptors. + */ +#define MAX_FDS FD_SETSIZE /* for compatibility with fd_set and select() */ + +/** + * Maximum length of path prefix (not including zero terminator) + */ +#define ESP_VFS_PATH_MAX 15 + +/** + * Default value of flags member in esp_vfs_t structure. + */ +#define ESP_VFS_FLAG_DEFAULT 0 + +/** + * Flag which indicates that FS needs extra context pointer in syscalls. + */ +#define ESP_VFS_FLAG_CONTEXT_PTR 1 + +/* + * @brief VFS identificator used for esp_vfs_register_with_id() + */ +typedef int esp_vfs_id_t; + +/** + * @brief VFS definition structure + * + * This structure should be filled with pointers to corresponding + * FS driver functions. + * + * VFS component will translate all FDs so that the filesystem implementation + * sees them starting at zero. The caller sees a global FD which is prefixed + * with an pre-filesystem-implementation. + * + * Some FS implementations expect some state (e.g. pointer to some structure) + * to be passed in as a first argument. For these implementations, + * populate the members of this structure which have _p suffix, set + * flags member to ESP_VFS_FLAG_CONTEXT_PTR and provide the context pointer + * to esp_vfs_register function. + * If the implementation doesn't use this extra argument, populate the + * members without _p suffix and set flags member to ESP_VFS_FLAG_DEFAULT. + * + * If the FS driver doesn't provide some of the functions, set corresponding + * members to NULL. + */ +typedef struct +{ + int flags; /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT */ + union { + ssize_t (*write_p)(void* p, int fd, const void * data, size_t size); + ssize_t (*write)(int fd, const void * data, size_t size); + }; + union { + off_t (*lseek_p)(void* p, int fd, off_t size, int mode); + off_t (*lseek)(int fd, off_t size, int mode); + }; + union { + ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size); + ssize_t (*read)(int fd, void * dst, size_t size); + }; + union { + int (*open_p)(void* ctx, const char * path, int flags, int mode); + int (*open)(const char * path, int flags, int mode); + }; + union { + int (*close_p)(void* ctx, int fd); + int (*close)(int fd); + }; + union { + int (*fstat_p)(void* ctx, int fd, struct stat * st); + int (*fstat)(int fd, struct stat * st); + }; + union { + int (*stat_p)(void* ctx, const char * path, struct stat * st); + int (*stat)(const char * path, struct stat * st); + }; + union { + int (*link_p)(void* ctx, const char* n1, const char* n2); + int (*link)(const char* n1, const char* n2); + }; + union { + int (*unlink_p)(void* ctx, const char *path); + int (*unlink)(const char *path); + }; + union { + int (*rename_p)(void* ctx, const char *src, const char *dst); + int (*rename)(const char *src, const char *dst); + }; + union { + DIR* (*opendir_p)(void* ctx, const char* name); + DIR* (*opendir)(const char* name); + }; + union { + struct dirent* (*readdir_p)(void* ctx, DIR* pdir); + struct dirent* (*readdir)(DIR* pdir); + }; + union { + int (*readdir_r_p)(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent); + int (*readdir_r)(DIR* pdir, struct dirent* entry, struct dirent** out_dirent); + }; + union { + long (*telldir_p)(void* ctx, DIR* pdir); + long (*telldir)(DIR* pdir); + }; + union { + void (*seekdir_p)(void* ctx, DIR* pdir, long offset); + void (*seekdir)(DIR* pdir, long offset); + }; + union { + int (*closedir_p)(void* ctx, DIR* pdir); + int (*closedir)(DIR* pdir); + }; + union { + int (*mkdir_p)(void* ctx, const char* name, mode_t mode); + int (*mkdir)(const char* name, mode_t mode); + }; + union { + int (*rmdir_p)(void* ctx, const char* name); + int (*rmdir)(const char* name); + }; + union { + int (*fcntl_p)(void* ctx, int fd, int cmd, va_list args); + int (*fcntl)(int fd, int cmd, va_list args); + }; + union { + int (*ioctl_p)(void* ctx, int fd, int cmd, va_list args); + int (*ioctl)(int fd, int cmd, va_list args); + }; + union { + int (*fsync_p)(void* ctx, int fd); + int (*fsync)(int fd); + }; + union { + int (*access_p)(void* ctx, const char *path, int amode); + int (*access)(const char *path, int amode); + }; + union { + int (*truncate_p)(void* ctx, const char *path, off_t length); + int (*truncate)(const char *path, off_t length); + }; + union { + int (*utime_p)(void* ctx, const char *path, const struct utimbuf *times); + int (*utime)(const char *path, const struct utimbuf *times); + }; + union { + int (*ftruncate_p)(void* ctx, int fd, off_t length); + int (*ftruncate)(int fd, off_t length); + }; + union { + int (*writev_p)(void* ctx, int fd, const struct iovec *iov, int iovcnt); + int (*writev)(int fd, const struct iovec *iov, int iovcnt); + }; + union { + int (*select_p)(void* ctx, int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout); + int (*select)(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout); + }; +#ifdef CONFIG_SUPPORT_TERMIOS + union { + int (*tcsetattr_p)(void *ctx, int fd, int optional_actions, const struct termios *p); + int (*tcsetattr)(int fd, int optional_actions, const struct termios *p); + }; + union { + int (*tcgetattr_p)(void *ctx, int fd, struct termios *p); + int (*tcgetattr)(int fd, struct termios *p); + }; + union { + int (*tcdrain_p)(void *ctx, int fd); + int (*tcdrain)(int fd); + }; + union { + int (*tcflush_p)(void *ctx, int fd, int select); + int (*tcflush)(int fd, int select); + }; + union { + int (*tcflow_p)(void *ctx, int fd, int action); + int (*tcflow)(int fd, int action); + }; + union { + pid_t (*tcgetsid_p)(void *ctx, int fd); + pid_t (*tcgetsid)(int fd); + }; + union { + int (*tcsendbreak_p)(void *ctx, int fd, int duration); + int (*tcsendbreak)(int fd, int duration); + }; +#endif // CONFIG_SUPPORT_TERMIOS + + /** start_select is called for setting up synchronous I/O multiplexing of the desired file descriptors in the given VFS */ + esp_err_t (*start_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, SemaphoreHandle_t *signal_sem); + /** socket select function for socket FDs with the functionality of POSIX select(); this should be set only for the socket VFS */ + int (*socket_select)(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout); + /** called by VFS to interrupt the socket_select call when select is activated from a non-socket VFS driver; set only for the socket driver */ + void (*stop_socket_select)(); + /** stop_socket_select which can be called from ISR; set only for the socket driver */ + void (*stop_socket_select_isr)(BaseType_t *woken); + /** end_select is called to stop the I/O multiplexing and deinitialize the environment created by start_select for the given VFS */ + void (*end_select)(); +} esp_vfs_t; + + +/** + * Register a virtual filesystem for given path prefix. + * + * @param base_path file path prefix associated with the filesystem. + * Must be a zero-terminated C string, up to ESP_VFS_PATH_MAX + * characters long, and at least 2 characters long. + * Name must start with a "/" and must not end with "/". + * For example, "/data" or "/dev/spi" are valid. + * These VFSes would then be called to handle file paths such as + * "/data/myfile.txt" or "/dev/spi/0". + * @param vfs Pointer to esp_vfs_t, a structure which maps syscalls to + * the filesystem driver functions. VFS component doesn't + * assume ownership of this pointer. + * @param ctx If vfs->flags has ESP_VFS_FLAG_CONTEXT_PTR set, a pointer + * which should be passed to VFS functions. Otherwise, NULL. + * + * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are + * registered. + */ +esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx); + + +/** + * Special case function for registering a VFS that uses a method other than + * open() to open new file descriptors from the interval +#include + +/** + * This header file provides POSIX-compatible definitions of directory + * access functions and related data types. + * See http://pubs.opengroup.org/onlinepubs/7908799/xsh/dirent.h.html + * for reference. + */ + +/** + * @brief Opaque directory structure + */ +typedef struct { + uint16_t dd_vfs_idx; /*!< VFS index, not to be used by applications */ + uint16_t dd_rsv; /*!< field reserved for future extension */ + /* remaining fields are defined by VFS implementation */ +} DIR; + +/** + * @brief Directory entry structure + */ +struct dirent { + int d_ino; /*!< file number */ + uint8_t d_type; /*!< not defined in POSIX, but present in BSD and Linux */ +#define DT_UNKNOWN 0 +#define DT_REG 1 +#define DT_DIR 2 + char d_name[256]; /*!< zero-terminated file name */ + uint32_t d_fsize; +}; + +DIR* opendir(const char* name); +struct dirent* readdir(DIR* pdir); +long telldir(DIR* pdir); +void seekdir(DIR* pdir, long loc); +void rewinddir(DIR* pdir); +int closedir(DIR* pdir); +int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent); + diff --git a/libiot/vfs/lfs.c b/libiot/vfs/lfs.c new file mode 100644 index 0000000..c008e5e --- /dev/null +++ b/libiot/vfs/lfs.c @@ -0,0 +1,949 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS lfs vfs + * + */ + +#include "sdkconfig.h" + +#if CONFIG_LUA_RTOS_USE_LFS + +#include "rom/spi_flash.h" +#include "esp_partition.h" + +#include + +#include +#include +#include +#include + +#include + +#include "esp_vfs.h" +#include + +#include "lfs.h" + +#include +#include +#include +#include +#include +#include +#include + +static int vfs_lfs_open(const char *path, int flags, int mode); +static ssize_t vfs_lfs_write(int fd, const void *data, size_t size); +static ssize_t vfs_lfs_read(int fd, void * dst, size_t size); +static int vfs_lfs_fstat(int fd, struct stat * st); +static int vfs_lfs_close(int fd); +static off_t vfs_lfs_lseek(int fd, off_t size, int mode); +static int vfs_lfs_access(const char *path, int amode); +static long vfs_lfs_telldir(DIR *dirp); + +static struct list files; +static lfs_t lfs; + +struct vfs_lfs_context { + uint32_t base_addr; + struct mtx lock; +}; + +/* + * This function translate error codes from lfs to errno error codes + * + */ +static int lfs_to_errno(int res) { + switch (res) { + case LFS_ERR_OK: + return 0; + + case LFS_ERR_IO: + case LFS_ERR_CORRUPT: + return EIO; + + case LFS_ERR_NOENT: + return ENOENT; + + case LFS_ERR_EXIST: + return EEXIST; + + case LFS_ERR_NOTDIR: + return ENOTDIR; + + case LFS_ERR_ISDIR: + return EISDIR; + + case LFS_ERR_NOTEMPTY: + return ENOTEMPTY; + + case LFS_ERR_BADF: + return EBADF; + + case LFS_ERR_NOMEM: + return ENOMEM; + + case LFS_ERR_NOSPC: + return ENOSPC; + + case LFS_ERR_INVAL: + return EINVAL; + + default: + return res; + } +} + +static int vfs_lfs_open(const char *path, int flags, int mode) { + int fd; + int result; + + // Allocate new file + vfs_file_t *file = calloc(1, sizeof(vfs_file_t)); + if (!file) { + errno = ENOMEM; + return -1; + } + + file->fs_file = (void *)calloc(1, sizeof(lfs_file_t)); + if (!file->fs_file) { + free(file); + errno = ENOMEM; + return -1; + } + + file->path = strdup(path); + if (!file->path) { + free(file->fs_file); + free(file); + errno = ENOMEM; + return -1; + } + + // Add file to file list and get the file descriptor + int res = lstadd(&files, file, &fd); + if (res) { + free(file->fs_file); + free(file->path); + free(file); + errno = res; + return -1; + } + + // Translate flags to lfs flags + int lfs_flags = 0; + + if (flags == O_APPEND) + lfs_flags |= LFS_O_APPEND; + + if (flags == O_RDONLY) + lfs_flags |= LFS_O_RDONLY; + + if (flags & O_WRONLY) + lfs_flags |= LFS_O_WRONLY; + + if (flags & O_RDWR) + lfs_flags |= LFS_O_RDWR; + + if (flags & O_EXCL) + lfs_flags |= LFS_O_EXCL; + + if (flags & O_CREAT) + lfs_flags |= LFS_O_CREAT; + + if (flags & O_TRUNC) + lfs_flags |= LFS_O_TRUNC; + + if (*path == '/') { + path++; + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + if ((result = lfs_file_open(&lfs, file->fs_file, path, lfs_flags)) < 0) { + errno = lfs_to_errno(result); + lstremove(&files, fd, 0); + + free(file->fs_file); + free(file->path); + free(file); + + mtx_unlock(&ctx->lock); + + return -1; + } + + mtx_unlock(&ctx->lock); + + return fd; +} + +static ssize_t vfs_lfs_write(int fd, const void *data, size_t size) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + // Write to file + result = lfs_file_write(&lfs, file->fs_file, (void *)data, size); + if (result < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + return result; +} + +static ssize_t vfs_lfs_read(int fd, void *dst, size_t size) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + // Read from file + result = lfs_file_read(&lfs, file->fs_file, dst, size); + if (result < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + return result; +} + +static int vfs_lfs_fstat(int fd, struct stat *st) { + vfs_file_t *file; + struct lfs_info info; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + // Init stats + memset(st, 0, sizeof(struct stat)); + + // Set block size for this file system + st->st_blksize = lfs.cfg->block_size; + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + // Get the file stats + result = lfs_stat(&lfs, file->path, &info); + if (result < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + st->st_size = info.size; + st->st_mode = ((info.type==LFS_TYPE_REG)?S_IFREG:S_IFDIR); + + return 0; +} + +static int vfs_lfs_close(int fd) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + // Close file + result = lfs_file_close(&lfs, file->fs_file); + if (result < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + // Remove file from file list + lstremove(&files, fd, 0); + + free(file->fs_file); + free(file->path); + free(file); + + mtx_unlock(&ctx->lock); + + return 0; +} + +static off_t vfs_lfs_lseek(int fd, off_t size, int mode) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + int whence = LFS_SEEK_CUR; + + switch (mode) { + case SEEK_SET: + whence = LFS_SEEK_SET; + break; + case SEEK_CUR: + whence = LFS_SEEK_CUR; + break; + case SEEK_END: + whence = LFS_SEEK_END; + break; + default: + errno = EINVAL; + return -1; + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + result = lfs_file_seek(&lfs, file->fs_file, size, whence); + if (result < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + return result; +} + +static int vfs_lfs_stat(const char *path, struct stat *st) { + struct lfs_info info; + int result; + + // Init stats + memset(st, 0, sizeof(struct stat)); + + // Set block size for this file system + st->st_blksize = lfs.cfg->block_size; + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + // Get the file stats + result = lfs_stat(&lfs, path, &info); + if (result < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + st->st_size = info.size; + st->st_mode = ((info.type==LFS_TYPE_REG)?S_IFREG:S_IFDIR); + + return 0; +} + +static int vfs_lfs_access(const char *path, int amode) { +#if 0 + struct stat s; + + if (vfs_lfs_stat(path, &s) < 0) { + return -1; + } + + if (s.st_mode != S_IFREG) { + errno = EACCES; + return -1; + } + + return 0; +#endif + return 0; + +} + +static int vfs_lfs_unlink(const char *path) { + struct lfs_info info; + int result; + + // Sanity checks + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + // Is not a directory + result = lfs_stat(&lfs, path, &info); + if (result < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + if (info.type == LFS_TYPE_DIR) { + mtx_unlock(&ctx->lock); + errno = EPERM; + return -1; + } + + // Unlink + if ((result = lfs_remove(&lfs, path)) < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + return 0; +} + +static int vfs_lfs_rename(const char *src, const char *dst) { + int result; + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + if ((result = lfs_rename(&lfs, src, dst)) < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + return 0; +} + +static DIR* vfs_lfs_opendir(const char *name) { + int result; + + vfs_dir_t *dir; + + dir = vfs_allocate_dir("lfs", name); + if (!dir) { + return NULL; + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + // Open directory + if ((result = lfs_dir_open(&lfs, dir->fs_dir, name)) < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + vfs_free_dir(dir); + + return NULL; + } + + mtx_unlock(&ctx->lock); + + return (DIR *)dir; +} + +static int vfs_lfs_rmdir(const char *name) { + struct lfs_info info; + int result; + + // Sanity checks + if (strcmp(name,"/") == 0) { + errno = EBUSY; + return -1; + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + // Is a directory + result = lfs_stat(&lfs, name, &info); + if (result < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + if (info.type != LFS_TYPE_DIR) { + mtx_unlock(&ctx->lock); + errno = ENOTDIR; + return -1; + } + + // Unlink + if ((result = lfs_remove(&lfs, name)) < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + return 0; +} + +static struct dirent *vfs_lfs_readdir(DIR *pdir) { + vfs_dir_t *dir = (vfs_dir_t *)pdir; + struct dirent *ent = &dir->ent; + int result; + + // Clear current entry + memset(ent, 0, sizeof(struct dirent)); + + // If there are mount points to read, read them first + if (dir->mount) { + struct dirent *ment = mount_readdir((DIR *)dir); + if (ment) { + return ment; + } + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + +again: + // Read next directory entry + if ((result = lfs_dir_read(&lfs, ((vfs_dir_t *)pdir)->fs_dir, (struct lfs_info *)dir->fs_info)) < 0) { + errno = lfs_to_errno(result); + } else if (result > 0) { + if ((strcmp(((struct lfs_info *)dir->fs_info)->name,".") == 0) || (strcmp(((struct lfs_info *)dir->fs_info)->name,"..") == 0)) { + goto again; + } + + ent->d_type = (((struct lfs_info *)dir->fs_info)->type == LFS_TYPE_REG)?DT_REG:DT_DIR; + ent->d_fsize = ((struct lfs_info *)dir->fs_info)->size; + strlcpy(ent->d_name, ((struct lfs_info *)dir->fs_info)->name, MAXNAMLEN); + + mtx_unlock(&ctx->lock); + + return ent; + } + + mtx_unlock(&ctx->lock); + + return NULL; +} + +static long vfs_lfs_telldir(DIR *dirp) { + vfs_dir_t *dir = (vfs_dir_t *)dirp; + + lfs_soff_t offset; + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + offset = lfs_dir_tell(&lfs, dir->fs_dir); + if (offset < 0) { + mtx_unlock(&ctx->lock); + errno = EBADF; + return -1; + } + + mtx_unlock(&ctx->lock); + + return (long)offset; +} + +static int vfs_piffs_closedir(DIR *pdir) { + vfs_dir_t *dir = ((vfs_dir_t *)pdir); + int result; + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + if ((result = lfs_dir_close(&lfs, dir->fs_dir)) < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + vfs_free_dir(dir); + + mtx_unlock(&ctx->lock); + + return 0; +} + +static int vfs_lfs_mkdir(const char *path, mode_t mode) { + int result; + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + if ((result = lfs_mkdir(&lfs, path)) < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + return 0; +} + +static int vfs_lfs_fsync(int fd) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + result = lfs_file_sync(&lfs, file->fs_file); + if (result < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + return 0; +} + +static int vfs_lfs_ftruncate(int fd, off_t length) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)lfs.cfg->context; + + mtx_lock(&ctx->lock); + + result = lfs_file_truncate(&lfs, file->fs_file, length); + if (result < 0) { + mtx_unlock(&ctx->lock); + errno = lfs_to_errno(result); + return -1; + } + + mtx_unlock(&ctx->lock); + + return 0; +} + +static int lfs_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)c->context; + + if (spi_flash_read(ctx->base_addr + (block * c->block_size) + off, buffer, size) != 0) { + return LFS_ERR_IO; + } + + return 0; +} + +static int lfs_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)c->context; + + if (spi_flash_write(ctx->base_addr + (block * c->block_size) + off, buffer, size) != 0) { + return LFS_ERR_IO; + } + + return 0; +} + +static int lfs_erase(const struct lfs_config *c, lfs_block_t block) { + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)c->context; + + if (spi_flash_erase_sector((ctx->base_addr + (block * c->block_size)) >> 12) != 0) { + return LFS_ERR_IO; + } + + return 0; +} + +static int lfs_sync(const struct lfs_config *c) { + return 0; +} + +static struct lfs_config *lfs_config() { + // Find a partition + uint32_t base_address = 0; + uint32_t fs_size = 0; + + const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, LUA_RTOS_LFS_PART, NULL); + + if (!partition) { + syslog(LOG_ERR, "lfs can't find a valid partition"); + return NULL; + } else { + base_address = partition->address; + fs_size = partition->size; + } + + // Allocate file system configuration data + struct lfs_config *cfg = calloc(1, sizeof(struct lfs_config)); + if (!cfg) { + syslog(LOG_ERR, "lfs not enough memory"); + return NULL; + } + + struct vfs_lfs_context *ctx = calloc(1, sizeof(struct vfs_lfs_context)); + if (!ctx) { + free(cfg); + + syslog(LOG_ERR, "lfs not enough memory"); + return NULL; + } + + // Configure the file system + cfg->read = lfs_read; + cfg->prog = lfs_prog; + cfg->erase = lfs_erase; + cfg->sync = lfs_sync; + + cfg->block_size = CONFIG_LUA_RTOS_LFS_BLOCK_SIZE; + cfg->read_size = CONFIG_LUA_RTOS_LFS_READ_SIZE; + cfg->prog_size = CONFIG_LUA_RTOS_LFS_PROG_SIZE; + cfg->block_count = fs_size / cfg->block_size; + cfg->lookahead = cfg->block_count; + + cfg->context = ctx; + ctx->base_addr = base_address; + + return cfg; +} + +int vfs_lfs_mount(const char *target) { + esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &vfs_lfs_write, + .open = &vfs_lfs_open, + .fstat = &vfs_lfs_fstat, + .close = &vfs_lfs_close, + .read = &vfs_lfs_read, + .lseek = &vfs_lfs_lseek, + .stat = &vfs_lfs_stat, + .link = NULL, + .unlink = &vfs_lfs_unlink, + .rename = &vfs_lfs_rename, + .mkdir = &vfs_lfs_mkdir, + .opendir = &vfs_lfs_opendir, + .readdir = &vfs_lfs_readdir, + .closedir = &vfs_piffs_closedir, + .rmdir = &vfs_lfs_rmdir, + .fsync = &vfs_lfs_fsync, + .access = &vfs_lfs_access, + .ftruncate = &vfs_lfs_ftruncate, + .telldir = &vfs_lfs_telldir, + }; + + // Get configuration + struct lfs_config *cfg = lfs_config(); + if (!cfg) { + return -1; + } + + struct vfs_lfs_context *ctx = (struct vfs_lfs_context *)(cfg->context); + + mtx_init(&ctx->lock, NULL, NULL, 0); + + syslog(LOG_INFO, + "lfs start address at 0x%x, size %d Kb", + ctx->base_addr, (cfg->block_count * cfg->block_size) / 1024); + + syslog(LOG_INFO, "lfs %d blocks, %d bytes/block, %d bytes/read, %d bytes/write",cfg->block_count,cfg->block_size, cfg->read_size, cfg->prog_size); + + // Mount file system + int err = lfs_mount(&lfs, cfg); + if (err < 0) { + syslog(LOG_INFO, "lfs formatting ..."); + + int block; + for(block = 0;block < cfg->block_count;block++) { + lfs_erase(cfg, block); + } + + lfs_format(&lfs, cfg); + err = lfs_mount(&lfs, cfg); + } + + if (err == LFS_ERR_OK) { + lstinit(&files, 0, LIST_DEFAULT); + + // Register the file system + ESP_ERROR_CHECK(esp_vfs_register("/lfs", &vfs, NULL)); + + syslog(LOG_INFO, "lfs mounted on %s", target); + + return 0; + } else { + free(cfg); + free(ctx); + + syslog(LOG_INFO, "lfs mount error"); + } + + return -1; +} + +int vfs_lfs_umount(const char *target) { + // Unmount + lfs_umount(&lfs); + + // Free resources + if (lfs.cfg) { + if (lfs.cfg->context) { + mtx_destroy(&((struct vfs_lfs_context *)lfs.cfg->context)->lock); + free(lfs.cfg->context); + } + + free((struct lfs_config *)lfs.cfg); + } + + lstdestroy(&files, 1); + + // Unregister vfs + esp_vfs_unregister("/lfs"); + + syslog(LOG_INFO, "lfs unmounted"); + + return 0; +} + +int vfs_lfs_format(const char *target) { + // Unmount first + vfs_lfs_umount(target); + + // Get configuration + struct lfs_config *cfg = lfs_config(); + if (!cfg) { + return -1; + } + + // Format + int block; + for(block = 0;block < cfg->block_count;block++) { + lfs_erase(cfg, block); + } + + int err = lfs_format(&lfs, cfg); + + // Free resources + if (lfs.cfg) { + if (lfs.cfg->context) { + free(lfs.cfg->context); + } + + free((struct lfs_config *)lfs.cfg); + } + + if (err == LFS_ERR_OK) { + // Mount again + vfs_lfs_mount(target); + } + + return -1; +} + +int vfs_lfs_fsstat(const char *target, u32_t *total, u32_t *used) { + + int err = lfs_info(&lfs, total, used); + if (err != 0) { + syslog(LOG_ERR, "lfs get fs info of '%s' (%i)", target, err); + return -1; + } + + return 0; +} + +#endif diff --git a/libiot/vfs/mkfile b/libiot/vfs/mkfile new file mode 100644 index 0000000..600ebc7 --- /dev/null +++ b/libiot/vfs/mkfile @@ -0,0 +1,7 @@ +OFILES=\ + $OFILES\ + vfs/spiffs.$O\ + +CFLAGS=\ + $CFLAGS\ + -Ivfs/include\ diff --git a/libiot/vfs/ramfs.c b/libiot/vfs/ramfs.c new file mode 100644 index 0000000..270209c --- /dev/null +++ b/libiot/vfs/ramfs.c @@ -0,0 +1,634 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS, RAM file system + * + */ + +#include "sdkconfig.h" + +#if CONFIG_LUA_RTOS_USE_RAM_FS + +#include "esp_partition.h" + +#include + +#include +#include +#include +#include + +#include + +#include "esp_vfs.h" + +#include + +#include "ramfs.h" + +#include +#include +#include +#include +#include +#include +#include + +static int vfs_ramfs_open(const char *path, int flags, int mode); +static ssize_t vfs_ramfs_write(int fd, const void *data, size_t size); +static ssize_t vfs_ramfs_read(int fd, void * dst, size_t size); +static int vfs_ramfs_fstat(int fd, struct stat * st); +static int vfs_ramfs_close(int fd); +static off_t vfs_ramfs_lseek(int fd, off_t size, int mode); +static int vfs_ramfs_access(const char *path, int amode); + +static struct list files; +static ramfs_t fs; + +/* + * This function translate file system errors to POSIX errors + * + */ +static int ramfs_to_errno(int res) { + switch (res) { + case RAMFS_ERR_OK: + return 0; + case RAMFS_ERR_NOMEM: + return ENOMEM; + case RAMFS_ERR_NOENT: + return ENOENT; + case RAMFS_ERR_EXIST: + return EEXIST; + case RAMFS_ERR_NOTDIR: + return ENOTDIR; + case RAMFS_ERR_BADF: + return EBADF; + case RAMFS_ERR_ACCESS: + return EACCES; + case RAMFS_ERR_NOSPC: + return ENOSPC; + case RAMFS_ERR_INVAL: + return EINVAL; + case RAMFS_ERR_ISDIR: + return EISDIR; + case RAMFS_ERR_NOTEMPTY: + return ENOTEMPTY; + case RAMFS_ERR_BUSY: + return EBUSY; + case RAMFS_ERR_PERM: + return EPERM; + case RAMFS_ERR_NAMETOOLONG: + return ENAMETOOLONG; + } + + return ENOTSUP; +} + +static int vfs_ramfs_open(const char *path, int flags, int mode) { + int fd; + int result; + + // Allocate new file + vfs_file_t *file = calloc(1, sizeof(vfs_file_t)); + if (!file) { + errno = ENOMEM; + return -1; + } + + file->fs_file = (void *)calloc(1, sizeof(ramfs_file_t)); + if (!file->fs_file) { + free(file); + errno = ENOMEM; + return -1; + } + + // Add file to file list and get the file descriptor + int res = lstadd(&files, file, &fd); + if (res) { + free(file->fs_file); + free(file); + errno = res; + return -1; + } + + // Translate POSIX flags to file system flags + int ramfs_flags = 0; + + // Access mode + int access_mode = flags & O_ACCMODE; + + if (access_mode == O_RDONLY) { + ramfs_flags |= RAMFS_O_RDONLY; + } else if (access_mode == O_WRONLY) { + ramfs_flags |= RAMFS_O_WRONLY; + } else if (access_mode == O_RDWR) { + ramfs_flags |= RAMFS_O_RDWR; + } + + // File status + if (flags & O_CREAT) { + ramfs_flags |= RAMFS_O_CREAT; + } + + if (flags & O_EXCL) { + ramfs_flags |= RAMFS_O_EXCL; + } + + if (flags & O_TRUNC) { + ramfs_flags |= RAMFS_O_TRUNC; + } + + if (flags & O_APPEND) { + ramfs_flags |= RAMFS_O_APPEND; + } + + if ((result = ramfs_file_open(&fs, file->fs_file, path, ramfs_flags)) != RAMFS_ERR_OK) { + errno = ramfs_to_errno(result); + lstremove(&files, fd, 0); + + free(file->fs_file); + free(file); + + return -1; + } + + return fd; +} + +static ssize_t vfs_ramfs_write(int fd, const void *data, size_t size) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + // Write to file + result = ramfs_file_write(&fs, file->fs_file, (void *)data, size); + if (result < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + return result; +} + +static ssize_t vfs_ramfs_read(int fd, void *dst, size_t size) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + // Read from file + result = ramfs_file_read(&fs, file->fs_file, dst, size); + if (result < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + return result; +} + +static int vfs_ramfs_fstat(int fd, struct stat *st) { + vfs_file_t *file; + ramfs_info_t info; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + // Init stats + memset(st, 0, sizeof(struct stat)); + + // Get the file stats + result = ramfs_file_stat(&fs, file->fs_file, &info); + if (result < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + st->st_size = info.size; + st->st_mode = ((info.type==RAMFS_FILE)?S_IFREG:S_IFDIR); + st->st_blksize = fs.block_size; + + return 0; +} + +static int vfs_ramfs_close(int fd) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + // Close file + result = ramfs_file_close(&fs, file->fs_file); + if (result < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + // Remove file from file list + lstremove(&files, fd, 0); + + free(file->fs_file); + free(file); + + return 0; +} + +static off_t vfs_ramfs_lseek(int fd, off_t size, int mode) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + // Convert POSIX whence to file system whence + int whence = RAMFS_SEEK_CUR; + + switch (mode) { + case SEEK_SET: + whence = RAMFS_SEEK_SET; + break; + case SEEK_CUR: + whence = RAMFS_SEEK_CUR; + break; + case SEEK_END: + whence = RAMFS_SEEK_END; + break; + default: + errno = EINVAL; + return -1; + } + + result = ramfs_file_seek(&fs, file->fs_file, size, whence); + if (result < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + return result; +} + +static int vfs_ramfs_stat(const char *path, struct stat *st) { + ramfs_info_t info; + int result; + + // Init stats + memset(st, 0, sizeof(struct stat)); + + // Get the file stats + result = ramfs_stat(&fs, path, &info); + if (result < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + st->st_size = info.size; + st->st_mode = ((info.type==RAMFS_FILE)?S_IFREG:S_IFDIR); + st->st_blksize = fs.block_size; + + return 0; +} + +static int vfs_ramfs_access(const char *path, int amode) { +#if 0 + struct stat s; + + if (vfs_ramfs_stat(path, &s) < 0) { + return -1; + } + + if (s.st_mode != S_IFREG) { + errno = EACCES; + return -1; + } + + return 0; +#endif + return 0; + +} + +static int vfs_ramfs_unlink(const char *path) { + int result; + + if ((result = ramfs_unlink(&fs, path)) < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + return 0; +} + +static int vfs_ramfs_rename(const char *src, const char *dst) { + int result; + + if ((result = ramfs_rename(&fs, src, dst)) < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + return 0; +} + +static DIR* vfs_ramfs_opendir(const char *name) { + int result; + + vfs_dir_t *dir = vfs_allocate_dir("ramfs", name); + if (!dir) { + return NULL; + } + + // Open directory + if ((result = ramfs_dir_open(&fs, dir->fs_dir, name)) < 0) { + errno = ramfs_to_errno(result); + vfs_free_dir(dir); + + return NULL; + } + + return (DIR *)dir; +} + +static int vfs_ramfs_rmdir(const char *name) { + int result; + + if ((result = ramfs_rmdir(&fs, name)) < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + return 0; +} + +static struct dirent *vfs_ramfs_readdir(DIR *pdir) { + vfs_dir_t *dir = (vfs_dir_t *)pdir; + struct dirent *ent = &dir->ent; + int result; + + // Clear current entry + memset(ent, 0, sizeof(struct dirent)); + + // If there are mount points to read, read them first + if (dir->mount) { + struct dirent *ment = mount_readdir((DIR *)dir); + if (ment) { + return ment; + } + } + + // Read next directory entry + if ((result = ramfs_dir_read(&fs, ((vfs_dir_t *)pdir)->fs_dir, (ramfs_info_t *)dir->fs_info)) == RAMFS_ERR_OK) { + ent->d_type = (((ramfs_info_t *)dir->fs_info)->type == RAMFS_FILE)?DT_REG:DT_DIR; + ent->d_fsize = ((ramfs_info_t *)dir->fs_info)->size; + strlcpy(ent->d_name, ((ramfs_info_t *)dir->fs_info)->name, MAXNAMLEN); + + return ent; + } + + if (result != RAMFS_ERR_NOENT) { + errno = ramfs_to_errno(result); + } + + return NULL; +} + +static long vfs_ramfs_telldir(DIR *dirp) { + vfs_dir_t *dir = (vfs_dir_t *)dirp; + ramfs_off_t offset; + + offset = ramfs_telldir(&fs, dir->fs_dir); + if (offset < 0) { + errno = ramfs_to_errno(offset); + return -1; + } + + return (long)offset; +} + +static int vfs_ramfs_closedir(DIR *pdir) { + vfs_dir_t *dir = ((vfs_dir_t *)pdir); + int result; + + if ((result = ramfs_dir_close(&fs, dir->fs_dir)) < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + vfs_free_dir(dir); + + return 0; +} + +static int vfs_ramfs_mkdir(const char *path, mode_t mode) { + int result; + + if ((result = ramfs_mkdir(&fs, path)) < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + return 0; +} + +static int vfs_ramfs_fsync(int fd) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + result = ramfs_file_sync(&fs, file->fs_file); + if (result < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + return 0; +} + +static int vfs_ramfs_ftruncate(int fd, off_t length) { + vfs_file_t *file; + int result; + + // Get file from file list + result = lstget(&files, fd, (void **) &file); + if (result) { + errno = EBADF; + return -1; + } + + if ((result = ramfs_file_truncate(&fs, file->fs_file, length)) != RAMFS_ERR_OK) { + errno = ramfs_to_errno(result); + return -1; + } + + return 0; +} + +static int vfs_ramfs_truncate(const char *path, off_t length) { + ramfs_file_t file; + int result; + + if ((result = ramfs_file_open(&fs, &file, path, RAMFS_O_RDWR)) != RAMFS_ERR_OK) { + errno = ramfs_to_errno(result); + return -1; + } + + if ((result = ramfs_file_truncate(&fs, &file, length)) != RAMFS_ERR_OK) { + errno = ramfs_to_errno(result); + return -1; + } + + result = ramfs_file_close(&fs, &file); + if (result < 0) { + errno = ramfs_to_errno(result); + return -1; + } + + return 0; +} + +int vfs_ramfs_mount(const char *target) { + esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &vfs_ramfs_write, + .open = &vfs_ramfs_open, + .fstat = &vfs_ramfs_fstat, + .close = &vfs_ramfs_close, + .read = &vfs_ramfs_read, + .lseek = &vfs_ramfs_lseek, + .stat = &vfs_ramfs_stat, + .link = NULL, + .unlink = &vfs_ramfs_unlink, + .rename = &vfs_ramfs_rename, + .mkdir = &vfs_ramfs_mkdir, + .opendir = &vfs_ramfs_opendir, + .readdir = &vfs_ramfs_readdir, + .closedir = &vfs_ramfs_closedir, + .rmdir = &vfs_ramfs_rmdir, + .fsync = &vfs_ramfs_fsync, + .access = &vfs_ramfs_access, + .telldir = &vfs_ramfs_telldir, + .truncate = &vfs_ramfs_truncate, + .ftruncate = &vfs_ramfs_ftruncate, + }; + + int res; + ramfs_config_t config; + + config.size = CONFIG_LUA_RTOS_RAM_FS_SIZE; + config.block_size = CONFIG_LUA_RTOS_RAM_FS_BLOCK_SIZE; + + syslog(LOG_INFO, "ramfs size %d Kb, block size %d bytes", config.size / 1024, config.block_size); + + if ((res = ramfs_mount(&fs, &config)) == RAMFS_ERR_OK) { + lstinit(&files, 0, LIST_DEFAULT); + + // Register the file system + ESP_ERROR_CHECK(esp_vfs_register("/ramfs", &vfs, NULL)); + + syslog(LOG_INFO, "ramfs mounted on %s", target); + + return 0; + } else { + syslog(LOG_INFO, "ramfs mount error"); + } + + return -1; +} + +int vfs_ramfs_umount(const char *target) { + ramfs_umount(&fs); + esp_vfs_unregister("/ramfs"); + + syslog(LOG_INFO, "ramfs unmounted"); + + return 0; +} + +int vfs_ramfs_format(const char *target) { + vfs_ramfs_umount(target); + vfs_ramfs_mount(target); + + return 0; +} + +int vfs_ramfs_fsstat(const char *target, u32_t *total, u32_t *used) { + + if (total) { + *total = fs.size; + } + + if (used) { + *used = fs.current_size; + } + + return 0; +} + +#endif diff --git a/libiot/vfs/spiffs.c b/libiot/vfs/spiffs.c new file mode 100644 index 0000000..cdd445a --- /dev/null +++ b/libiot/vfs/spiffs.c @@ -0,0 +1,1423 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS spiffs vfs + * + */ + +//#include "sdkconfig.h" + +//#if CONFIG_LUA_RTOS_USE_SPIFFS + +//#include "esp_partition.h" + +#include + +#include +#include +#include + +#include + +#include "esp_vfs.h" +//#include "esp_attr.h" +#include + +#include "../spiffs/spiffs.h" +#include "../spiffs/k210_spiffs.h" +#include "../spiffs/spiffs_nucleus.h" +//#include +//#include +#include "../sys/mutex.h" +#include "../sys/list.h" +#include +#include "vfs.h" +#include + +#include "../hal/w25qxx.h" + +#define syslog(a, ...) printf(__VA_ARGS__) + +static int vfs_spiffs_open(const char *path, int flags, int mode); +static ssize_t vfs_spiffs_write(int fdi, const void *data, size_t size); +static ssize_t vfs_spiffs_read(int fdi, void * dst, size_t size); +static int vfs_spiffs_fstat(int fdi, struct stat * st); +static int vfs_spiffs_close(int fdi); +static off_t vfs_spiffs_lseek(int fdi, off_t size, int mode); +static int vfs_spiffs_access(const char *path, int amode); + +#if !defined(max) +#define max(A,B) ( (A) > (B) ? (A):(B)) +#endif + +#if !defined(min) +#define min(A,B) ( (A) < (B) ? (A):(B)) +#endif + +#define VFS_SPIFFS_FLAGS_READONLY (1 << 0) + +static spiffs fs; +static struct list files; + +static u8_t *my_spiffs_work_buf = NULL; +static u8_t *my_spiffs_fds = NULL; +static u8_t *my_spiffs_cache = NULL; + +static struct mtx vfs_mtx; +static struct mtx ll_mtx; + + +typedef struct fd_itm fd_itm; + +struct fd_itm { + long fd; + fd_itm* next; +}; + +fd_itm* fds_root = NULL; +fd_itm** fds_root_last = &fds_root; +int fds_cnt = 0; + + +static int new_fd_itm(long fd){ + fd_itm *f = malloc(sizeof(fd_itm)); + + f->next = NULL; + f->fd = fd; + + *fds_root_last = f; + fds_root_last = &f->next; + + fds_cnt++; + + return fds_cnt - 1; +} + +static long del_fd_itm(int fdi){ + long fd = -1; + int i; + fd_itm *f = fds_root; + fd_itm **pref = &fds_root; + + if(fdi < 0 || fdi >= fds_cnt || f == NULL) + return -1; + + for(i = 0; i < fdi; i++){ + pref = &f->next; + f = f->next; + } + *pref = f->next; + + fd = f->fd; + + free(f); + + fds_cnt--; + + return fd; +} + +static long get_fd_itm(int fdi){ + long fd = -1; + int i; + fd_itm *f = fds_root; + + if(fdi < 0 || fdi >= fds_cnt || f == NULL) + return -1; + + for(i = 0; i < fdi; i++){ + f = f->next; + } + fd = f->fd; + + return fd; +} + + + +static void dir_path(char *npath, uint8_t base) { + int len = strlen(npath); + + if (base) { + char *c; + + c = &npath[len - 1]; + while (c >= npath) { + if (*c == '/') { + break; + } + + len--; + c--; + } + } + + if (len > 1) { + if (npath[len - 1] == '.') { + npath[len - 1] = '\0'; + + if (npath[len - 2] == '/') { + npath[len - 2] = '\0'; + } + } else { + if (npath[len - 1] == '/') { + npath[len - 1] = '\0'; + } + } + } else { + if ((npath[len - 1] == '/') || (npath[len - 1] == '.')) { + npath[len - 1] = '\0'; + } + } + + strlcat(npath, "/.", PATH_MAX); +} + +static void check_path(const char *path, uint8_t *base_is_dir, + uint8_t *full_is_dir, uint8_t *is_file, int *filenum) { + char bpath[PATH_MAX + 1]; // Base path + char fpath[PATH_MAX + 1]; // Full path + struct spiffs_dirent e; + spiffs_DIR d; + int file_num = 0; + + *filenum = 0; + *base_is_dir = 0; + *full_is_dir = 0; + *is_file = 0; + + // Get base directory name + strlcpy(bpath, path, PATH_MAX); + dir_path(bpath, 1); + + // Get full directory name + strlcpy(fpath, path, PATH_MAX); + dir_path(fpath, 0); + + SPIFFS_opendir(&fs, "/", &d); + while (SPIFFS_readdir(&d, &e)) { + if (!strcmp(bpath, (const char *) e.name)) { + *base_is_dir = 1; + } + + if (!strcmp(fpath, (const char *) e.name)) { + *full_is_dir = 1; + } + + if (!strcmp(path, (const char *) e.name)) { + *is_file = 1; + } + + if (!strncmp(fpath, (const char *) e.name, min(strlen((char * )e.name), strlen(fpath) - 1))) { + if (strlen((const char *) e.name) >= strlen(fpath) && strcmp(fpath, (const char *) e.name)) { + file_num++; + } + } + } + SPIFFS_closedir(&d); + + *filenum = file_num; +} + +/* + * This function translate error codes from SPIFFS to errno error codes + * + */ +static int spiffs_result(int res) { + switch(res) { + case SPIFFS_OK : + case SPIFFS_ERR_END_OF_OBJECT: + return 0; + case SPIFFS_ERR_NOT_WRITABLE: + case SPIFFS_ERR_NOT_READABLE: + return EACCES; + case SPIFFS_ERR_NOT_MOUNTED : + case SPIFFS_ERR_NOT_A_FS : + return ENODEV; + case SPIFFS_ERR_FULL : + return ENOSPC; + case SPIFFS_ERR_BAD_DESCRIPTOR : + return EBADF; + case SPIFFS_ERR_MOUNTED : + return EEXIST; + case SPIFFS_ERR_FILE_EXISTS : + return EEXIST; + case SPIFFS_ERR_NOT_FOUND : + case SPIFFS_ERR_CONFLICTING_NAME: + case SPIFFS_ERR_NOT_A_FILE : + case SPIFFS_ERR_DELETED : + case SPIFFS_ERR_FILE_DELETED : + return ENOENT; + case SPIFFS_ERR_NAME_TOO_LONG : + return ENAMETOOLONG; + case SPIFFS_ERR_RO_NOT_IMPL : + case SPIFFS_ERR_RO_ABORTED_OPERATION : + return EROFS; + default: + return EIO; + } + + return ENOTSUP; +} + +static int vfs_spiffs_traverse(const char *pathp, int *pis_dir, int *filenum, int *valid_prefix) { + char path[PATH_MAX + 1]; + char current[PATH_MAX + 3]; // Current path + char *dir; // Current directory + spiffs_stat stat; + int res; + + int is_dir = 0; + + if (pis_dir) { + *pis_dir = 0; + } + + if (filenum) { + *filenum = 0; + } + + if (valid_prefix) { + *valid_prefix = 1; + } + + if (strlen(pathp) > PATH_MAX - 3) { + return ENAMETOOLONG; + } + + // Copy original path, because the strtok function changes the original string + strlcpy(path, pathp, PATH_MAX); + + // Current directory is empty + strcpy(current, ""); + + // Get the first directory in path + dir = strtok((char *)path, "/"); + while (dir) { + // Append current directory to the current path + strncat(current, "/", PATH_MAX); + strncat(current, dir, PATH_MAX); + + // To check if the current path is a directory + // we must append /. + strncat(current, "/.", PATH_MAX); + + res = SPIFFS_stat(&fs, current, &stat); + + // Remove /. from the current path to check if the current path + // corresponds to a file in case that is required later + *(current + strlen(current) - 2) = '\0'; + + // Get next directory in path + dir = strtok(NULL, "/"); + + if (res != SPIFFS_OK) { + // Current path is not a directory, then check if it is a file + res = SPIFFS_stat(&fs, current, &stat); + if (res != SPIFFS_OK) { + // Current path is not a directory, and it is not a file. + if (dir) { + if (valid_prefix) { + *valid_prefix = 0; + } + } + + // Error: A component in pathname does not exist + return ENOENT; + } else { + // Current path is a file + if (dir) { + // There are more directories in path to check. + + if (valid_prefix) { + *valid_prefix = 0; + } + + // Error: a component used as a directory in pathname is not, + // in fact, a directory + return ENOTDIR; + } + + is_dir = 0; + } + } else { + // Current path is a directory + is_dir = 1; + } + } + + if (is_dir && filenum) { + // Count files in directory + struct spiffs_dirent e; + spiffs_DIR d; + + // Get full directory name + char fpath[PATH_MAX + 1]; + + strlcpy(fpath, pathp, PATH_MAX); + dir_path(fpath, 0); + + SPIFFS_opendir(&fs, "/", &d); + while (SPIFFS_readdir(&d, &e)) { + if (!strncmp(fpath, (const char *) e.name, min(strlen((char * )e.name), strlen(fpath) - 1))) { + if (strlen((const char *) e.name) >= strlen(fpath) && strcmp(fpath, (const char *) e.name)) { + *filenum = *filenum + 1; + } + } + } + SPIFFS_closedir(&d); + } + + if (pis_dir) { + *pis_dir = (is_dir || (strcmp(pathp, "/") == 0)); + } + + return EEXIST; +} + +static int vfs_spiffs_open(const char *path, int flags, int mode) { + char npath[PATH_MAX + 1]; + long fd; + int result = 0; + + // Allocate new file + vfs_file_t *file = calloc(1, sizeof(vfs_file_t)); + if (!file) { + errno = ENOMEM; + return -1; + } + + file->fs_file = (void *)calloc(1, sizeof(spiffs_file)); + if (!file->fs_file) { + free(file); + errno = ENOMEM; + return -1; + } + + // Add file to file list. List index is file descriptor. + int res = lstadd(&files, file, &fd); + if (res) { + free(file->fs_file); + free(file); + errno = res; + return -1; + } + + // Open file + spiffs_flags spiffs_flgs = 0; + + // Translate flags to SPIFFS flags + if (flags == O_RDONLY) + spiffs_flgs |= SPIFFS_RDONLY; + + if (flags & O_WRONLY) + spiffs_flgs |= SPIFFS_WRONLY; + + if (flags & O_RDWR) + spiffs_flgs = SPIFFS_RDWR; + + if (flags & O_EXCL) + spiffs_flgs |= SPIFFS_EXCL; + + if (flags & O_CREAT) + spiffs_flgs |= SPIFFS_CREAT; + + if (flags & O_TRUNC) + spiffs_flgs |= SPIFFS_TRUNC; + + // Check path + uint8_t base_is_dir = 0; + uint8_t full_is_dir = 0; + uint8_t is_file = 0; + int file_num = 0; + + mtx_lock(&vfs_mtx); + + check_path(path, &base_is_dir, &full_is_dir, &is_file, &file_num); + + if (full_is_dir) { + // We want to open a directory. + // If in flags are set some write access mode this is an error, because we only + // can open a directory in read mode. + if (spiffs_flgs & (SPIFFS_WRONLY | SPIFFS_CREAT | SPIFFS_TRUNC)) { + lstremove(&files, fd, 0); + free(file->fs_file); + free(file); + mtx_unlock(&vfs_mtx); + errno = EISDIR; + return -1; + } + + // Open the directory + strlcpy(npath, path, PATH_MAX); + dir_path((char *) npath, 0); + + // Open SPIFFS file + *((spiffs_file *)file->fs_file) = SPIFFS_open(&fs, npath, SPIFFS_RDONLY, 0); + if (*((spiffs_file *)file->fs_file) < 0) { + result = spiffs_result(fs.err_code); + } + + file->is_dir = 1; + } else { + if (!base_is_dir) { + // If base path is not a directory we return an error + lstremove(&files, fd, 0); + free(file->fs_file); + free(file); + mtx_unlock(&vfs_mtx); + errno = ENOENT; + return -1; + } else { + // Open SPIFFS file + *((spiffs_file *)file->fs_file) = SPIFFS_open(&fs, path, spiffs_flgs, 0); + if (*((spiffs_file *)file->fs_file) < 0) { + result = spiffs_result(fs.err_code); + } + } + } + + if (result != 0) { + lstremove(&files, fd, 0); + free(file->fs_file); + free(file); + mtx_unlock(&vfs_mtx); + errno = result; + return -1; + } + + int fdi = new_fd_itm(fd); + + mtx_unlock(&vfs_mtx); + + return fdi; //fd; +} + +static ssize_t vfs_spiffs_write(int fdi, const void *data, size_t size) { + vfs_file_t *file; + int res; + long fd = get_fd_itm(fdi); + + res = lstget(&files, fd, (void **) &file); + if (res || file->is_dir) { + errno = EBADF; + return -1; + } + + mtx_lock(&vfs_mtx); + + // Write SPIFFS file + res = SPIFFS_write(&fs, *((spiffs_file *)file->fs_file), (void *) data, size); + if (res >= 0) { + mtx_unlock(&vfs_mtx); + return res; + } else { + res = spiffs_result(fs.err_code); + if (res != 0) { + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + } + + mtx_unlock(&vfs_mtx); + + return -1; +} + +static ssize_t vfs_spiffs_read(int fdi, void * dst, size_t size) { + vfs_file_t *file; + int res; + long fd = get_fd_itm(fdi); + + res = lstget(&files, fd, (void **) &file); + if (res || file->is_dir) { + errno = EBADF; + return -1; + } + + mtx_lock(&vfs_mtx); + + // Read SPIFFS file + res = SPIFFS_read(&fs, *((spiffs_file *)file->fs_file), dst, size); + if (res >= 0) { + mtx_unlock(&vfs_mtx); + return res; + } else { + res = spiffs_result(fs.err_code); + if (res != 0) { + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + + // EOF + mtx_unlock(&vfs_mtx); + return 0; + } + + mtx_unlock(&vfs_mtx); + + return -1; +} + +static int vfs_spiffs_fstat(int fdi, struct stat * st) { + vfs_file_t *file; + spiffs_stat stat; + int res; + long fd = get_fd_itm(fdi); + + res = lstget(&files, fd, (void **) &file); + if (res) { + errno = EBADF; + return -1; + } + + // We have not time in SPIFFS + st->st_mtime = 0; + st->st_atime = 0; + st->st_ctime = 0; + + // Set block size for this file system + st->st_blksize = w25qxx_FLASH_PAGE_SIZE; //{} CONFIG_LUA_RTOS_SPIFFS_LOG_PAGE_SIZE; + + // First test if it's a directory entry + if (file->is_dir) { + st->st_mode = S_IFDIR; + st->st_size = 0; + return 0; + } + + mtx_lock(&vfs_mtx); + + // If is not a directory get file statistics + res = SPIFFS_fstat(&fs, *((spiffs_file *)file->fs_file), &stat); + if (res == SPIFFS_OK) { + st->st_size = stat.size; + } else { + st->st_size = 0; + res = spiffs_result(fs.err_code); + } + + st->st_mode = S_IFREG; + + if (res < 0) { + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + + mtx_unlock(&vfs_mtx); + + return 0; +} + +static int vfs_spiffs_close(int fdi) { + vfs_file_t *file; + int res; + long fd = del_fd_itm(fdi); + + res = lstget(&files, fd, (void **) &file); + if (res) { + errno = EBADF; + return -1; + } + + mtx_lock(&vfs_mtx); + + res = SPIFFS_close(&fs, *((spiffs_file *)file->fs_file)); + if (res) { + res = spiffs_result(fs.err_code); + } + + if (res < 0) { + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + + lstremove(&files, fd, 0); + + free(file->fs_file); + free(file); + + mtx_unlock(&vfs_mtx); + + return 0; +} + +static off_t vfs_spiffs_lseek(int fdi, off_t size, int mode) { + vfs_file_t *file; + int res; + long fd = get_fd_itm(fdi); + + res = lstget(&files, fd, (void **) &file); + if (res) { + errno = EBADF; + return -1; + } + + if (file->is_dir) { + errno = EBADF; + return -1; + } + + int whence = SPIFFS_SEEK_CUR; + + switch (mode) { + case SEEK_SET: + whence = SPIFFS_SEEK_SET; + break; + case SEEK_CUR: + whence = SPIFFS_SEEK_CUR; + break; + case SEEK_END: + whence = SPIFFS_SEEK_END; + break; + } + + mtx_lock(&vfs_mtx); + + res = SPIFFS_lseek(&fs, *((spiffs_file *)file->fs_file), size, whence); + if (res < 0) { + res = spiffs_result(fs.err_code); + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + + mtx_unlock(&vfs_mtx); + + return res; +} + +static int vfs_spiffs_stat(const char * path, struct stat * st) { + int fd; + int res; + + mtx_lock(&vfs_mtx); + + fd = vfs_spiffs_open(path, 0, 0); + if (fd < 0) { + mtx_unlock(&vfs_mtx); + errno = spiffs_result(fs.err_code); + return -1; + } + + res = vfs_spiffs_fstat(fd, st); + if (fd < 0) { + mtx_unlock(&vfs_mtx); + errno = spiffs_result(fs.err_code); + return -1; + } + + vfs_spiffs_close(fd); + + mtx_unlock(&vfs_mtx); + + return res; +} + +static int vfs_spiffs_access(const char *path, int amode) { + struct stat s; + + mtx_lock(&vfs_mtx); + + if (vfs_spiffs_stat(path, &s) < 0) { + mtx_unlock(&vfs_mtx); + return -1; + } + + if (s.st_mode != S_IFREG) { + mtx_unlock(&vfs_mtx); + errno = EACCES; + return -1; + } + + mtx_unlock(&vfs_mtx); + + return 0; +} + +static int vfs_spiffs_unlink(const char *path) { + int is_dir; + + mtx_lock(&vfs_mtx); + + int res = vfs_spiffs_traverse(path, &is_dir, NULL, NULL); + if ((res != 0) && (res != EEXIST)) { + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + + if (is_dir) { + mtx_unlock(&vfs_mtx); + errno = EPERM; + return -1; + } + + res = SPIFFS_remove(&fs, path); + if (res) { + mtx_unlock(&vfs_mtx); + errno = spiffs_result(fs.err_code); + return -1; + } + + mtx_unlock(&vfs_mtx); + + return 0; +} + +static int vfs_spiffs_rename(const char *src, const char *dst) { + int src_is_dir; + int dst_is_dir; + int dst_files; + + mtx_lock(&vfs_mtx); + + int res = vfs_spiffs_traverse(src, &src_is_dir, NULL, NULL); + if ((res != 0) && (res != EEXIST) && (res != ENOENT)) { + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + + res = vfs_spiffs_traverse(dst, &dst_is_dir, &dst_files, NULL); + if ((res != 0) && (res != EEXIST) && (res != ENOENT)) { + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + + if (dst_is_dir && !src_is_dir) { + mtx_unlock(&vfs_mtx); + errno = EISDIR; + return -1; + } + + if (dst_is_dir && (dst_files > 0)) { + mtx_unlock(&vfs_mtx); + errno = ENOTEMPTY; + return -1; + } + + char dpath[PATH_MAX + 1]; + char *csrc; + char *cname; + + if (src_is_dir) { + // We need to rename all tree + struct spiffs_dirent e; + spiffs_DIR d; + + SPIFFS_opendir(&fs, "/", &d); + while (SPIFFS_readdir(&d, &e)) { + if (!strncmp(src, (const char *) e.name, strlen(src)) && e.name[strlen(src)] == '/') { + strlcpy(dpath, dst, PATH_MAX); + csrc = (char *) src; + cname = (char *) e.name; + + while (*csrc && *cname && (*csrc == *cname)) { + ++csrc; + ++cname; + } + + strlcat(dpath, cname, PATH_MAX); + + if (SPIFFS_rename(&fs, (char *) e.name, dpath) != SPIFFS_OK) { + if (fs.err_code != SPIFFS_ERR_CONFLICTING_NAME) { + mtx_unlock(&vfs_mtx); + errno = spiffs_result(fs.err_code); + return -1; + } else { + // This only happens when e.name and dpath are directories. In this case + // remove e.name + if (SPIFFS_remove(&fs, (char *) e.name) != SPIFFS_OK) { + mtx_unlock(&vfs_mtx); + errno = spiffs_result(fs.err_code); + return -1; + } + } + } + } + } + SPIFFS_closedir(&d); + } else { + if (SPIFFS_rename(&fs, src, dst) < 0) { + mtx_unlock(&vfs_mtx); + errno = spiffs_result(fs.err_code); + return -1; + } + } + + mtx_unlock(&vfs_mtx); + + return 0; +} + +static DIR* vfs_spiffs_opendir(const char* name) { + int is_dir; + + vfs_dir_t *dir = vfs_allocate_dir("spiffs", name); + if (!dir) { + return NULL; + } + + mtx_lock(&vfs_mtx); + + int res = vfs_spiffs_traverse(name, &is_dir, NULL, NULL); + if ((res != 0) && (res != EEXIST)) { + vfs_free_dir(dir); + mtx_unlock(&vfs_mtx); + errno = res; + return NULL; + } + + if ((res == EEXIST) && !is_dir) { + vfs_free_dir(dir); + mtx_unlock(&vfs_mtx); + errno = ENOTDIR; + return NULL; + } + + if (!is_dir) { + vfs_free_dir(dir); + mtx_unlock(&vfs_mtx); + errno = ENOENT; + return NULL; + } + + if (!SPIFFS_opendir(&fs, name, (spiffs_DIR *)dir->fs_dir)) { + vfs_free_dir(dir); + mtx_unlock(&vfs_mtx); + errno = spiffs_result(fs.err_code); + return NULL; + } + + mtx_unlock(&vfs_mtx); + + return (DIR *) dir; +} + +static int vfs_spiffs_rmdir(const char *path) { + int is_dir; + int filenum; + + if (strcmp(path,"/") == 0) { + errno = EBUSY; + return -1; + } + + mtx_lock(&vfs_mtx); + + int res = vfs_spiffs_traverse(path, &is_dir, &filenum, NULL); + if ((res != 0) && (res != EEXIST)) { + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + + if (!is_dir) { + mtx_unlock(&vfs_mtx); + errno = ENOTDIR; + return -1; + } + + if (filenum > 0) { + mtx_unlock(&vfs_mtx); + errno = ENOTEMPTY; + return -1; + } + + // Remove directory + char npath[PATH_MAX + 1]; + + strlcpy(npath, path, PATH_MAX); + dir_path(npath, 0); + + res = SPIFFS_remove(&fs, npath); + if (res) { + mtx_unlock(&vfs_mtx); + errno = spiffs_result(fs.err_code); + return -1; + } + + mtx_unlock(&vfs_mtx); + + return 0; +} + +static struct dirent* vfs_spiffs_readdir(DIR* pdir) { + int res = 0, len = 0, entries = 0; + vfs_dir_t *dir = (vfs_dir_t *) pdir; + + struct dirent *ent = &dir->ent; + + char *fn; + + // Clear the current dirent + memset(ent, 0, sizeof(struct dirent)); + + // If there are mount points to read, read them first + if (dir->mount) { + struct dirent *ment = mount_readdir((DIR *)dir); + if (ment) { + return ment; + } + } + + mtx_lock(&vfs_mtx); + + // Search for next entry + for (;;) { + // Read directory + dir->fs_info = (void *)SPIFFS_readdir((spiffs_DIR *)dir->fs_dir, (struct spiffs_dirent *)dir->fs_info); + if (!dir->fs_info) { + if (fs.err_code != SPIFFS_VIS_END) { + res = spiffs_result(fs.err_code); + errno = res; + } + + break; + } + + // Break condition + if (((struct spiffs_dirent *)(dir->fs_info))->name[0] == 0) + break; + + // Get name and length + fn = (char *)(((struct spiffs_dirent *)(dir->fs_info))->name); + len = strlen(fn); + + // Get entry type and size + ent->d_type = DT_REG; + + if (len >= 2) { + if (fn[len - 1] == '.') { + if (fn[len - 2] == '/') { + ent->d_type = DT_DIR; + + fn[len - 2] = '\0'; + + len = strlen(fn); + + // Skip root dir + if (len == 0) { + continue; + } + } + } + } + + // Skip entries not belonged to path + if (strncmp(fn, dir->path, strlen(dir->path)) != 0) { + continue; + } + + if (strlen(dir->path) > 1) { + if (*(fn + strlen(dir->path)) != '/') { + continue; + } + } + + // Skip root directory + fn = fn + strlen(dir->path); + len = strlen(fn); + if (len == 0) { + continue; + } + + // Skip initial / + if (len > 1) { + if (*fn == '/') { + fn = fn + 1; + len--; + } + } + + // Skip subdirectories + if (strchr(fn, '/')) { + continue; + } + + ent->d_fsize = ((struct spiffs_dirent *)(dir->fs_info))->size; + + strlcpy(ent->d_name, fn, MAXNAMLEN); + + entries++; + dir->offset++; + + break; + } + + mtx_unlock(&vfs_mtx); + + if (entries > 0) { + return ent; + } else { + return NULL; + } +} + +static long vfs_spiffs_telldir(DIR *dirp) { + vfs_dir_t *dir = (vfs_dir_t *)dirp; + + if (dir->offset < 0) { + errno = EBADF; + return -1; + } + + return (long)dir->offset; +} + +static int vfs_spiffs_closedir(DIR* pdir) { + vfs_dir_t *dir = (vfs_dir_t *) pdir; + int res; + + if (!pdir) { + errno = EBADF; + return -1; + } + + mtx_lock(&vfs_mtx); + + if ((res = SPIFFS_closedir((spiffs_DIR *)dir->fs_dir)) < 0) { + mtx_unlock(&vfs_mtx); + errno = spiffs_result(fs.err_code); + return -1; + } + + vfs_free_dir(dir); + + mtx_unlock(&vfs_mtx); + + return 0; +} + +static int vfs_spiffs_mkdir(const char *path, mode_t mode) { + char npath[PATH_MAX + 1]; + int res; + int valid_prefix; + + mtx_lock(&vfs_mtx); + + res = vfs_spiffs_traverse(path, NULL, NULL, &valid_prefix); + if ((res != 0) && ((res != ENOENT) || (!valid_prefix))) { + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + + // Create directory + strlcpy(npath, path, PATH_MAX); + dir_path(npath, 0); + + spiffs_file fd = SPIFFS_open(&fs, npath, SPIFFS_CREAT | SPIFFS_RDWR, 0); + if (fd < 0) { + mtx_unlock(&vfs_mtx); + res = spiffs_result(fs.err_code); + errno = res; + return -1; + } + + if (SPIFFS_close(&fs, fd) < 0) { + mtx_unlock(&vfs_mtx); + res = spiffs_result(fs.err_code); + errno = res; + return -1; + } + + mtx_unlock(&vfs_mtx); + + return 0; +} + +static int vfs_spiffs_fsync(int fdi) { + vfs_file_t *file; + int res; + long fd = get_fd_itm(fdi); + + res = lstget(&files, fd, (void **) &file); + if (res) { + errno = EBADF; + return -1; + } + + if (file->is_dir) { + errno = EBADF; + return -1; + } + + mtx_lock(&vfs_mtx); + + res = SPIFFS_fflush(&fs, *((spiffs_file *)file->fs_file)); + if (res >= 0) { + mtx_unlock(&vfs_mtx); + return res; + } else { + res = spiffs_result(fs.err_code); + if (res != 0) { + mtx_unlock(&vfs_mtx); + errno = res; + return -1; + } + + mtx_unlock(&vfs_mtx); + errno = EIO; + return -1; + } + + mtx_unlock(&vfs_mtx); + + return 0; +} + +static void vfs_spiffs_free_resources() { + if (my_spiffs_work_buf) free(my_spiffs_work_buf); + if (my_spiffs_fds) free(my_spiffs_fds); + if (my_spiffs_cache) free(my_spiffs_cache); + + mtx_destroy(&vfs_mtx); + mtx_destroy(&ll_mtx); +} + +int vfs_spiffs_mount(const char *target) { + esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &vfs_spiffs_write, + .open = &vfs_spiffs_open, + .fstat = &vfs_spiffs_fstat, + .close = &vfs_spiffs_close, + .read = &vfs_spiffs_read, + .lseek = &vfs_spiffs_lseek, + .stat = &vfs_spiffs_stat, + .link = NULL, + .unlink = &vfs_spiffs_unlink, + .rename = &vfs_spiffs_rename, + .mkdir = &vfs_spiffs_mkdir, + .opendir = &vfs_spiffs_opendir, + .readdir = &vfs_spiffs_readdir, + .closedir = &vfs_spiffs_closedir, + .rmdir = &vfs_spiffs_rmdir, + .fsync = &vfs_spiffs_fsync, + .access = &vfs_spiffs_access, + .telldir = &vfs_spiffs_telldir, + }; + + // Mount spiffs file system + spiffs_config cfg; + int res = 0; + int retries = 0; + + // Find partition + const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, LUA_RTOS_SPIFFS_PART, NULL); + + if (!partition) { + vfs_spiffs_free_resources(); + syslog(LOG_ERR, "spiffs can't find a valid partition"); + return -1; + } else { + cfg.phys_addr = partition->address; + cfg.phys_size = partition->size; + } + + cfg.phys_erase_block = w25qxx_FLASH_SECTOR_SIZE; //{} CONFIG_LUA_RTOS_SPIFFS_ERASE_SIZE; + cfg.log_page_size = w25qxx_FLASH_PAGE_SIZE; //{} CONFIG_LUA_RTOS_SPIFFS_LOG_PAGE_SIZE; + cfg.log_block_size = w25qxx_FLASH_SECTOR_SIZE; //{} CONFIG_LUA_RTOS_SPIFFS_LOG_BLOCK_SIZE; + + if (partition) { + syslog(LOG_INFO, + "spiffs start address at 0x%x, size %d Kb", + cfg.phys_addr, cfg.phys_size / 1024); + } else { + syslog(LOG_INFO, "spiffs start address at 0x%x, size %d Kb", + cfg.phys_addr, cfg.phys_size / 1024); + } + + cfg.hal_read_f = (spiffs_read) low_spiffs_read; + cfg.hal_write_f = (spiffs_write) low_spiffs_write; + cfg.hal_erase_f = (spiffs_erase) low_spiffs_erase; + + my_spiffs_work_buf = malloc(cfg.log_page_size * 2); + if (!my_spiffs_work_buf) { + vfs_spiffs_free_resources(); + syslog(LOG_ERR, "spiffs can't allocate memory for file system"); + return -1; + } + + int fds_len = sizeof(spiffs_fd) * 5; + my_spiffs_fds = malloc(fds_len); + if (!my_spiffs_fds) { + vfs_spiffs_free_resources(); + syslog(LOG_ERR, "spiffs can't allocate memory for file system"); + return -1; + } + + int cache_len = cfg.log_page_size * 5; + my_spiffs_cache = malloc(cache_len); + if (!my_spiffs_cache) { + vfs_spiffs_free_resources(); + syslog(LOG_ERR, "spiffs can't allocate memory for file system"); + return -1; + } + + // Init mutex + mtx_init(&vfs_mtx, NULL, NULL, MTX_RECURSE); + + mtx_init(&ll_mtx, NULL, NULL, 0); + fs.user_data = &ll_mtx; + + while (retries < 2) { + res = SPIFFS_mount(&fs, &cfg, my_spiffs_work_buf, my_spiffs_fds, + fds_len, my_spiffs_cache, cache_len, NULL); + + if (res < 0) { + if (fs.err_code == SPIFFS_ERR_NOT_A_FS) { + syslog(LOG_ERR, "spiffs no file system detected, formating..."); + SPIFFS_unmount(&fs); + res = SPIFFS_format(&fs); + if (res < 0) { + vfs_spiffs_free_resources(); + syslog(LOG_ERR, "spiffs format error"); + return -1; + } + } else { + vfs_spiffs_free_resources(); + syslog(LOG_ERR, "spiff can't mount file system (%s)", + strerror(spiffs_result(fs.err_code))); + return -1; + } + } else { + break; + } + + retries++; + } + + if (retries > 0) { + syslog(LOG_INFO, "spiffs creating root folder"); + + // Create the root folder + spiffs_file fd = SPIFFS_open(&fs, "/.", SPIFFS_CREAT | SPIFFS_RDWR, 0); + if (fd < 0) { + vfs_spiffs_umount(target); + syslog(LOG_ERR, "spiffs can't create root folder (%s)", + strerror(spiffs_result(fs.err_code))); + return -1; + } + + if (SPIFFS_close(&fs, fd) < 0) { + vfs_spiffs_umount(target); + syslog(LOG_ERR, "spiffs can't create root folder (%s)", + strerror(spiffs_result(fs.err_code))); + return -1; + } + } + + lstinit(&files, 0, LIST_DEFAULT); + + ESP_ERROR_CHECK(esp_vfs_register("/spiffs", &vfs, NULL)); + + syslog(LOG_INFO, "spiffs mounted on %s", target); + + return 0; +} + +int vfs_spiffs_umount(const char *target) { + esp_vfs_unregister("/spiffs"); + SPIFFS_unmount(&fs); + + vfs_spiffs_free_resources(); + + syslog(LOG_INFO, "spiffs unmounted"); + + return 0; +} + +int vfs_spiffs_format(const char *target) { + vfs_spiffs_umount(target); + + mtx_init(&ll_mtx, NULL, NULL, 0); + + int res = SPIFFS_format(&fs); + if (res < 0) { + syslog(LOG_ERR, "spiffs format error"); + return -1; + } + + mtx_destroy(&ll_mtx); + + vfs_spiffs_mount(target); + + syslog(LOG_INFO, "spiffs creating root folder"); + + // Create the root folder + spiffs_file fd = SPIFFS_open(&fs, "/.", SPIFFS_CREAT | SPIFFS_RDWR, 0); + if (fd < 0) { + vfs_spiffs_umount(target); + syslog(LOG_ERR, "spiffs can't create root folder (%s)", + strerror(spiffs_result(fs.err_code))); + return -1; + } + + if (SPIFFS_close(&fs, fd) < 0) { + vfs_spiffs_umount(target); + syslog(LOG_ERR, "spiffs can't create root folder (%s)", + strerror(spiffs_result(fs.err_code))); + return -1; + } + + return 0; +} + +int vfs_spiffs_fsstat(const char *target, u32_t *total, u32_t *used) { + + if (SPIFFS_info(&fs, total, used) != SPIFFS_OK) { + syslog(LOG_ERR, "spiffs get fs info of '%s' (%s)", target, + strerror(spiffs_result(fs.err_code))); + return -1; + } + + return 0; +} + +//#endif diff --git a/libiot/vfs/vfs.h b/libiot/vfs/vfs.h new file mode 100644 index 0000000..eb743fd --- /dev/null +++ b/libiot/vfs/vfs.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 - 2018, IBEROXARXA SERVICIOS INTEGRALES, S.L. + * Copyright (C) 2015 - 2018, Jaume Olivé Petrus (jolive@whitecatboard.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * * The WHITECAT logotype cannot be changed, you can remove it, but you + * cannot change it in any way. The WHITECAT logotype is: + * + * /\ /\ + * / \_____/ \ + * /_____________\ + * W H I T E C A T + * + * * Redistributions in binary form must retain all copyright notices printed + * to any local or remote output device. This include any reference to + * Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may + * appear in the future. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Lua RTOS common vfs functions + * + */ + +#include "esp_vfs.h" + +#include +#include + +//#include + +#define LUA_RTOS_SPIFFS_PART 0x40 +#define LUA_RTOS_LFS_PART 0x41 + +typedef struct { + void *fs_file; + char *path; + uint8_t is_dir; +} vfs_file_t; + +typedef struct { + DIR dir; + long offset; + void *fs_dir; + void *fs_info; + char *path; + struct dirent ent; + const struct mount_pt *mount; +} vfs_dir_t; + +typedef struct { + int flags; // FD flags +} vfs_fd_local_storage_t; + +// Return if there are available bytes for read from the file descriptor. +// This function is blocking. +typedef int(*vfs_has_bytes)(int, int); + +// Return the bytes available for write to the file descriptor. +// This function is non blocking. +typedef int(*vfs_free_bytes)(int); + +// Get one byte from the file descriptor. +// This functions is blocking. +typedef int(*vfs_get_byte)(int,char *); + +// Put one byte to the file descriptor. +// This function is blocking. +typedef void(*vfs_put_byte)(int,char *); + +/* +int vfs_fat_mount(const char *target); +int vfs_fat_umount(const char *target); +int vfs_fat_format(const char *target); +int vfs_fat_fsstat(const char *target, u32_t *total, u32_t *used); +*/ + +int vfs_spiffs_mount(const char *target); +int vfs_spiffs_umount(const char *target); +int vfs_spiffs_format(const char *target); +int vfs_spiffs_fsstat(const char *target, u32_t *total, u32_t *used); + +/* +int vfs_lfs_mount(const char *target); +int vfs_lfs_umount(const char *target); +int vfs_lfs_format(const char *target); +int vfs_lfs_fsstat(const char *target, u32_t *total, u32_t *used); + +int vfs_ramfs_mount(const char *target); +int vfs_ramfs_umount(const char *target); +int vfs_ramfs_format(const char *target); +int vfs_ramfs_fsstat(const char *target, u32_t *total, u32_t *used); + +int vfs_romfs_mount(const char *target); +int vfs_romfs_umount(const char *target); +int vfs_romfs_fsstat(const char *target, u32_t *total, u32_t *used); + +int vfs_generic_fcntl(vfs_fd_local_storage_t *local_storage, int fd, int cmd, va_list args); +ssize_t vfs_generic_read(vfs_fd_local_storage_t *local_storage, vfs_has_bytes has_bytes, vfs_get_byte get, int fd, void * dst, size_t size); +ssize_t vfs_generic_write(vfs_fd_local_storage_t *local_storage, vfs_put_byte put, int fd, const void *data, size_t size); +ssize_t vfs_generic_writev(vfs_fd_local_storage_t *local_storage, vfs_put_byte put, int fd, const struct iovec *iov, int iovcnt); +int vfs_generic_select(vfs_fd_local_storage_t *local_storage, vfs_has_bytes has_bytes, vfs_free_bytes free, int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout); + +vfs_dir_t *vfs_allocate_dir(const char *vfs, const char *name); +void vfs_free_dir(vfs_dir_t *dir); +vfs_fd_local_storage_t *vfs_create_fd_local_storage(int num); +*/ diff --git a/libmath/FPcontrol-FreeRTOS.c b/libmath/FPcontrol-FreeRTOS.c new file mode 100644 index 0000000..180c8bd --- /dev/null +++ b/libmath/FPcontrol-FreeRTOS.c @@ -0,0 +1,77 @@ +#include "lib9.h" +#include "mathi.h" + +void +FPinit(void) +{ + setfsr(0); /* Clear pending exceptions */ + setfcr(FPPDBL|FPRNR|FPINVAL|FPZDIV|FPUNFL|FPOVFL); +} + +ulong +getFPstatus(void) +{ + ulong fsr = 0, fsr9 = getfsr(); + /* on specific machines, could be table lookup */ + if(fsr9&FPAINEX) fsr |= INEX; + if(fsr9&FPAOVFL) fsr |= OVFL; + if(fsr9&FPAUNFL) fsr |= UNFL; + if(fsr9&FPAZDIV) fsr |= ZDIV; + if(fsr9&FPAINVAL) fsr |= INVAL; + return fsr; +} + +ulong +FPstatus(ulong fsr, ulong mask) +{ + ulong fsr9 = 0; + ulong old = getFPstatus(); + fsr = (fsr&mask) | (old&~mask); + if(fsr&INEX) fsr9 |= FPAINEX; + if(fsr&OVFL) fsr9 |= FPAOVFL; + if(fsr&UNFL) fsr9 |= FPAUNFL; + if(fsr&ZDIV) fsr9 |= FPAZDIV; + if(fsr&INVAL) fsr9 |= FPAINVAL; + setfsr(fsr9); + return(old&mask); +} + +ulong +getFPcontrol(void) +{ + ulong fcr = 0, fcr9 = getfcr(); + switch(fcr9&FPRMASK){ + case FPRNR: fcr = RND_NR; break; + case FPRNINF: fcr = RND_NINF; break; + case FPRPINF: fcr = RND_PINF; break; + case FPRZ: fcr = RND_Z; break; + } + if(fcr9&FPINEX) fcr |= INEX; + if(fcr9&FPOVFL) fcr |= OVFL; + if(fcr9&FPUNFL) fcr |= UNFL; + if(fcr9&FPZDIV) fcr |= ZDIV; + if(fcr9&FPINVAL) fcr |= INVAL; + return fcr; +} + +ulong +FPcontrol(ulong fcr, ulong mask) +{ + ulong fcr9 = FPPDBL; + ulong old = getFPcontrol(); + fcr = (fcr&mask) | (old&~mask); + if(fcr&INEX) fcr9 |= FPINEX; + if(fcr&OVFL) fcr9 |= FPOVFL; + if(fcr&UNFL) fcr9 |= FPUNFL; + if(fcr&ZDIV) fcr9 |= FPZDIV; + if(fcr&INVAL) fcr9 |= FPINVAL; + switch(fcr&RND_MASK){ + case RND_NR: fcr9 |= FPRNR; break; + case RND_NINF: fcr9 |= FPRNINF; break; + case RND_PINF: fcr9 |= FPRPINF; break; + case RND_Z: fcr9 |= FPRZ; break; + } + setfcr(fcr9); + return(old&mask); +} + diff --git a/mkconfig.FreeRTOS-riscv64 b/mkconfig.FreeRTOS-riscv64 new file mode 100644 index 0000000..e448987 --- /dev/null +++ b/mkconfig.FreeRTOS-riscv64 @@ -0,0 +1,35 @@ +# +# Set the following 4 variables. The host system is the system where +# the software will be built; the target system is where it will run. +# They are almost always the same. + +# On Nt systems, the ROOT path MUST be of the form `drive:/path' +ROOT=$ROOT + +# +# Specify the flavour of Tk (std for standard builds) +# +TKSTYLE=std + +WINDOW_BACKEND=fb # andr, clutter, x11a, fb + +# +# Except for building kernels, SYSTARG must always be the same as SYSHOST +# +SYSHOST=Linux #Nt #Plan9 # build system OS type (Hp, Inferno, Irix, Linux, MacOSX, Nt, Plan9, Solaris) +SYSTARG=FreeRTOS #Linux #Android #Linux #$SYSHOST # target system OS type (Hp, Inferno, Irix, Linux, Nt, Plan9, Solaris, FreeRTOS) + +# +# specify the architecture of the target system - Plan 9 imports it from the +# environment; for other systems it is usually just hard-coded +# +OBJTYPE=riscv64 #arm #386 #arm #386 # target system object type (eg, 386, arm, mips, power, s800, sparc, riscv64) +#OBJTYPE=$objtype + +# +# no changes required beyond this point +# +OBJDIR=$SYSTARG/$OBJTYPE + +<$ROOT/mkfiles/mkhost-$SYSHOST # variables appropriate for host system +<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE # variables used to build target object type diff --git a/mkconfig.Linux-386 b/mkconfig.Linux-386 new file mode 100644 index 0000000..6669b53 --- /dev/null +++ b/mkconfig.Linux-386 @@ -0,0 +1,35 @@ +# +# Set the following 4 variables. The host system is the system where +# the software will be built; the target system is where it will run. +# They are almost always the same. + +# On Nt systems, the ROOT path MUST be of the form `drive:/path' +ROOT=$ROOT + +# +# Specify the flavour of Tk (std for standard builds) +# +TKSTYLE=std + +WINDOW_BACKEND=x11a # andr, clutter, x11a, fb + +# +# Except for building kernels, SYSTARG must always be the same as SYSHOST +# +SYSHOST=Linux #Nt #Plan9 # build system OS type (Hp, Inferno, Irix, Linux, MacOSX, Nt, Plan9, Solaris) +SYSTARG=Linux #Android #Linux #$SYSHOST # target system OS type (Hp, Inferno, Irix, Linux, Nt, Plan9, Solaris, FreeRTOS) + +# +# specify the architecture of the target system - Plan 9 imports it from the +# environment; for other systems it is usually just hard-coded +# +OBJTYPE=386 #arm #386 # target system object type (eg, 386, arm, mips, power, s800, sparc, riscv64) +#OBJTYPE=$objtype + +# +# no changes required beyond this point +# +OBJDIR=$SYSTARG/$OBJTYPE + +<$ROOT/mkfiles/mkhost-$SYSHOST # variables appropriate for host system +<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE # variables used to build target object type diff --git a/mkfiles/mkfile-FreeRTOS-riscv64 b/mkfiles/mkfile-FreeRTOS-riscv64 new file mode 100644 index 0000000..b0f9e79 --- /dev/null +++ b/mkfiles/mkfile-FreeRTOS-riscv64 @@ -0,0 +1,37 @@ +TARGMODEL= Posix +TARGSHTYPE= sh +CPUS= arm + +O= o +OS= o + +AR= ${CROSS}ar +ARFLAGS= ruvs + +AS= ${CROSS}gcc -c +ASFLAGS= + +CC= ${CROSS}gcc -c +CFLAGS= -O\ + -Wuninitialized -Wunused-variable -Wreturn-type -Wimplicit\ + -I$ROOT/FreeRTOS/riscv64/include\ + -I$FreeRTOS_ROOT/include\ + -I$FreeRTOS_ROOT/conf\ + -I$FreeRTOS_ROOT/portable\ + -I$K210_SDK/lib/arch/include\ + -I$K210_SDK/lib/posix/include\ + -I$K210_SDK/third_party/lwip/src/include/compat/posix\ + -I$K210_SDK/third_party/lwip/src/include\ + -I$ROOT/include\ + -DFREERTOS_RISCV64 + +# -DLINUX_ARM + +ANSICPP= +LD= ${CROSS}gcc +LDFLAGS= + +SYSLIBS= + +YACC= iyacc +YFLAGS= -d -- cgit v1.2.3