diff --git a/docker-build b/docker-build new file mode 100755 index 0000000000..198adb625f --- /dev/null +++ b/docker-build @@ -0,0 +1 @@ +docker run --rm -v $PWD:/sources infinitime-build diff --git a/docker/Dockerfile b/docker/Dockerfile index 7f0fb4b077..257f4fbfab 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,6 @@ RUN apt-get update -qq \ # x86_64 / generic packages bash \ build-essential \ - cmake \ git \ make \ python3 \ @@ -22,6 +21,16 @@ RUN apt-get update -qq \ RUN pip3 install adafruit-nrfutil +# install latest CMake (and utils) +RUN apt install -y \ + make +ARG CMAKE_VERSION=3.17.2 +ADD https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-Linux-x86_64.sh /tmp/cmake-install.sh +RUN apt install -y \ + tar gzip +RUN yes | sh /tmp/cmake-install.sh --skip-license --prefix=/ +RUN rm /tmp/cmake-install.sh + # build.sh knows how to compile COPY build.sh /opt/ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fde1f58672..e469ea5f06 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -476,6 +476,9 @@ include_directories( . ../ libs/ + # FIXME workaround hack + ${CMAKE_BINARY_DIR}/_deps/project_littlefs-src/ + components/littlefs/ FreeRTOS/ libs/date/includes libs/mynewt-nimble/porting/npl/freertos/include @@ -624,7 +627,8 @@ set(EXECUTABLE_FILE_NAME ${EXECUTABLE_NAME}-${pinetime_VERSION_MAJOR}.${pinetime set(NRF5_LINKER_SCRIPT "${CMAKE_SOURCE_DIR}/gcc_nrf52.ld") add_executable(${EXECUTABLE_NAME} ${SOURCE_FILES}) set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_FILE_NAME}) -target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl) +add_subdirectory("components/littlefs") +target_link_libraries(${EXECUTABLE_NAME} nimble nrf-sdk lvgl littlefs) target_compile_options(${EXECUTABLE_NAME} PUBLIC $<$,$>: ${COMMON_FLAGS} -O0 -g3> $<$,$>: ${COMMON_FLAGS} -O3> diff --git a/src/components/littlefs/CMakeLists.txt b/src/components/littlefs/CMakeLists.txt new file mode 100644 index 0000000000..f6148dce4f --- /dev/null +++ b/src/components/littlefs/CMakeLists.txt @@ -0,0 +1,52 @@ + +include(FetchContent) +set(FETCHCONTENT_QUIET OFF) # enable GIT_PROGRESS output + +FetchContent_Declare(project_littlefs + GIT_REPOSITORY https://github.com/littlefs-project/littlefs.git + GIT_SHALLOW ON + GIT_PROGRESS ON +) + +# littlefs Pull +#------------------------------------------------------------------------------- +if(NOT project_littlefs_POPULATED) + FetchContent_Populate(project_littlefs) +endif() + +add_library(littlefs + ${project_littlefs_SOURCE_DIR}/lfs_util.c + ${project_littlefs_SOURCE_DIR}/lfs.c +) +target_include_directories(littlefs + PUBLIC + ${project_littlefs_SOURCE_DIR}/ +) +target_link_libraries(littlefs littlefs-target) +target_compile_options(littlefs + PUBLIC + $<$,$>: ${COMMON_FLAGS} -O0 -g3> + $<$,$>: ${COMMON_FLAGS} -O3> + $<$,$>: ${COMMON_FLAGS} -O0 -g3> + $<$,$>: ${COMMON_FLAGS} -O3> + $<$: -MP -MD -std=c99 -x assembler-with-cpp> +) + +add_library(littlefs-target + LittleFs.cpp +) +target_include_directories(littlefs-target + PUBLIC + ./ + #FIXME refactor source into actual components + ../../ +) +target_link_libraries(littlefs-target littlefs) +target_compile_options(littlefs-target + PUBLIC + $<$,$>: ${COMMON_FLAGS} -O0 -g3> + $<$,$>: ${COMMON_FLAGS} -O3> + $<$,$>: ${COMMON_FLAGS} -O0 -g3> + $<$,$>: ${COMMON_FLAGS} -O3> + $<$: -MP -MD -std=c99 -x assembler-with-cpp> +) diff --git a/src/components/littlefs/LittleFs.cpp b/src/components/littlefs/LittleFs.cpp new file mode 100644 index 0000000000..3699c9052f --- /dev/null +++ b/src/components/littlefs/LittleFs.cpp @@ -0,0 +1,116 @@ +#include "LittleFs.h" + +using namespace Pinetime::System; + +static int read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + +static int prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + +static int erase(const struct lfs_config *c, lfs_block_t block); + +static int sync(const struct lfs_config *c); + +constexpr size_t BLOCK_SIZE_BYTES = 256u; +constexpr size_t PAGE_SIZE_BYTES = BLOCK_SIZE_BYTES; +constexpr size_t CACHE_PAGE_COUNT = 1u; +constexpr size_t CACHE_SIZE_BYTES = (CACHE_PAGE_COUNT * PAGE_SIZE_BYTES); +constexpr size_t LOOKAHEAD_SIZE_BYTES = (CACHE_PAGE_COUNT * 8u); +static uint8_t readBuffer[CACHE_SIZE_BYTES]; +static uint8_t progBuffer[CACHE_SIZE_BYTES]; +static uint8_t __attribute__((aligned(32))) + lookaheadBuffer[LOOKAHEAD_SIZE_BYTES]; +const static struct lfs_config baseLfsConfig = { + .read = read, + .prog = prog, + .erase = erase, + .sync = sync, + + .read_size = PAGE_SIZE_BYTES, + .prog_size = PAGE_SIZE_BYTES, + .block_size = BLOCK_SIZE_BYTES, + .block_cycles = 500u, + + .cache_size = CACHE_SIZE_BYTES, + .lookahead_size = LOOKAHEAD_SIZE_BYTES, + + .read_buffer = readBuffer, + .prog_buffer = progBuffer, + .lookahead_buffer = lookaheadBuffer, + + .name_max = 0u, /** use LFS default */ + .file_max = 0u, /** use LFS default */ + .attr_max = 0u, /** use LFS default */ +}; + +constexpr struct lfs_config createLfsConfig(Pinetime::System::LittleFs& lfs, const size_t totalSize_bytes) { + struct lfs_config config = baseLfsConfig; + config.context = &lfs; + config.block_count = totalSize_bytes / BLOCK_SIZE_BYTES; + return config; +} + +LittleFs::LittleFs(Pinetime::Drivers::SpiNorFlash& driver, + const size_t startAddress, + const size_t size_bytes, + const bool allowFormat) +:mDriver{driver}, mStartAddress{startAddress}, mSize_bytes{size_bytes}, + mLfsConfig{createLfsConfig(*this, size_bytes)} +{ + // try mount + if(0u != lfs_mount(&mLfs, &mLfsConfig)) { + if(allowFormat) { + // format and mount + if(0u == lfs_format(&mLfs, &mLfsConfig)) { + lfs_mount(&mLfs, &mLfsConfig); + } + } + } +} + +static int read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + Pinetime::System::LittleFs& lfs = *(reinterpret_cast(c->context)); + const size_t address = lfs.mStartAddress + (block * BLOCK_SIZE_BYTES) + off; + lfs.mDriver.Read(address, (uint8_t*)buffer, size); + // TODO assumes READ was successful + return 0u; +} + +static int prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + Pinetime::System::LittleFs& lfs = *(reinterpret_cast(c->context)); + const size_t address = lfs.mStartAddress + (block * BLOCK_SIZE_BYTES) + off; + lfs.mDriver.Write(address, (uint8_t*)buffer, size); + return lfs.mDriver.ProgramFailed() ? -1u : 0u; +} + +static int erase(const struct lfs_config *c, lfs_block_t block) { + Pinetime::System::LittleFs& lfs = *(reinterpret_cast(c->context)); + const size_t address = lfs.mStartAddress + (block * BLOCK_SIZE_BYTES); + lfs.mDriver.SectorErase(address); + return lfs.mDriver.EraseFailed() ? -1u : 0u; +} + +static int sync(const struct lfs_config *c) { + // no hardware caching used + return 0u; +} + +const LittleFs::LittleFsDir::LittleFsEntry LittleFs::LittleFsDir::next() { + lfs_info entryInfo; + const int rtn = lfs_dir_read(&mLfs, &mLfs_dir, &entryInfo); + if(rtn == 0) { + LittleFsEntry entry{ + .type = (entryInfo.type == LFS_TYPE_DIR) ? + LittleFs::LittleFsDir::LittleFsEntry::Type::DIR : + LittleFs::LittleFsDir::LittleFsEntry::Type::FILE, + .size = entryInfo.size + }; + strncpy(entry.name, entryInfo.name, sizeof(entry.name)); + return entry; + } + else + return LittleFsEntry{ .type = LittleFs::LittleFsDir::LittleFsEntry::Type::NULLENTRY }; +} \ No newline at end of file diff --git a/src/components/littlefs/LittleFs.h b/src/components/littlefs/LittleFs.h new file mode 100644 index 0000000000..590d34c57f --- /dev/null +++ b/src/components/littlefs/LittleFs.h @@ -0,0 +1,101 @@ +#pragma once + +#include +#include + +namespace Pinetime { + namespace System { + class LittleFs { + public: + class LittleFsFile; + class LittleFsDir; + explicit LittleFs(Pinetime::Drivers::SpiNorFlash&, + const size_t startAddress, ///< address of first block to use + const size_t size_bytes, ///< must be a multiple of block size + const bool allowFormat = true ///< if filesystem not detected, format the space + ); + LittleFs(const LittleFs&) = delete; + LittleFs& operator=(const LittleFs&) = delete; + LittleFs(LittleFs&&) = delete; + LittleFs& operator=(LittleFs&&) = delete; + + bool remove(const char* path) { return 0u == lfs_remove(&mLfs, path); } + bool rename(const char* oldpath, const char* newpath) { return 0u == lfs_rename(&mLfs, oldpath, newpath); } + ssize_t filesize(const char* path) { + struct lfs_info info; + if(0u == lfs_stat(&mLfs, path, &info)) + return info.size; + return -1; + } + /// file resource is closed upon leave of scope + LittleFsFile& open(const char* const path, + const int flags ///< @see lfs_open_flags + ) { return *(new LittleFsFile(mLfs, path, flags)); } + bool mkdir(const char* path) { return 0u == lfs_mkdir(&mLfs, path); } + /// dir resource is closed upon leave of scope + LittleFsDir& opendir(const char* path) { return *(new LittleFsDir(mLfs, path)); } + + // the following require owner to delete the instance + LittleFsFile* open_danger(const char* const path, + const int flags ///< @see lfs_open_flags + ) { return new LittleFsFile(mLfs, path, flags); } + LittleFsDir* opendir_danger(const char* const path) { return new LittleFsDir(mLfs, path); } + + Pinetime::Drivers::SpiNorFlash& mDriver; + const size_t mStartAddress; + const size_t mSize_bytes; + + private: + lfs_t mLfs; + const struct lfs_config mLfsConfig; + + public: + class LittleFsFile { + public: + explicit LittleFsFile(lfs_t& lfs, const char* const path, const int flags) + : mLfs{lfs} { mLfs_file.type = ~LFS_TYPE_REG; lfs_file_open(&mLfs, &mLfs_file, path, flags); } + LittleFsFile(const LittleFsFile&) = delete; + LittleFsFile& operator=(const LittleFsFile&) = delete; + LittleFsFile(LittleFsFile&&) = delete; + LittleFsFile& operator=(LittleFsFile&&) = delete; + ~LittleFsFile() { + lfs_file_close(&mLfs, &mLfs_file); + } + bool isNULL() { return mLfs_file.type != LFS_TYPE_REG; } + ssize_t read(void* buffer, size_t size) { return lfs_file_read(&mLfs, &mLfs_file, buffer, size); } + ssize_t write(const void* buffer, size_t size) { return lfs_file_write(&mLfs, &mLfs_file, buffer, size); } + ssize_t seek(size_t offset, size_t whence) { return 0u == lfs_file_seek(&mLfs, &mLfs_file, offset, whence); } + bool truncate(size_t sz) { return 0u == lfs_file_truncate(&mLfs, &mLfs_file, sz); } + /// return the current position within the file + ssize_t tell() { return lfs_file_tell(&mLfs, &mLfs_file); } + ssize_t size() { return lfs_file_size(&mLfs, &mLfs_file); } + private: + lfs_t& mLfs; + lfs_file_t mLfs_file; + }; + + class LittleFsDir { + public: + explicit LittleFsDir(lfs_t& lfs, const char* path) + : mLfs{lfs} { mLfs_dir.type = ~LFS_TYPE_DIR; lfs_dir_open(&mLfs, &mLfs_dir, path); } + LittleFsDir(const LittleFsDir&) = delete; + LittleFsDir& operator=(const LittleFsDir&) = delete; + LittleFsDir(LittleFsDir&&) = delete; + LittleFsDir& operator=(LittleFsDir&&) = delete; + ~LittleFsDir() { + lfs_dir_close(&mLfs, &mLfs_dir); + } + bool isNULL() { return mLfs_dir.type != LFS_TYPE_DIR; } + struct LittleFsEntry { + enum class Type : uint8_t { NULLENTRY, FILE, DIR } type; + char name[LFS_NAME_MAX+1]; + size_t size = 0; + }; + const LittleFsEntry next(); + private: + lfs_t& mLfs; + lfs_dir_t mLfs_dir; + }; + }; + } +}