| /* |
| * Copyright (c) 2018-2021 mcumgr authors |
| * Copyright (c) 2022-2023 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/sys/util_macro.h> |
| #include <zephyr/toolchain.h> |
| #include <string.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/dfu/mcuboot.h> |
| |
| #include <zcbor_common.h> |
| #include <zcbor_decode.h> |
| #include <zcbor_encode.h> |
| |
| #include <bootutil/bootutil_public.h> |
| |
| #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h> |
| #include <zephyr/mgmt/mcumgr/smp/smp.h> |
| #include <zephyr/mgmt/mcumgr/grp/img_mgmt/img_mgmt.h> |
| |
| #include <mgmt/mcumgr/util/zcbor_bulk.h> |
| #include <mgmt/mcumgr/grp/img_mgmt/img_mgmt_priv.h> |
| |
| #ifdef CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS |
| #include <zephyr/mgmt/mcumgr/mgmt/callbacks.h> |
| #endif |
| |
| LOG_MODULE_DECLARE(mcumgr_img_grp, CONFIG_MCUMGR_GRP_IMG_LOG_LEVEL); |
| |
| /* The value here sets how many "characteristics" that describe image is |
| * encoded into a map per each image (like bootable flags, and so on). |
| * This value is only used for zcbor to predict map size and map encoding |
| * and does not affect memory allocation. |
| * In case when more "characteristics" are added to image map then |
| * zcbor_map_end_encode may fail it this value does not get updated. |
| */ |
| #define MAX_IMG_CHARACTERISTICS 15 |
| |
| #ifndef CONFIG_MCUMGR_GRP_IMG_FRUGAL_LIST |
| #define ZCBOR_ENCODE_FLAG(zse, label, value) \ |
| (zcbor_tstr_put_lit(zse, label) && zcbor_bool_put(zse, value)) |
| #else |
| /* In "frugal" lists flags are added to response only when they evaluate to true */ |
| /* Note that value is evaluated twice! */ |
| #define ZCBOR_ENCODE_FLAG(zse, label, value) \ |
| (!(value) || \ |
| (zcbor_tstr_put_lit(zse, label) && zcbor_bool_put(zse, (value)))) |
| #endif |
| |
| /* Flags returned by img_mgmt_state_read() for queried slot */ |
| #define REPORT_SLOT_ACTIVE BIT(0) |
| #define REPORT_SLOT_PENDING BIT(1) |
| #define REPORT_SLOT_CONFIRMED BIT(2) |
| #define REPORT_SLOT_PERMANENT BIT(3) |
| |
| #if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) |
| #define DIRECT_XIP_BOOT_UNSET 0 |
| #define DIRECT_XIP_BOOT_ONCE 1 |
| #define DIRECT_XIP_BOOT_REVERT 2 |
| #define DIRECT_XIP_BOOT_FOREVER 3 |
| #endif |
| |
| /** |
| * Collects information about the specified image slot. |
| */ |
| #ifndef CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP |
| uint8_t |
| img_mgmt_state_flags(int query_slot) |
| { |
| uint8_t flags; |
| int swap_type; |
| int image = query_slot / 2; /* We support max 2 images for now */ |
| int active_slot = img_mgmt_active_slot(image); |
| |
| flags = 0; |
| |
| /* Determine if this is is pending or confirmed (only applicable for |
| * unified images and loaders. |
| */ |
| swap_type = img_mgmt_swap_type(query_slot); |
| switch (swap_type) { |
| case IMG_MGMT_SWAP_TYPE_NONE: |
| if (query_slot == active_slot) { |
| flags |= IMG_MGMT_STATE_F_CONFIRMED; |
| } |
| break; |
| |
| case IMG_MGMT_SWAP_TYPE_TEST: |
| if (query_slot == active_slot) { |
| flags |= IMG_MGMT_STATE_F_CONFIRMED; |
| } else { |
| flags |= IMG_MGMT_STATE_F_PENDING; |
| } |
| break; |
| |
| case IMG_MGMT_SWAP_TYPE_PERM: |
| if (query_slot == active_slot) { |
| flags |= IMG_MGMT_STATE_F_CONFIRMED; |
| } else { |
| flags |= IMG_MGMT_STATE_F_PENDING | IMG_MGMT_STATE_F_PERMANENT; |
| } |
| break; |
| |
| case IMG_MGMT_SWAP_TYPE_REVERT: |
| if (query_slot != active_slot) { |
| flags |= IMG_MGMT_STATE_F_CONFIRMED; |
| } |
| break; |
| } |
| |
| /* Only running application is active */ |
| if (image == img_mgmt_active_image() && query_slot == active_slot) { |
| flags |= IMG_MGMT_STATE_F_ACTIVE; |
| } |
| |
| return flags; |
| } |
| #else |
| uint8_t |
| img_mgmt_state_flags(int query_slot) |
| { |
| uint8_t flags = 0; |
| int image = query_slot / 2; /* We support max 2 images for now */ |
| int active_slot = img_mgmt_active_slot(image); |
| |
| /* In case when MCUboot is configured for DirectXIP slot may only be |
| * active or pending. Slot is marked pending only when version in that slot |
| * is higher than version of active slot. |
| */ |
| if (image == img_mgmt_active_image() && query_slot == active_slot) { |
| flags = IMG_MGMT_STATE_F_ACTIVE; |
| } else { |
| struct image_version sver; |
| struct image_version aver; |
| int rcs = img_mgmt_read_info(query_slot, &sver, NULL, NULL); |
| int rca = img_mgmt_read_info(active_slot, &aver, NULL, NULL); |
| |
| if (rcs == 0 && rca == 0 && img_mgmt_vercmp(&aver, &sver) < 0) { |
| flags = IMG_MGMT_STATE_F_PENDING | IMG_MGMT_STATE_F_PERMANENT; |
| } |
| } |
| |
| return flags; |
| } |
| #endif |
| |
| #if !defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP) && \ |
| !defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) |
| int img_mgmt_get_next_boot_slot(int image, enum img_mgmt_next_boot_type *type) |
| { |
| const int active_slot = img_mgmt_active_slot(image); |
| const int state = mcuboot_swap_type_multi(image); |
| /* All cases except BOOT_SWAP_TYPE_NONE return opposite slot */ |
| int slot = img_mgmt_get_opposite_slot(active_slot); |
| enum img_mgmt_next_boot_type lt = NEXT_BOOT_TYPE_NORMAL; |
| |
| switch (state) { |
| case BOOT_SWAP_TYPE_NONE: |
| /* Booting to the same slot, keeping type to NEXT_BOOT_TYPE_NORMAL */ |
| slot = active_slot; |
| break; |
| case BOOT_SWAP_TYPE_PERM: |
| /* For BOOT_SWAP_TYPE_PERM reported type will be NEXT_BOOT_TYPE_NORMAL, |
| * and only difference between this and BOOT_SWAP_TYPE_NONE is that |
| * the later boots to the application in currently active slot while the former |
| * to the application in the opposite to active slot. |
| * Normal here means that it is ordinary boot and slot has not been marked |
| * for revert or pending for test, and will change on reset. |
| */ |
| break; |
| case BOOT_SWAP_TYPE_REVERT: |
| /* Application is in test mode and has not yet been confirmed, |
| * which means that on the next boot the application will revert to |
| * the copy from reported slot. |
| */ |
| lt = NEXT_BOOT_TYPE_REVERT; |
| break; |
| case BOOT_SWAP_TYPE_TEST: |
| /* Reported next boot slot is set for one boot only and app needs to |
| * confirm itself or it will be reverted. |
| */ |
| lt = NEXT_BOOT_TYPE_TEST; |
| break; |
| default: |
| /* Should never, ever happen */ |
| LOG_DBG("Unexpected swap state %d", state); |
| return -1; |
| } |
| LOG_DBG("(%d, *) => slot = %d, type = %d", image, slot, lt); |
| |
| if (type != NULL) { |
| *type = lt; |
| } |
| return slot; |
| } |
| #else |
| |
| #if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) |
| |
| static int read_directxip_state(int slot) |
| { |
| struct boot_swap_state bss; |
| int fa_id = img_mgmt_flash_area_id(slot); |
| const struct flash_area *fa; |
| int rc = 0; |
| |
| __ASSERT(fa_id != -1, "Could not map slot to area ID"); |
| |
| rc = flash_area_open(fa_id, &fa); |
| if (rc < 0) { |
| return rc; |
| } |
| rc = boot_read_swap_state(fa, &bss); |
| flash_area_close(fa); |
| if (rc != 0) { |
| LOG_ERR("Failed to read state of slot %d with error %d", slot, rc); |
| return -1; |
| } |
| |
| if (bss.magic == BOOT_MAGIC_GOOD) { |
| if (bss.image_ok == BOOT_FLAG_SET) { |
| return DIRECT_XIP_BOOT_FOREVER; |
| } else if (bss.copy_done == BOOT_FLAG_SET) { |
| return DIRECT_XIP_BOOT_REVERT; |
| } |
| return DIRECT_XIP_BOOT_ONCE; |
| } |
| return DIRECT_XIP_BOOT_UNSET; |
| } |
| #endif /* defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) */ |
| |
| int img_mgmt_get_next_boot_slot(int image, enum img_mgmt_next_boot_type *type) |
| { |
| struct image_version aver; |
| struct image_version over; |
| int active_slot = img_mgmt_active_slot(image); |
| int other_slot = img_mgmt_get_opposite_slot(active_slot); |
| #if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) |
| int active_slot_state; |
| int other_slot_state; |
| #endif /* defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) */ |
| enum img_mgmt_next_boot_type lt = NEXT_BOOT_TYPE_NORMAL; |
| int return_slot = active_slot; |
| |
| |
| int rcs = img_mgmt_read_info(other_slot, &over, NULL, NULL); |
| int rca = img_mgmt_read_info(active_slot, &aver, NULL, NULL); |
| |
| #if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) |
| active_slot_state = read_directxip_state(active_slot); |
| other_slot_state = read_directxip_state(other_slot); |
| if (rca != 0 || |
| (rcs != 0 && rcs != IMG_MGMT_ERR_NO_IMAGE)) { |
| /* We do not really know what will happen, as we can not |
| * read states from bootloader. |
| */ |
| LOG_ERR("img_mgmt_read_info_failed rca = %d, rcs = %d", |
| rca, rcs); |
| goto out; |
| } |
| if (other_slot_state < 0 || active_slot_state < 0) { |
| LOG_ERR("Slot state read failed with status: active %d, other %d", |
| active_slot_state, other_slot_state); |
| /* We do not really know what will happen, as we can not |
| * read states from bootloader. |
| */ |
| goto out; |
| } |
| |
| /* There is not other image, the active one will boot next time */ |
| if (rcs == IMG_MGMT_ERR_NO_IMAGE) { |
| goto out; |
| } |
| |
| if (active_slot_state == DIRECT_XIP_BOOT_REVERT) { |
| lt = NEXT_BOOT_TYPE_REVERT; |
| return_slot = other_slot; |
| } else if (other_slot_state == DIRECT_XIP_BOOT_UNSET) { |
| if (active_slot_state == DIRECT_XIP_BOOT_ONCE) { |
| lt = NEXT_BOOT_TYPE_TEST; |
| } |
| } else if (img_mgmt_vercmp(&aver, &over) < 0) { |
| if (other_slot_state == DIRECT_XIP_BOOT_FOREVER) { |
| return_slot = other_slot; |
| } else if (other_slot_state == DIRECT_XIP_BOOT_ONCE) { |
| lt = NEXT_BOOT_TYPE_TEST; |
| return_slot = other_slot; |
| } |
| } |
| #else |
| if (rcs == 0 && rca == 0 && img_mgmt_vercmp(&aver, &over) < 0) { |
| return_slot = other_slot; |
| } |
| #endif /* defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) */ |
| |
| out: |
| if (type != NULL) { |
| *type = lt; |
| } |
| |
| return return_slot; |
| } |
| #endif /* !defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP) && \ |
| * !defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) |
| */ |
| |
| |
| /** |
| * Indicates whether any image slot is pending (i.e., whether a test swap will |
| * happen on the next reboot. |
| */ |
| int |
| img_mgmt_state_any_pending(void) |
| { |
| return img_mgmt_state_flags(0) & IMG_MGMT_STATE_F_PENDING || |
| img_mgmt_state_flags(1) & IMG_MGMT_STATE_F_PENDING; |
| } |
| |
| /** |
| * Indicates whether the specified slot has any flags. If no flags are set, |
| * the slot can be freely erased. |
| */ |
| int |
| img_mgmt_slot_in_use(int slot) |
| { |
| int image = img_mgmt_slot_to_image(slot); |
| int active_slot = img_mgmt_active_slot(image); |
| |
| #if !defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP) |
| enum img_mgmt_next_boot_type type = NEXT_BOOT_TYPE_NORMAL; |
| int nbs = img_mgmt_get_next_boot_slot(image, &type); |
| |
| if (slot == nbs && type == NEXT_BOOT_TYPE_REVERT) { |
| LOG_DBG("(%d) Refused erase revert", slot); |
| return 1; |
| } |
| |
| if ((slot == nbs && type == NEXT_BOOT_TYPE_TEST) || |
| (active_slot != nbs && type == NEXT_BOOT_TYPE_NORMAL)) { |
| #if defined(CONFIG_MCUMGR_GRP_IMG_ALLOW_ERASE_PENDING) |
| LOG_DBG("(%d) Allowed erase pending", slot); |
| /* Pass through to return (active_slot == slot) */ |
| #else |
| LOG_DBG("(%d) Refused erase pending", slot); |
| return 1; |
| #endif |
| } |
| #endif |
| |
| return (active_slot == slot); |
| } |
| |
| /** |
| * Sets the pending flag for the specified image slot. That is, the system |
| * will swap to the specified image on the next reboot. If the permanent |
| * argument is specified, the system doesn't require a confirm after the swap |
| * occurs. |
| */ |
| int |
| img_mgmt_state_set_pending(int slot, int permanent) |
| { |
| uint8_t state_flags; |
| int rc; |
| |
| state_flags = img_mgmt_state_flags(slot); |
| |
| /* Unconfirmed slots are always runnable. A confirmed slot can only be |
| * run if it is a loader in a split image setup. |
| */ |
| if (state_flags & IMG_MGMT_STATE_F_CONFIRMED && slot != 0) { |
| rc = IMG_MGMT_ERR_IMAGE_ALREADY_PENDING; |
| goto done; |
| } |
| |
| rc = img_mgmt_write_pending(slot, permanent); |
| |
| done: |
| |
| return rc; |
| } |
| |
| /** |
| * Confirms the current image state. Prevents a fallback from occurring on the |
| * next reboot if the active image is currently being tested. |
| */ |
| int |
| img_mgmt_state_confirm(void) |
| { |
| int rc; |
| |
| #if defined(CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS) |
| int32_t err_rc; |
| uint16_t err_group; |
| #endif |
| |
| /* Confirm disallowed if a test is pending. */ |
| if (img_mgmt_state_any_pending()) { |
| rc = IMG_MGMT_ERR_IMAGE_ALREADY_PENDING; |
| goto err; |
| } |
| |
| rc = img_mgmt_write_confirmed(); |
| |
| #if defined(CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS) |
| (void)mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_DFU_CONFIRMED, NULL, 0, &err_rc, |
| &err_group); |
| #endif |
| |
| err: |
| return rc; |
| } |
| |
| /* Return zcbor encoding result */ |
| static bool img_mgmt_state_encode_slot(zcbor_state_t *zse, uint32_t slot, int state_flags) |
| { |
| uint32_t flags; |
| char vers_str[IMG_MGMT_VER_MAX_STR_LEN]; |
| uint8_t hash[IMAGE_HASH_LEN]; /* SHA256 hash */ |
| struct zcbor_string zhash = { .value = hash, .len = IMAGE_HASH_LEN }; |
| struct image_version ver; |
| bool ok; |
| int rc = img_mgmt_read_info(slot, &ver, hash, &flags); |
| |
| if (rc != 0) { |
| /* zcbor encoding did not fail */ |
| return true; |
| } |
| |
| ok = zcbor_map_start_encode(zse, MAX_IMG_CHARACTERISTICS) && |
| (CONFIG_MCUMGR_GRP_IMG_UPDATABLE_IMAGE_NUMBER == 1 || |
| (zcbor_tstr_put_lit(zse, "image") && |
| zcbor_uint32_put(zse, slot >> 1))) && |
| zcbor_tstr_put_lit(zse, "slot") && |
| zcbor_uint32_put(zse, slot % 2) && |
| zcbor_tstr_put_lit(zse, "version"); |
| |
| if (ok) { |
| if (img_mgmt_ver_str(&ver, vers_str) < 0) { |
| ok = zcbor_tstr_put_lit(zse, "<\?\?\?>"); |
| } else { |
| vers_str[sizeof(vers_str) - 1] = '\0'; |
| ok = zcbor_tstr_put_term(zse, vers_str, sizeof(vers_str)); |
| } |
| } |
| |
| ok = ok && zcbor_tstr_put_lit(zse, "hash") && |
| zcbor_bstr_encode(zse, &zhash) && |
| ZCBOR_ENCODE_FLAG(zse, "bootable", !(flags & IMAGE_F_NON_BOOTABLE)) && |
| ZCBOR_ENCODE_FLAG(zse, "pending", state_flags & REPORT_SLOT_PENDING) && |
| ZCBOR_ENCODE_FLAG(zse, "confirmed", state_flags & REPORT_SLOT_CONFIRMED) && |
| ZCBOR_ENCODE_FLAG(zse, "active", state_flags & REPORT_SLOT_ACTIVE) && |
| ZCBOR_ENCODE_FLAG(zse, "permanent", state_flags & REPORT_SLOT_PERMANENT) && |
| zcbor_map_end_encode(zse, MAX_IMG_CHARACTERISTICS); |
| |
| return ok; |
| } |
| |
| /** |
| * Command handler: image state read |
| */ |
| int |
| img_mgmt_state_read(struct smp_streamer *ctxt) |
| { |
| zcbor_state_t *zse = ctxt->writer->zs; |
| uint32_t i; |
| bool ok; |
| |
| ok = zcbor_tstr_put_lit(zse, "images") && |
| zcbor_list_start_encode(zse, 2 * CONFIG_MCUMGR_GRP_IMG_UPDATABLE_IMAGE_NUMBER); |
| |
| img_mgmt_take_lock(); |
| |
| for (i = 0; ok && i < CONFIG_MCUMGR_GRP_IMG_UPDATABLE_IMAGE_NUMBER; i++) { |
| /* _a is active slot, _o is opposite slot */ |
| enum img_mgmt_next_boot_type type = NEXT_BOOT_TYPE_NORMAL; |
| int next_boot_slot = img_mgmt_get_next_boot_slot(i, &type); |
| int slot_a = img_mgmt_active_slot(i); |
| int slot_o = img_mgmt_get_opposite_slot(slot_a); |
| int flags_a = REPORT_SLOT_ACTIVE; |
| int flags_o = 0; |
| |
| if (type != NEXT_BOOT_TYPE_REVERT) { |
| flags_a |= REPORT_SLOT_CONFIRMED; |
| } |
| |
| if (next_boot_slot != slot_a) { |
| if (type == NEXT_BOOT_TYPE_NORMAL) { |
| flags_o = REPORT_SLOT_PENDING | REPORT_SLOT_PERMANENT; |
| } else if (type == NEXT_BOOT_TYPE_REVERT) { |
| flags_o = REPORT_SLOT_CONFIRMED; |
| } else if (type == NEXT_BOOT_TYPE_TEST) { |
| flags_o = REPORT_SLOT_PENDING; |
| } |
| } |
| |
| /* Need to report slots in proper order */ |
| if (slot_a < slot_o) { |
| ok = img_mgmt_state_encode_slot(zse, slot_a, flags_a) && |
| img_mgmt_state_encode_slot(zse, slot_o, flags_o); |
| } else { |
| ok = img_mgmt_state_encode_slot(zse, slot_o, flags_o) && |
| img_mgmt_state_encode_slot(zse, slot_a, flags_a); |
| } |
| } |
| |
| /* Ending list encoding for two slots per image */ |
| ok = ok && zcbor_list_end_encode(zse, 2 * CONFIG_MCUMGR_GRP_IMG_UPDATABLE_IMAGE_NUMBER); |
| /* splitStatus is always 0 so in frugal list it is not present at all */ |
| if (!IS_ENABLED(CONFIG_MCUMGR_GRP_IMG_FRUGAL_LIST) && ok) { |
| ok = zcbor_tstr_put_lit(zse, "splitStatus") && |
| zcbor_int32_put(zse, 0); |
| } |
| |
| img_mgmt_release_lock(); |
| |
| return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; |
| } |
| |
| static int img_mgmt_set_next_boot_slot_common(int slot, int active_slot, bool confirm) |
| { |
| const struct flash_area *fa; |
| int area_id = img_mgmt_flash_area_id(slot); |
| int rc = 0; |
| |
| if (flash_area_open(area_id, &fa) != 0) { |
| return IMG_MGMT_ERR_FLASH_OPEN_FAILED; |
| } |
| |
| rc = boot_set_next(fa, slot == active_slot, confirm); |
| if (rc != 0) { |
| /* Failed to set next slot for boot as desired */ |
| LOG_ERR("Faled boot_set_next with code %d, for slot %d," |
| " with active slot %d and confirm %d", |
| rc, slot, active_slot, confirm); |
| |
| /* Translate from boot util error code to IMG mgmt group error code */ |
| if (rc == BOOT_EFLASH) { |
| rc = IMG_MGMT_ERR_FLASH_WRITE_FAILED; |
| } else if (rc == BOOT_EBADVECT) { |
| rc = IMG_MGMT_ERR_INVALID_IMAGE_VECTOR_TABLE; |
| } else if (rc == BOOT_EBADIMAGE) { |
| rc = IMG_MGMT_ERR_INVALID_IMAGE_HEADER_MAGIC; |
| } else { |
| rc = IMG_MGMT_ERR_UNKNOWN; |
| } |
| } |
| flash_area_close(fa); |
| |
| #if defined(CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS) |
| if (rc == 0 && slot == active_slot && confirm) { |
| int32_t err_rc; |
| uint16_t err_group; |
| |
| /* Confirm event is only sent for active slot */ |
| (void)mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_DFU_CONFIRMED, NULL, 0, &err_rc, |
| &err_group); |
| } |
| #endif |
| |
| return rc; |
| } |
| |
| #ifndef CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT |
| int img_mgmt_set_next_boot_slot(int slot, bool confirm) |
| { |
| /* image the requested slot is defined within */ |
| int image = img_mgmt_slot_to_image(slot); |
| /* active_slot is slot that is considered active/primary/executing |
| * for a given image. |
| */ |
| int active_slot = img_mgmt_active_slot(image); |
| enum img_mgmt_next_boot_type type = NEXT_BOOT_TYPE_NORMAL; |
| int next_boot_slot = img_mgmt_get_next_boot_slot(image, &type); |
| |
| LOG_DBG("(%d, %s)", slot, confirm ? "confirm" : "test"); |
| LOG_DBG("aimg = %d, img = %d, aslot = %d, slot = %d, nbs = %d", |
| img_mgmt_active_image(), image, active_slot, slot, next_boot_slot); |
| |
| /* MCUmgr should not allow to confirm non-active image slots to prevent |
| * confirming something that might not have been verified to actually be bootable |
| * or have stuck in primary slot of other image. Unfortunately there was |
| * a bug in logic that always allowed to confirm secondary slot of any |
| * image. Now the behaviour is controlled via Kconfig options. |
| */ |
| #ifndef CONFIG_MCUMGR_GRP_IMG_ALLOW_CONFIRM_NON_ACTIVE_IMAGE_ANY |
| if (confirm && image != img_mgmt_active_image() && |
| (!IS_ENABLED(CONFIG_MCUMGR_GRP_IMG_ALLOW_CONFIRM_NON_ACTIVE_IMAGE_SECONDARY) || |
| slot == active_slot)) { |
| LOG_DBG("Not allowed to confirm non-active images"); |
| return IMG_MGMT_ERR_IMAGE_CONFIRMATION_DENIED; |
| } |
| #endif |
| |
| /* Setting test to active slot is not allowed. */ |
| if (!confirm && slot == active_slot) { |
| return IMG_MGMT_ERR_IMAGE_SETTING_TEST_TO_ACTIVE_DENIED; |
| } |
| |
| if (type == NEXT_BOOT_TYPE_TEST) { |
| /* Do nothing when requested to test the slot already set for test. */ |
| if (!confirm && slot == next_boot_slot) { |
| return 0; |
| } |
| /* Changing to other, for test or not, is not allowed/ */ |
| return IMG_MGMT_ERR_IMAGE_ALREADY_PENDING; |
| } |
| |
| /* Normal boot means confirmed boot to either active slot or the opposite slot. */ |
| if (type == NEXT_BOOT_TYPE_NORMAL) { |
| /* Do nothing when attempting to confirm slot that will be boot next time. */ |
| if (confirm && slot == next_boot_slot) { |
| return 0; |
| } |
| |
| /* Can not change slot once other than running has been confirmed. */ |
| if ((slot == active_slot && active_slot != next_boot_slot) || |
| (!confirm && slot != active_slot && slot == next_boot_slot)) { |
| return IMG_MGMT_ERR_IMAGE_ALREADY_PENDING; |
| } |
| /* Allow selecting non-active slot for boot */ |
| } |
| |
| if (type == NEXT_BOOT_TYPE_REVERT) { |
| /* Nothing to do when requested to confirm the next boot slot, |
| * as it is already confirmed in this mode. |
| */ |
| if (confirm && slot == next_boot_slot) { |
| return 0; |
| } |
| /* Trying to set any slot for test is an error */ |
| if (!confirm) { |
| return IMG_MGMT_ERR_IMAGE_ALREADY_PENDING; |
| } |
| /* Allow confirming slot == active_slot */ |
| } |
| |
| return img_mgmt_set_next_boot_slot_common(slot, active_slot, confirm); |
| } |
| #else |
| int img_mgmt_set_next_boot_slot(int slot, bool confirm) |
| { |
| int active_image = img_mgmt_active_image(); |
| int active_slot = img_mgmt_active_slot(active_image); |
| |
| LOG_DBG("(%d, %s)", slot, confirm ? "confirm" : "test"); |
| LOG_DBG("aimg = %d, aslot = %d, slot = %d", |
| active_image, active_slot, slot); |
| |
| if (slot == active_slot && !confirm) { |
| return IMG_MGMT_ERR_IMAGE_SETTING_TEST_TO_ACTIVE_DENIED; |
| } |
| |
| return img_mgmt_set_next_boot_slot_common(slot, active_slot, confirm); |
| } |
| #endif |
| |
| /** |
| * Command handler: image state write |
| */ |
| int |
| img_mgmt_state_write(struct smp_streamer *ctxt) |
| { |
| bool confirm = false; |
| int slot; |
| int rc; |
| size_t decoded = 0; |
| bool ok; |
| struct zcbor_string zhash = { 0 }; |
| |
| struct zcbor_map_decode_key_val image_list_decode[] = { |
| ZCBOR_MAP_DECODE_KEY_DECODER("hash", zcbor_bstr_decode, &zhash), |
| ZCBOR_MAP_DECODE_KEY_DECODER("confirm", zcbor_bool_decode, &confirm) |
| }; |
| |
| zcbor_state_t *zse = ctxt->writer->zs; |
| zcbor_state_t *zsd = ctxt->reader->zs; |
| |
| ok = zcbor_map_decode_bulk(zsd, image_list_decode, |
| ARRAY_SIZE(image_list_decode), &decoded) == 0; |
| |
| if (!ok) { |
| return MGMT_ERR_EINVAL; |
| } |
| |
| img_mgmt_take_lock(); |
| |
| /* Determine which slot is being operated on. */ |
| if (zhash.len == 0) { |
| if (confirm) { |
| slot = img_mgmt_active_slot(img_mgmt_active_image()); |
| } else { |
| /* A 'test' without a hash is invalid. */ |
| ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_IMAGE, |
| IMG_MGMT_ERR_INVALID_HASH); |
| goto end; |
| } |
| } else if (zhash.len != IMAGE_HASH_LEN) { |
| /* The img_mgmt_find_by_hash does exact length compare |
| * so just fail here. |
| */ |
| ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_IMAGE, IMG_MGMT_ERR_INVALID_HASH); |
| goto end; |
| } else { |
| uint8_t hash[IMAGE_HASH_LEN]; |
| |
| memcpy(hash, zhash.value, zhash.len); |
| |
| slot = img_mgmt_find_by_hash(hash, NULL); |
| if (slot < 0) { |
| ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_IMAGE, |
| IMG_MGMT_ERR_HASH_NOT_FOUND); |
| goto end; |
| } |
| } |
| |
| rc = img_mgmt_set_next_boot_slot(slot, confirm); |
| if (rc != 0) { |
| ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_IMAGE, rc); |
| goto end; |
| } |
| |
| /* Send the current image state in the response. */ |
| rc = img_mgmt_state_read(ctxt); |
| if (rc != 0) { |
| img_mgmt_release_lock(); |
| return rc; |
| } |
| |
| end: |
| img_mgmt_release_lock(); |
| |
| if (!ok) { |
| return MGMT_ERR_EMSGSIZE; |
| } |
| |
| return MGMT_ERR_EOK; |
| } |