From bbd8c9b41152630e0b178a6264e5686aedf5db64 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 28 Jul 2015 10:26:43 +0300 Subject: [PATCH] FS wrapper --- cores/esp8266/FS.cpp | 230 ++++++++++++++++++ cores/esp8266/FS.h | 106 ++++++++ cores/esp8266/FSImpl.h | 67 ++++++ cores/esp8266/interrupts.h | 30 +++ cores/esp8266/spiffs/spiffs_config.h | 17 +- cores/esp8266/spiffs/spiffs_gc.c | 1 - cores/esp8266/spiffs_api.cpp | 348 +++++++++++++++++++++++++++ cores/esp8266/spiffs_hal.cpp | 192 +++++++++++++++ tests/FSWrapper/FSWrapper.ino | 76 ++++++ 9 files changed, 1058 insertions(+), 9 deletions(-) create mode 100644 cores/esp8266/FS.cpp create mode 100644 cores/esp8266/FS.h create mode 100644 cores/esp8266/FSImpl.h create mode 100644 cores/esp8266/interrupts.h create mode 100644 cores/esp8266/spiffs_api.cpp create mode 100644 cores/esp8266/spiffs_hal.cpp create mode 100644 tests/FSWrapper/FSWrapper.ino diff --git a/cores/esp8266/FS.cpp b/cores/esp8266/FS.cpp new file mode 100644 index 000000000..b2d32fbb5 --- /dev/null +++ b/cores/esp8266/FS.cpp @@ -0,0 +1,230 @@ +/* + FS.cpp - file system wrapper + Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "FS.h" +#include "FSImpl.h" + +static bool sflags(const char* mode, OpenMode& om, AccessMode& am); + +size_t File::write(uint8_t c) { + if (!_p) + return 0; + + _p->write(&c, 1); +} + +size_t File::write(const uint8_t *buf, size_t size) { + if (!_p) + return 0; + + _p->write(buf, size); +} + +int File::available() { + if (!_p) + return false; + + return _p->position() < _p->size(); +} + +int File::read() { + if (!_p) + return -1; + + uint8_t result; + if (_p->read(&result, 1) != 1) { + return -1; + } + + return result; +} + +size_t File::read(uint8_t* buf, size_t size) { + if (!_p) + return -1; + + return _p->read(buf, size); +} + +int File::peek() { + if (!_p) + return -1; + + size_t curPos = _p->position(); + int result = read(); + seek(curPos, SeekSet); + return result; +} + +void File::flush() { + if (!_p) + return; + + _p->flush(); +} + +bool File::seek(uint32_t pos, SeekMode mode) { + if (!_p) + return false; + + return _p->seek(pos, mode); +} + +size_t File::position() const { + if (!_p) + return 0; + + return _p->position(); +} + +size_t File::size() const { + if (!_p) + return 0; + + return _p->size(); +} + +void File::close() { + if (_p) { + _p->close(); + _p = nullptr; + } +} + +File::operator bool() const { + return !!_p; +} + +File Dir::openFile(const char* mode) { + if (!_impl) { + return File(); + } + + OpenMode om; + AccessMode am; + if (!sflags(mode, om, am)) { + DEBUGV("Dir::openFile: invalid mode `%s`\r\n", mode); + return File(); + } + + return File(_impl->openFile(om, am)); +} + +String Dir::fileName() { + if (!_impl) { + return String(); + } + + return _impl->fileName(); +} + +bool Dir::next() { + if (!_impl) { + return false; + } + + return _impl->next(); +} + + + +File FS::open(const char* path, const char* mode) { + if (!_impl) { + return File(); + } + + OpenMode om; + AccessMode am; + if (!sflags(mode, om, am)) { + DEBUGV("FS::open: invalid mode `%s`\r\n", mode); + return File(); + } + + return File(_impl->open(path, om, am)); +} + +File FS::open(const String& path, const char* mode) { + return FS::open(path.c_str(), mode); +} + +Dir FS::openDir(const char* path) { + if (!_impl) { + return Dir(); + } + + return Dir(_impl->openDir(path)); +} + +Dir FS::openDir(const String& path) { + return FS::openDir(path.c_str()); +} + +struct MountEntry { + FSImplPtr fs; + String path; + MountEntry* next; +}; + +static MountEntry* s_mounted = nullptr; + +template<> +bool mount(FS& fs, const char* mountPoint) { + FSImplPtr p = fs._impl; + if (!p || !p->mount()) { + DEBUGV("FSImpl mount failed\r\n"); + return false; + } + + MountEntry* entry = new MountEntry; + entry->fs = p; + entry->path = mountPoint; + entry->next = s_mounted; + s_mounted = entry; + return true; +} + +static bool sflags(const char* mode, OpenMode& om, AccessMode& am) { + switch (mode[0]) { + case 'r': + am = AM_READ; + om = OM_DEFAULT; + break; + case 'w': + am = AM_WRITE; + om = (OpenMode) (OM_CREATE | OM_TRUNCATE); + break; + case 'a': + am = AM_WRITE; + om = (OpenMode) (OM_CREATE | OM_APPEND); + break; + default: + return false; + } + switch(mode[1]) { + case '+': + am = (AccessMode) (AM_WRITE | AM_READ); + break; + case 0: + break; + default: + return false; + } + return true; +} diff --git a/cores/esp8266/FS.h b/cores/esp8266/FS.h new file mode 100644 index 000000000..e45871a62 --- /dev/null +++ b/cores/esp8266/FS.h @@ -0,0 +1,106 @@ +/* + FS.h - file system wrapper + Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef FS_H +#define FS_H + +#include +#include + +class FileImpl; +typedef std::shared_ptr FileImplPtr; +class FSImpl; +typedef std::shared_ptr FSImplPtr; +class DirImpl; +typedef std::shared_ptr DirImplPtr; + +template +bool mount(Tfs& fs, const char* mountPoint); + +enum SeekMode { + SeekSet = 0, + SeekCur = 1, + SeekEnd = 2 +}; + +class File : public Stream +{ +public: + File(FileImplPtr p = FileImplPtr()) : _p(p) {} + + // Print methods: + size_t write(uint8_t) override; + size_t write(const uint8_t *buf, size_t size) override; + + // Stream methods: + int available() override; + int read() override; + int peek() override; + void flush() override; + + size_t read(uint8_t* buf, size_t size); + bool seek(uint32_t pos, SeekMode mode); + size_t position() const; + size_t size() const; + void close(); + operator bool() const; + +protected: + FileImplPtr _p; +}; + +class Dir { +public: + Dir(DirImplPtr impl = DirImplPtr()): _impl(impl) { } + + File openFile(const char* mode); + String fileName(); + bool next(); + +protected: + DirImplPtr _impl; +}; + +class FS +{ +public: + FS(FSImplPtr impl) : _impl(impl) { } + File open(const char* path, const char* mode); + File open(const String& path, const char* mode); + Dir openDir(const char* path); + Dir openDir(const String& path); + +protected: + FSImplPtr _impl; + + template + friend bool mount(Tfs& fs, const char* mountPoint); +}; + +extern FS SPIFFS; + +template<> +bool mount(FS& fs, const char* mountPoint); + +File open(const char* path, const char* mode); + +Dir openDir(const char* path); + +#endif //FS_H diff --git a/cores/esp8266/FSImpl.h b/cores/esp8266/FSImpl.h new file mode 100644 index 000000000..1bc6992d7 --- /dev/null +++ b/cores/esp8266/FSImpl.h @@ -0,0 +1,67 @@ +/* + FSImpl.h - base file system interface + Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef FSIMPL_H +#define FSIMPL_H + +#include +#include + +class FileImpl { +public: + virtual ~FileImpl() { } + virtual size_t write(const uint8_t *buf, size_t size) = 0; + virtual size_t read(uint8_t* buf, size_t size) = 0; + virtual void flush() = 0; + virtual bool seek(uint32_t pos, SeekMode mode) = 0; + virtual size_t position() const = 0; + virtual size_t size() const = 0; + virtual void close() = 0; +}; + +enum OpenMode { + OM_DEFAULT = 0, + OM_CREATE = 1, + OM_APPEND = 2, + OM_TRUNCATE = 4 +}; + +enum AccessMode { + AM_READ = 1, + AM_WRITE = 2, + AM_RW = AM_READ | AM_WRITE +}; + +class DirImpl { +public: + virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0; + virtual const char* fileName() = 0; + virtual bool next() = 0; +}; + +class FSImpl { +public: + virtual FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) = 0; + virtual DirImplPtr openDir(const char* path) = 0; + virtual bool mount() = 0; + +}; + + +#endif //FSIMPL_H diff --git a/cores/esp8266/interrupts.h b/cores/esp8266/interrupts.h new file mode 100644 index 000000000..d86f89b1f --- /dev/null +++ b/cores/esp8266/interrupts.h @@ -0,0 +1,30 @@ +#ifndef INTERRUPTS_H +#define INTERRUPTS_H + +#include +#include +extern "C" { +#include "c_types.h" +#include "ets_sys.h" +} + + +#define xt_disable_interrupts(state, level) __asm__ __volatile__("rsil %0," __STRINGIFY(level) : "=a" (state)) +#define xt_enable_interrupts(state) __asm__ __volatile__("wsr %0,ps; isync" :: "a" (state) : "memory") + +class InterruptLock { +public: + InterruptLock() { + xt_disable_interrupts(_state, 15); + } + + ~InterruptLock() { + xt_enable_interrupts(_state); + } + +protected: + uint32_t _state; +}; + + +#endif //INTERRUPTS_H diff --git a/cores/esp8266/spiffs/spiffs_config.h b/cores/esp8266/spiffs/spiffs_config.h index fa4bbd7b1..10497c819 100644 --- a/cores/esp8266/spiffs/spiffs_config.h +++ b/cores/esp8266/spiffs/spiffs_config.h @@ -8,15 +8,16 @@ #ifndef SPIFFS_CONFIG_H_ #define SPIFFS_CONFIG_H_ -#include "mem.h" +#include +#include +#include #include "c_types.h" -#include "stddef.h" -#include "osapi.h" #include "ets_sys.h" -#define c_memcpy os_memcpy -#define c_printf os_printf -#define c_memset os_memset + +#define c_memcpy memcpy +#define c_printf ets_printf +#define c_memset memset typedef signed short file_t; typedef int32_t s32_t; @@ -65,7 +66,7 @@ typedef uint8_t u8_t; // 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 +#define SPIFFS_BUFFER_HELP 1 #endif // Enables/disable memory read caching of nucleus file system operations. @@ -140,7 +141,7 @@ typedef uint8_t u8_t; // not on mount point. If not, SPIFFS_format must be called prior to mounting // again. #ifndef SPIFFS_USE_MAGIC -#define SPIFFS_USE_MAGIC (0) +#define SPIFFS_USE_MAGIC (1) #endif // SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level diff --git a/cores/esp8266/spiffs/spiffs_gc.c b/cores/esp8266/spiffs/spiffs_gc.c index 87e4faf90..fff659e3a 100644 --- a/cores/esp8266/spiffs/spiffs_gc.c +++ b/cores/esp8266/spiffs/spiffs_gc.c @@ -556,4 +556,3 @@ s32_t spiffs_gc_clean(spiffs *fs, spiffs_block_ix bix) { return res; } - diff --git a/cores/esp8266/spiffs_api.cpp b/cores/esp8266/spiffs_api.cpp new file mode 100644 index 000000000..d86f18272 --- /dev/null +++ b/cores/esp8266/spiffs_api.cpp @@ -0,0 +1,348 @@ +/* + spiffs_api.cpp - file system wrapper for SPIFFS + Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. + + This code was influenced by NodeMCU and Sming libraries, and first version of + Arduino wrapper written by Hristo Gochkov. + + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "FS.h" +#include "FSImpl.h" +#include "spiffs/spiffs.h" +#include "debug.h" + +extern "C" { +#include "c_types.h" +#include "spi_flash.h" +} + +extern int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src); +extern int32_t spiffs_hal_erase(uint32_t addr, uint32_t size); +extern int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst); + +int getSpiffsMode(OpenMode openMode, AccessMode accessMode); + +class SPIFFSFileImpl; +class SPIFFSDirImpl; + +class SPIFFSImpl : public FSImpl { +public: + SPIFFSImpl(uint32_t start, uint32_t size, uint32_t pageSize, uint32_t blockSize, uint32_t maxOpenFds) + : _fs({0}) + , _start(start) + , _size(size) + , _pageSize(pageSize) + , _blockSize(blockSize) + , _maxOpenFds(maxOpenFds) + { + } + + FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) override; + + DirImplPtr openDir(const char* path) override; + + bool mount() override { + if (SPIFFS_mounted(&_fs) != 0) { + return true; + } + + if (_tryMount()) { + return true; + } + + auto rc = SPIFFS_format(&_fs); + if (rc != SPIFFS_OK) { + DEBUGV("SPIFFS_format: rc=%d, err=%d\r\n", rc, _fs.err_code); + return false; + } + + return _tryMount(); + } + +protected: + friend class SPIFFSFileImpl; + friend class SPIFFSDirImpl; + + spiffs* getFs() { + return &_fs; + } + + bool _tryMount() { + spiffs_config config = {0}; + + config.hal_read_f = &spiffs_hal_read; + config.hal_write_f = &spiffs_hal_write; + config.hal_erase_f = &spiffs_hal_erase; + config.phys_size = _size; + config.phys_addr = _start; + config.phys_erase_block = SPI_FLASH_SEC_SIZE; + config.log_block_size = _blockSize; + config.log_page_size = _pageSize; + + // hack: even though fs is not initialized at this point, + // SPIFFS_buffer_bytes_for_cache uses only fs->config.log_page_size + // suggestion: change SPIFFS_buffer_bytes_for_cache to take + // spiffs_config* instead of spiffs* as an argument + _fs.cfg.log_page_size = config.log_page_size; + + size_t workBufSize = 2 * _pageSize; + size_t fdsBufSize = SPIFFS_buffer_bytes_for_filedescs(&_fs, _maxOpenFds); + size_t cacheBufSize = SPIFFS_buffer_bytes_for_cache(&_fs, _maxOpenFds); + + if (!_workBuf) { + DEBUGV("SPIFFSImpl: allocating %d+%d+%d=%d bytes\r\n", + workBufSize, fdsBufSize, cacheBufSize, + workBufSize + fdsBufSize + cacheBufSize); + _workBuf.reset(new uint8_t[workBufSize]); + _fdsBuf.reset(new uint8_t[fdsBufSize]); + _cacheBuf.reset(new uint8_t[cacheBufSize]); + } + + DEBUGV("SPIFFSImpl: mounting fs @%x, size=%x, block=%x, page=%x\r\n", + _start, _size, _blockSize, _pageSize); + + auto err = SPIFFS_mount(&_fs, &config, _workBuf.get(), + _fdsBuf.get(), fdsBufSize, _cacheBuf.get(), cacheBufSize, + &SPIFFSImpl::_check_cb); + + DEBUGV("SPIFFSImpl: mount rc=%d\r\n", err); + + return err == SPIFFS_OK; + } + + static void _check_cb(spiffs_check_type type, spiffs_check_report report, + uint32_t arg1, uint32_t arg2) { + // TODO: spiffs doesn't pass any context pointer along with _check_cb, + // so we can't do anything useful here other than perhaps + // feeding the watchdog + } + + spiffs _fs; + + uint32_t _start; + uint32_t _size; + uint32_t _pageSize; + uint32_t _blockSize; + uint32_t _maxOpenFds; + + std::unique_ptr _workBuf; + std::unique_ptr _fdsBuf; + std::unique_ptr _cacheBuf; +}; + +#define CHECKFD() while (_fd == 0) { DEBUGV("SPIFFSFileImpl(%d) _fd == 0\r\n", __LINE__); abort(); } + +class SPIFFSFileImpl : public FileImpl { +public: + SPIFFSFileImpl(SPIFFSImpl* fs, spiffs_file fd) + : _fs(fs) + , _fd(fd) + , _stat({0}) + { + CHECKFD(); + auto rc = SPIFFS_fstat(_fs->getFs(), _fd, &_stat); + if (rc != SPIFFS_OK) { + DEBUGV("SPIFFS_fstat rc=%d\r\n", rc); + _stat = {0}; + } + } + + ~SPIFFSFileImpl() override { + close(); + } + + size_t write(const uint8_t *buf, size_t size) override { + CHECKFD(); + + auto result = SPIFFS_write(_fs->getFs(), _fd, (void*) buf, size); + if (result < 0) { + DEBUGV("SPIFFS_write rc=%d\r\n", result); + return 0; + } + + return result; + } + + size_t read(uint8_t* buf, size_t size) override { + CHECKFD(); + + auto result = SPIFFS_read(_fs->getFs(), _fd, (void*) buf, size); + if (result < 0) { + DEBUGV("SPIFFS_read rc=%d\r\n", result); + return 0; + } + + return result; + } + + void flush() override { + CHECKFD(); + + auto rc = SPIFFS_fflush(_fs->getFs(), _fd); + if (rc < 0) { + DEBUGV("SPIFFS_fflush rc=%d\r\n", rc); + } + } + + bool seek(uint32_t pos, SeekMode mode) override { + CHECKFD(); + + auto rc = SPIFFS_lseek(_fs->getFs(), _fd, pos, (int) mode); + if (rc < 0) { + DEBUGV("SPIFFS_lseek rc=%d\r\n", rc); + return false; + } + + return true; + } + + size_t position() const override { + CHECKFD(); + + auto result = SPIFFS_tell(_fs->getFs(), _fd); + if (result < 0) { + DEBUGV("SPIFFS_tell rc=%d\r\n", result); + return 0; + } + + return result; + } + + size_t size() const override { + CHECKFD(); + + return _stat.size; + } + + void close() override { + CHECKFD(); + + SPIFFS_close(_fs->getFs(), _fd); + DEBUGV("SPIFFS_close: fd=%d\r\n", _fd); + } + +protected: + SPIFFSImpl* _fs; + spiffs_file _fd; + spiffs_stat _stat; +}; + +class SPIFFSDirImpl : public DirImpl { +public: + SPIFFSDirImpl(SPIFFSImpl* fs, spiffs_DIR& dir) + : _fs(fs) + , _dir(dir) + , _valid(false) + { + } + + FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) override { + if (!_valid) { + return FileImplPtr(); + } + int mode = getSpiffsMode(openMode, accessMode); + spiffs_file fd = SPIFFS_open_by_dirent(_fs->getFs(), &_dirent, mode, 0); + if (fd < 0) { + DEBUGV("SPIFFSDirImpl::openFile: fd=%d path=`%s` openMode=%d accessMode=%d err=%d\r\n", + fd, _dirent.name, openMode, accessMode, _fs.err_code); + return FileImplPtr(); + } + return std::make_shared(_fs, fd); + } + + const char* fileName() override { + if (!_valid) + return nullptr; + + return (const char*) _dirent.name; + } + + bool next() override { + spiffs_dirent* result = SPIFFS_readdir(&_dir, &_dirent); + _valid = (result != nullptr); + return _valid; + } + +protected: + SPIFFSImpl* _fs; + spiffs_DIR _dir; + spiffs_dirent _dirent; + bool _valid; +}; + + +FileImplPtr SPIFFSImpl::open(const char* path, OpenMode openMode, AccessMode accessMode) { + int mode = getSpiffsMode(openMode, accessMode); + char tmpName[SPIFFS_OBJ_NAME_LEN]; + strlcpy(tmpName, path, sizeof(tmpName)); + int fd = SPIFFS_open(&_fs, tmpName, mode, 0); + if (fd < 0) { + DEBUGV("SPIFFSImpl::open: fd=%d path=`%s` openMode=%d accessMode=%d err=%d\r\n", + fd, path, openMode, accessMode, _fs.err_code); + return FileImplPtr(); + } + return std::make_shared(this, fd); +} + +DirImplPtr SPIFFSImpl::openDir(const char* path) { + spiffs_DIR dir; + char tmpName[SPIFFS_OBJ_NAME_LEN]; + strlcpy(tmpName, path, sizeof(tmpName)); + spiffs_DIR* result = SPIFFS_opendir(&_fs, tmpName, &dir); + if (!result) { + DEBUGV("SPIFFSImpl::openDir: path=`%s` err=%d\r\n", path, _fs.err_code); + return DirImplPtr(); + } + return std::make_shared(this, dir); +} + +int getSpiffsMode(OpenMode openMode, AccessMode accessMode) { + int mode = 0; + if (openMode & OM_CREATE) { + mode |= SPIFFS_CREAT; + } + if (openMode & OM_APPEND) { + mode |= SPIFFS_APPEND; + } + if (openMode & OM_TRUNCATE) { + mode |= SPIFFS_TRUNC; + } + if (accessMode & AM_READ) { + mode |= SPIFFS_RDONLY; + } + if (accessMode & AM_WRITE) { + mode |= SPIFFS_WRONLY; + } + return mode; +} + +// these symbols should be defined in the linker script for each flash layout +extern "C" uint32_t _SPIFFS_start; +extern "C" uint32_t _SPIFFS_end; +extern "C" uint32_t _SPIFFS_page; +extern "C" uint32_t _SPIFFS_block; + +static SPIFFSImpl s_defaultFs( + (uint32_t) &_SPIFFS_start - 0x40200000, + (uint32_t) (&_SPIFFS_end - &_SPIFFS_start), + (uint32_t) &_SPIFFS_page, + (uint32_t) &_SPIFFS_block, + 5); + +FS SPIFFS = FS(FSImplPtr(&s_defaultFs)); diff --git a/cores/esp8266/spiffs_hal.cpp b/cores/esp8266/spiffs_hal.cpp new file mode 100644 index 000000000..2719eb6e0 --- /dev/null +++ b/cores/esp8266/spiffs_hal.cpp @@ -0,0 +1,192 @@ +/* + spiffs_hal.cpp - SPI read/write/erase functions for SPIFFS. + Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include "spiffs/spiffs.h" +#include "debug.h" +#include "interrupts.h" + +extern "C" { +#include "c_types.h" +#include "spi_flash.h" +} + +static int spi_flash_read_locked(uint32_t addr, uint32_t* dst, uint32_t size) { + InterruptLock lock; + return spi_flash_read(addr, dst, size); +} + +static int spi_flash_write_locked(uint32_t addr, const uint32_t* src, uint32_t size) { + InterruptLock lock; + return spi_flash_write(addr, (uint32_t*) src, size); +} + +static int spi_flash_erase_sector_locked(uint32_t sector) { + optimistic_yield(10000); + InterruptLock lock; + return spi_flash_erase_sector(sector); +} + + +/* + spi_flash_read function requires flash address to be aligned on word boundary. + We take care of this by reading first and last words separately and memcpy + relevant bytes into result buffer. + +alignment: 012301230123012301230123 +bytes requested: -------***********------ +read directly: --------xxxxxxxx-------- +read pre: ----aaaa---------------- +read post: ----------------bbbb---- +alignedBegin: ^ +alignedEnd: ^ +*/ + +int32_t spiffs_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) { + uint32_t result = SPIFFS_OK; + uint32_t alignedBegin = (addr + 3) & (~3); + uint32_t alignedEnd = (addr + size) & (~3); + + if (addr < alignedBegin) { + uint32_t nb = alignedBegin - addr; + uint32_t tmp; + if (spi_flash_read_locked(alignedBegin - 4, &tmp, 4) != SPI_FLASH_RESULT_OK) { + DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n", + __LINE__, addr, size, alignedBegin, alignedEnd); + return SPIFFS_ERR_INTERNAL; + } + memcpy(dst, &tmp + 4 - nb, nb); + } + + if (alignedEnd != alignedBegin) { + if (spi_flash_read_locked(alignedBegin, (uint32_t*) (dst + alignedBegin - addr), + alignedEnd - alignedBegin) != SPI_FLASH_RESULT_OK) { + DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n", + __LINE__, addr, size, alignedBegin, alignedEnd); + return SPIFFS_ERR_INTERNAL; + } + } + + if (addr + size > alignedEnd) { + uint32_t nb = addr + size - alignedEnd; + uint32_t tmp; + if (spi_flash_read_locked(alignedEnd, &tmp, 4) != SPI_FLASH_RESULT_OK) { + DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n", + __LINE__, addr, size, alignedBegin, alignedEnd); + return SPIFFS_ERR_INTERNAL; + } + + memcpy(dst + size - nb, &tmp, nb); + } + + return result; +} + +/* + Like spi_flash_read, spi_flash_write has a requirement for flash address to be + aligned. However it also requires RAM address to be aligned as it reads data + in 32-bit words. Flash address (mis-)alignment is handled much the same way + as for reads, but for RAM alignment we have to copy data into a temporary + buffer. The size of this buffer is a tradeoff between number of writes required + and amount of stack required. This is chosen to be 512 bytes here, but might + be adjusted in the future if there are good reasons to do so. +*/ + +static const int UNALIGNED_WRITE_BUFFER_SIZE = 512; + +int32_t spiffs_hal_write(uint32_t addr, uint32_t size, uint8_t *src) { + uint32_t alignedBegin = (addr + 3) & (~3); + uint32_t alignedEnd = (addr + size) & (~3); + + if (addr < alignedBegin) { + uint32_t nb = alignedBegin - addr; + uint32_t tmp = 0xffffffff; + memcpy(((uint8_t* )&tmp) + 4 - nb, src, nb); + if (spi_flash_write_locked(alignedBegin - 4, &tmp, 4) != SPI_FLASH_RESULT_OK) { + DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n", + __LINE__, addr, size, alignedBegin, alignedEnd); + return SPIFFS_ERR_INTERNAL; + } + } + + if (alignedEnd != alignedBegin) { + uint32_t* srcLeftover = (uint32_t*) (src + alignedBegin - addr); + uint32_t srcAlign = ((uint32_t) srcLeftover) & 3; + if (!srcAlign) { + if (spi_flash_write_locked(alignedBegin, (uint32_t*) srcLeftover, + alignedEnd - alignedBegin) != SPI_FLASH_RESULT_OK) { + DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n", + __LINE__, addr, size, alignedBegin, alignedEnd); + return SPIFFS_ERR_INTERNAL; + } + } + else { + uint8_t buf[UNALIGNED_WRITE_BUFFER_SIZE]; + for (uint32_t sizeLeft = alignedEnd - alignedBegin; sizeLeft; ) { + size_t willCopy = std::min(sizeLeft, sizeof(buf)); + memcpy(buf, srcLeftover, willCopy); + + if (spi_flash_write_locked(alignedBegin, (uint32_t*) buf, + willCopy) != SPI_FLASH_RESULT_OK) { + DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n", + __LINE__, addr, size, alignedBegin, alignedEnd); + return SPIFFS_ERR_INTERNAL; + } + + sizeLeft -= willCopy; + srcLeftover += willCopy; + alignedBegin += willCopy; + } + } + } + + if (addr + size > alignedEnd) { + uint32_t nb = addr + size - alignedEnd; + uint32_t tmp = 0xffffffff; + memcpy(&tmp, src + size - nb, nb); + + if (spi_flash_write_locked(alignedEnd, &tmp, 4) != SPI_FLASH_RESULT_OK) { + DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n", + __LINE__, addr, size, alignedBegin, alignedEnd); + return SPIFFS_ERR_INTERNAL; + } + } + + return SPIFFS_OK; +} + +int32_t spiffs_hal_erase(uint32_t addr, uint32_t size) { + if ((size & (SPI_FLASH_SEC_SIZE - 1)) != 0 || + (addr & (SPI_FLASH_SEC_SIZE - 1)) != 0) { + DEBUGV("_spif_erase called with addr=%x, size=%d\r\n", addr, size); + abort(); + } + const uint32_t sector = addr / SPI_FLASH_SEC_SIZE; + const uint32_t sectorCount = size / SPI_FLASH_SEC_SIZE; + for (uint32_t i = 0; i < sectorCount; ++i) { + if (spi_flash_erase_sector_locked(sector + i) != 0) { + DEBUGV("_spif_erase addr=%x size=%d i=%d\r\n", addr, size, i); + return SPIFFS_ERR_INTERNAL; + } + } + return SPIFFS_OK; +} diff --git a/tests/FSWrapper/FSWrapper.ino b/tests/FSWrapper/FSWrapper.ino new file mode 100644 index 000000000..95632f35e --- /dev/null +++ b/tests/FSWrapper/FSWrapper.ino @@ -0,0 +1,76 @@ +#include +#include "FS.h" + +void fail(const char* msg) { + Serial.println(msg); + while(true) { + yield(); + } +} + +void setup() { + Serial.begin(115200); + Serial.setDebugOutput(true); + WiFi.mode(WIFI_OFF); + Serial.println("\n\nFS test\n"); + + if (!mount(SPIFFS, "/")) { + fail("mount failed"); + } + + String text = "write test"; + { + File out = SPIFFS.open("/tmp.txt", "w"); + if (!out) { + fail("failed to open tmp.txt for writing"); + } + out.print(text); + } + + { + File in = SPIFFS.open("/tmp.txt", "r"); + if (!in) { + fail("failed to open tmp.txt for reading"); + } + Serial.printf("size=%d\r\n", in.size()); + if (in.size() != text.length()) { + fail("invalid size of tmp.txt"); + } + Serial.print("Reading data: "); + in.setTimeout(0); + String result = in.readString(); + Serial.println(result); + if (result != text) { + fail("invalid data in tmp.txt"); + } + } + + { + for (int i = 0; i < 10; ++i) { + String name = "seq_"; + name += i; + name += ".txt"; + + File out = SPIFFS.open(name, "w"); + if (!out) { + fail("can't open seq_ file"); + } + + out.println(i); + } + } + { + Dir root = SPIFFS.openDir("/"); + while (root.next()) { + String fileName = root.fileName(); + File f = root.openFile("r"); + Serial.printf("%s: %d\r\n", fileName.c_str(), f.size()); + } + } + + + Serial.println("success"); +} + +void loop() { +}