Skip to content

Commit

Permalink
applications: serial_lte_modem: Full modem fota support for SLM
Browse files Browse the repository at this point in the history
- new user interface parameter for #XFOTA AT command
- full modem fota is using an external flash
- full fota is activated via overlay

Signed-off-by: Veli-Tapani Peltoketo <veli-tapani.peltoketo@nordicsemi.no>
  • Loading branch information
VTPeltoketo committed Sep 18, 2023
1 parent 9b7d537 commit 1142e90
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 20 deletions.
4 changes: 4 additions & 0 deletions applications/serial_lte_modem/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,10 @@
};
};
};

/* Enable external flash */
&spi3 {
mx25r64: mx25r6435f@1 {
status = "okay";
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,10 @@
};
};
};

/* Enable external flash */
&spi3 {
gd25lb256: gd25lb256e3ir@1 {
status = "okay";
};
};
17 changes: 13 additions & 4 deletions applications/serial_lte_modem/doc/FOTA_AT_commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 ``<file url>`` parameter is a string.
It represents the full HTTP or HTTPS path of the target image to download.
Expand All @@ -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
~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -188,6 +197,6 @@ Examples

AT#XFOTA=?

#XFOTA: (0,1,2,6,7,8,9),<file_url>,<sec_tag>,<apn>
#XFOTA: (0,1,2,3,7,9),<file_url>,<sec_tag>,<apn>

OK
3 changes: 3 additions & 0 deletions applications/serial_lte_modem/doc/slm_description.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
19 changes: 19 additions & 0 deletions applications/serial_lte_modem/overlay-full_fota.conf
Original file line number Diff line number Diff line change
@@ -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
5 changes: 3 additions & 2 deletions applications/serial_lte_modem/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
5 changes: 3 additions & 2 deletions applications/serial_lte_modem/src/slm_at_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
173 changes: 161 additions & 12 deletions applications/serial_lte_modem/src/slm_at_fota.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@
#include <nrf_modem_delta_dfu.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_ctrl.h>
#include <zephyr/net/tls_credentials.h>
#include <zephyr/net/http/parser_url.h>
#include <zephyr/device.h>
#include <zephyr/storage/stream_flash.h>
#include <zephyr/sys/reboot.h>
#include <net/fota_download.h>
#include <dfu/dfu_target_full_modem.h>
#include <dfu/fmfu_fdev.h>
#include "slm_util.h"
#include "slm_settings.h"
#include "slm_at_host.h"
Expand All @@ -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 */
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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)[,<file_url>[,<sec_tag>[,<pdn_id>]]]\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)[,<file_url>[,<sec_tag>[,<pdn_id>]]]\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;

Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 1142e90

Please sign in to comment.