diff --git a/arduino/libretuya/core/LibreTuyaAPI.cpp b/arduino/libretuya/core/LibreTuyaAPI.cpp index 9e0abcd..48ce84d 100644 --- a/arduino/libretuya/core/LibreTuyaAPI.cpp +++ b/arduino/libretuya/core/LibreTuyaAPI.cpp @@ -107,3 +107,56 @@ const char *LibreTuya::getDeviceName() { sprintf(deviceName, "LT-%s-%02x%02x%02x", board, id[0], id[1], id[2]); return deviceName; } + +static uint8_t otaRunningIndex = 0; + +/** + * @brief Get the currently running firmware OTA index. + */ +uint8_t LibreTuya::otaGetRunning() { + if (otaRunningIndex) + return otaRunningIndex; + // otaRunningIndex will be correct even after switchOta() + return otaRunningIndex = otaGetStoredIndex(); +} + +/** + * @brief Get the OTA index for updated firmware. + * + * Note: returns 1 for chips without dual-OTA. + */ +uint8_t LibreTuya::otaGetTarget() { + if (!otaSupportsDual()) + return 1; + return otaGetRunning() ^ 0b11; +} + +/** + * @brief Perform OTA rollback. + * + * @return false if no second image to run, writing failed or dual-OTA not supported + */ +bool LibreTuya::otaRollback() { + if (!otaCanRollback()) + return false; + if (otaGetRunning() != otaGetStoredIndex()) + // force switching back to current image + return otaSwitch(true); + return true; +} + +/** + * @brief Check if OTA rollback is supported and available (there is another image to run). + * @return false if no second image to run or dual-OTA not supported + */ +bool LibreTuya::otaCanRollback() { + if (!otaSupportsDual()) + return false; + if (otaGetRunning() == otaGetStoredIndex()) + return true; + if (otaGetRunning() == 1 && otaHasImage1()) + return true; + if (otaGetRunning() == 2 && otaHasImage2()) + return true; + return false; +} diff --git a/arduino/libretuya/core/LibreTuyaAPI.h b/arduino/libretuya/core/LibreTuyaAPI.h index 2307b3e..825d1c1 100644 --- a/arduino/libretuya/core/LibreTuyaAPI.h +++ b/arduino/libretuya/core/LibreTuyaAPI.h @@ -70,9 +70,13 @@ class LibreTuya { public: /* Common methods - note: these are documented in LibreTuyaAPI.cpp */ const char *getVersion(); const char *getBoard(); - const char *getDeviceName(); ChipFamily getChipFamily(); const char *getChipFamilyName(); + const char *getDeviceName(); + uint8_t otaGetRunning(); + uint8_t otaGetTarget(); + bool otaRollback(); + bool otaCanRollback(); public: /* Inline methods */ inline uint32_t getFlashChipSize() { @@ -146,21 +150,30 @@ class LibreTuya { public: /* OTA-related */ /** - * @brief Get the currently running firmware OTA index. + * @brief Read the currently active OTA index, i.e. the one that will boot upon restart. */ - uint8_t getOtaRunning(); + uint8_t otaGetStoredIndex(); /** - * @brief Get the OTA index for updated firmware. - * - * Note: should return 1 for chips without dual-OTA. + * @brief Check if the chip supports dual-OTA. */ - uint8_t getOtaTarget(); + bool otaSupportsDual(); + /** + * @brief Check if OTA1 image is valid. + */ + bool otaHasImage1(); + /** + * @brief Check if OTA2 image is valid. + */ + bool otaHasImage2(); /** * @brief Try to switch OTA index to the other image. * - * @return false if writing failed or dual-OTA not supported; true otherwise + * Note: should return true for chips without dual-OTA. Should return false if one of two images is not valid. + * + * @param force switch even if other image already marked as active + * @return false if writing failed; true otherwise */ - bool switchOta(); + bool otaSwitch(bool force = false); }; extern LibreTuya LT; diff --git a/arduino/realtek-ambz/cores/arduino/LibreTuyaAPI.cpp b/arduino/realtek-ambz/cores/arduino/LibreTuyaAPI.cpp index 714f72f..4d151ea 100644 --- a/arduino/realtek-ambz/cores/arduino/LibreTuyaAPI.cpp +++ b/arduino/realtek-ambz/cores/arduino/LibreTuyaAPI.cpp @@ -2,8 +2,11 @@ #include +extern "C" { +#include #include #include +} void LibreTuya::restart() { sys_reset(); @@ -71,13 +74,14 @@ uint32_t LibreTuya::getMaxAllocHeap() { /* OTA-related */ -uint8_t LibreTuya::getOtaRunning() { +uint8_t LibreTuya::otaGetStoredIndex() { uint32_t *otaAddress = (uint32_t *)0x8009000; if (*otaAddress == 0xFFFFFFFF) return 1; uint32_t otaCounter = *((uint32_t *)0x8009004); - // what the- - // "LSB 0 bits number is odd/even" + // even count of zero-bits means OTA1, odd count means OTA2 + // this allows to switch OTA images by simply clearing next bits, + // without needing to erase the flash uint8_t count = 0; for (uint8_t i = 0; i < 32; i++) { if ((otaCounter & (1 << i)) == 0) @@ -86,11 +90,55 @@ uint8_t LibreTuya::getOtaRunning() { return 1 + (count % 2); } -uint8_t LibreTuya::getOtaTarget() { - return getOtaRunning() ^ 0b11; +bool LibreTuya::otaSupportsDual() { + return true; } -bool LibreTuya::switchOta() {} +bool LibreTuya::otaHasImage1() { + uint8_t *ota1Addr = (uint8_t *)(SPI_FLASH_BASE + FLASH_OTA1_OFFSET); + return memcmp(ota1Addr, "81958711", 8) == 0; +} + +bool LibreTuya::otaHasImage2() { + uint8_t *ota2Addr = (uint8_t *)(SPI_FLASH_BASE + FLASH_OTA2_OFFSET); + return memcmp(ota2Addr, "81958711", 8) == 0; +} + +bool LibreTuya::otaSwitch(bool force) { + if (!force && otaGetRunning() != otaGetStoredIndex()) + // OTA has already been switched + return true; + // this function does: + // - read OTA1 firmware magic from 0xB000 + // - read OTA2 address from 0x9000 + // - read OTA2 firmware magic from that address + // - read current OTA switch value from 0x9004 + // - reset OTA switch to 0xFFFFFFFF if it's 0x0 + // - check first non-zero bit of OTA switch + // - write OTA switch with first non-zero bit cleared + // sys_clear_ota_signature(); + // ok, this function is broken (crashes with HardFault) + + if (!otaHasImage1() || !otaHasImage2()) + return false; + + uint32_t value = HAL_READ32(SPI_FLASH_BASE, FLASH_SYSTEM_OFFSET + 4); + if (value == 0) { + // TODO does this work at all? + FLASH_EreaseDwordsXIP(FLASH_SYSTEM_OFFSET + 4, 1); + } + uint8_t i; + // find first non-zero bit + for (i = 0; i < 32; i++) { + if (value & (1 << i)) + break; + } + // clear the bit + value &= ~(1 << i); + // write OTA switch to flash + flash_write_word(NULL, FLASH_SYSTEM_OFFSET + 4, value); + return true; +} /* Global instance */