Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

applications: serial_lte_modem: Full modem fota support for SLM #11892

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
MarkusLassila marked this conversation as resolved.
Show resolved Hide resolved

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 @@
};
};
};

VTPeltoketo marked this conversation as resolved.
Show resolved Hide resolved
/* Enable external flash */
&spi3 {
tomi-font marked this conversation as resolved.
Show resolved Hide resolved
mx25r64: mx25r6435f@1 {
status = "okay";
VTPeltoketo marked this conversation as resolved.
Show resolved Hide resolved
};
};
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.

tomi-font marked this conversation as resolved.
Show resolved Hide resolved
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.

tomi-font marked this conversation as resolved.
Show resolved Hide resolved
.. 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
tomi-font marked this conversation as resolved.
Show resolved Hide resolved
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
tomi-font marked this conversation as resolved.
Show resolved Hide resolved
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);
MarkusLassila marked this conversation as resolved.
Show resolved Hide resolved

/* 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)
tomi-font marked this conversation as resolved.
Show resolved Hide resolved
{
if (handle_nrf_modem_lib_init_ret(modem_lib_init_ret)) {
#if defined(CONFIG_SLM_FULL_FOTA)
tomi-font marked this conversation as resolved.
Show resolved Hide resolved
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();
MarkusLassila marked this conversation as resolved.
Show resolved Hide resolved
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe nrf_modem_lib_init() in that case to still try to exit from this function with a running modem?
And I think the same kind of fine tuning can be done to some of the other failure cases below.

Though I agree with your comment above, about the trickiness of this. And certain errors are likely not even possible during a modem firmware update.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to reset the device if something goes wrong after nrf_modem_lib_shutdown? If something is truly wrong in the sequence, nrf_modem_lib_init() may not be successful either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we make reset here, it has to delayed one because we have to return error event. Sounds quite complicated

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What error event?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After FOTA and reset/modemreset, slm_fota_post_process() function is called. If the FOTA activation fails, slm_fota_post_process function sends an error AT event.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. slm_fota_post_process() is called later in the boot process and only depends on slm_fota_stage not being FOTA_STAGE_INIT to print the FOTA result. So it seems that @MarkusLassila's proposal to reset the device would be fine as the FOTA result would end up getting printed after the additional reset. Though one potential risk I see is entering into a reset loop, so it would be preferable to not attempt to do this more than once.
One scenario I can imagine working in these hypothetical failure cases is:

  1. Set slm_fota_stage to FOTA_STAGE_COMPLETE, with the other FOTA variables indicating an error.
  2. Save the FOTA settings.
  3. Reset the device.
  4. Avoid calling slm_finish_modem_fota() again.
  5. Reach slm_fota_post_process() that will print the FOTA result. If for some reason the code fails before this, it means that something is terribly wrong and there is nothing we can do anymore as a reboot didn't help.

Step 4 would require changing main.c:445, probably to if (slm_fota_stage == FOTA_STAGE_ACTIVATE).
This is very hypothetical, but adds a safety net in case one of these calls in slm_finish_modem_fota() would fail in a non-fatal way. Otherwise it's not impossible that the code would return from the function without a running modem. So doing something about it would allow SLM to kind of self-heal.

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this might work. I have to simulate a finalization error and test. There are actually two different use cases to test, modem reset and device reset.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling of the full FOTA activation has been changed.

/* 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)) {
tomi-font marked this conversation as resolved.
Show resolved Hide resolved

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
Loading