[core] Migrate to uf2ota v5.0.0, refactor OTA API
This commit is contained in:
@@ -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)
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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<lt_ota_ctx_t *>(malloc(sizeof(lt_ota_ctx_t)));
|
||||
lt_ota_begin(this->ctx, size);
|
||||
this->ctx->callback = reinterpret_cast<void (*)(void *)>(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<uf2_block_t *>(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;
|
||||
|
||||
@@ -4,39 +4,30 @@
|
||||
#include <functional>
|
||||
#include <uf2ota/uf2ota.h>
|
||||
|
||||
// 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<void(size_t, size_t)> 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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,26 +2,46 @@
|
||||
|
||||
#include "Update.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
extern "C" {
|
||||
#include <fal.h>
|
||||
}
|
||||
|
||||
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,153 +49,14 @@ 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;
|
||||
|
||||
bytesWritten = 0;
|
||||
bytesTotal = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
return false;
|
||||
cleanup(errorMap[err], err);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Abort the update with UPDATE_ERROR_ABORT reason.
|
||||
*/
|
||||
void UpdateClass::abort() {
|
||||
LT_DM(OTA, "Aborting update");
|
||||
cleanup(UPDATE_ERROR_ABORT);
|
||||
}
|
||||
|
||||
void UpdateClass::bufAlloc() {
|
||||
if (!buf)
|
||||
buf = bufPos = (uint8_t *)malloc(UF2_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
uint16_t UpdateClass::bufLeft() {
|
||||
return buf + UF2_BLOCK_SIZE - bufPos;
|
||||
}
|
||||
|
||||
uint16_t UpdateClass::bufSize() {
|
||||
return bufPos - buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print string error info to the stream.
|
||||
*/
|
||||
void UpdateClass::printError(Print &out) {
|
||||
out.println(errorString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print details about the error and current OTA state.
|
||||
*/
|
||||
void UpdateClass::printErrorContext1() {
|
||||
#if LT_DEBUG_OTA
|
||||
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());
|
||||
|
||||
if (ctx)
|
||||
LT_EM(
|
||||
OTA,
|
||||
"- ctx: seq=%u, part=%s",
|
||||
ctx->seq - 1, // print last parsed block seq
|
||||
ctx->part ? ctx->part->name : NULL
|
||||
);
|
||||
|
||||
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);
|
||||
#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;
|
||||
void UpdateClass::progressHandler(UpdateClass *self) {
|
||||
if (self->callback)
|
||||
self->callback(self->ctx->bytes_written, self->ctx->bytes_total);
|
||||
}
|
||||
|
||||
/** @copydoc lt_ota_can_rollback() */
|
||||
@@ -187,5 +68,85 @@ bool UpdateClass::canRollBack() {
|
||||
bool UpdateClass::rollBack() {
|
||||
if (!lt_ota_can_rollback())
|
||||
return false;
|
||||
return lt_ota_switch(false);
|
||||
return lt_ota_switch(/* revert= */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get combined error code of the update.
|
||||
*/
|
||||
uint16_t UpdateClass::getErrorCode() const {
|
||||
return (this->getError() << 8) | this->getUF2Error();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) const {
|
||||
out.println(errorString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print details about the error and current OTA state.
|
||||
*/
|
||||
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", 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",
|
||||
this->ctx->uf2.seq - 1, // print last parsed block seq
|
||||
this->ctx->uf2.part ? this->ctx->uf2.part->name : nullptr
|
||||
);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,10 +2,148 @@
|
||||
|
||||
#include "lt_ota.h"
|
||||
|
||||
#include <uf2ota/uf2ota.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
/* Copyright (c) Kuba Szczodrzyński 2022-04-28. */
|
||||
|
||||
#include "lt_logger.h"
|
||||
|
||||
#if __has_include(<sdk_private.h>)
|
||||
#include <sdk_private.h>
|
||||
#endif
|
||||
|
||||
#if LT_HAS_PRINTF
|
||||
#include <printf/printf.h>
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user