Skip to content

Commit

Permalink
net: nrf_cloud_fota_poll: Add API to apply FOTA images
Browse files Browse the repository at this point in the history
Add API to apply FOTA images for asyncronous use.

Signed-off-by: Simen S. Røstad <simen.rostad@nordicsemi.no>
  • Loading branch information
simensrostad committed Feb 21, 2025
1 parent 693b886 commit a5679e3
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 22 deletions.
6 changes: 6 additions & 0 deletions include/net/nrf_cloud.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,12 @@ enum nrf_cloud_fota_status {
NRF_CLOUD_FOTA_CANCELED = 5,
NRF_CLOUD_FOTA_REJECTED = 6,
NRF_CLOUD_FOTA_DOWNLOADING = 7,

/* Validation for Full modem FOTA needed, disconnect from LTE network and call
* nrf_cloud_fota_poll_update_apply(). This event is not reported to nRF Cloud and is only
* used internally to signal the application to apply the full modem FOTA update.
*/
NRF_CLOUD_FOTA_FMFU_VALIDATION_NEEDED = 8,
};

/** @brief FOTA update type. */
Expand Down
10 changes: 10 additions & 0 deletions include/net/nrf_cloud_fota_poll.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ int nrf_cloud_fota_poll_process_pending(struct nrf_cloud_fota_poll_ctx *ctx);
*/
int nrf_cloud_fota_poll_process(struct nrf_cloud_fota_poll_ctx *ctx);

/**
* @brief Apply downloaded image. For full modem FOTA this must be called after the network has
* been disconnected. Only applicable in non-blocking mode.
*
* @param[in] ctx Pointer to context used for FOTA polling operations.
*
* @return 0 on success, negative value on failure.
*/
int nrf_cloud_fota_poll_update_apply(struct nrf_cloud_fota_poll_ctx *ctx);

/** @} */

#ifdef __cplusplus
Expand Down
94 changes: 72 additions & 22 deletions subsys/net/lib/nrf_cloud/src/nrf_cloud_fota_poll.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ static enum nrf_cloud_fota_status fota_status = NRF_CLOUD_FOTA_QUEUED;
static char const *fota_status_details = FOTA_STATUS_DETAILS_SUCCESS;

/* Forward-declarations */
static void handle_download_succeeded_and_reboot(struct nrf_cloud_fota_poll_ctx *ctx);
static int handle_downloaded_image(struct nrf_cloud_fota_poll_ctx *ctx, bool cloud_disconnect);
static int update_job_status(struct nrf_cloud_fota_poll_ctx *ctx);

/****************************************************/
Expand Down Expand Up @@ -103,7 +103,7 @@ static int disconnect(struct nrf_cloud_fota_poll_ctx *ctx)
err = -ENOTSUP;
#endif
#if defined(CONFIG_LTE_LINK_CONTROL)
(void)lte_lc_power_off();
err = lte_lc_power_off();
#endif

return err;
Expand All @@ -118,6 +118,21 @@ static void cleanup(void)
#endif
}

static void fota_status_callback(struct nrf_cloud_fota_poll_ctx *ctx,
enum nrf_cloud_fota_status status)
{ if (ctx->status_fn) {
ctx->status_fn(status, NULL);
}
}

static void fota_reboot_callback(struct nrf_cloud_fota_poll_ctx *ctx,
enum nrf_cloud_fota_reboot_status status)
{
if (ctx->reboot_fn) {
ctx->reboot_fn(status);
}
}

/****************************************************/
/* End of transport-specific wrappers. */
/****************************************************/
Expand All @@ -139,10 +154,30 @@ static void http_fota_dl_handler(const struct fota_download_evt *evt)

if (ctx_ptr->is_nonblocking) {
k_work_cancel_delayable(&ctx_ptr->timeout_work);
handle_download_succeeded_and_reboot(ctx_ptr);

/* If the update is a full modem FOTA we tell the application to apply the
* image via nrf_cloud_fota_poll_update_apply() once it has disconnected
* from the network.
*
* If its not a full modem FOTA, we can just apply the update directly here
* as its not dependent on the network state.
*/
if (job.type == NRF_CLOUD_FOTA_MODEM_FULL) {
fota_status_callback(ctx_ptr,
NRF_CLOUD_FOTA_FMFU_VALIDATION_NEEDED);
} else {
int err = handle_downloaded_image(ctx_ptr, false);

if (err) {
LOG_ERR("handle_downloaded_image() failed: %d", err);
nrf_cloud_download_cancel();
}
}

} else {
k_sem_give(&fota_download_sem);
}

break;
case FOTA_DOWNLOAD_EVT_ERASE_PENDING:
case FOTA_DOWNLOAD_EVT_ERASE_TIMEOUT:
Expand Down Expand Up @@ -233,9 +268,7 @@ static void process_pending_job(struct nrf_cloud_fota_poll_ctx *ctx)
/* Save validate status and reboot */
(void)nrf_cloud_fota_settings_save(&pending_job);

if (ctx->reboot_fn) {
ctx->reboot_fn(FOTA_REBOOT_REQUIRED);
}
fota_reboot_callback(ctx, FOTA_REBOOT_REQUIRED);
}
}

Expand Down Expand Up @@ -494,7 +527,7 @@ static int wait_for_download(void)
return 0;
}

static void handle_download_succeeded_and_reboot(struct nrf_cloud_fota_poll_ctx *ctx)
static int handle_downloaded_image(struct nrf_cloud_fota_poll_ctx *ctx, bool cloud_disconnect)
{
int err;

Expand All @@ -506,17 +539,26 @@ static void handle_download_succeeded_and_reboot(struct nrf_cloud_fota_poll_ctx

err = nrf_cloud_bootloader_fota_slot_set(&pending_job);
if (err) {
LOG_WRN("Failed to set B1 slot flag, BOOT FOTA validation may be incorrect");
LOG_ERR("Failed to set B1 slot flag, BOOT FOTA validation may be incorrect");
return err;
}

disconnect(ctx);
if (cloud_disconnect) {
err = disconnect(ctx);
if (err) {
LOG_ERR("Failed to disconnect from nRF Cloud, error: %d", err);
return err;
}
}

#if defined(CONFIG_NRF_CLOUD_FOTA_FULL_MODEM_UPDATE)
if (job.type == NRF_CLOUD_FOTA_MODEM_FULL) {
LOG_INF("Applying full modem FOTA update...");

err = nrf_cloud_fota_fmfu_apply();
if (err) {
LOG_ERR("Failed to apply full modem FOTA update %d", err);

pending_job.validate = NRF_CLOUD_FOTA_VALIDATE_FAIL;
} else {
pending_job.validate = NRF_CLOUD_FOTA_VALIDATE_PASS;
Expand All @@ -529,9 +571,11 @@ static void handle_download_succeeded_and_reboot(struct nrf_cloud_fota_poll_ctx
bool reboot_required = false;

LOG_INF("Installing SMP FOTA update...");

err = nrf_cloud_pending_fota_job_process(&pending_job, &reboot_required);
if (err < 0) {
LOG_ERR("Failed to install SMP FOTA update %d", err);

pending_job.validate = NRF_CLOUD_FOTA_VALIDATE_FAIL;
} else {
pending_job.validate = NRF_CLOUD_FOTA_VALIDATE_PASS;
Expand All @@ -541,18 +585,14 @@ static void handle_download_succeeded_and_reboot(struct nrf_cloud_fota_poll_ctx

err = nrf_cloud_fota_settings_save(&pending_job);
if (err) {
LOG_WRN("FOTA job will be marked as successful without validation");
fota_status_details = FOTA_STATUS_DETAILS_NO_VALIDATE;
(void)update_job_status(ctx);
LOG_ERR("Failed saving pending FOTA job, error: %d", err);
return err;
}

if (ctx_ptr->status_fn) {
ctx_ptr->status_fn(NRF_CLOUD_FOTA_SUCCEEDED, NULL);
}
fota_status_callback(ctx, NRF_CLOUD_FOTA_SUCCEEDED);
fota_reboot_callback(ctx, FOTA_REBOOT_SUCCESS);

if (ctx->reboot_fn) {
ctx->reboot_fn(FOTA_REBOOT_SUCCESS);
}
return 0;
}

static void wait_after_job_update(void)
Expand All @@ -563,6 +603,18 @@ static void wait_after_job_update(void)
k_sleep(K_SECONDS(JOB_WAIT_S));
}

int nrf_cloud_fota_poll_update_apply(struct nrf_cloud_fota_poll_ctx *ctx)
{
int err = handle_downloaded_image(ctx, false);

if (err) {
LOG_ERR("handle_downloaded_image, error: %d", err);
return err;
}

return 0;
}

int nrf_cloud_fota_poll_process(struct nrf_cloud_fota_poll_ctx *ctx)
{
int err;
Expand Down Expand Up @@ -592,9 +644,7 @@ int nrf_cloud_fota_poll_process(struct nrf_cloud_fota_poll_ctx *ctx)
return -EAGAIN;
}

if (ctx_ptr->status_fn) {
ctx_ptr->status_fn(NRF_CLOUD_FOTA_DOWNLOADING, NULL);
}
fota_status_callback(ctx, NRF_CLOUD_FOTA_DOWNLOADING);

/* Start the FOTA download process and wait for completion (or timeout) */
err = start_download();
Expand Down Expand Up @@ -624,7 +674,7 @@ int nrf_cloud_fota_poll_process(struct nrf_cloud_fota_poll_ctx *ctx)
* Job status will be sent to nRF Cloud after reboot and validation.
*/
if (fota_status == NRF_CLOUD_FOTA_SUCCEEDED) {
handle_download_succeeded_and_reboot(ctx);
handle_downloaded_image(ctx, true);
/* Application was expected to reboot... */
return -EBUSY;
}
Expand Down

0 comments on commit a5679e3

Please sign in to comment.