diff --git a/docker-build b/docker-build new file mode 100755 index 0000000000..8b4abd44ac --- /dev/null +++ b/docker-build @@ -0,0 +1,2 @@ +# docker run --rm -v $PWD:/sources --env VERBOSE=1 infinitime-build +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..75b1f98763 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -367,6 +367,7 @@ list(APPEND SOURCE_FILES components/ble/CurrentTimeClient.cpp components/ble/AlertNotificationClient.cpp components/ble/DfuService.cpp + components/ble/FtpService.cpp components/ble/CurrentTimeService.cpp components/ble/AlertNotificationService.cpp components/ble/MusicService.cpp @@ -449,6 +450,7 @@ set(INCLUDE_FILES components/ble/CurrentTimeClient.h components/ble/AlertNotificationClient.h components/ble/DfuService.h + components/ble/FtpService.h components/firmwarevalidator/FirmwareValidator.h components/ble/BatteryInformationService.h components/ble/ImmediateAlertService.h @@ -475,6 +477,9 @@ set(INCLUDE_FILES include_directories( . ../ + # FIXME workaround hack + ${CMAKE_BINARY_DIR}/_deps/project_littlefs-src/ + components/littlefs/ libs/ FreeRTOS/ libs/date/includes @@ -624,7 +629,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> @@ -655,7 +661,8 @@ set(IMAGE_MCUBOOT_FILE_NAME image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_M set(DFU_FILE_NAME dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip) set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld") add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES}) -target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl) +# target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl) +target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl littlefs) set_target_properties(${EXECUTABLE_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_FILE_NAME}) target_compile_options(${EXECUTABLE_MCUBOOT_NAME} PUBLIC $<$,$>: ${COMMON_FLAGS} -O0 -g3> diff --git a/src/components/ble/FtpService.cpp b/src/components/ble/FtpService.cpp new file mode 100644 index 0000000000..4344b2e165 --- /dev/null +++ b/src/components/ble/FtpService.cpp @@ -0,0 +1,321 @@ +#include "FtpService.h" + +#include +#include + +using namespace Pinetime::Controllers; + +// provide linktime space for constants +constexpr ble_uuid128_t FtpService::serviceUuid; +constexpr uint16_t FtpService::revision; +constexpr ble_uuid128_t FtpService::revisionCharacteristicUuid; +constexpr ble_uuid128_t FtpService::pathCharacteristicUuid; +constexpr ble_uuid128_t FtpService::controlPointCharacteristicUuid; +constexpr ble_uuid128_t FtpService::packetCharacteristicUuid; + +/// C level hook to call class methods +int FtpServiceCallback(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) { + auto ftpService = static_cast(arg); + return ftpService->OnServiceData(conn_handle, attr_handle, ctxt); +} + +FtpService::FtpService(Pinetime::System::SystemTask &systemTask, + Pinetime::Controllers::Ble &bleController, + Pinetime::System::LittleFs &littleFs) +: mSystemTask{systemTask}, mBleController{bleController}, mLittleFs{littleFs}, + mCharacteristicDefinitions{ + { .uuid = reinterpret_cast(&revisionCharacteristicUuid), + .access_cb = FtpServiceCallback, .arg = this, + .flags = BLE_GATT_CHR_F_READ, .val_handle = nullptr }, + { .uuid = reinterpret_cast(&pathCharacteristicUuid), + .access_cb = FtpServiceCallback, .arg = this, + .flags = (BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_READ), .val_handle = nullptr }, + { .uuid = reinterpret_cast(&controlPointCharacteristicUuid), + .access_cb = FtpServiceCallback, .arg = this, + .flags = (BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY), .val_handle = nullptr }, + { .uuid = reinterpret_cast(&packetCharacteristicUuid), + .access_cb = FtpServiceCallback, .arg = this, + .flags = (BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_READ), .val_handle = nullptr }, + { 0 /* null terminator */ } + }, + mServiceDefinitions{ + { .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = reinterpret_cast(&serviceUuid), + .characteristics = mCharacteristicDefinitions + }, + { 0 /* null terminator */ } + } +{ + +} + +void FtpService::Init() { + int res; + res = ble_gatts_count_cfg(mServiceDefinitions); + ASSERT(res == 0); + + res = ble_gatts_add_svcs(mServiceDefinitions); + ASSERT(res == 0); +} + +int FtpService::OnServiceData(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context) { + + { uint16_t revisionCharacteristicHandle; + ble_gatts_find_chr(reinterpret_cast(&serviceUuid), + reinterpret_cast(&revisionCharacteristicUuid), + nullptr /* don't get defintion handle */, + &revisionCharacteristicHandle); + if(attributeHandle == revisionCharacteristicHandle) { + const int rtn = os_mbuf_append(context->om, &revision, sizeof(revision)); + return (rtn == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + } + + { uint16_t pathCharacteristicHandle; + ble_gatts_find_chr(reinterpret_cast(&serviceUuid), + reinterpret_cast(&pathCharacteristicUuid), + nullptr /* don't get defintion handle */, + &pathCharacteristicHandle); + if(attributeHandle == pathCharacteristicHandle) { + switch(context->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: { + const int rtn = os_mbuf_append(context->om, &mCurrentPath, mCurrentPath_sz); + return (rtn == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + case BLE_GATT_ACCESS_OP_WRITE_CHR: { + closeActive(); + mCurrentPath_sz = context->om->om_len; + memcpy(mCurrentPath, context->om->om_data, mCurrentPath_sz); + // always force in null terminator + mCurrentPath[mCurrentPath_sz - 1] = '\0'; + return 0; + } + default: + // nimble should never have called us + NRF_LOG_INFO("[DFU] Unknown Characteristic Access : %d", context->op); + return BLE_ATT_ERR_UNLIKELY; + } + } + } + + { uint16_t controlPointCharacteristicHandle; + ble_gatts_find_chr(reinterpret_cast(&serviceUuid), + reinterpret_cast(&controlPointCharacteristicUuid), + nullptr /* don't get defintion handle */, + &controlPointCharacteristicHandle); + if(attributeHandle == controlPointCharacteristicHandle) { + const auto opcode = static_cast(context->om->om_data[0]); + switch(opcode) { + case Opcodes::REMOVE: { + closeActive(); + int rtn = mLittleFs.remove(mCurrentPath); + uint8_t data[1]; + data[0] = (rtn == 0) ? static_cast(ResultCodes::SUCCESS) : + static_cast(ResultCodes::FAILURE); + rtn = os_mbuf_append(context->om, &data, sizeof(data)); + return (rtn == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + case Opcodes::MKDIR: { + closeActive(); + int rtn = mLittleFs.mkdir(mCurrentPath); + uint8_t data[1]; + data[0] = (rtn == 0) ? static_cast(ResultCodes::SUCCESS) : + static_cast(ResultCodes::FAILURE); + rtn = os_mbuf_append(context->om, &data, sizeof(data)); + return (rtn == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + case Opcodes::READ: { + closeActive(); + mFile = mLittleFs.open_danger(mCurrentPath, LFS_O_RDONLY); + uint8_t data[1]; + data[0] = ((mFile == nullptr) || mFile->isNULL()) ? + static_cast(ResultCodes::FAILURE) : + static_cast(ResultCodes::SUCCESS); + const int rtn = os_mbuf_append(context->om, &data, sizeof(data)); + return (rtn == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + case Opcodes::WRITE: { + closeActive(); + mFile = mLittleFs.open_danger(mCurrentPath, (LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC | LFS_O_APPEND)); + uint8_t data[1]; + data[0] = ((mFile == nullptr) || mFile->isNULL()) ? + static_cast(ResultCodes::FAILURE) : + static_cast(ResultCodes::SUCCESS); + const int rtn = os_mbuf_append(context->om, &data, sizeof(data)); + return (rtn == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + break; + } + case Opcodes::CLOSE: { + closeActive(); + const uint8_t data[1] = { static_cast(ResultCodes::SUCCESS) }; + const int rtn = os_mbuf_append(context->om, &data, sizeof(data)); + return (rtn == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + case Opcodes::LIST: { + closeActive(); + mDir = mLittleFs.opendir_danger(mCurrentPath); + uint8_t data[1]; + data[0] = ((mDir == nullptr) || mDir->isNULL()) ? + static_cast(ResultCodes::FAILURE) : + static_cast(ResultCodes::SUCCESS); + const int rtn = os_mbuf_append(context->om, &data, sizeof(data)); + return (rtn == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + default: + return BLE_ATT_ERR_REQ_NOT_SUPPORTED; + } + } + } + + { uint16_t packetCharacteristicHandle; + ble_gatts_find_chr(reinterpret_cast(&serviceUuid), + reinterpret_cast(&packetCharacteristicUuid), + nullptr /* don't get defintion handle */, + &packetCharacteristicHandle); + if(attributeHandle == packetCharacteristicHandle) { + switch(context->op) { + case BLE_GATT_ACCESS_OP_WRITE_CHR: { + return handleWrite(context); + } + case BLE_GATT_ACCESS_OP_READ_CHR: { + return handleRead(context); + } + default: + // nimble should never have called us + NRF_LOG_INFO("[DFU] Unknown Characteristic : %d", attributeHandle); + return BLE_ATT_ERR_UNLIKELY; + } + } + } + + // nimble should never have called us + NRF_LOG_INFO("[DFU] Unknown Characteristic : %d", attributeHandle); + return BLE_ATT_ERR_UNLIKELY; +} + +void FtpService::closeActive() { + // deletion will invoke the destructor which releases LFS resources + delete mFile; + mFile = nullptr; + delete mDir; + mDir = nullptr; +} + +int FtpService::handleWrite(ble_gatt_access_ctxt* context) { + if(mFile != nullptr) { + // handle file write + const int rtn = mFile->write(context->om->om_data, context->om->om_len); + return (rtn == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + return BLE_ATT_ERR_WRITE_NOT_PERMITTED; +} + +static int sendEntry(ble_gatt_access_ctxt* context, const Pinetime::System::LittleFs::LittleFsDir::LittleFsEntry entry); + +int FtpService::handleRead(ble_gatt_access_ctxt* context) { + constexpr size_t GATT_MAX = 512; + if(mFile != nullptr) { + // handle file read + auto buffer = new uint8_t[GATT_MAX]; + int rtn = mFile->read(buffer, sizeof(buffer)); + if (rtn == 0) + rtn = os_mbuf_append(context->om, buffer, sizeof(buffer)); + delete[] buffer; + return (rtn == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + else if(mDir != nullptr) { + switch(mListingState) { + case ListingState::DONE: { + return 0; + } + case ListingState::HDR_1: { + constexpr char data[] = "\n"; + const int rtn = os_mbuf_append(context->om, &data, sizeof(data)); + if(rtn == 0) { + mListingState = ListingState::HDR_2; + return 0; + } + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + case ListingState::HDR_2: { + constexpr char data[] = "\n"; + const int rtn = os_mbuf_append(context->om, &data, sizeof(data)); + if(rtn == 0) { + mListingState = ListingState::HDR_3; + return 0; + } + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + case ListingState::HDR_3: { + constexpr char data[] = "\n\n"; + const int rtn = os_mbuf_append(context->om, &data, sizeof(data)); + if(rtn == 0) { + mListingState = ListingState::ENTRIES; + return 0; + } + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + case ListingState::ENTRIES: { + System::LittleFs::LittleFsDir::LittleFsEntry entry = mDir->next(); + if(entry.type != System::LittleFs::LittleFsDir::LittleFsEntry::Type::NULLENTRY) { + return sendEntry(context, entry); + } + // fallthrough to FTR case + } + case ListingState::FTR: { + constexpr char data[] = "\n"; + const int rtn = os_mbuf_append(context->om, &data, sizeof(data)); + if(rtn == 0) { + mListingState = ListingState::DONE; + return 0; + } + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + } + } + return BLE_ATT_ERR_READ_NOT_PERMITTED; +} + +static int sendEntry(ble_gatt_access_ctxt* context, const Pinetime::System::LittleFs::LittleFsDir::LittleFsEntry entry) { + switch(entry.type) { + case Pinetime::System::LittleFs::LittleFsDir::LittleFsEntry::Type::FILE: { + constexpr char start[] = "om, start, sizeof(start)); + if(rtn != 0) + return BLE_ATT_ERR_INSUFFICIENT_RES; + rtn = os_mbuf_append(context->om, entry.name, strlen(entry.name)); + if(rtn != 0) + return BLE_ATT_ERR_INSUFFICIENT_RES; + constexpr char middle[] = "\" size=\""; + rtn = os_mbuf_append(context->om, middle, sizeof(middle)); + if(rtn != 0) + return BLE_ATT_ERR_INSUFFICIENT_RES; + auto size_string = std::to_string(entry.size); + rtn = os_mbuf_append(context->om, size_string.c_str(), size_string.size()); + if(rtn != 0) + return BLE_ATT_ERR_INSUFFICIENT_RES; + constexpr char last[] = "\" />\n"; + rtn = os_mbuf_append(context->om, last, sizeof(last)); + if(rtn != 0) + return BLE_ATT_ERR_INSUFFICIENT_RES; + return 0; + } + case Pinetime::System::LittleFs::LittleFsDir::LittleFsEntry::Type::DIR: { + constexpr char start[] = "om, start, sizeof(start)); + if(rtn != 0) + return BLE_ATT_ERR_INSUFFICIENT_RES; + rtn = os_mbuf_append(context->om, entry.name, strlen(entry.name)); + if(rtn != 0) + return BLE_ATT_ERR_INSUFFICIENT_RES; + constexpr char last[] = "\" size=\"0\" type=\"folder\" />\n"; + rtn = os_mbuf_append(context->om, last, sizeof(last)); + if(rtn != 0) + return BLE_ATT_ERR_INSUFFICIENT_RES; + return 0; + } + default: + return -1; + } +} diff --git a/src/components/ble/FtpService.h b/src/components/ble/FtpService.h new file mode 100644 index 0000000000..31cadd28a3 --- /dev/null +++ b/src/components/ble/FtpService.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include + +#define min // workaround: nimble's min/max macros conflict with libstdc++ +#define max +#include +#undef max +#undef min + +#include + +namespace Pinetime { + namespace System { + class SystemTask; + class LittleFs; + } + namespace Controllers { + class Ble; + + /** + * Implements [Bluetooth FTP](https://www.bluetooth.org/docman/handlers/DownloadDoc.ashx?doc_id=309003) + * over GATT. + */ + class FtpService { + public: + FtpService(Pinetime::System::SystemTask &systemTask, + Pinetime::Controllers::Ble &bleController, + Pinetime::System::LittleFs &littleFs); + void Init(); + int OnServiceData(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt *context); + + private: + static constexpr ble_uuid128_t serviceUuid{ + .u {.type = BLE_UUID_TYPE_128}, + .value = {0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, + 0x98, 0x4E, 0x52, 0x54, 0x00, 0xDC, 0x9E, 0x09} + }; + + static constexpr uint16_t revision{0x0001}; + static constexpr ble_uuid128_t revisionCharacteristicUuid{ + .u {.type = BLE_UUID_TYPE_128}, + .value = {0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, + 0x98, 0x4E, 0x52, 0x54, 0x34, 0x15, 0x00, 0x00} + }; + + static constexpr ble_uuid128_t pathCharacteristicUuid{ + .u {.type = BLE_UUID_TYPE_128}, + .value = {0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, + 0x98, 0x4E, 0x52, 0x54, 0x30, 0x15, 0x00, 0x00} + }; + + static constexpr ble_uuid128_t controlPointCharacteristicUuid{ + .u {.type = BLE_UUID_TYPE_128}, + .value = {0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, + 0x98, 0x4E, 0x52, 0x54, 0x31, 0x15, 0x00, 0x00} + }; + + static constexpr ble_uuid128_t packetCharacteristicUuid{ + .u {.type = BLE_UUID_TYPE_128}, + .value = {0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, + 0x98, 0x4E, 0x52, 0x54, 0x32, 0x15, 0x00, 0x00} + }; + + Pinetime::System::SystemTask &mSystemTask; + Pinetime::Controllers::Ble &mBleController; + Pinetime::System::LittleFs &mLittleFs; + const struct ble_gatt_chr_def mCharacteristicDefinitions[5]; ///< number of characteristics (plus one null) + const struct ble_gatt_svc_def mServiceDefinitions[2]; ///< number of services (plus one null) + static constexpr size_t MAX_PATH_LENGTH = 512; + char mCurrentPath[MAX_PATH_LENGTH] = ""; + size_t mCurrentPath_sz = 1; + Pinetime::System::LittleFs::LittleFsFile* mFile; + Pinetime::System::LittleFs::LittleFsDir* mDir; + void closeActive(); + int handleWrite(ble_gatt_access_ctxt*); + int handleRead(ble_gatt_access_ctxt*); + + enum class Opcodes : uint8_t { + REMOVE = 0x82, ///< delete file or empty directory + MKDIR = 0x85, + READ = 0x87, /**< streams out file contents upon each read of + * packet characteristic */ + WRITE = 0x88, /**< will create new file if non-existent, appends + * to trunced file upon each write of packet characteristic */ + CLOSE = 0x89, ///< commit written data + LIST = 0x80, ///< get directory contents (x-obex/folderlisting) + }; + enum class ResultCodes : uint8_t { + SUCCESS = 0x00, + FAILURE = 0xFF, + }; + + enum class ListingState : uint8_t { + DONE, + HDR_1, ///< xml version string + HDR_2, ///< doctype + HDR_3, ///< + ENTRIES, + FTR, ///< + }; + ListingState mListingState = ListingState::DONE; + }; + } +} \ No newline at end of file diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp index a6f3cc39a2..c77bcd4e19 100644 --- a/src/components/ble/NimbleController.cpp +++ b/src/components/ble/NimbleController.cpp @@ -22,13 +22,15 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask, DateTime& dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager, Controllers::Battery& batteryController, - Pinetime::Drivers::SpiNorFlash& spiNorFlash) : + Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Pinetime::System::LittleFs& littleFs) : systemTask{systemTask}, bleController{bleController}, dateTimeController{dateTimeController}, notificationManager{notificationManager}, spiNorFlash{spiNorFlash}, dfuService{systemTask, bleController, spiNorFlash}, + ftpService{systemTask, bleController, littleFs}, currentTimeClient{dateTimeController}, anService{systemTask, notificationManager}, alertNotificationClient{systemTask, notificationManager}, @@ -56,6 +58,7 @@ void NimbleController::Init() { musicService.Init(); anService.Init(); dfuService.Init(); + ftpService.Init(); batteryInformationService.Init(); immediateAlertService.Init(); int res; diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h index 914f11e62f..91d2f074cc 100644 --- a/src/components/ble/NimbleController.h +++ b/src/components/ble/NimbleController.h @@ -14,6 +14,7 @@ #include "CurrentTimeService.h" #include "DeviceInformationService.h" #include "DfuService.h" +#include "FtpService.h" #include "ImmediateAlertService.h" #include "MusicService.h" #include "ServiceDiscovery.h" @@ -37,7 +38,8 @@ namespace Pinetime { public: NimbleController(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::Ble& bleController, DateTime& dateTimeController, Pinetime::Controllers::NotificationManager& notificationManager, - Controllers::Battery& batteryController, Pinetime::Drivers::SpiNorFlash& spiNorFlash); + Controllers::Battery& batteryController, Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Pinetime::System::LittleFs& littleFs); void Init(); void StartAdvertising(); int OnGAPEvent(ble_gap_event *event); @@ -65,6 +67,7 @@ namespace Pinetime { Pinetime::Controllers::NotificationManager& notificationManager; Pinetime::Drivers::SpiNorFlash& spiNorFlash; Pinetime::Controllers::DfuService dfuService; + Pinetime::Controllers::FtpService ftpService; DeviceInformationService deviceInformationService; CurrentTimeClient currentTimeClient; 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; + }; + }; + } +} diff --git a/src/main.cpp b/src/main.cpp index 350570451f..74197d69c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -73,6 +73,15 @@ Pinetime::Drivers::St7789 lcd {lcdSpi, pinLcdDataCommand}; Pinetime::Drivers::Spi flashSpi {spi, pinSpiFlashCsn}; Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi}; +constexpr size_t MB = 1 << 10; +constexpr size_t GB = 1 << 20; +constexpr size_t RESERVED_SZ = 512 * MB; +constexpr size_t FS_START_ADDRESS = RESERVED_SZ; +constexpr size_t BLOCK_SIZE_BYTES = 256; +static_assert( (FS_START_ADDRESS % BLOCK_SIZE_BYTES) == 0, "filesystem doesn't start on a block address"); +constexpr size_t TOTAL_SIZE = 4 * GB; +constexpr size_t FS_SIZE = TOTAL_SIZE - RESERVED_SZ; +Pinetime::System::LittleFs littleFs{spiNorFlash, FS_START_ADDRESS, FS_SIZE}; // The TWI device should work @ up to 400Khz but there is a HW bug which prevent it from // respecting correct timings. According to erratas heet, this magic value makes it run @@ -236,7 +245,7 @@ int main(void) { debounceTimer = xTimerCreate ("debounceTimer", 200, pdFALSE, (void *) 0, DebounceTimerCallback); - systemTask.reset(new Pinetime::System::SystemTask(spi, lcd, spiNorFlash, twiMaster, touchPanel, lvgl, batteryController, bleController, + systemTask.reset(new Pinetime::System::SystemTask(spi, lcd, spiNorFlash, littleFs, twiMaster, touchPanel, lvgl, batteryController, bleController, dateTimeController, notificationManager)); systemTask->Start(); nimble_port_init(); diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 9cd2f5e9ee..4c437d6c0d 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -35,6 +35,7 @@ void IdleTimerCallback(TimerHandle_t xTimer) { SystemTask::SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd, Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Pinetime::System::LittleFs& littleFs, Drivers::TwiMaster& twiMaster, Drivers::Cst816S &touchPanel, Components::LittleVgl &lvgl, Controllers::Battery &batteryController, Controllers::Ble &bleController, @@ -44,7 +45,7 @@ SystemTask::SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd, twiMaster{twiMaster}, touchPanel{touchPanel}, lvgl{lvgl}, batteryController{batteryController}, bleController{bleController}, dateTimeController{dateTimeController}, watchdog{}, watchdogView{watchdog}, notificationManager{notificationManager}, - nimbleController(*this, bleController,dateTimeController, notificationManager, batteryController, spiNorFlash) { + nimbleController(*this, bleController,dateTimeController, notificationManager, batteryController, spiNorFlash, littleFs) { systemTasksMsgQueue = xQueueCreate(10, 1); } diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h index fe6e7cbe45..a9fd311b59 100644 --- a/src/systemtask/SystemTask.h +++ b/src/systemtask/SystemTask.h @@ -30,6 +30,7 @@ namespace Pinetime { SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd, Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Pinetime::System::LittleFs& littleFs, Drivers::TwiMaster& twiMaster, Drivers::Cst816S &touchPanel, Components::LittleVgl &lvgl, Controllers::Battery &batteryController, Controllers::Ble &bleController,