diff --git a/extras/tools/bin2ota.py b/extras/tools/bin2ota.py index 648aa2395..ffb21a856 100755 --- a/extras/tools/bin2ota.py +++ b/extras/tools/bin2ota.py @@ -24,6 +24,8 @@ magic_number = 0x23418057.to_bytes(4,byteorder='little') elif board == "PORTENTA_H7_M7": magic_number = 0x2341025B.to_bytes(4,byteorder='little') +elif board == "NANO_RP2040_CONNECT": + magic_number = 0x2341005E.to_bytes(4,byteorder='little') else: print ("Error,", board, "is not a supported board type") sys.exit() diff --git a/src/AIoTC_Config.h b/src/AIoTC_Config.h index 7c54d1751..9ceb2ac62 100644 --- a/src/AIoTC_Config.h +++ b/src/AIoTC_Config.h @@ -22,10 +22,6 @@ * USER CONFIGURABLE DEFINES ******************************************************************************/ -#ifndef OTA_STORAGE_SFU - #define OTA_STORAGE_SFU (0) -#endif - #ifndef NTP_USE_RANDOM_PORT #define NTP_USE_RANDOM_PORT (1) #endif @@ -94,6 +90,12 @@ #define OTA_STORAGE_SNU (0) #endif +#if defined(ARDUINO_NANO_RP2040_CONNECT) + #define OTA_STORAGE_SFU (1) +#else + #define OTA_STORAGE_SFU (0) +#endif + #ifdef ARDUINO_SAMD_MKRGSM1400 #define OTA_STORAGE_SSU (1) #else @@ -143,4 +145,7 @@ #define AIOT_CONFIG_TIMEOUT_FOR_LASTVALUES_SYNC_ms (30000UL) #define AIOT_CONFIG_LASTVALUES_SYNC_MAX_RETRY_CNT (10UL) +#define AIOT_CONFIG_RP2040_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms (10*1000UL) +#define AIOT_CONFIG_RP2040_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms (4*60*1000UL) + #endif /* ARDUINO_AIOTC_CONFIG_H_ */ diff --git a/src/ArduinoIoTCloudTCP.cpp b/src/ArduinoIoTCloudTCP.cpp index 653952e42..68347d97c 100644 --- a/src/ArduinoIoTCloudTCP.cpp +++ b/src/ArduinoIoTCloudTCP.cpp @@ -160,7 +160,7 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, sha256_str += buf; }); DEBUG_VERBOSE("SHA256: %d bytes (of %d) read", bytes_read, app_size); -#else +#elif defined(ARDUINO_ARCH_SAMD) /* Calculate the SHA256 checksum over the firmware stored in the flash of the * MCU. Note: As we don't know the length per-se we read chunks of the flash * until we detect one containing only 0xFF (= flash erased). This only works @@ -172,6 +172,13 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, * range 0 to 0x2000, total flash size of 0x40000 bytes (256 kByte). */ String const sha256_str = FlashSHA256::calc(0x2000, 0x40000 - 0x2000); +#elif defined(ARDUINO_NANO_RP2040_CONNECT) + /* The maximum size of a RP2040 OTA update image is 1 MByte (that is 1024 * + * 1024 bytes or 0x100'000 bytes). + */ + String const sha256_str = FlashSHA256::calc(XIP_BASE, 0x100000); +#else +# error "No method for SHA256 checksum calculation over application image defined for this architecture." #endif DEBUG_VERBOSE("SHA256: HASH(%d) = %s", strlen(sha256_str.c_str()), sha256_str.c_str()); _ota_img_sha256 = sha256_str; @@ -259,6 +266,10 @@ int ArduinoIoTCloudTCP::begin(bool const enable_watchdog, String brokerAddress, } #endif /* OTA_STORAGE_SNU */ +#if defined(ARDUINO_NANO_RP2040_CONNECT) + _ota_cap = true; +#endif + #ifdef BOARD_HAS_OFFLOADED_ECCX08 if (String(WiFi.firmwareVersion()) < String("1.4.4")) { DEBUG_ERROR("ArduinoIoTCloudTCP::%s In order to connect to Arduino IoT Cloud, NINA firmware needs to be >= 1.4.4, current %s", __FUNCTION__, WiFi.firmwareVersion()); @@ -309,6 +320,16 @@ void ArduinoIoTCloudTCP::update() } _state = next_state; + /* This watchdog feed is actually needed only by the RP2040 CONNECT cause its + * maximum watchdog window is 8388ms; despite this we feed it for all + * supported ARCH to keep code aligned. + */ +#ifdef ARDUINO_ARCH_SAMD + samd_watchdog_reset(); +#elif defined(ARDUINO_ARCH_MBED) + mbed_watchdog_reset(); +#endif + /* Check for new data from the MQTT client. */ if (_mqttClient.connected()) _mqttClient.poll(); @@ -491,6 +512,7 @@ ArduinoIoTCloudTCP::State ArduinoIoTCloudTCP::handle_Connected() /* Request a OTA download if the hidden property * OTA request has been set. */ + if (_ota_req) { /* Clear the error flag. */ @@ -581,12 +603,16 @@ int ArduinoIoTCloudTCP::write(String const topic, byte const data[], int const l #if OTA_ENABLED void ArduinoIoTCloudTCP::onOTARequest() { - DEBUG_VERBOSE("ArduinoIoTCloudTCP::%s _ota_url = %s", __FUNCTION__, _ota_url.c_str()); + DEBUG_INFO("ArduinoIoTCloudTCP::%s _ota_url = %s", __FUNCTION__, _ota_url.c_str()); #ifdef ARDUINO_ARCH_SAMD _ota_error = samd_onOTARequest(_ota_url.c_str()); #endif +#ifdef ARDUINO_NANO_RP2040_CONNECT + _ota_error = rp2040_connect_onOTARequest(_ota_url.c_str()); +#endif + #if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_PORTENTA_H7_M4) _ota_error = portenta_h7_onOTARequest(_ota_url.c_str()); #endif diff --git a/src/utility/ota/FlashSHA256.cpp b/src/utility/ota/FlashSHA256.cpp index 3bdc034c9..cac4db619 100644 --- a/src/utility/ota/FlashSHA256.cpp +++ b/src/utility/ota/FlashSHA256.cpp @@ -45,11 +45,12 @@ String FlashSHA256::calc(uint32_t const start_addr, uint32_t const max_flash_siz sha256.begin(); /* Read the first two chunks of flash. */ - uint32_t flash_addr = start_addr; + uint32_t flash_addr = start_addr; + uint32_t bytes_read = 0; memcpy(chunk, reinterpret_cast(flash_addr), FLASH_READ_CHUNK_SIZE); flash_addr += FLASH_READ_CHUNK_SIZE; - for(; flash_addr < max_flash_size; flash_addr += FLASH_READ_CHUNK_SIZE) + for(; bytes_read < max_flash_size; flash_addr += FLASH_READ_CHUNK_SIZE) { /* Read the next chunk of memory. */ memcpy(next_chunk, reinterpret_cast(flash_addr), FLASH_READ_CHUNK_SIZE); @@ -75,6 +76,7 @@ String FlashSHA256::calc(uint32_t const start_addr, uint32_t const max_flash_siz } /* Update with the remaining bytes. */ sha256.update(chunk, valid_bytes_in_chunk); + bytes_read += valid_bytes_in_chunk; break; } @@ -82,6 +84,7 @@ String FlashSHA256::calc(uint32_t const start_addr, uint32_t const max_flash_siz * any erased elements, just update the SHA256 hash calcultion. */ sha256.update(chunk, FLASH_READ_CHUNK_SIZE); + bytes_read += FLASH_READ_CHUNK_SIZE; /* Copy next_chunk to chunk. */ memcpy(chunk, next_chunk, FLASH_READ_CHUNK_SIZE); @@ -100,7 +103,7 @@ String FlashSHA256::calc(uint32_t const start_addr, uint32_t const max_flash_siz sha256_str += buf; }); /* Do some debug printout. */ - DEBUG_VERBOSE("SHA256: %d bytes read", flash_addr); + DEBUG_VERBOSE("SHA256: %d bytes read", bytes_read); return sha256_str; } diff --git a/src/utility/ota/OTA-nano-rp2040.cpp b/src/utility/ota/OTA-nano-rp2040.cpp new file mode 100644 index 000000000..8e0c3106b --- /dev/null +++ b/src/utility/ota/OTA-nano-rp2040.cpp @@ -0,0 +1,252 @@ +/* + This file is part of ArduinoIoTCloud. + + Copyright 2020 ARDUINO SA (http://www.arduino.cc/) + + This software is released under the GNU General Public License version 3, + which covers the main part of arduino-cli. + The terms of this license can be found at: + https://www.gnu.org/licenses/gpl-3.0.en.html + + You can be released from the requirements of the above licenses by purchasing + a commercial license. Buying such a license is mandatory if you want to modify or + otherwise use the software for commercial activities involving the Arduino + software without disclosing the source code of your own applications. To purchase + a commercial license, send an email to license@arduino.cc. +*/ + +#if defined(ARDUINO_NANO_RP2040_CONNECT) + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include "OTA.h" + +#include "../watchdog/Watchdog.h" + +#include + +#include + +#include "mbed.h" +#include "FATFileSystem.h" +#include "FlashIAPBlockDevice.h" + +/****************************************************************************** + * FUNCTION DEFINITION + ******************************************************************************/ + +/* Original code: http://stackoverflow.com/questions/2616011/easy-way-to-parse-a-url-in-c-cross-platform */ +#include +#include +#include +#include +#include + +struct URI { + public: + URI(const std::string& url_s) { + this->parse(url_s); + } + std::string protocol_, host_, path_, query_; + private: + void parse(const std::string& url_s); +}; + +using namespace std; + +// ctors, copy, equality, ... +// TODO: change me into something embedded friendly (this function adds ~100KB to flash) +void URI::parse(const string& url_s) +{ + const string prot_end("://"); + string::const_iterator prot_i = search(url_s.begin(), url_s.end(), + prot_end.begin(), prot_end.end()); + protocol_.reserve(distance(url_s.begin(), prot_i)); + transform(url_s.begin(), prot_i, + back_inserter(protocol_), + ptr_fun(tolower)); // protocol is icase + if( prot_i == url_s.end() ) + return; + advance(prot_i, prot_end.length()); + string::const_iterator path_i = find(prot_i, url_s.end(), '/'); + host_.reserve(distance(prot_i, path_i)); + transform(prot_i, path_i, + back_inserter(host_), + ptr_fun(tolower)); // host is icase + string::const_iterator query_i = find(path_i, url_s.end(), '?'); + path_.assign(path_i, query_i); + if( query_i != url_s.end() ) + ++query_i; + query_.assign(query_i, url_s.end()); +} + +int rp2040_connect_onOTARequest(char const * ota_url) +{ + mbed_watchdog_reset(); + + int err = -1; + FlashIAPBlockDevice flash(XIP_BASE + 0xF00000, 0x100000); + if ((err = flash.init()) < 0) + { + DEBUG_ERROR("%s: flash.init() failed with %d", __FUNCTION__, err); + return static_cast(OTAError::RP2040_ErrorFlashInit); + } + + mbed_watchdog_reset(); + + flash.erase(XIP_BASE + 0xF00000, 0x100000); + + mbed_watchdog_reset(); + + mbed::FATFileSystem fs("ota"); + if ((err = fs.reformat(&flash)) != 0) + { + DEBUG_ERROR("%s: fs.reformat() failed with %d", __FUNCTION__, err); + return static_cast(OTAError::RP2040_ErrorReformat); + } + + mbed_watchdog_reset(); + + FILE * file = fopen("/ota/UPDATE.BIN.LZSS", "wb"); + if (!file) + { + DEBUG_ERROR("%s: fopen() failed", __FUNCTION__); + fclose(file); + return static_cast(OTAError::RP2040_ErrorOpenUpdateFile); + } + + mbed_watchdog_reset(); + + URI url(ota_url); + Client * client = nullptr; + int port = 0; + + if (url.protocol_ == "http") { + client = new WiFiClient(); + port = 80; + } else if (url.protocol_ == "https") { + client = new WiFiSSLClient(); + port = 443; + } else { + DEBUG_ERROR("%s: Failed to parse OTA URL %s", __FUNCTION__, ota_url); + fclose(file); + return static_cast(OTAError::RP2040_UrlParseError); + } + + mbed_watchdog_reset(); + + if (!client->connect(url.host_.c_str(), port)) + { + DEBUG_ERROR("%s: Connection failure with OTA storage server %s", __FUNCTION__, url.host_.c_str()); + fclose(file); + return static_cast(OTAError::RP2040_ServerConnectError); + } + + mbed_watchdog_reset(); + + client->println(String("GET ") + url.path_.c_str() + " HTTP/1.1"); + client->println(String("Host: ") + url.host_.c_str()); + client->println("Connection: close"); + client->println(); + + mbed_watchdog_reset(); + + /* Receive HTTP header. */ + String http_header; + bool is_header_complete = false, + is_http_header_timeout = false; + for (unsigned long const start = millis(); !is_header_complete;) + { + is_http_header_timeout = (millis() - start) > AIOT_CONFIG_RP2040_OTA_HTTP_HEADER_RECEIVE_TIMEOUT_ms; + if (is_http_header_timeout) break; + + mbed_watchdog_reset(); + + if (client->available()) + { + char const c = client->read(); + + http_header += c; + if (http_header.endsWith("\r\n\r\n")) + is_header_complete = true; + } + } + + if (!is_header_complete) + { + DEBUG_ERROR("%s: Error receiving HTTP header %s", __FUNCTION__, is_http_header_timeout ? "(timeout)":""); + fclose(file); + return static_cast(OTAError::RP2040_HttpHeaderError); + } + + /* Extract concent length from HTTP header. A typical entry looks like + * "Content-Length: 123456" + */ + char const * content_length_ptr = strstr(http_header.c_str(), "Content-Length"); + if (!content_length_ptr) + { + DEBUG_ERROR("%s: Failure to extract content length from http header", __FUNCTION__); + fclose(file); + return static_cast(OTAError::RP2040_ErrorParseHttpHeader); + } + /* Find start of numerical value. */ + char * ptr = const_cast(content_length_ptr); + for (; (*ptr != '\0') && !isDigit(*ptr); ptr++) { } + /* Extract numerical value. */ + String content_length_str; + for (; isDigit(*ptr); ptr++) content_length_str += *ptr; + int const content_length_val = atoi(content_length_str.c_str()); + DEBUG_VERBOSE("%s: Length of OTA binary according to HTTP header = %d bytes", __FUNCTION__, content_length_val); + + /* Receive as many bytes as are indicated by the HTTP header - or die trying. */ + int bytes_received = 0; + bool is_http_data_timeout = false; + for(unsigned long const start = millis(); bytes_received < content_length_val;) + { + is_http_data_timeout = (millis() - start) > AIOT_CONFIG_RP2040_OTA_HTTP_DATA_RECEIVE_TIMEOUT_ms; + if (is_http_data_timeout) break; + + mbed_watchdog_reset(); + + if (client->available()) + { + char const c = client->read(); + + if (fwrite(&c, 1, sizeof(c), file) != sizeof(c)) + { + DEBUG_ERROR("%s: Writing of firmware image to flash failed", __FUNCTION__); + fclose(file); + return static_cast(OTAError::RP2040_ErrorWriteUpdateFile); + } + + bytes_received++; + } + } + + if (bytes_received != content_length_val) { + DEBUG_ERROR("%s: Error receiving HTTP data %s (%d bytes received, %d expected)", __FUNCTION__, is_http_data_timeout ? "(timeout)":"", bytes_received, content_length_val); + fclose(file); + return static_cast(OTAError::RP2040_HttpDataError); + } + + DEBUG_INFO("%s: %d bytes received", __FUNCTION__, ftell(file)); + fclose(file); + + /* Unmount the filesystem. */ + if ((err = fs.unmount()) != 0) + { + DEBUG_ERROR("%s: fs.unmount() failed with %d", __FUNCTION__, err); + return static_cast(OTAError::RP2040_ErrorUnmount); + } + + /* Perform the reset to reboot to SFU. */ + mbed_watchdog_trigger_reset(); + /* If watchdog is enabled we should not reach this point */ + NVIC_SystemReset(); + + return static_cast(OTAError::None); +} + +#endif /* ARDUINO_NANO_RP2040_CONNECT */ diff --git a/src/utility/ota/OTA.h b/src/utility/ota/OTA.h index 6a9b2312f..bc92dede1 100644 --- a/src/utility/ota/OTA.h +++ b/src/utility/ota/OTA.h @@ -24,13 +24,11 @@ #include -#if OTA_STORAGE_SSU - #include -#endif /* OTA_STORAGE_SSU */ +/****************************************************************************** + * DEFINES + ******************************************************************************/ -#if OTA_STORAGE_SFU - #include -#endif /* OTA_STORAGE_SFU */ +#define RP2040_OTA_ERROR_BASE (-100) /****************************************************************************** * TYPEDEF @@ -40,6 +38,16 @@ enum class OTAError : int { None = 0, DownloadFailed = 1, + RP2040_UrlParseError = RP2040_OTA_ERROR_BASE - 0, + RP2040_ServerConnectError = RP2040_OTA_ERROR_BASE - 1, + RP2040_HttpHeaderError = RP2040_OTA_ERROR_BASE - 2, + RP2040_HttpDataError = RP2040_OTA_ERROR_BASE - 3, + RP2040_ErrorOpenUpdateFile = RP2040_OTA_ERROR_BASE - 4, + RP2040_ErrorWriteUpdateFile = RP2040_OTA_ERROR_BASE - 5, + RP2040_ErrorParseHttpHeader = RP2040_OTA_ERROR_BASE - 6, + RP2040_ErrorFlashInit = RP2040_OTA_ERROR_BASE - 7, + RP2040_ErrorReformat = RP2040_OTA_ERROR_BASE - 8, + RP2040_ErrorUnmount = RP2040_OTA_ERROR_BASE - 9, }; /****************************************************************************** @@ -50,6 +58,10 @@ enum class OTAError : int int samd_onOTARequest(char const * ota_url); #endif +#ifdef ARDUINO_NANO_RP2040_CONNECT +int rp2040_connect_onOTARequest(char const * ota_url); +#endif + #if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_PORTENTA_H7_M4) int portenta_h7_onOTARequest(char const * ota_url); #endif diff --git a/src/utility/watchdog/Watchdog.cpp b/src/utility/watchdog/Watchdog.cpp index 14c5d68e6..314e12e18 100644 --- a/src/utility/watchdog/Watchdog.cpp +++ b/src/utility/watchdog/Watchdog.cpp @@ -110,4 +110,25 @@ void mbed_watchdog_reset() hal_watchdog_kick(); } } + +void mbed_watchdog_trigger_reset() +{ + watchdog_config_t cfg; +#if defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_PORTENTA_H7_M4) + cfg.timeout_ms = 1; +#elif defined(ARDUINO_NANO_RP2040_CONNECT) + cfg.timeout_ms = 1; +#else +# error "You need to define the maximum possible timeout for this architecture." +#endif + + if (hal_watchdog_init(&cfg) == WATCHDOG_STATUS_OK) { + is_watchdog_enabled = true; + while(1){} + } + else { + DEBUG_WARNING("%s: watchdog could not be reconfigured", __FUNCTION__); + } + +} #endif /* ARDUINO_ARCH_MBED */ diff --git a/src/utility/watchdog/Watchdog.h b/src/utility/watchdog/Watchdog.h index 91161d072..e1c8d2c27 100644 --- a/src/utility/watchdog/Watchdog.h +++ b/src/utility/watchdog/Watchdog.h @@ -30,6 +30,7 @@ void samd_watchdog_reset(); #ifdef ARDUINO_ARCH_MBED void mbed_watchdog_enable(); void mbed_watchdog_reset(); +void mbed_watchdog_trigger_reset(); #endif /* ARDUINO_ARCH_MBED */ #endif /* ARDUINO_AIOTC_UTILITY_WATCHDOG_H_ */