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 35aa122e099d..82bbb6738464 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -783,7 +783,10 @@ See the changelog for each library in the :doc:`nrfxlib documentation +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief DFU target reset callback for activating MCUboot serial recovery mode. + * + * @return 0 on success, negative errno otherwise. + */ +typedef int (*dfu_target_reset_cb_t)(void); + +/** + * @brief Register recovery mode reset callback. + * + * Function that boots the target in MCUboot serial recovery mode, needed before sending + * SMP commands. + * + * @param[in] cb User defined target reset function for entering recovery mode. + * + * @return 0 on success, negative errno otherwise. + */ +int dfu_target_smp_recovery_mode_enable(dfu_target_reset_cb_t cb); + +/** + * @brief Initialize dfu target SMP client. + * + * @retval 0 on success, negative errno otherwise. + */ +int dfu_target_smp_client_init(void); + +/** + * @brief Read image list. + * + * @param res_buf Image list buffer. + * + * @return 0 on success, negative errno otherwise. + */ +int dfu_target_smp_image_list_get(struct mcumgr_image_state *res_buf); + +/** + * @brief Reboot SMP target device, and apply new image. + * + * @return 0 on success, negative errno otherwise. + */ +int dfu_target_smp_reboot(void); + +/** + * @brief Confirm new image activation after reset command. + * + * Use only without MCUboot recovery mode. + * + * @return 0 on success, negative errno otherwise. + */ +int dfu_target_smp_confirm_image(void); + +/** + * @brief Check if data in buffer indicates MCUboot style upgrade. + * + * @retval true if data matches, false otherwise. + */ +bool dfu_target_smp_identify(const void *const buf); + +/** + * @brief Initialize DFU target, perform steps necessary to receive firmware. + * + * @param[in] file_size Size of the current file being downloaded. + * @param[in] img_num Image pair index. + * @param[in] cb Callback for signaling events (unused). + * + * @retval 0 on success, negative errno otherwise. + */ +int dfu_target_smp_init(size_t file_size, int img_num, dfu_target_callback_t cb); + +/** + * @brief Get offset of firmware. + * + * @param[out] offset Returns the offset of the firmware upgrade. + * + * @return 0 on success, otherwise negative value if unable to get the offset. + */ +int dfu_target_smp_offset_get(size_t *offset); + +/** + * @brief Write firmware data. + * + * @param[in] buf Pointer to data that should be written. + * @param[in] len Length of data to write. + * + * @return 0 on success, negative errno otherwise. + */ +int dfu_target_smp_write(const void *const buf, size_t len); + +/** + * @brief Deinitialize resources and finalize firmware upgrade if successful. + * + * @param[in] successful Indicate whether the firmware was successfully received. + * + * @return 0 on success, negative errno otherwise. + */ +int dfu_target_smp_done(bool successful); + +/** + * @brief Schedule update of image. + * + * This call requests image update. The update will be performed after + * the device resets. + * + * @param[in] img_num Given image pair index or -1 for all + * of image pair indexes. + * + * @return 0 for a successful request or a negative error + * code identicating reason of failure. + **/ +int dfu_target_smp_schedule_update(int img_num); + +/** + * @brief Release resources and erase the download area. + * + * Cancels any ongoing updates. + * + * @return 0 on success, negative errno otherwise. + */ +int dfu_target_smp_reset(void); + +#ifdef __cplusplus +} +#endif + +#endif /* DFU_TARGET_SMP_H__ */ + +/**@} */ diff --git a/subsys/dfu/dfu_target/CMakeLists.txt b/subsys/dfu/dfu_target/CMakeLists.txt index d5ab0217ecf5..ec6bd16cdbe5 100644 --- a/subsys/dfu/dfu_target/CMakeLists.txt +++ b/subsys/dfu/dfu_target/CMakeLists.txt @@ -23,3 +23,6 @@ zephyr_library_sources_ifdef(CONFIG_DFU_TARGET_FULL_MODEM zephyr_library_sources_ifdef(CONFIG_DFU_TARGET_MCUBOOT src/dfu_target_mcuboot.c ) +zephyr_library_sources_ifdef(CONFIG_DFU_TARGET_SMP + src/dfu_target_smp.c + ) diff --git a/subsys/dfu/dfu_target/Kconfig b/subsys/dfu/dfu_target/Kconfig index b8eb14f77ff3..e66345e7ab5e 100644 --- a/subsys/dfu/dfu_target/Kconfig +++ b/subsys/dfu/dfu_target/Kconfig @@ -19,6 +19,26 @@ config DFU_TARGET_MCUBOOT help Enable support for updates that are performed by MCUboot. +config DFU_TARGET_SMP + bool "DFU SMP target for external update support" + depends on SMP_CLIENT + depends on MCUMGR_GRP_IMG_CLIENT + depends on MCUMGR_GRP_OS_CLIENT + select MCUMGR_GRP_OS_CLIENT_RESET + select MCUMGR_GRP_OS_CLIENT_ECHO + help + Enable support for Update external MCU by SMP client. + +if DFU_TARGET_SMP + +config DFU_TARGET_SMP_IMAGE_LIST_SIZE + int "Supported Image list size" + default 2 + help + Define size for Image cache list. + +endif + config DFU_TARGET_STREAM bool "Generic DFU stream target" depends on STREAM_FLASH_ERASE diff --git a/subsys/dfu/dfu_target/src/dfu_target.c b/subsys/dfu/dfu_target/src/dfu_target.c index 94bf4fde1a88..a3cb877db2a4 100644 --- a/subsys/dfu/dfu_target/src/dfu_target.c +++ b/subsys/dfu/dfu_target/src/dfu_target.c @@ -30,6 +30,10 @@ DEF_DFU_TARGET(mcuboot); #include "dfu/dfu_target_full_modem.h" DEF_DFU_TARGET(full_modem); #endif +#ifdef CONFIG_DFU_TARGET_SMP +#include "dfu/dfu_target_smp.h" +DEF_DFU_TARGET(smp); +#endif #define MIN_SIZE_IDENTIFY_BUF 32 @@ -62,6 +66,20 @@ enum dfu_target_image_type dfu_target_img_type(const void *const buf, size_t len return DFU_TARGET_IMAGE_TYPE_NONE; } +enum dfu_target_image_type dfu_target_smp_img_type_check(const void *const buf, size_t len) +{ +#ifdef CONFIG_DFU_TARGET_SMP + if (len < MIN_SIZE_IDENTIFY_BUF) { + return DFU_TARGET_IMAGE_TYPE_NONE; + } + if (dfu_target_smp_identify(buf)) { + return DFU_TARGET_IMAGE_TYPE_SMP; + } +#endif + LOG_ERR("No supported image type found"); + return DFU_TARGET_IMAGE_TYPE_NONE; +} + int dfu_target_init(int img_type, int img_num, size_t file_size, dfu_target_callback_t cb) { const struct dfu_target *new_target = NULL; @@ -81,6 +99,12 @@ int dfu_target_init(int img_type, int img_num, size_t file_size, dfu_target_call new_target = &dfu_target_full_modem; } #endif +#ifdef CONFIG_DFU_TARGET_SMP + if (img_type == DFU_TARGET_IMAGE_TYPE_SMP) { + new_target = &dfu_target_smp; + } +#endif + if (new_target == NULL) { LOG_ERR("Unknown image type"); return -ENOTSUP; @@ -93,9 +117,8 @@ int dfu_target_init(int img_type, int img_num, size_t file_size, dfu_target_call * modem_delta upgrades to re-open the DFU socket that is closed on * abort and to change the image number. */ - if (new_target == current_target - && img_type != DFU_TARGET_IMAGE_TYPE_MODEM_DELTA - && current_img_num == img_num) { + if (new_target == current_target && img_type != DFU_TARGET_IMAGE_TYPE_MODEM_DELTA && + img_type != DFU_TARGET_IMAGE_TYPE_SMP && current_img_num == img_num) { return 0; } diff --git a/subsys/dfu/dfu_target/src/dfu_target_smp.c b/subsys/dfu/dfu_target/src/dfu_target_smp.c new file mode 100644 index 000000000000..78e9fc3bb090 --- /dev/null +++ b/subsys/dfu/dfu_target/src/dfu_target_smp.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(dfu_target_smp, CONFIG_DFU_TARGET_LOG_LEVEL); + +#define MCUBOOT_HEADER_MAGIC 0x96f3b83d + +struct upload_progress { + size_t image_size; + size_t offset; + uint32_t image_num; + int smp_status; + int smp_echo; +}; + +static struct upload_progress upload_state; +static struct mcumgr_image_data image_info[CONFIG_DFU_TARGET_SMP_IMAGE_LIST_SIZE]; +static int image_count; +static bool recovery_mode_active; +static dfu_target_reset_cb_t dfu_target_reset_cb; +static struct smp_client_object smp_client; +static struct img_mgmt_client img_gr_client; +static struct os_mgmt_client os_gr_client; + +static const char dfu_smp_echo_str[] = "Recovery"; + +static int dfu_target_smp_recovery_enable(void); + +static int img_state_res_update(struct mcumgr_image_state *res_buf) +{ + if (res_buf->status) { + LOG_ERR("Image state response command fail, err:%d", res_buf->status); + image_count = 0; + } else { + image_count = res_buf->image_list_length; + } + + return res_buf->status; +} + +static int img_upload_res_update(struct mcumgr_image_upload *res_buf) +{ + if (res_buf->status) { + LOG_ERR("Image Upload command fail err:%d", res_buf->status); + } else { + upload_state.offset = res_buf->image_upload_offset; + } + return res_buf->status; +} + +int dfu_target_smp_client_init(void) +{ + int rc; + + rc = smp_client_object_init(&smp_client, SMP_SERIAL_TRANSPORT); + if (rc) { + return rc; + } + + img_mgmt_client_init(&img_gr_client, &smp_client, CONFIG_DFU_TARGET_SMP_IMAGE_LIST_SIZE, + image_info); + os_mgmt_client_init(&os_gr_client, &smp_client); + + return rc; +} + +bool dfu_target_smp_identify(const void *const buf) +{ + /* MCUBoot headers starts with 4 byte magic word */ + return *((const uint32_t *)buf) == MCUBOOT_HEADER_MAGIC; +} + +int dfu_target_smp_init(size_t file_size, int img_num, dfu_target_callback_t cb) +{ + ARG_UNUSED(cb); + if (img_num < 0) { + return -ENOENT; + } + + if (dfu_target_reset_cb) { + upload_state.image_num = 2; + } else { + upload_state.image_num = (uint32_t)img_num; + } + + upload_state.image_size = file_size; + upload_state.offset = 0; + + return img_mgmt_client_upload_init(&img_gr_client, file_size, upload_state.image_num, NULL); +} + +int dfu_target_smp_offset_get(size_t *out) +{ + *out = upload_state.offset; + return 0; +} + +static int dfu_target_smp_enable(void) +{ + if (dfu_target_reset_cb && !recovery_mode_active) { + if (dfu_target_reset_cb()) { + LOG_ERR("SMP server reset fail"); + return -ENOENT; + } + + if (dfu_target_smp_recovery_enable()) { + LOG_ERR("SMP Server Recovery mode fail"); + dfu_target_reset_cb(); + return -ENOENT; + } + recovery_mode_active = true; + } + return 0; +} + +int dfu_target_smp_write(const void *const buf, size_t len) +{ + int err = 0; + struct mcumgr_image_upload upload_res; + + err = dfu_target_smp_enable(); + if (err) { + return err; + } + + err = img_mgmt_client_upload(&img_gr_client, buf, len, &upload_res); + if (err) { + return err; + } + + return img_upload_res_update(&upload_res); +} + +int dfu_target_smp_done(bool successful) +{ + int rc; + struct mcumgr_image_state res_buf; + + rc = dfu_target_smp_enable(); + if (rc) { + return rc; + } + + if (successful) { + /* Read Image list */ + LOG_INF("MCUBoot SMP image download ready."); + image_count = 0; + rc = img_mgmt_client_state_read(&img_gr_client, &res_buf); + if (rc) { + goto end; + } + rc = img_state_res_update(&res_buf); + } else { + LOG_INF("MCUBoot SMP image upgrade aborted %d", upload_state.image_num); + } +end: + upload_state.image_size = 0; + upload_state.offset = 0; + + return rc; +} + +static struct mcumgr_image_data *discover_secondary_image_info(void) +{ + if (!image_count) { + return NULL; + } + + for (int i = 0; i < image_count; i++) { + if (image_info[i].slot_num == 0) { + continue; + } + return &image_info[i]; + } + + return NULL; +} + +int dfu_target_smp_schedule_update(int img_num) +{ + int err = 0; + struct mcumgr_image_state res_buf; + struct mcumgr_image_data *secondary_image; + + /* Discover always Secondary image */ + secondary_image = discover_secondary_image_info(); + /* Test functionality here */ + if (!secondary_image) { + LOG_ERR("Secondary Image info not available"); + return -ENOENT; + } + + err = dfu_target_smp_enable(); + if (err) { + return err; + } + + LOG_INF("MCUBoot image-%d upgrade scheduled. Reset device to apply", + secondary_image->slot_num); + + err = img_mgmt_client_state_write(&img_gr_client, secondary_image->hash, false, &res_buf); + if (err) { + image_count = 0; + return err; + } + + return img_state_res_update(&res_buf); +} + +int dfu_target_smp_reset(void) +{ + int rc; + + upload_state.image_size = 0; + upload_state.offset = 0; + + rc = dfu_target_smp_enable(); + if (rc) { + return rc; + } + + if (dfu_target_reset_cb) { + /* MCUBoot not support Erase yet */ + rc = 0; + } else { + rc = img_mgmt_client_erase(&img_gr_client, upload_state.image_num); + } + + return rc; +} + +int dfu_target_smp_reboot(void) +{ + int rc; + + upload_state.image_size = 0; + upload_state.offset = 0; + + rc = dfu_target_smp_enable(); + if (rc) { + return rc; + } + + rc = os_mgmt_client_reset(&os_gr_client); + LOG_DBG("OS Reset command status:%d", rc); + if (rc) { + return rc; + } + recovery_mode_active = false; + + return 0; +} + +int dfu_target_smp_confirm_image(void) +{ + int rc; + struct mcumgr_image_state res_buf; + + if (dfu_target_reset_cb) { + /* Recovery mode can't confirm image Application need to handle that */ + return -EACCES; + } + + LOG_INF("Confirm Image"); + rc = img_mgmt_client_state_write(&img_gr_client, NULL, true, &res_buf); + if (rc) { + LOG_INF("Confirm fault err:%d", rc); + return rc; + } + return img_state_res_update(&res_buf); +} + +int dfu_target_smp_recovery_mode_enable(dfu_target_reset_cb_t cb) +{ + dfu_target_reset_cb = cb; + return 0; +} + +int dfu_target_smp_image_list_get(struct mcumgr_image_state *res_buf) +{ + int err = 0; + + err = dfu_target_smp_enable(); + if (err) { + return err; + } + err = img_mgmt_client_state_read(&img_gr_client, res_buf); + if (err == 0) { + img_state_res_update(res_buf); + } + + return err; +} + +static int dfu_target_smp_recovery_enable(void) +{ + int rc; + + rc = os_mgmt_client_echo(&os_gr_client, dfu_smp_echo_str); + LOG_DBG("Recovery activate status %d", rc); + + return rc; +}