From a3bbdf1c16cfb52aea13f0aa607907a9cfe4c61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Szczodrzy=C5=84ski?= Date: Thu, 13 Jul 2023 12:15:48 +0200 Subject: [PATCH] [core] Migrate to uf2ota v5.0.0, refactor OTA API --- builder/frameworks/base.py | 2 +- .../libraries/common/Update/Update.cpp | 266 ++++++++---------- .../arduino/libraries/common/Update/Update.h | 208 +++++++------- .../libraries/common/Update/UpdateUtil.cpp | 229 +++++++-------- cores/common/base/api/lt_ota.c | 140 +++++++++ cores/common/base/api/lt_ota.h | 66 +++++ cores/common/base/lt_logger.c | 9 +- docs/scripts/write_apis.py | 6 +- external-libs.json | 8 +- platform.json | 10 +- 10 files changed, 547 insertions(+), 397 deletions(-) diff --git a/builder/frameworks/base.py b/builder/frameworks/base.py index fd8a886f5..c9317c23a 100644 --- a/builder/frameworks/base.py +++ b/builder/frameworks/base.py @@ -95,7 +95,7 @@ env.Prepend(CPPDEFINES=[(f"LT_{f.code.upper()}", "1")]) # Sources - external libraries -queue.AddExternalLibrary("ltchiptool") # uf2ota source code +queue.AddExternalLibrary("uf2ota") queue.AddExternalLibrary("flashdb") queue.AddExternalLibrary("printf") diff --git a/cores/common/arduino/libraries/common/Update/Update.cpp b/cores/common/arduino/libraries/common/Update/Update.cpp index 13f83de8a..6a2c6cdde 100644 --- a/cores/common/arduino/libraries/common/Update/Update.cpp +++ b/cores/common/arduino/libraries/common/Update/Update.cpp @@ -2,9 +2,25 @@ #include "Update.h" -UpdateClass::UpdateClass() : ctx(NULL), info(NULL), buf(NULL) { - cleanup(); -} +static const UpdateError errorMap[] = { + UPDATE_ERROR_OK, /* UF2_ERR_OK - no error */ + UPDATE_ERROR_OK, /* UF2_ERR_IGNORE - block should be ignored */ + UPDATE_ERROR_MAGIC_BYTE, /* UF2_ERR_MAGIC - wrong magic numbers */ + UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_FAMILY - family ID mismatched */ + UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_NOT_HEADER - block is not a header */ + UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_OTA_VER - unknown/invalid OTA format version */ + UPDATE_ERROR_MAGIC_BYTE, /* UF2_ERR_OTA_WRONG - no data for current OTA scheme */ + UPDATE_ERROR_NO_PARTITION, /* UF2_ERR_PART_404 - no partition with that name */ + UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_PART_INVALID - invalid partition info tag */ + UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_PART_UNSET - attempted to write without target partition */ + UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_DATA_TOO_LONG - data too long - tags won't fit */ + UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_SEQ_MISMATCH - sequence number mismatched */ + UPDATE_ERROR_ERASE, /* UF2_ERR_ERASE_FAILED - erasing flash failed */ + UPDATE_ERROR_WRITE, /* UF2_ERR_WRITE_FAILED - writing to flash failed */ + UPDATE_ERROR_WRITE, /* UF2_ERR_WRITE_LENGTH - wrote fewer data than requested */ + UPDATE_ERROR_WRITE, /* UF2_ERR_WRITE_PROTECT - target area is write-protected */ + UPDATE_ERROR_WRITE, /* UF2_ERR_ALLOC_FAILED - dynamic memory allocation failed */ +}; /** * @brief Initialize the update process. @@ -13,55 +29,88 @@ UpdateClass::UpdateClass() : ctx(NULL), info(NULL), buf(NULL) { * @param command must be U_FLASH * @return false if parameters are invalid or update is running, true otherwise */ -bool UpdateClass::begin(size_t size, int command, int unused2, uint8_t unused3, const char *unused4) { - if (ctx) +bool UpdateClass::begin( + size_t size, + int command, + __attribute__((unused)) int ledPin, + __attribute__((unused)) uint8_t ledOn, + __attribute__((unused)) const char *label +) { + if (this->ctx) { return false; - cleanup(); - - LT_DM(OTA, "begin(%u, ...) / OTA curr: %u, scheme: %u", size, lt_ota_dual_get_current(), lt_ota_get_uf2_scheme()); - - ctx = uf2_ctx_init(lt_ota_get_uf2_scheme(), FAMILY); - info = uf2_info_init(); - - if (!size) { - cleanup(UPDATE_ERROR_SIZE); + } + this->clearError(); + if (size == 0) { + this->errArd = UPDATE_ERROR_SIZE; return false; } - if (command != U_FLASH) { - cleanup(UPDATE_ERROR_BAD_ARGUMENT); + this->errArd = UPDATE_ERROR_BAD_ARGUMENT; return false; } + if (size == UPDATE_SIZE_UNKNOWN) { + size = 0; + } - bytesTotal = size; + this->ctx = static_cast(malloc(sizeof(lt_ota_ctx_t))); + lt_ota_begin(this->ctx, size); + this->ctx->callback = reinterpret_cast(progressHandler); + this->ctx->callback_param = this; return true; } /** * @brief Finalize the update process. Check for errors and update completion, then activate the new firmware image. * - * @param evenIfRemaining no idea - * @return false in case of errors or no update running, true otherwise + * @param evenIfRemaining don't raise errors if still in progress + * @return false in case of errors or no update running; true otherwise */ bool UpdateClass::end(bool evenIfRemaining) { - if (hasError() || !ctx) - // false if not running + if (!this->ctx) return false; - if (!isFinished() && !evenIfRemaining) { + // update is running or finished; cleanup and end it + if (!isFinished() && !evenIfRemaining) // abort if not finished - cleanup(UPDATE_ERROR_ABORT); - return false; - } - // TODO what is evenIfRemaining for? - // try to activate the second OTA - if (!lt_ota_switch(/* revert= */ false)) { - cleanup(UPDATE_ERROR_ACTIVATE); - return false; + this->errArd = UPDATE_ERROR_ABORT; + + this->cleanup(/* clearError= */ evenIfRemaining); + return !this->hasError(); +} + +/** + * @brief Cleanup (free) the update context. + * Try activating the firmware if possible, set local error codes. + * + * @param clearError assume successful finish after correct activation + */ +void UpdateClass::cleanup(bool clearError) { + if (!this->ctx) + return; + + if (!lt_ota_end(this->ctx)) { + // activating firmware failed + this->errArd = UPDATE_ERROR_ACTIVATE; + this->errUf2 = UF2_ERR_OK; + } else if (clearError) { + // successful finish and activation, clear error codes + this->clearError(); + } else if (this->ctx->error > UF2_ERR_IGNORE) { + // make error code based on UF2OTA code + this->errArd = errorMap[this->ctx->error]; + this->errUf2 = this->ctx->error; + } else { + // only keep Arduino error code (set by the caller) + this->errUf2 = UF2_ERR_OK; } - cleanup(); - return true; +#if LT_DEBUG_OTA + if (this->hasError()) + this->printErrorContext(); +#endif + + free(this->ctx); + this->ctx = nullptr; } /** @@ -69,60 +118,44 @@ bool UpdateClass::end(bool evenIfRemaining) { * * It's advised to write in 512-byte chunks (or its multiples). * - * @param data - * @param len - * @return size_t + * @param data chunk of data + * @param len length of the chunk + * @return size_t amount of bytes written */ -size_t UpdateClass::write(uint8_t *data, size_t len) { - size_t written = 0; - if (hasError() || !ctx) - // 0 if not running +size_t UpdateClass::write(const uint8_t *data, size_t len) { + if (!this->ctx) return 0; - LT_VM(OTA, "write(%u) / buf %u/512", len, bufSize()); - - /* while (buf == bufPos && len >= UF2_BLOCK_SIZE) { - // buffer empty and entire block is in data - if (!tryWriteData(data, UF2_BLOCK_SIZE)) { - // returns 0 if data contains an invalid block - return written; - } - data += UF2_BLOCK_SIZE; - len -= UF2_BLOCK_SIZE; - written += UF2_BLOCK_SIZE; - } */ - - // write until buffer space is available - uint16_t toWrite; // 1..512 - while (len && (toWrite = min(len, bufLeft()))) { - tryWriteData(data, toWrite); - if (hasError()) { - // return on errors - printErrorContext2(data, toWrite); - return written; - } - data += toWrite; - len -= toWrite; - written += toWrite; - } + size_t written = lt_ota_write(ctx, data, len); + if (written != len) + this->cleanup(/* clearError= */ false); return written; } +/** + * @brief Write all data remaining in the given stream. + * + * If the stream doesn't produce any data within UPDATE_TIMEOUT_MS, + * the update process will be aborted. + * + * @param data stream to read from + * @return size_t amount of bytes written + */ size_t UpdateClass::writeStream(Stream &data) { - size_t written = 0; - if (hasError() || !ctx) - // 0 if not running + if (!this->ctx) return 0; + size_t written = 0; uint32_t lastData = millis(); // loop until the update is complete while (remaining()) { // check stream availability - int available = data.available(); + auto available = data.available(); if (available <= 0) { if (millis() - lastData > UPDATE_TIMEOUT_MS) { // waited for data too long; abort with error - cleanup(UPDATE_ERROR_STREAM); + this->errArd = UPDATE_ERROR_STREAM; + this->cleanup(/* clearError= */ false); return written; } continue; @@ -131,94 +164,21 @@ size_t UpdateClass::writeStream(Stream &data) { lastData = millis(); // read data to fit in the remaining buffer space - bufAlloc(); - uint16_t read = data.readBytes(bufPos, bufLeft()); - bufPos += read; - written += read; - tryWriteData(); + auto bufSize = this->ctx->buf_pos - this->ctx->buf; + auto read = data.readBytes(this->ctx->buf_pos, UF2_BLOCK_SIZE - bufSize); + // increment buffer writing head + this->ctx->buf_pos += read; + // process the block if complete + if (bufSize + read == UF2_BLOCK_SIZE) + lt_ota_write_block(this->ctx, reinterpret_cast(this->ctx->buf)); + // abort on errors if (hasError()) { - // return on errors - printErrorContext2(NULL, read); // buf is not valid anymore + this->cleanup(/* clearError= */ false); return written; } + written += read; } return written; } -/** - * @brief Try to use the buffer as a block to write. In case of UF2 errors, - * error codes are set, the update is aborted and 0 is returned - * - * @param data received data to copy to buffer or NULL if already in buffer - * @param len received data length - must be at most bufLeft() - * @return size_t "used" data size - 0 or 512 - */ -size_t UpdateClass::tryWriteData(uint8_t *data, size_t len) { - uf2_block_t *block = NULL; - - LT_VM(OTA, "Writing %u to buffer (%u/512)", len, bufSize()); - - if (len == UF2_BLOCK_SIZE) { - // data has a complete block - block = (uf2_block_t *)data; - } else if (data && len) { - // data has a part of a block, copy it to buffer - bufAlloc(); - memcpy(bufPos, data, len); - bufPos += len; - } - - if (!block && bufSize() == UF2_BLOCK_SIZE) { - // use buffer as block (only if not found above) - block = (uf2_block_t *)buf; - } - - // a complete block has been found - if (block) { - if (checkUf2Error(uf2_check_block(ctx, block))) - // block is invalid - return 0; - - if (errUf2 == UF2_ERR_IGNORE) - // treat ignored blocks as valid - return UF2_BLOCK_SIZE; - - if (!bytesWritten) { - // parse header block to allow retrieving firmware info - if (checkUf2Error(uf2_parse_header(ctx, block, info))) - // header is invalid - return 0; - - LT_IM(OTA, "%s v%s - LT v%s @ %s", info->fw_name, info->fw_version, info->lt_version, info->board); - - if (bytesTotal == UPDATE_SIZE_UNKNOWN) { - // set total update size from block count info - bytesTotal = block->block_count * UF2_BLOCK_SIZE; - } else if (bytesTotal != block->block_count * UF2_BLOCK_SIZE) { - // given update size does not match the block count - LT_EM(OTA, "Image size wrong; got %u, calculated %u", bytesTotal, block->block_count * UF2_BLOCK_SIZE); - cleanup(UPDATE_ERROR_SIZE); - return 0; - } - } else { - // write data blocks normally - if (checkUf2Error(uf2_write(ctx, block))) - // block writing failed - return 0; - } - - // increment total writing progress - bytesWritten += UF2_BLOCK_SIZE; - // call progress callback - if (callback) - callback(bytesWritten, bytesTotal); - // reset the buffer as it's used already - if (bufSize() == UF2_BLOCK_SIZE) - bufPos = buf; - return UF2_BLOCK_SIZE; - } - - return 0; -} - UpdateClass Update; diff --git a/cores/common/arduino/libraries/common/Update/Update.h b/cores/common/arduino/libraries/common/Update/Update.h index 7e817906b..e5277e583 100644 --- a/cores/common/arduino/libraries/common/Update/Update.h +++ b/cores/common/arduino/libraries/common/Update/Update.h @@ -4,39 +4,30 @@ #include #include -// No Error -#define UPDATE_ERROR_OK (0) -// Flash Write Failed -#define UPDATE_ERROR_WRITE (1) -// Flash Erase Failed -#define UPDATE_ERROR_ERASE (2) -// Flash Read Failed -#define UPDATE_ERROR_READ (3) -// Not Enough Space -#define UPDATE_ERROR_SPACE (4) -// Bad Size Given -#define UPDATE_ERROR_SIZE (5) -// Stream Read Timeout -#define UPDATE_ERROR_STREAM (6) -// MD5 Check Failed -#define UPDATE_ERROR_MD5 (7) -// Wrong Magic Byte -#define UPDATE_ERROR_MAGIC_BYTE (8) -// Could Not Activate The Firmware -#define UPDATE_ERROR_ACTIVATE (9) -// Partition Could Not be Found -#define UPDATE_ERROR_NO_PARTITION (10) -// Bad Argument -#define UPDATE_ERROR_BAD_ARGUMENT (11) -// Aborted -#define UPDATE_ERROR_ABORT (12) +typedef enum { + UPDATE_ERROR_OK = 0, //!< No Error + UPDATE_ERROR_WRITE = 1, //!< Flash Write Failed + UPDATE_ERROR_ERASE = 2, //!< Flash Erase Failed + UPDATE_ERROR_READ = 3, //!< Flash Read Failed + UPDATE_ERROR_SPACE = 4, //!< Not Enough Space + UPDATE_ERROR_SIZE = 5, //!< Bad Size Given + UPDATE_ERROR_STREAM = 6, //!< Stream Read Timeout + UPDATE_ERROR_MD5 = 7, //!< MD5 Check Failed + UPDATE_ERROR_MAGIC_BYTE = 8, //!< Wrong Magic Byte + UPDATE_ERROR_ACTIVATE = 9, //!< Could Not Activate The Firmware + UPDATE_ERROR_NO_PARTITION = 10, //!< Partition Could Not be Found + UPDATE_ERROR_BAD_ARGUMENT = 11, //!< Bad Argument + UPDATE_ERROR_ABORT = 12, //!< Aborted +} UpdateError; + +typedef enum { + U_FLASH = 0, + U_SPIFFS = 100, + U_AUTH = 200, +} UpdateCommand; #define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF -#define U_FLASH 0 -#define U_SPIFFS 100 -#define U_AUTH 200 - #define ENCRYPTED_BLOCK_SIZE 16 #define UPDATE_TIMEOUT_MS 30 * 1000 @@ -46,109 +37,132 @@ class UpdateClass { typedef std::function THandlerFunction_Progress; public: /* Update.cpp */ - UpdateClass(); bool begin( - size_t size = UPDATE_SIZE_UNKNOWN, - int command = U_FLASH, - int unused2 = -1, - uint8_t unused3 = LOW, - const char *unused4 = NULL // this is for SPIFFS + size_t size = UPDATE_SIZE_UNKNOWN, + int command = U_FLASH, + int ledPin = -1, + uint8_t ledOn = LOW, + const char *label = nullptr ); bool end(bool evenIfRemaining = false); - size_t write(uint8_t *data, size_t len); + + size_t write(const uint8_t *data, size_t len); size_t writeStream(Stream &data); - bool canRollBack(); - bool rollBack(); - // bool setMD5(const char *expected_md5); private: /* Update.cpp */ - size_t tryWriteData(uint8_t *data = NULL, size_t len = 0); + void cleanup(bool clearError = false); public: /* UpdateUtil.cpp */ - UpdateClass &onProgress(THandlerFunction_Progress callback); - void abort(); - void printError(Print &out); - const char *errorString(); - const char *getFirmwareName(); - const char *getFirmwareVersion(); - const char *getLibreTinyVersion(); - const char *getBoardName(); + UpdateClass &onProgress(THandlerFunction_Progress handler); + static bool canRollBack(); + static bool rollBack(); + uint16_t getErrorCode() const; + bool hasError() const; + void clearError(); + const char *errorString() const; + void printError(Print &out) const; private: /* UpdateUtil.cpp */ - void cleanup(uint8_t ardErr = UPDATE_ERROR_OK, uf2_err_t uf2Err = UF2_ERR_OK); - bool checkUf2Error(uf2_err_t err); - void bufAlloc(); - void printErrorContext1(); - void printErrorContext2(const uint8_t *data, size_t len); - uint16_t bufLeft(); - uint16_t bufSize(); + static void progressHandler(UpdateClass *self); + void printErrorContext(); private: - // uf2ota context - uf2_ota_t *ctx; - uf2_info_t *info; - // block buffer - uint8_t *buf; - uint8_t *bufPos; - // update progress - multiplies of 512 bytes - uint32_t bytesWritten; - uint32_t bytesTotal; - // errors - uf2_err_t errUf2; - uint8_t errArd; - // progress callback - THandlerFunction_Progress callback; - // String _target_md5; - // MD5Builder _md5; + lt_ota_ctx_t *ctx{nullptr}; + uf2_err_t errUf2{UF2_ERR_OK}; + UpdateError errArd{UPDATE_ERROR_OK}; + THandlerFunction_Progress callback{nullptr}; public: - String md5String(void) { - // return _md5.toString(); - return ""; + /** + * @brief Get Arduino error code of the update. + */ + inline UpdateError getError() const { + return this->errArd; } - void md5(uint8_t *result) { - // return _md5.getBytes(result); + /** + * @brief Get UF2OTA error code of the update. + */ + inline uf2_err_t getUF2Error() const { + return this->ctx ? this->ctx->error : this->errUf2; } - uint8_t getError() { - return errArd; + /** + * @brief Same as end(). + */ + inline void abort() { + this->end(); } - uf2_err_t getUF2Error() { - return errUf2; + /** + * @brief Check if the update process has been started. + */ + inline bool isRunning() { + return this->ctx; } - uint16_t getErrorCode() { - return (errArd << 8) | errUf2; + /** + * @brief Check if the update process hasn't been started or has been completed. + */ + inline bool isFinished() { + return !(this->ctx && this->ctx->bytes_written != this->ctx->bytes_total); } - void clearError() { - cleanup(UPDATE_ERROR_OK); + /** + * @brief Return complete update image size. + */ + inline size_t size() { + return this->ctx ? this->ctx->bytes_total : 0; } - bool hasError() { - return errArd != UPDATE_ERROR_OK; + /** + * @brief Return amount of bytes already written. + */ + inline size_t progress() { + return this->ctx ? this->ctx->bytes_written : 0; } - bool isRunning() { - return ctx != NULL; + /** + * @brief Return amount of bytes remaining to write. + */ + inline size_t remaining() { + return this->size() - this->progress(); } - bool isFinished() { - return bytesWritten == bytesTotal; + /** + * @brief Get firmware name from UF2 info. + */ + inline const char *getFirmwareName() { + if (this->ctx) + return this->ctx->info.fw_name; + return nullptr; } - size_t size() { - return bytesTotal; + /** + * @brief Get firmware version from UF2 info. + */ + inline const char *getFirmwareVersion() { + if (this->ctx) + return this->ctx->info.fw_version; + return nullptr; } - size_t progress() { - return bytesWritten; + /** + * @brief Get LibreTiny version from UF2 info. + */ + inline const char *getLibreTinyVersion() { + if (this->ctx) + return this->ctx->info.lt_version; + return nullptr; } - size_t remaining() { - return bytesTotal - bytesWritten; + /** + * @brief Get target board name from UF2 info. + */ + inline const char *getBoardName() { + if (this->ctx) + return this->ctx->info.board; + return nullptr; } }; diff --git a/cores/common/arduino/libraries/common/Update/UpdateUtil.cpp b/cores/common/arduino/libraries/common/Update/UpdateUtil.cpp index d355c7a40..4fe09ee87 100644 --- a/cores/common/arduino/libraries/common/Update/UpdateUtil.cpp +++ b/cores/common/arduino/libraries/common/Update/UpdateUtil.cpp @@ -2,26 +2,46 @@ #include "Update.h" +#include + extern "C" { #include } -static const uint8_t errorMap[] = { - UPDATE_ERROR_OK, /* UF2_ERR_OK - no error */ - UPDATE_ERROR_OK, /* UF2_ERR_IGNORE - block should be ignored */ - UPDATE_ERROR_MAGIC_BYTE, /* UF2_ERR_MAGIC - wrong magic numbers */ - UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_FAMILY - family ID mismatched */ - UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_NOT_HEADER - block is not a header */ - UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_OTA_VER - unknown/invalid OTA format version */ - UPDATE_ERROR_MAGIC_BYTE, /* UF2_ERR_OTA_WRONG - no data for current OTA scheme */ - UPDATE_ERROR_NO_PARTITION, /* UF2_ERR_PART_404 - no partition with that name */ - UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_PART_INVALID - invalid partition info tag */ - UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_PART_UNSET - attempted to write without target partition */ - UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_DATA_TOO_LONG - data too long - tags won't fit */ - UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_SEQ_MISMATCH - sequence number mismatched */ - UPDATE_ERROR_ERASE, /* UF2_ERR_ERASE_FAILED - erasing flash failed */ - UPDATE_ERROR_WRITE, /* UF2_ERR_WRITE_FAILED - writing to flash failed */ - UPDATE_ERROR_WRITE /* UF2_ERR_WRITE_LENGTH - wrote fewer data than requested */ +static const char *errUf2Text[] = { + nullptr, // UF2_ERR_OK (0) + nullptr, // UF2_ERR_IGNORE (1) + "Bad Magic Number", // UF2_ERR_MAGIC (2) + "Bad Family ID", // UF2_ERR_FAMILY (3) + "Missing Header", // UF2_ERR_NOT_HEADER (4) + "Old OTA Format Found", // UF2_ERR_OTA_VER (5) + "Image Not Applicable", // UF2_ERR_OTA_WRONG (6) + "Partition Not Found", // UF2_ERR_PART_404 (7) + "Partition Info Invalid", // UF2_ERR_PART_INVALID (8) + "Partition Info Missing", // UF2_ERR_PART_UNSET (9) + "Block Data Too Long", // UF2_ERR_DATA_TOO_LONG (10) + "Bad Block Sequence Number", // UF2_ERR_SEQ_MISMATCH (11) + "Flash Erase Failed", // UF2_ERR_ERASE_FAILED (12) + "Flash Write Failed", // UF2_ERR_WRITE_FAILED (13) + "Write Failed Length", // UF2_ERR_WRITE_LENGTH (14) + "Partition Write-Protected", // UF2_ERR_WRITE_PROTECT (15) + "Memory Alloc Failed", // UF2_ERR_ALLOC_FAILED (16) +}; + +static const char *errArdText[] = { + nullptr, // UPDATE_ERROR_OK (0) + nullptr, // UPDATE_ERROR_WRITE (1) + nullptr, // UPDATE_ERROR_ERASE (2) + nullptr, // UPDATE_ERROR_READ (3) + nullptr, // UPDATE_ERROR_SPACE (4) + "Bad Size Given", // UPDATE_ERROR_SIZE (5) + "Stream Read Timeout", // UPDATE_ERROR_STREAM (6) + "MD5 Check Failed", // UPDATE_ERROR_MD5 (7) + nullptr, // UPDATE_ERROR_MAGIC_BYTE (8) + "Could Not Activate The Firmware", // UPDATE_ERROR_ACTIVATE (9) + nullptr, // UPDATE_ERROR_NO_PARTITION (10) + "Bad Argument", // UPDATE_ERROR_BAD_ARGUMENT (11) + "Aborted", // UPDATE_ERROR_ABORT (12) }; static char errorStr[14]; @@ -29,163 +49,104 @@ static char errorStr[14]; /** * @brief Set the callback invoked after writing data to flash. */ -UpdateClass &UpdateClass::onProgress(THandlerFunction_Progress callback) { - this->callback = callback; +UpdateClass &UpdateClass::onProgress(THandlerFunction_Progress handler) { + this->callback = std::move(handler); return *this; } -void UpdateClass::cleanup(uint8_t ardErr, uf2_err_t uf2Err) { - errUf2 = uf2Err; - errArd = ardErr; - -#if LT_DEBUG_OTA - if (hasError()) - printErrorContext1(); -#endif - - uf2_ctx_free(ctx); // NULL in constructor - ctx = NULL; - uf2_info_free(info); // NULL in constructor - info = NULL; - free(buf); // NULL in constructor - buf = bufPos = NULL; +void UpdateClass::progressHandler(UpdateClass *self) { + if (self->callback) + self->callback(self->ctx->bytes_written, self->ctx->bytes_total); +} - bytesWritten = 0; - bytesTotal = 0; +/** @copydoc lt_ota_can_rollback() */ +bool UpdateClass::canRollBack() { + return lt_ota_can_rollback(); } -/** - * @brief Check for UF2 errors. Set errArd and errUf2 in case of errors. - * Ignored blocks are not reported as errors. - * Abort the update. - * Use like: "if (errorUf2(...)) return false;" - * @return true if err is not OK, false otherwise - */ -bool UpdateClass::checkUf2Error(uf2_err_t err) { - if (err <= UF2_ERR_IGNORE) +/** @copydoc lt_ota_switch() */ +bool UpdateClass::rollBack() { + if (!lt_ota_can_rollback()) return false; - cleanup(errorMap[err], err); - return true; + return lt_ota_switch(/* revert= */ false); } /** - * @brief Abort the update with UPDATE_ERROR_ABORT reason. + * @brief Get combined error code of the update. */ -void UpdateClass::abort() { - LT_DM(OTA, "Aborting update"); - cleanup(UPDATE_ERROR_ABORT); +uint16_t UpdateClass::getErrorCode() const { + return (this->getError() << 8) | this->getUF2Error(); } -void UpdateClass::bufAlloc() { - if (!buf) - buf = bufPos = (uint8_t *)malloc(UF2_BLOCK_SIZE); +/** + * @brief Check if any error has occurred (incl. aborting the update). + */ +bool UpdateClass::hasError() const { + return this->getError() != UPDATE_ERROR_OK || this->getUF2Error() > UF2_ERR_IGNORE; } -uint16_t UpdateClass::bufLeft() { - return buf + UF2_BLOCK_SIZE - bufPos; +/** + * @brief Clear all errors. This is NOT recommended. + */ +void UpdateClass::clearError() { + this->errArd = UPDATE_ERROR_OK; + this->errUf2 = UF2_ERR_OK; + if (this->ctx) + this->ctx->error = UF2_ERR_OK; } -uint16_t UpdateClass::bufSize() { - return bufPos - buf; +/** + * @brief Get a textual description of the error. + */ +const char *UpdateClass::errorString() const { + uint8_t err; + if ((err = this->getUF2Error()) > UF2_ERR_IGNORE) + return errUf2Text[err]; + if ((err = this->getError()) != UPDATE_ERROR_OK) + return errArdText[err]; + if (!this->hasError()) + return ""; + sprintf(errorStr, "ard=%u,uf2=%u", this->getError(), this->getUF2Error()); + return errorStr; } /** * @brief Print string error info to the stream. */ -void UpdateClass::printError(Print &out) { +void UpdateClass::printError(Print &out) const { out.println(errorString()); } /** * @brief Print details about the error and current OTA state. */ -void UpdateClass::printErrorContext1() { +void UpdateClass::printErrorContext() { #if LT_DEBUG_OTA + if (!this->ctx) + return; + LT_EM(OTA, "Error: %s", errorString()); if (errArd == UPDATE_ERROR_ABORT) return; - LT_EM(OTA, "- written: %u of %u", bytesWritten, bytesTotal); - LT_EM(OTA, "- buf: size=%u, left=%u", bufSize(), bufLeft()); - hexdump(buf, bufSize()); + LT_EM(OTA, "- written: %u of %u", this->ctx->bytes_written, this->ctx->bytes_total); + LT_EM( + OTA, + "- buf: size=%lld, left=%lld", + this->ctx->buf_pos - this->ctx->buf, + this->ctx->buf + UF2_BLOCK_SIZE - this->ctx->buf_pos + ); + hexdump(this->ctx->buf, this->ctx->buf_pos - this->ctx->buf); if (ctx) LT_EM( OTA, "- ctx: seq=%u, part=%s", - ctx->seq - 1, // print last parsed block seq - ctx->part ? ctx->part->name : NULL + this->ctx->uf2.seq - 1, // print last parsed block seq + this->ctx->uf2.part ? this->ctx->uf2.part->name : nullptr ); - uf2_block_t *block = (uf2_block_t *)buf; - if (buf) - LT_EM(OTA, "- buf: seq=%u/%u, addr=%u, len=%u", block->block_seq, block->block_count, block->addr, block->len); -#endif -} - -void UpdateClass::printErrorContext2(const uint8_t *data, size_t len) { -#if LT_DEBUG_OTA - LT_EM(OTA, "- while writing %u bytes", len); - if (data) - hexdump(data, len); + auto *block = (uf2_block_t *)this->ctx->buf; + LT_EM(OTA, "- buf: seq=%u/%u, addr=%u, len=%u", block->block_seq, block->block_count, block->addr, block->len); #endif } - -/** - * @brief Get string representation of the error in format - * "ard=..,uf2=..". Returns "" if no error. - */ -const char *UpdateClass::errorString() { - if (!errArd && !errUf2) - return ""; - sprintf(errorStr, "ard=%u,uf2=%u", errArd, errUf2); - return errorStr; -} - -/** - * @brief Get firmware name from UF2 info. - */ -const char *UpdateClass::getFirmwareName() { - if (info) - return info->fw_name; - return NULL; -} - -/** - * @brief Get firmware version from UF2 info. - */ -const char *UpdateClass::getFirmwareVersion() { - if (info) - return info->fw_version; - return NULL; -} - -/** - * @brief Get LibreTiny version from UF2 info. - */ -const char *UpdateClass::getLibreTinyVersion() { - if (info) - return info->lt_version; - return NULL; -} - -/** - * @brief Get target board name from UF2 info. - */ -const char *UpdateClass::getBoardName() { - if (info) - return info->board; - return NULL; -} - -/** @copydoc lt_ota_can_rollback() */ -bool UpdateClass::canRollBack() { - return lt_ota_can_rollback(); -} - -/** @copydoc lt_ota_switch() */ -bool UpdateClass::rollBack() { - if (!lt_ota_can_rollback()) - return false; - return lt_ota_switch(false); -} diff --git a/cores/common/base/api/lt_ota.c b/cores/common/base/api/lt_ota.c index 5e7da4b4a..e1223848e 100644 --- a/cores/common/base/api/lt_ota.c +++ b/cores/common/base/api/lt_ota.c @@ -2,10 +2,148 @@ #include "lt_ota.h" +#include + +#define UF2_CTX_SIZE (sizeof(uf2_ota_t) + sizeof(uf2_info_t)) + +static inline size_t lt_ota_buf_left(lt_ota_ctx_t *ctx) { + return ctx->buf + UF2_BLOCK_SIZE - ctx->buf_pos; +} + +static inline size_t lt_ota_buf_size(lt_ota_ctx_t *ctx) { + return ctx->buf_pos - ctx->buf; +} + +void lt_ota_begin(lt_ota_ctx_t *ctx, size_t size) { + if (!ctx) + return; + + memset((void *)ctx + UF2_CTX_SIZE, 0, sizeof(lt_ota_ctx_t) - UF2_CTX_SIZE); + uf2_ctx_init(&ctx->uf2, lt_ota_get_uf2_scheme(), lt_cpu_get_family()); + uf2_info_init(&ctx->info); + ctx->buf_pos = ctx->buf; + ctx->bytes_total = size; + ctx->running = true; + + lt_ota_set_write_protect(&ctx->uf2); + + LT_DM(OTA, "begin(%u, ...) / OTA curr: %u, scheme: %u", size, lt_ota_dual_get_current(), lt_ota_get_uf2_scheme()); +} + +bool lt_ota_end(lt_ota_ctx_t *ctx) { + if (!ctx || !ctx->running) + return true; + + uf2_ctx_free(&ctx->uf2); + uf2_info_free(&ctx->info); + ctx->running = false; + + if (ctx->bytes_written && ctx->bytes_written == ctx->bytes_total) { + // try to activate the 2nd image + return lt_ota_switch(/* revert= */ false); + } + + // activation not attempted (update aborted) + return true; +} + +__attribute__((weak)) void lt_ota_set_write_protect(uf2_ota_t *uf2) {} + +size_t lt_ota_write(lt_ota_ctx_t *ctx, const uint8_t *data, size_t len) { + if (!ctx || !ctx->running) + return 0; + + // write until buffer space is available + size_t written = 0; + uint16_t to_write; // 1..512 + while (len && (to_write = MIN((uint16_t)len, lt_ota_buf_left(ctx)))) { + LT_VM(OTA, "Writing %u to buffer (%u/512)", len, lt_ota_buf_size(ctx)); + + uf2_block_t *block = NULL; + if (to_write == UF2_BLOCK_SIZE) { + // data has a complete block; don't use the buffer + block = (uf2_block_t *)data; + } else { + // data has a part of a block; append it to the buffer + memcpy(ctx->buf_pos, data, to_write); + ctx->buf_pos += to_write; + if (lt_ota_buf_size(ctx) == UF2_BLOCK_SIZE) { + // the block is complete now + block = (uf2_block_t *)ctx->buf; + } + } + + // write if a block is ready + if (block && lt_ota_write_block(ctx, block) == false) + // return on errors + return written; + data += to_write; + len -= to_write; + written += to_write; + } + return written; +} + +bool lt_ota_write_block(lt_ota_ctx_t *ctx, uf2_block_t *block) { + ctx->error = uf2_check_block(&ctx->uf2, block); + if (ctx->error > UF2_ERR_IGNORE) + // block is invalid + return false; + + if (!ctx->bytes_written) { + // parse header block to allow retrieving firmware info + ctx->error = uf2_parse_header(&ctx->uf2, block, &ctx->info); + if (ctx->error != UF2_ERR_OK) + return false; + + LT_IM( + OTA, + "%s v%s - LT v%s @ %s", + ctx->info.fw_name, + ctx->info.fw_version, + ctx->info.lt_version, + ctx->info.board + ); + + if (ctx->bytes_total == 0) { + // set total update size from block count info + ctx->bytes_total = block->block_count * UF2_BLOCK_SIZE; + } else if (ctx->bytes_total != block->block_count * UF2_BLOCK_SIZE) { + // given update size does not match the block count + LT_EM( + OTA, + "Image size wrong; got %u, calculated %llu", + ctx->bytes_total, + block->block_count * UF2_BLOCK_SIZE + ); + return false; + } + } else if (ctx->error == UF2_ERR_OK) { + // write data blocks normally + ctx->error = uf2_write(&ctx->uf2, block); + if (ctx->error > UF2_ERR_IGNORE) + // block writing failed + return false; + } + + // increment total writing progress + ctx->bytes_written += UF2_BLOCK_SIZE; + // call progress callback + if (ctx->callback) + ctx->callback(ctx->callback_param); + // reset the buffer as it's used already + if (lt_ota_buf_size(ctx) == UF2_BLOCK_SIZE) + ctx->buf_pos = ctx->buf; + + return true; +} + bool lt_ota_can_rollback() { if (lt_ota_get_type() != OTA_TYPE_DUAL) return false; uint8_t current = lt_ota_dual_get_current(); + if (current == 0) + return false; return lt_ota_is_valid(current ^ 0b11); } @@ -13,6 +151,8 @@ uf2_ota_scheme_t lt_ota_get_uf2_scheme() { if (lt_ota_get_type() == OTA_TYPE_SINGLE) return UF2_SCHEME_DEVICE_SINGLE; uint8_t current = lt_ota_dual_get_current(); + if (current == 0) + return UF2_SCHEME_DEVICE_DUAL_1; // UF2_SCHEME_DEVICE_DUAL_1 or UF2_SCHEME_DEVICE_DUAL_2 return (uf2_ota_scheme_t)(current ^ 0b11); } diff --git a/cores/common/base/api/lt_ota.h b/cores/common/base/api/lt_ota.h index 864fb2b2e..e86bf8632 100644 --- a/cores/common/base/api/lt_ota.h +++ b/cores/common/base/api/lt_ota.h @@ -14,6 +14,72 @@ typedef enum { OTA_TYPE_FILE = 2, } lt_ota_type_t; +/** + * @brief OTA update process context. + */ +typedef struct { + uf2_ota_t uf2; + uf2_info_t info; + uint8_t buf[UF2_BLOCK_SIZE]; // block data buffer + uint8_t *buf_pos; // buffer writing position + uint32_t bytes_written; // update progress + uint32_t bytes_total; // total update size + uf2_err_t error; // LT OTA/uf2ota error code + bool running; // whether update has begun + void (*callback)(void *param); // progress callback + void *callback_param; // callback argument +} lt_ota_ctx_t; + +/** + * @brief Initialize the update context to begin OTA process. + * + * @param ctx OTA context + * @param size length of the update file; 0 if unknown + */ +void lt_ota_begin(lt_ota_ctx_t *ctx, size_t size); + +/** + * @brief Finish the update process. If the update has been written completely, + * try to activate the target image. Free allocated internal structures, regardless + * of the activation result. + * + * @param ctx OTA context + * @return false if activation was attempted and not successful; true otherwise + */ +bool lt_ota_end(lt_ota_ctx_t *ctx); + +/** + * @brief Set family-specific, write-protected flash areas in the OTA update context. + * This shouldn't be called manually, as it's done by lt_ota_begin(). + * + * @param uf2 uf2ota context + */ +void lt_ota_set_write_protect(uf2_ota_t *uf2); + +/** + * @brief Process a chunk of data. + * + * Data is written to the buffer, unless a full UF2 block is already available, + * in which case it's also processed by UF2OTA and written to flash. + * + * It's advised to write in 512-byte chunks (or its multiples). + * + * @param ctx OTA context + * @param data chunk of bytes to process + * @param len size of the chunk + * @return number of bytes correctly processed; should equal 'len' in case of no errors + */ +size_t lt_ota_write(lt_ota_ctx_t *ctx, const uint8_t *data, size_t len); + +/** + * @brief Try to write the block. In case of UF2 errors, error code is set in the context. + * Note: use lt_ota_write() instead. This is for internal usage only. + * + * @param block UF2 block to check and write; cannot be NULL + * @return whether no error has occurred + */ +bool lt_ota_write_block(lt_ota_ctx_t *ctx, uf2_block_t *block); + /** * @brief Get OTA type of the device's chip. */ diff --git a/cores/common/base/lt_logger.c b/cores/common/base/lt_logger.c index bec236103..8b8018629 100644 --- a/cores/common/base/lt_logger.c +++ b/cores/common/base/lt_logger.c @@ -1,7 +1,10 @@ /* Copyright (c) Kuba SzczodrzyƄski 2022-04-28. */ #include "lt_logger.h" + +#if __has_include() #include +#endif #if LT_HAS_PRINTF #include @@ -33,7 +36,11 @@ #define COLOR_BRIGHT_CYAN 0x16 #define COLOR_BRIGHT_WHITE 0x17 -static uint32_t uart_port = LT_UART_DEFAULT_LOGGER; +#ifdef LT_UART_DEFAULT_PORT +static uint32_t uart_port = LT_UART_DEFAULT_LOGGER; +#else +static uint32_t uart_port = 0; +#endif static const char levels[] = {'V', 'D', 'I', 'W', 'E', 'F'}; #if LT_LOGGER_COLOR diff --git a/docs/scripts/write_apis.py b/docs/scripts/write_apis.py index 9346cbd8c..d1bf4de95 100644 --- a/docs/scripts/write_apis.py +++ b/docs/scripts/write_apis.py @@ -38,7 +38,7 @@ implementation = re.sub(macro_regex, "", implementation, flags=re.MULTILINE) implementation = re.sub(line_regex, "\n", implementation) - declaration_regex = r"^([\d\w\s]+ \*?)([\S]+?)\(.*?\);$" + declaration_regex = r"^([^\s][\d\w\s]+ \*?)([\S]+?)\(.*?\);$" implementation_regex = r"^(__attribute__\(\(weak\)\) )?([\w\d* ]+?)([\w\d]+)\(.+?{" function_types = {} @@ -85,11 +85,15 @@ + Style.RESET_ALL ) + function_types[function_name] = function_type if is_weak: weak_functions.add(function_name) else: impl_functions.add(function_name) +for function in list(impl_functions): + if "static" in function_types[function]: + impl_functions.remove(function) common_functions = impl_functions.union(weak_functions) family_functions = decl_functions - common_functions diff --git a/external-libs.json b/external-libs.json index 9b3ff07bb..264e7ba39 100644 --- a/external-libs.json +++ b/external-libs.json @@ -36,13 +36,13 @@ "PRINTF_INCLUDE_CONFIG_H": "1" } }, - "ltchiptool": { - "package": "tool-ltchiptool", + "uf2ota": { + "package": "library-uf2ota", "sources": [ - "+" + "+" ], "includes": [ - "+<.>" + "+" ] }, "arduino-api": { diff --git a/platform.json b/platform.json index facb6a338..cc4639731 100644 --- a/platform.json +++ b/platform.json @@ -98,6 +98,10 @@ "type": "framework", "version": "https://github.com/libretiny-eu/library-printf#6.1.0" }, + "library-uf2ota": { + "type": "framework", + "version": "https://github.com/libretiny-eu/library-uf2ota#5.0.0" + }, "toolchain-gccarmnoneeabi": { "type": "toolchain", "optionalVersions": [ @@ -107,12 +111,6 @@ "~1.100301.0" ] }, - "tool-ltchiptool": { - "type": "uploader", - "version": "https://github.com/libretiny-eu/ltchiptool#v4.0.0", - "version_prefix": true, - "note": "This is used only for C/C++ code from ltchiptool." - }, "tool-openocd": { "type": "uploader", "optional": true,