diff --git a/applications/serial_lte_modem/Kconfig b/applications/serial_lte_modem/Kconfig index 044b6c50166..390645508fb 100644 --- a/applications/serial_lte_modem/Kconfig +++ b/applications/serial_lte_modem/Kconfig @@ -164,6 +164,10 @@ config SLM_NRF_CLOUD bool "nRF Cloud support in SLM" default y +config SLM_FULL_FOTA + bool "Full modem FOTA support in SLM" + default n + rsource "src/ftp_c/Kconfig" rsource "src/mqtt_c/Kconfig" rsource "src/http_c/Kconfig" diff --git a/applications/serial_lte_modem/boards/nrf9160dk_nrf9160_ns.overlay b/applications/serial_lte_modem/boards/nrf9160dk_nrf9160_ns.overlay index 9019a6d2900..bcc872f9d1b 100644 --- a/applications/serial_lte_modem/boards/nrf9160dk_nrf9160_ns.overlay +++ b/applications/serial_lte_modem/boards/nrf9160dk_nrf9160_ns.overlay @@ -53,3 +53,10 @@ }; }; }; + +/* Enable external flash */ +&spi3 { + mx25r64: mx25r6435f@1 { + status = "okay"; + }; +}; diff --git a/applications/serial_lte_modem/boards/nrf9161dk_nrf9161_ns.overlay b/applications/serial_lte_modem/boards/nrf9161dk_nrf9161_ns.overlay index 730779b02e3..f23bd965bbd 100644 --- a/applications/serial_lte_modem/boards/nrf9161dk_nrf9161_ns.overlay +++ b/applications/serial_lte_modem/boards/nrf9161dk_nrf9161_ns.overlay @@ -53,3 +53,10 @@ }; }; }; + +/* Enable external flash */ +&spi3 { + gd25lb256: gd25lb256e3ir@1 { + status = "okay"; + }; +}; diff --git a/applications/serial_lte_modem/doc/FOTA_AT_commands.rst b/applications/serial_lte_modem/doc/FOTA_AT_commands.rst index d817ab01ad8..45d907ca48a 100644 --- a/applications/serial_lte_modem/doc/FOTA_AT_commands.rst +++ b/applications/serial_lte_modem/doc/FOTA_AT_commands.rst @@ -31,8 +31,10 @@ Syntax * ``0`` - Cancel FOTA (during download only). * ``1`` - Start FOTA for application update. * ``2`` - Start FOTA for modem delta update. - * ``7`` - Read modem DFU area size and firmware image offset. - * ``9`` - Erase modem DFU area. + * ``3`` - Start FOTA for full modem update. + Can only be used when the :file:`overlay-full_fota.conf` configuration file is used. + * ``7`` - Read modem DFU area size and firmware image offset (for modem delta update). + * ``9`` - Erase modem DFU area (for modem delta update). * The ```` parameter is a string. It represents the full HTTP or HTTPS path of the target image to download. @@ -47,10 +49,17 @@ Syntax .. note:: - When doing modem FOTA, erasing the modem DFU area is optional since the update process will automatically erase the area if needed. + During a delta update to the modem, the modem DFU area is automatically erased as needed as part of the update process. Erasing this area manually is optional. However, this leads to the command starting the update taking longer to complete, and also leaves the connection to the FOTA server idle while the area is being erased, which can provoke issues. +.. note:: + + The firmware image is stored in external flash memory during a full modem update. + The external flash is erased automatically after a new firmware activation. + + Activating the new full modem firmware is done identically to a modem delta update, by resetting either the whole device or only the modem. + Response syntax ~~~~~~~~~~~~~~~ @@ -188,6 +197,6 @@ Examples AT#XFOTA=? - #XFOTA: (0,1,2,6,7,8,9),,, + #XFOTA: (0,1,2,3,7,9),,, OK diff --git a/applications/serial_lte_modem/doc/slm_description.rst b/applications/serial_lte_modem/doc/slm_description.rst index 0c016c5ffe7..b4697b0c413 100644 --- a/applications/serial_lte_modem/doc/slm_description.rst +++ b/applications/serial_lte_modem/doc/slm_description.rst @@ -250,6 +250,9 @@ The following configuration files are provided: * :file:`overlay-carrier.conf` - Configuration file that adds |NCS| :ref:`liblwm2m_carrier_readme` support. See :ref:`slm_carrier_library_support` for more information on how to connect to an operator's device management platform. +* :file:`overlay-full_fota.conf` - Configuration file that adds full modem FOTA support. + See :ref:`SLM_AT_FOTA` for more information on how to use full modem FOTA functionality. + * :file:`boards/nrf9160dk_nrf9160_ns.conf` - Configuration file specific for the nRF9160 DK. This file is automatically merged with the :file:`prj.conf` file when you build for the ``nrf9160dk_nrf9160_ns`` build target. diff --git a/applications/serial_lte_modem/overlay-full_fota.conf b/applications/serial_lte_modem/overlay-full_fota.conf new file mode 100644 index 00000000000..3f7fec68775 --- /dev/null +++ b/applications/serial_lte_modem/overlay-full_fota.conf @@ -0,0 +1,19 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Enable external flash for full FOTA +CONFIG_SPI_NOR=y + +# Full modem FOTA required settings +CONFIG_DFU_TARGET_STREAM=y +CONFIG_DFU_TARGET_FULL_MODEM=y +CONFIG_FMFU_FDEV_SKIP_PREVALIDATION=n +CONFIG_MBEDTLS_SHA256_C=y +CONFIG_FMFU_FDEV=y +CONFIG_ZCBOR=y + +# Enable full FOTA support +CONFIG_SLM_FULL_FOTA=y diff --git a/applications/serial_lte_modem/src/main.c b/applications/serial_lte_modem/src/main.c index 0c032a0a1b0..6e2d5725cc1 100644 --- a/applications/serial_lte_modem/src/main.c +++ b/applications/serial_lte_modem/src/main.c @@ -442,8 +442,9 @@ int main(void) } /* Post-FOTA handling */ - if (slm_fota_stage != FOTA_STAGE_INIT) { - if (slm_fota_type == DFU_TARGET_IMAGE_TYPE_MODEM_DELTA) { + if (slm_fota_stage == FOTA_STAGE_ACTIVATE) { + if (slm_fota_type == DFU_TARGET_IMAGE_TYPE_MODEM_DELTA || + slm_fota_type == DFU_TARGET_IMAGE_TYPE_FULL_MODEM) { slm_finish_modem_fota(ret); } else if (slm_fota_type == DFU_TARGET_IMAGE_TYPE_MCUBOOT) { handle_mcuboot_swap_ret(); diff --git a/applications/serial_lte_modem/src/slm_at_commands.c b/applications/serial_lte_modem/src/slm_at_commands.c index 036b3887b42..3d24804b163 100644 --- a/applications/serial_lte_modem/src/slm_at_commands.c +++ b/applications/serial_lte_modem/src/slm_at_commands.c @@ -229,8 +229,9 @@ static int handle_at_modemreset(enum at_cmd_type type) } ++step; - if (ret > 0 || (slm_fota_stage != FOTA_STAGE_INIT - && slm_fota_type == DFU_TARGET_IMAGE_TYPE_MODEM_DELTA)) { + if (ret > 0 || (slm_fota_stage == FOTA_STAGE_ACTIVATE + && (slm_fota_type == DFU_TARGET_IMAGE_TYPE_MODEM_DELTA || + slm_fota_type == DFU_TARGET_IMAGE_TYPE_FULL_MODEM))) { slm_finish_modem_fota(ret); slm_fota_post_process(); } diff --git a/applications/serial_lte_modem/src/slm_at_fota.c b/applications/serial_lte_modem/src/slm_at_fota.c index 13f53d873a0..5cc2717dea7 100644 --- a/applications/serial_lte_modem/src/slm_at_fota.c +++ b/applications/serial_lte_modem/src/slm_at_fota.c @@ -9,9 +9,15 @@ #include #include #include +#include #include #include +#include +#include +#include #include +#include +#include #include "slm_util.h" #include "slm_settings.h" #include "slm_at_host.h" @@ -26,7 +32,7 @@ LOG_MODULE_REGISTER(slm_fota, CONFIG_SLM_LOG_LEVEL); #define SCHEMA_HTTPS "https" #define URI_HOST_MAX CONFIG_DOWNLOAD_CLIENT_MAX_HOSTNAME_SIZE #define URI_SCHEMA_MAX 8 -#define ERASE_WAIT_TIME 20 +#define ERASE_WAIT_TIME 30 #define ERASE_POLL_TIME 2 /* Some features need fota_download update */ @@ -36,6 +42,7 @@ enum slm_fota_operation { SLM_FOTA_STOP = 0, SLM_FOTA_START_APP = 1, SLM_FOTA_START_MFW = 2, + SLM_FOTA_START_FULL_FOTA = 3, SLM_FOTA_PAUSE_RESUME = 4, SLM_FOTA_MFW_READ = 7, SLM_FOTA_ERASE_MFW = 9 @@ -44,6 +51,20 @@ enum slm_fota_operation { static char path[FILE_URI_MAX]; static char hostname[URI_HOST_MAX]; +#if defined(CONFIG_SLM_FULL_FOTA) +/* Buffer used as temporary storage when downloading the modem firmware. + */ +#define FMFU_BUF_SIZE 32 +static uint8_t fmfu_buf[FMFU_BUF_SIZE]; + +/* External flash device for full modem firmware storage */ +static const struct device *flash_dev = DEVICE_DT_GET_ONE(jedec_spi_nor); + +/* dfu_target specific configurations */ +static struct dfu_target_fmfu_fdev fdev; +static struct dfu_target_full_modem_params full_modem_fota_params; +#endif + static int do_fota_mfw_read(void) { int err; @@ -274,9 +295,15 @@ int handle_at_fota(enum at_cmd_type cmd_type) if (err < 0) { return err; } - if (op == SLM_FOTA_STOP) { + switch (op) { + case SLM_FOTA_STOP: err = fota_download_cancel(); - } else if (op == SLM_FOTA_START_APP || op == SLM_FOTA_START_MFW) { + break; + case SLM_FOTA_START_APP: + case SLM_FOTA_START_MFW: +#if defined(CONFIG_SLM_FULL_FOTA) + case SLM_FOTA_START_FULL_FOTA: +#endif char uri[FILE_URI_MAX]; uint16_t pdn_id; int size = FILE_URI_MAX; @@ -292,7 +319,29 @@ int handle_at_fota(enum at_cmd_type cmd_type) } if (op == SLM_FOTA_START_APP) { type = DFU_TARGET_IMAGE_TYPE_MCUBOOT; - } else { + } +#if defined(CONFIG_SLM_FULL_FOTA) + else if (op == SLM_FOTA_START_FULL_FOTA) { + fdev.dev = flash_dev; + full_modem_fota_params.buf = fmfu_buf; + full_modem_fota_params.len = sizeof(fmfu_buf); + full_modem_fota_params.dev = &fdev; + + if (!device_is_ready(flash_dev)) { + LOG_ERR("Flash device %s not ready\n", flash_dev->name); + return -ENXIO; + } + + err = dfu_target_full_modem_cfg(&full_modem_fota_params); + if (err != 0 && err != -EALREADY) { + LOG_ERR("dfu_target_full_modem_cfg failed: %d\n", err); + return err; + } + + type = DFU_TARGET_IMAGE_TYPE_FULL_MODEM; + } +#endif + else { type = DFU_TARGET_IMAGE_TYPE_MODEM_DELTA; } if (at_params_valid_count_get(&slm_at_param_list) > 4) { @@ -301,8 +350,9 @@ int handle_at_fota(enum at_cmd_type cmd_type) } else { err = do_fota_start(op, uri, sec_tag, 0, type); } + break; #if FOTA_FUTURE_FEATURE - } else if (op == SLM_FOTA_PAUSE_RESUME) { + case SLM_FOTA_PAUSE_RESUME: if (paused) { fota_download_resume(); paused = false; @@ -311,19 +361,30 @@ int handle_at_fota(enum at_cmd_type cmd_type) paused = true; } err = 0; + break; #endif - } else if (op == SLM_FOTA_MFW_READ) { + case SLM_FOTA_MFW_READ: err = do_fota_mfw_read(); - } else if (op == SLM_FOTA_ERASE_MFW) { + break; + case SLM_FOTA_ERASE_MFW: err = do_fota_erase_mfw(); - } else { + break; + default: err = -EINVAL; - } break; + break; + } + break; case AT_CMD_TYPE_TEST_COMMAND: +#if defined(CONFIG_SLM_FULL_FOTA) + rsp_send("\r\n#XFOTA: (%d,%d,%d,%d,%d,%d)[,[,[,]]]\r\n", + SLM_FOTA_STOP, SLM_FOTA_START_APP, SLM_FOTA_START_MFW, + SLM_FOTA_MFW_READ, SLM_FOTA_ERASE_MFW, SLM_FOTA_START_FULL_FOTA); +#else rsp_send("\r\n#XFOTA: (%d,%d,%d,%d,%d)[,[,[,]]]\r\n", SLM_FOTA_STOP, SLM_FOTA_START_APP, SLM_FOTA_START_MFW, SLM_FOTA_MFW_READ, SLM_FOTA_ERASE_MFW); +#endif err = 0; break; @@ -368,12 +429,100 @@ void slm_fota_post_process(void) } } +#if defined(CONFIG_SLM_FULL_FOTA) +static void handle_full_fota_activation_fail(int ret) +{ + int err; + /* All errors during the new modem firmware activation are + * considered irrecoverable and a reboot is needed. + */ + LOG_ERR("Modem firmware activation failed, error: %d", ret); + slm_fota_stage = FOTA_STAGE_COMPLETE; + slm_fota_status = FOTA_STATUS_ERROR; + slm_fota_info = ret; + slm_settings_fota_save(); + + /* Extenal flash needs to be erased and internal counters cleared */ + err = dfu_target_reset(); + if (err != 0) + LOG_ERR("dfu_target_reset() failed: %d\n", err); + else + LOG_INF("External flash erase succeeded"); + + /* slm_fota_post_process is executed after the reboot and an error is sent via AT reply */ + LOG_WRN("Rebooting..."); + LOG_PANIC(); + sys_reboot(SYS_REBOOT_COLD); +} +#endif + void slm_finish_modem_fota(int modem_lib_init_ret) { - if (handle_nrf_modem_lib_init_ret(modem_lib_init_ret)) { +#if defined(CONFIG_SLM_FULL_FOTA) + int err; + + if (slm_fota_type == DFU_TARGET_IMAGE_TYPE_FULL_MODEM) { + /* All erroneous steps in activation stage are fatal. In this case we cannot + * activate the new firmware. + */ + slm_fota_status = FOTA_STATUS_ERROR; + + /* Full fota activation differs from delta modem fota. */ + LOG_INF("Applying full modem firmware update from external flash\n"); + + err = nrf_modem_lib_shutdown(); + if (err != 0) { + LOG_ERR("nrf_modem_lib_shutdown() failed: %d\n", err); + /* The function will make a reboot. */ + handle_full_fota_activation_fail(err); + } + + err = nrf_modem_lib_bootloader_init(); + if (err != 0) { + LOG_ERR("nrf_modem_lib_bootloader_init() failed: %d\n", err); + /* The function will make a reboot. */ + handle_full_fota_activation_fail(err); + } + + /* Loading data from external flash to modem's flash. */ + err = fmfu_fdev_load(fmfu_buf, sizeof(fmfu_buf), flash_dev, 0); + if (err != 0) { + LOG_ERR("fmfu_fdev_load failed: %d\n", err); + /* The function will make a reboot. */ + handle_full_fota_activation_fail(err); + } + + err = nrf_modem_lib_shutdown(); + if (err != 0) { + LOG_ERR("nrf_modem_lib_shutdown() failed: %d\n", err); + /* The function will make a reboot. */ + handle_full_fota_activation_fail(err); + } + + err = nrf_modem_lib_init(); + if (err != 0) { + LOG_ERR("nrf_modem_lib_init() failed: %d\n", err); + /* The function will make a reboot. */ + handle_full_fota_activation_fail(err); + } + + slm_fota_stage = FOTA_STAGE_COMPLETE; + slm_fota_status = FOTA_STATUS_OK; + slm_fota_info = 0; + LOG_INF("Full modem firmware update succeeded. Will run new firmware"); + + /* Extenal flash needs to be erased and internal counters cleared */ + err = dfu_target_reset(); + if (err != 0) + LOG_ERR("dfu_target_reset() failed: %d\n", err); + else + LOG_INF("External flash erase succeeded"); + } +#endif + if (slm_fota_type == DFU_TARGET_IMAGE_TYPE_MODEM_DELTA && + handle_nrf_modem_lib_init_ret(modem_lib_init_ret)) { - LOG_INF("Re-initializing the modem due to" - " ongoing modem firmware update."); + LOG_INF("Re-initializing the modem due to ongoing delta modem firmware update."); /* The second init needs to be done regardless of the return value. * Refer to the below link for more information on the procedure. diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index e4e7d535e65..d66fd17440f 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -206,6 +206,7 @@ Serial LTE modem * ``#XMODEMRESET`` AT command to reset the modem while keeping the application running. It is expected to be used during modem firmware update, which now only requires a reset of the modem. * DTLS connection identifier support to the ``#XSSOCKETOPT`` and ``#XUDPCLI`` AT commands. + * Full modem FOTA support to the ``#XFOTA`` AT command. * An ``auto_connect`` operation in the ``#XCARRIER`` carrier command. The operation controls automatic registration of UE to LTE network. * ``#XNRFCLOUDPOS`` AT command to send location requests to nRF Cloud using cellular or Wi-Fi positioning, or both.