aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FreeRTOS/riscv64/include/emu.h41
-rw-r--r--FreeRTOS/riscv64/include/fpuctl.h0
-rw-r--r--FreeRTOS/riscv64/include/lib9.h512
-rwxr-xr-xbld-lin-fb-k210.sh25
-rwxr-xr-xbld-lin.sh2
-rw-r--r--emu/FreeRTOS/arm-tas-v5.S18
-rw-r--r--emu/FreeRTOS/arm-tas-v7.S30
-rw-r--r--emu/FreeRTOS/asm-386.S51
-rw-r--r--emu/FreeRTOS/asm-arm.S62
-rw-r--r--emu/FreeRTOS/asm-mips.S28
-rw-r--r--emu/FreeRTOS/asm-power.S61
-rw-r--r--emu/FreeRTOS/asm-spim.S29
-rw-r--r--emu/FreeRTOS/audio-oss.c441
-rw-r--r--emu/FreeRTOS/cmd.c213
-rw-r--r--emu/FreeRTOS/deveia.c44
-rw-r--r--emu/FreeRTOS/devfs-posix.c1096
-rw-r--r--emu/FreeRTOS/devfs.c26
-rw-r--r--emu/FreeRTOS/emu112
-rw-r--r--emu/FreeRTOS/emu-g101
-rw-r--r--emu/FreeRTOS/emu-wrt111
-rw-r--r--emu/FreeRTOS/kproc-xthreads.c264
-rw-r--r--emu/FreeRTOS/mk-wrt8
-rw-r--r--emu/FreeRTOS/mkfile62
-rw-r--r--emu/FreeRTOS/mkfile-3860
-rw-r--r--emu/FreeRTOS/mkfile-arm2
-rw-r--r--emu/FreeRTOS/mkfile-mips0
-rw-r--r--emu/FreeRTOS/mkfile-power0
-rw-r--r--emu/FreeRTOS/mkfile-riscv640
-rw-r--r--emu/FreeRTOS/os.c332
-rw-r--r--emu/FreeRTOS/segflush-386.c11
-rw-r--r--emu/FreeRTOS/segflush-arm.c14
-rw-r--r--emu/FreeRTOS/segflush-mips.S16
-rw-r--r--emu/FreeRTOS/segflush-power.c34
-rw-r--r--emu/FreeRTOS/segflush-riscv64.c11
-rw-r--r--emu/FreeRTOS/segflush-spim.S16
-rw-r--r--emu/FreeRTOS/win-fb.c1881
-rw-r--r--include/png/lodepng.h4
-rw-r--r--lib9/getcallerpc-FreeRTOS-riscv64.c12
-rw-r--r--lib9/setfcr-FreeRTOS-riscv64.c64
-rw-r--r--libdynld/dynld-riscv64.c42
-rw-r--r--libinterp/comp-riscv64.c1998
-rw-r--r--libinterp/das-riscv64.c9
-rw-r--r--libiot/freertos/adds.c317
-rw-r--r--libiot/freertos/adds.h198
-rw-r--r--libiot/freertos/mkfile3
-rw-r--r--libiot/hal/mkfile3
-rw-r--r--libiot/hal/w25qxx.c288
-rw-r--r--libiot/hal/w25qxx.h88
-rw-r--r--libiot/include/esp_err.h148
-rw-r--r--libiot/lfs/lfs.c2592
-rw-r--r--libiot/lfs/lfs.h495
-rw-r--r--libiot/lfs/lfs_util.c31
-rw-r--r--libiot/lfs/lfs_util.h186
-rw-r--r--libiot/linenoise/linenoise.c637
-rw-r--r--libiot/linenoise/linenoise.h56
-rw-r--r--libiot/mkfile26
-rw-r--r--libiot/pthread/_pthread.c743
-rw-r--r--libiot/pthread/_pthread.h241
-rw-r--r--libiot/pthread/attr.c244
-rw-r--r--libiot/pthread/cond.c204
-rw-r--r--libiot/pthread/create.c55
-rw-r--r--libiot/pthread/join.c70
-rw-r--r--libiot/pthread/key.c163
-rw-r--r--libiot/pthread/kill.c60
-rw-r--r--libiot/pthread/mkfile10
-rw-r--r--libiot/pthread/mutex.c241
-rw-r--r--libiot/pthread/once.c67
-rw-r--r--libiot/pthread/self.c53
-rw-r--r--libiot/pthread/test/pthread-cleanup.c200
-rw-r--r--libiot/pthread/test/pthread-cond.c107
-rw-r--r--libiot/pthread/test/pthread-join.c85
-rw-r--r--libiot/pthread/test/pthread-once.c68
-rw-r--r--libiot/ramfs/ramfs.c1088
-rw-r--r--libiot/ramfs/ramfs.h274
-rw-r--r--libiot/spiffs/k210_spiffs.c190
-rw-r--r--libiot/spiffs/k210_spiffs.h59
-rw-r--r--libiot/spiffs/mkfile9
-rw-r--r--libiot/spiffs/spiffs.h816
-rw-r--r--libiot/spiffs/spiffs_cache.c314
-rw-r--r--libiot/spiffs/spiffs_check.c995
-rw-r--r--libiot/spiffs/spiffs_config.h360
-rw-r--r--libiot/spiffs/spiffs_gc.c606
-rw-r--r--libiot/spiffs/spiffs_hydrogen.c1405
-rw-r--r--libiot/spiffs/spiffs_nucleus.c2327
-rw-r--r--libiot/spiffs/spiffs_nucleus.h797
-rw-r--r--libiot/sys/console.c211
-rw-r--r--libiot/sys/console.h61
-rw-r--r--libiot/sys/history.c199
-rw-r--r--libiot/sys/history.h57
-rw-r--r--libiot/sys/list.c429
-rw-r--r--libiot/sys/list.h85
-rw-r--r--libiot/sys/mkfile5
-rw-r--r--libiot/sys/mutex.c155
-rw-r--r--libiot/sys/mutex.h69
-rw-r--r--libiot/sys/panic.c52
-rw-r--r--libiot/sys/panic.h51
-rw-r--r--libiot/vfs/fat.c453
-rw-r--r--libiot/vfs/include/esp_vfs.h424
-rw-r--r--libiot/vfs/include/sys/dirent.h56
-rw-r--r--libiot/vfs/lfs.c949
-rw-r--r--libiot/vfs/mkfile7
-rw-r--r--libiot/vfs/ramfs.c634
-rw-r--r--libiot/vfs/spiffs.c1423
-rw-r--r--libiot/vfs/vfs.h128
-rw-r--r--libmath/FPcontrol-FreeRTOS.c77
-rw-r--r--mkconfig.FreeRTOS-riscv6435
-rw-r--r--mkconfig.Linux-38635
-rw-r--r--mkfiles/mkfile-FreeRTOS-riscv6437
108 files changed, 30333 insertions, 1 deletions
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
--- /dev/null
+++ b/FreeRTOS/riscv64/include/fpuctl.h
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 <features.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#define sync __os_sync
+#include <unistd.h>
+#undef sync
+#include <errno.h>
+#define __NO_STRING_INLINES
+#include <string.h>
+#include "math.h"
+#include <fcntl.h>
+#include <setjmp.h>
+#include <float.h>
+//#include <endian.h>
+
+#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 <sys/asm.h>
+#include <sys/regdef.h>
+#include <asm/cachectl.h>
+
+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 <sys/asm.h>
+#include <sys/regdef.h>
+#include <asm/cachectl.h>
+
+
+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 <sys/ioctl.h>
+#include <sys/soundcard.h>
+
+#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<<what)), can);
+ if(!(can & (1<<what)))
+ return 0;
+ v = left | (right<<8);
+ if(ioctl(afd.ctl, MIXER_WRITE(what), &v) < 0)
+ return 0;
+ return 1;
+}
+
+static int
+audio_set_info(int fd, Audio_d *i, int d)
+{
+ int status, arg;
+ int oldfmt, newfmt;
+
+ USED(d);
+ DPRINT("audio_set_info (%d) %d %d\n", fd, afd.data, afd.ctl);
+ if(fd < 0)
+ return 0;
+
+ /* sample rate */
+ if(i->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 <sys/types.h>
+#include <signal.h>
+#include <pwd.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+
+#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 <sys/ioctl.h>
+#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 <sys/types.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+#include <sys/socket.h>
+#if 0 //{}
+#include <sys/un.h>
+#endif //{}
+#include <utime.h>
+#include <dirent.h>
+#include <stdio.h>
+#define __EXTENSIONS__
+#undef getwd
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+
+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(&current->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; n<offset; ) {
+ de = readdir(FS(c)->dir);
+ 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; i<NID; i++)
+ for(u = tab[i]; u != nil; u = u->next)
+ 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 <linux/hdreg.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+
+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 <unistd.h>
+#include <signal.h>
+#include <pthread.h>
+#include <limits.h>
+#include <errno.h>
+//{}#include <semaphore.h>
+
+
+#include <FreeRTOS.h>
+
+#include <semphr.h>
+
+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 <sched.h>
+#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, &param);
+ param.sched_priority = sched_get_priority_min(policy);
+ pthread_setschedparam(self, policy, &param);
+#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
+<mkfile-$OBJTYPE # sets $ARCHFILES
+
+OBJ=\
+ $ARCHFILES\
+ os.$O\
+ kproc-xthreads.$O\
+ segflush-$OBJTYPE.$O\
+ $CONF.root.$O\
+ lock.$O\
+ $DEVS\
+ $PORT\
+
+# asm-$OBJTYPE.$O\
+
+LIBNAMES=${LIBS:%=lib%.a}
+#libs=${LIBS:%=$ROOT/$OBJDIR/lib/lib%.a}
+
+HFILES=\
+
+KERNDATE=1234567 #`{$NDATE}
+CFLAGS='-DROOT="'$ROOT'"' -DEMU \
+ -DTOUCHSCREEN_CAPACITIVE=$TOUCHSCREEN_CAPACITIVE \
+ -I. \
+ -I../port \
+ -I$ROOT/$SYSTARG/$OBJTYPE/include \
+ -I$ROOT/include \
+ -I$ROOT/libinterp \
+ $CTHREADFLAGS $CFLAGS $EMUOPTIONS
+#SYSLIBS= $X11LIBS -lm -lpthread
+SYSLIBS= -lm -lpthread
+
+default:V: $O.$CONF
+
+$O.$CONF: $OBJ $CONF.c $CONF.root.h $LIBNAMES
+ $CC $CFLAGS '-DKERNDATE='$KERNDATE $CONF.c
+ $LD $LDFLAGS -o $target $OBJ $CONF.$O $LIBFILES $SYSLIBS
+
+install:V: $O.$CONF
+ cp $O.$CONF $INSTALLDIR/$CONF
+
+<../port/portmkfile
+
+devfs.$O: devfs-posix.c
diff --git a/emu/FreeRTOS/mkfile-386 b/emu/FreeRTOS/mkfile-386
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/emu/FreeRTOS/mkfile-386
diff --git a/emu/FreeRTOS/mkfile-arm b/emu/FreeRTOS/mkfile-arm
new file mode 100644
index 0000000..01cdd7c
--- /dev/null
+++ b/emu/FreeRTOS/mkfile-arm
@@ -0,0 +1,2 @@
+ARCHFILES=\
+ arm-tas-v7.$O\
diff --git a/emu/FreeRTOS/mkfile-mips b/emu/FreeRTOS/mkfile-mips
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/emu/FreeRTOS/mkfile-mips
diff --git a/emu/FreeRTOS/mkfile-power b/emu/FreeRTOS/mkfile-power
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/emu/FreeRTOS/mkfile-power
diff --git a/emu/FreeRTOS/mkfile-riscv64 b/emu/FreeRTOS/mkfile-riscv64
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/emu/FreeRTOS/mkfile-riscv64
diff --git a/emu/FreeRTOS/os.c b/emu/FreeRTOS/os.c
new file mode 100644
index 0000000..468b232
--- /dev/null
+++ b/emu/FreeRTOS/os.c
@@ -0,0 +1,332 @@
+#include <sys/types.h>
+#include <time.h>
+//#include <termios.h>
+#include <signal.h>
+#include <pwd.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+//#include <sys/time.h>
+#include <machine/time.h>
+
+#include <stdint.h>
+
+#include "dat.h"
+#include "fns.h"
+#include "error.h"
+
+
+
+#include <FreeRTOS.h>
+#include <task.h>
+
+#include <pthread.h>
+#include <semphr.h>
+
+
+
+
+//#include <semaphore.h>
+
+#include <raise.h>
+
+/* glibc 2.3.3-NTPL messes up getpid() by trying to cache the result, so we'll do it ourselves */
+//#include <sys/syscall.h>
+//#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 <sys/types.h>
+#include <sys/syscall.h>
+
+#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 <sys/types.h>
+#include <sys/syscall.h>
+
+#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 <sys/asm.h>
+#include <sys/regdef.h>
+#include <asm/cachectl.h>
+
+/*
+ * 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 <sys/types.h>
+#include <sys/syscall.h>
+
+#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 <sys/types.h>
+//#include <sys/syscall.h>
+
+#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 <sys/asm.h>
+#include <sys/regdef.h>
+#include <asm/cachectl.h>
+
+/*
+ * 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 <draw.h>
+#include "cursor.h"
+#include "keyboard.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <linux/fb.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <dirent.h>
+
+#include <linux/input.h>
+
+//#include "keysym2ucs.h"
+
+#include <sys/ipc.h>
+//#include <sys/shm.h>
+
+#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(x))
+#define LONG(x) ((x)/BITS_PER_LONG)
+#define test_bit(bit, array) ((array[LONG(bit)] >> 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<<i))
+ break;
+ if(i == 32)
+ return 0;
+ g->code = code;
+ g->bit = i;
+ g->mask = mask;
+ for(g->len = 0; i<32 && (mask & (1<<i))!=0; i++)
+ g->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; i<n; i++)
+ if(pfmt[i].depth == depth)
+ return pfmt[i].bits_per_pixel;
+ return -1;
+}
+
+static int
+xtruevisual(int screenno, int reqdepth, XVisualInfo *vi, ulong *chan)
+{
+ XVisual *xv;
+ Mg r, g, b;
+ int pad, d;
+ ulong c;
+ char buf[30];
+
+ if(XMatchVisualInfo(xdisplay, screenno, reqdepth, TrueColor, vi) ||
+ XMatchVisualInfo(xdisplay, screenno, reqdepth, DirectColor, vi)){
+ xv = vi->visual;
+ 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 <string.h> /*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 <lib9.h>
+
+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 <a.out.h>
+#include <dynld.h>
+
+#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 <lib9.h>
+#include <kernel.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <FreeRTOS.h>
+#include <task.h>
+#include "adds.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include <stdlib.h>
+
+#include <malloc.h>
+
+#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 <task_num; i++){
+ if (status_array[i].eCurrentState == eDeleted) {
+ continue;
+ }
+
+ // Get the task TCB
+ ctask = (tskTCB_t *)status_array[i].xHandle;
+
+ // Get the task type
+ // 0: freertos task
+ // 1: pthread task
+ // 2: lua thread task
+ task_type = 0;
+
+ info[i].thid = 0;
+ info[i].status = 0;
+
+ // Get Lua RTOS specific TCB parts for current task
+ if ((lua_rtos_tcb = pvTaskGetThreadLocalStoragePointer(status_array[i].xHandle, THREAD_LOCAL_STORAGE_POINTER_ID))) {
+ // Task has Lua RTOS specific TCB parts
+ if (lua_rtos_tcb->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 <organization> 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 <COPYRIGHT HOLDER> 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 <FreeRTOS.h>
+#include <task.h>
+
+#include <stdint.h>
+#include <pthread.h>
+
+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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <devices.h>
+#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(&reg_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 <stdint.h>
+
+/* 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 <stdint.h>
+#include <stdio.h>
+#include <assert.h>
+
+#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 <inttypes.h>
+
+
+/// 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 <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+/// Version info ///
+
+// Software library version
+// Major (top-nibble), incremented on backwards incompatible changes
+// Minor (bottom-nibble), incremented on feature additions
+#define LFS_VERSION 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 <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#ifndef LFS_NO_MALLOC
+#include <stdlib.h>
+#endif
+#ifndef LFS_NO_ASSERT
+#include <assert.h>
+#endif
+#if !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR)
+#include <stdio.h>
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+// Macros, may be replaced by system specific wrappers. Arguments to these
+// macros must not have side-effects as the macros can be removed for a smaller
+// code footprint
+
+// Logging functions
+#ifndef LFS_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 <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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+<char> 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/status.h>
+#include <sys/types.h>
+#include <sys/list.h>
+#include <reent.h>
+
+#include <sys/mount.h>
+#include <sys/path.h>
+#include <sys/history.h>
+
+#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 <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * 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\
+
+#<pthread/mkfile
+<freertos/mkfile
+<sys/mkfile
+<hal/mkfile
+<spiffs/mkfile
+<vfs/mkfile
+
+<$ROOT/mkfiles/mksyslib-$SHELLTYPE
diff --git a/libiot/pthread/_pthread.c b/libiot/pthread/_pthread.c
new file mode 100644
index 0000000..d0353e7
--- /dev/null
+++ b/libiot/pthread/_pthread.c
@@ -0,0 +1,743 @@
+/*
+ * 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 <organization> 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 <COPYRIGHT HOLDER> 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/mutex.h>
+#include <sys/queue.h>
+
+// 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 <organization> 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 <COPYRIGHT HOLDER> 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 <pthread.h>
+
+#include <errno.h>
+
+#include <sys/mutex.h>
+#include <sys/list.h>
+#include <sys/time.h>
+
+#include <signal.h>
+
+
+#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 <organization> 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 <COPYRIGHT HOLDER> 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 <organization> 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 <COPYRIGHT HOLDER> 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 <sys/mutex.h>
+#include <sys/time.h>
+
+#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 <organization> 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 <COPYRIGHT HOLDER> 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 <pthread.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <organization> 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 <COPYRIGHT HOLDER> 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 <stdlib.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <errno.h>
+#include <signal.h>
+#include <sys/_signal.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <stdlib.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <organization> 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 <COPYRIGHT HOLDER> 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+#include <sys/delay.h>
+
+#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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+
+#include <sys/delay.h>
+
+#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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pthread.h>
+
+#include <sys/delay.h>
+
+#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 <pthread.h>
+#include <sys/delay.h>
+
+#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 <organization> 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 <COPYRIGHT HOLDER> 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 <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <stdint.h>
+#include <stddef.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/mutex.h>
+
+#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 <organization> 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 <COPYRIGHT HOLDER> 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 <stdlib.h>
+
+#include "../sys/mutex.h"
+
+#include <devices.h>
+//#include <task.h>
+
+
+#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 <organization> 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 <COPYRIGHT HOLDER> 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<<i)) &&
+ (cp->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<<ix)) {
+ if (write_back &&
+ (cp->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<<i)) == 0) {
+ spiffs_cache_page *cp = spiffs_get_cache_page_hdr(fs, cache, i);
+ cache->cpage_use_map |= (1<<i);
+ cp->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<<i)) &&
+ (cp->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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <ctype.h>
+// ----------- >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<<bit_ix);
+ }
+ }
+ return SPIFFS_VIS_COUNTINUE;
+}
+
+static s32_t spiffs_obj_lu_find_free_obj_id_compact_v(spiffs *fs, spiffs_obj_id id, spiffs_block_ix bix, int ix_entry,
+ const void *user_const_p, void *user_var_p) {
+ (void)user_var_p;
+ if (id != SPIFFS_OBJ_ID_FREE && id != SPIFFS_OBJ_ID_DELETED && (id & SPIFFS_OBJ_ID_IX_FLAG)) {
+ s32_t res;
+ const spiffs_free_obj_id_state *state = (const spiffs_free_obj_id_state*)user_const_p;
+ spiffs_page_object_ix_header objix_hdr;
+
+ res = _spiffs_rd(fs, SPIFFS_OP_T_OBJ_LU2 | SPIFFS_OP_C_READ,
+ 0, SPIFFS_OBJ_LOOKUP_ENTRY_TO_PADDR(fs, bix, ix_entry), sizeof(spiffs_page_object_ix_header), (u8_t*)&objix_hdr);
+ if (res == SPIFFS_OK && objix_hdr.p_hdr.span_ix == 0 &&
+ ((objix_hdr.p_hdr.flags & (SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_DELET)) ==
+ (SPIFFS_PH_FLAG_DELET))) {
+ // ok object look up entry
+ if (state->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<<j)) == 0) {
+ *obj_id = (i<<3)+j+state.min_obj_id;
+ return SPIFFS_OK;
+ }
+ }
+ }
+ return SPIFFS_ERR_FULL;
+ } else {
+ // not possible to represent all ids in range in a bitmap, compact and count
+ if (state.compaction != 0) {
+ // select element in compacted table, decrease range and recompact
+ u32_t i, min_i = 0;
+ u8_t *map = (u8_t *)fs->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 <organization> 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 <COPYRIGHT HOLDER> 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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sys/console.h>
+#include <sys/fcntl.h>
+#include <sys/time.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <organization> 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 <COPYRIGHT HOLDER> 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 <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <sys/tail.h>
+#include <sys/mount.h>
+#include <sys/status.h>
+#include <sys/path.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <organization> 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 <COPYRIGHT HOLDER> 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 <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <assert.h>
+
+#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 <organization> 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 <COPYRIGHT HOLDER> 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 <stdint.h>
+#include <stdbool.h>
+#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 <organization> 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 <COPYRIGHT HOLDER> 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 <FreeRTOS.h>
+#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 <organization> 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 <COPYRIGHT HOLDER> 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 <FreeRTOS.h>
+#include <semphr.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <stdio.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <organization> 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 <COPYRIGHT HOLDER> 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 <stdio.h>
+
+#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 <drivers/gpio.h>
+#include <drivers/spi.h>
+#include <drivers/power_bus.h>
+
+#include <sys/driver.h>
+#include <sys/mount.h>
+#include <sys/syslog.h>
+
+#include <sys/vfs/vfs.h>
+
+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 <stdint.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <utime.h>
+#include <FreeRTOS.h>
+#include <semphr.h>
+#include <esp_err.h>
+#include <sys/types.h>
+#include <sys/reent.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+//#include <sys/termios.h>
+//#include <../platform_include/sys/termios.h>
+//#include <sys/poll.h>
+#include <dirent.h>
+#include <string.h>
+//#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 <min_fd; max_fd).
+ *
+ * This is a special-purpose function intended for registering LWIP sockets to VFS.
+ *
+ * @param vfs Pointer to esp_vfs_t. Meaning is the same as for esp_vfs_register().
+ * @param ctx Pointer to context structure. Meaning is the same as for esp_vfs_register().
+ * @param min_fd The smallest file descriptor this VFS will use.
+ * @param max_fd Upper boundary for file descriptors this VFS will use (the biggest file descriptor plus one).
+ *
+ * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are
+ * registered, ESP_ERR_INVALID_ARG if the file descriptor boundaries
+ * are incorrect.
+ */
+esp_err_t esp_vfs_register_fd_range(const esp_vfs_t *vfs, void *ctx, int min_fd, int max_fd);
+
+/**
+ * Special case function for registering a VFS that uses a method other than
+ * open() to open new file descriptors. In comparison with
+ * esp_vfs_register_fd_range, this function doesn't pre-registers an interval
+ * of file descriptors. File descriptors can be registered later, by using
+ * esp_vfs_register_fd.
+ *
+ * @param vfs Pointer to esp_vfs_t. Meaning is the same as for esp_vfs_register().
+ * @param ctx Pointer to context structure. Meaning is the same as for esp_vfs_register().
+ * @param vfs_id Here will be written the VFS ID which can be passed to
+ * esp_vfs_register_fd for registering file descriptors.
+ *
+ * @return ESP_OK if successful, ESP_ERR_NO_MEM if too many VFSes are
+ * registered, ESP_ERR_INVALID_ARG if the file descriptor boundaries
+ * are incorrect.
+ */
+esp_err_t esp_vfs_register_with_id(const esp_vfs_t *vfs, void *ctx, esp_vfs_id_t *vfs_id);
+
+/**
+ * Unregister a virtual filesystem for given path prefix
+ *
+ * @param base_path file prefix previously used in esp_vfs_register call
+ * @return ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for given prefix
+ * hasn't been registered
+ */
+esp_err_t esp_vfs_unregister(const char* base_path);
+
+/**
+ * Special function for registering another file descriptor for a VFS registered
+ * by esp_vfs_register_with_id.
+ *
+ * @param vfs_id VFS identificator returned by esp_vfs_register_with_id.
+ * @param fd The registered file descriptor will be written to this address.
+ *
+ * @return ESP_OK if the registration is successful,
+ * ESP_ERR_NO_MEM if too many file descriptors are registered,
+ * ESP_ERR_INVALID_ARG if the arguments are incorrect.
+ */
+esp_err_t esp_vfs_register_fd(esp_vfs_id_t vfs_id, int *fd);
+
+/**
+ * Special function for unregistering a file descriptor belonging to a VFS
+ * registered by esp_vfs_register_with_id.
+ *
+ * @param vfs_id VFS identificator returned by esp_vfs_register_with_id.
+ * @param fd File descriptor which should be unregistered.
+ *
+ * @return ESP_OK if the registration is successful,
+ * ESP_ERR_INVALID_ARG if the arguments are incorrect.
+ */
+esp_err_t esp_vfs_unregister_fd(esp_vfs_id_t vfs_id, int fd);
+
+/**
+ * These functions are to be used in newlib syscall table. They will be called by
+ * newlib when it needs to use any of the syscalls.
+ */
+/**@{*/
+ssize_t esp_vfs_write(struct _reent *r, int fd, const void * data, size_t size);
+off_t esp_vfs_lseek(struct _reent *r, int fd, off_t size, int mode);
+ssize_t esp_vfs_read(struct _reent *r, int fd, void * dst, size_t size);
+int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode);
+int esp_vfs_close(struct _reent *r, int fd);
+int esp_vfs_fstat(struct _reent *r, int fd, struct stat * st);
+int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st);
+int esp_vfs_link(struct _reent *r, const char* n1, const char* n2);
+int esp_vfs_unlink(struct _reent *r, const char *path);
+int esp_vfs_rename(struct _reent *r, const char *src, const char *dst);
+int esp_vfs_utime(const char *path, const struct utimbuf *times);
+/**@}*/
+
+/**
+ * @brief Synchronous I/O multiplexing which implements the functionality of POSIX select() for VFS
+ * @param nfds Specifies the range of descriptors which should be checked.
+ * The first nfds descriptors will be checked in each set.
+ * @param readfds If not NULL, then points to a descriptor set that on input
+ * specifies which descriptors should be checked for being
+ * ready to read, and on output indicates which descriptors
+ * are ready to read.
+ * @param writefds If not NULL, then points to a descriptor set that on input
+ * specifies which descriptors should be checked for being
+ * ready to write, and on output indicates which descriptors
+ * are ready to write.
+ * @param errorfds If not NULL, then points to a descriptor set that on input
+ * specifies which descriptors should be checked for error
+ * conditions, and on output indicates which descriptors
+ * have error conditions.
+ * @param timeout If not NULL, then points to timeval structure which
+ * specifies the time period after which the functions should
+ * time-out and return. If it is NULL, then the function will
+ * not time-out.
+ *
+ * @return The number of descriptors set in the descriptor sets, or -1
+ * when an error (specified by errno) have occurred.
+ */
+int esp_vfs_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
+
+/**
+ * @brief Notification from a VFS driver about a read/write/error condition
+ *
+ * This function is called when the VFS driver detects a read/write/error
+ * condition as it was requested by the previous call to start_select.
+ *
+ * @param signal_sem semaphore handle which was passed to the driver by the start_select call
+ */
+void esp_vfs_select_triggered(SemaphoreHandle_t *signal_sem);
+
+/**
+ * @brief Notification from a VFS driver about a read/write/error condition (ISR version)
+ *
+ * This function is called when the VFS driver detects a read/write/error
+ * condition as it was requested by the previous call to start_select.
+ *
+ * @param signal_sem semaphore handle which was passed to the driver by the start_select call
+ * @param woken is set to pdTRUE if the function wakes up a task with higher priority
+ */
+void esp_vfs_select_triggered_isr(SemaphoreHandle_t *signal_sem, BaseType_t *woken);
+
+/**
+ * @brief Implements the VFS layer for synchronous I/O multiplexing by poll()
+ *
+ * The implementation is based on esp_vfs_select. The parameters and return values are compatible with POSIX poll().
+ *
+ * @param fds Pointer to the array containing file descriptors and events poll() should consider.
+ * @param nfds Number of items in the array fds.
+ * @param timeout Poll() should wait at least timeout milliseconds. If the value is 0 then it should return
+ * immediately. If the value is -1 then it should wait (block) until the event occurs.
+ *
+ * @return A positive return value indicates the number of file descriptors that have been selected. The 0
+ * return value indicates a timed-out poll. -1 is return on failure and errno is set accordingly.
+ *
+ */
+int esp_vfs_poll(struct pollfd *fds, nfds_t nfds, int timeout);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif //__ESP_VFS_H__
diff --git a/libiot/vfs/include/sys/dirent.h b/libiot/vfs/include/sys/dirent.h
new file mode 100644
index 0000000..ffc764c
--- /dev/null
+++ b/libiot/vfs/include/sys/dirent.h
@@ -0,0 +1,56 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+/**
+ * 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 <organization> 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 <COPYRIGHT HOLDER> 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 <freertos/FreeRTOS.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <dirent.h>
+
+#include <sys/stat.h>
+
+#include "esp_vfs.h"
+#include <errno.h>
+
+#include "lfs.h"
+
+#include <sys/syslog.h>
+#include <sys/mount.h>
+#include <sys/mutex.h>
+#include <sys/list.h>
+#include <sys/fcntl.h>
+#include <sys/vfs/vfs.h>
+#include <dirent.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <freertos/FreeRTOS.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <dirent.h>
+
+#include <sys/stat.h>
+
+#include "esp_vfs.h"
+
+#include <errno.h>
+
+#include "ramfs.h"
+
+#include <sys/syslog.h>
+#include <sys/mount.h>
+#include <sys/mutex.h>
+#include <sys/list.h>
+#include <sys/fcntl.h>
+#include <sys/vfs/vfs.h>
+#include <dirent.h>
+
+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 <organization> 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 <COPYRIGHT HOLDER> 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 <FreeRTOS.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include <sys/stat.h>
+
+#include "esp_vfs.h"
+//#include "esp_attr.h"
+#include <errno.h>
+
+#include "../spiffs/spiffs.h"
+#include "../spiffs/k210_spiffs.h"
+#include "../spiffs/spiffs_nucleus.h"
+//#include <sys/syslog.h>
+//#include <sys/mount.h>
+#include "../sys/mutex.h"
+#include "../sys/list.h"
+#include <sys/fcntl.h>
+#include "vfs.h"
+#include <dirent.h>
+
+#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 <organization> 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 <COPYRIGHT HOLDER> 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 <stdarg.h>
+#include <unistd.h>
+
+//#include <sys/mount.h>
+
+#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