diff --git a/include/net/nrf_cloud.h b/include/net/nrf_cloud.h index 0f9efca47c67..cf64477b7c8f 100644 --- a/include/net/nrf_cloud.h +++ b/include/net/nrf_cloud.h @@ -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. */ diff --git a/include/net/nrf_cloud_fota_poll.h b/include/net/nrf_cloud_fota_poll.h index fcd6944af1d8..90aa524fec7d 100644 --- a/include/net/nrf_cloud_fota_poll.h +++ b/include/net/nrf_cloud_fota_poll.h @@ -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 diff --git a/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota_poll.c b/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota_poll.c index b0173b25721d..8a71472e6908 100644 --- a/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota_poll.c +++ b/subsys/net/lib/nrf_cloud/src/nrf_cloud_fota_poll.c @@ -23,6 +23,7 @@ LOG_MODULE_REGISTER(nrf_cloud_fota_poll, CONFIG_NRF_CLOUD_FOTA_POLL_LOG_LEVEL); /* FOTA job status strings that provide additional details for nrf_cloud_fota_status values */ const char * const FOTA_STATUS_DETAILS_TIMEOUT = "Download did not complete in the allotted time"; +const char * const FOTA_STATUS_DETAILS_CANCELED = "Download was canceled"; const char * const FOTA_STATUS_DETAILS_DL_ERR = "Error occurred while downloading the file"; const char * const FOTA_STATUS_DETAILS_MDM_REJ = "Modem rejected the update; invalid delta?"; const char * const FOTA_STATUS_DETAILS_MDM_ERR = "Modem was unable to apply the update"; @@ -50,7 +51,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); /****************************************************/ @@ -103,7 +104,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; @@ -118,6 +119,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. */ /****************************************************/ @@ -139,10 +155,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: @@ -200,8 +236,8 @@ static void http_fota_dl_handler(const struct fota_download_evt *evt) LOG_DBG("FOTA download cancelled"); nrf_cloud_download_end(); - fota_status = NRF_CLOUD_FOTA_TIMED_OUT; - fota_status_details = FOTA_STATUS_DETAILS_TIMEOUT; + fota_status = NRF_CLOUD_FOTA_CANCELED; + fota_status_details = FOTA_STATUS_DETAILS_CANCELED; k_work_cancel_delayable(&ctx_ptr->timeout_work); ctx_ptr->status_fn(fota_status, fota_status_details); @@ -233,9 +269,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); } } @@ -494,7 +528,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; @@ -506,17 +540,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; @@ -529,9 +572,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; @@ -541,18 +586,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) @@ -563,6 +604,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; @@ -592,9 +645,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(); @@ -624,7 +675,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; }