From 2eaedc95164f5831093917bb0424cf1f02d3f4a5 Mon Sep 17 00:00:00 2001 From: Sughosh Ganu Date: Fri, 21 Oct 2022 18:15:55 +0530 Subject: FWU: Add FWU metadata structure and driver for accessing metadata In the FWU Multi Bank Update feature, the information about the updatable images is stored as part of the metadata, which is stored on a dedicated partition. Add the metadata structure, and a driver model uclass which provides functions to access the metadata. These are generic API's, and implementations can be added based on parameters like how the metadata partition is accessed and what type of storage device houses the metadata. Signed-off-by: Sughosh Ganu Reviewed-by: Patrick Delaunay --- lib/fwu_updates/fwu.c | 474 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 474 insertions(+) create mode 100644 lib/fwu_updates/fwu.c (limited to 'lib') diff --git a/lib/fwu_updates/fwu.c b/lib/fwu_updates/fwu.c new file mode 100644 index 00000000000..e92533ea65c --- /dev/null +++ b/lib/fwu_updates/fwu.c @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022, Linaro Limited + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +enum { + IMAGE_ACCEPT_SET = 1, + IMAGE_ACCEPT_CLEAR, +}; + +enum { + PRIMARY_PART = 1, + SECONDARY_PART, + BOTH_PARTS, +}; + +static int fwu_get_dev_mdata(struct udevice **dev, struct fwu_mdata *mdata) +{ + int ret; + + ret = uclass_first_device_err(UCLASS_FWU_MDATA, dev); + if (ret) { + log_debug("Cannot find fwu device\n"); + return ret; + } + + if (!mdata) + return 0; + + ret = fwu_get_mdata(*dev, mdata); + if (ret < 0) + log_debug("Unable to get valid FWU metadata\n"); + + return ret; +} + +static int fwu_get_image_type_id(u8 *image_index, efi_guid_t *image_type_id) +{ + u8 index; + int i; + struct efi_fw_image *image; + + index = *image_index; + image = update_info.images; + for (i = 0; i < num_image_type_guids; i++) { + if (index == image[i].image_index) { + guidcpy(image_type_id, &image[i].image_type_id); + return 0; + } + } + + return -ENOENT; +} + +/** + * fwu_verify_mdata() - Verify the FWU metadata + * @mdata: FWU metadata structure + * @pri_part: FWU metadata partition is primary or secondary + * + * Verify the FWU metadata by computing the CRC32 for the metadata + * structure and comparing it against the CRC32 value stored as part + * of the structure. + * + * Return: 0 if OK, -ve on error + * + */ +int fwu_verify_mdata(struct fwu_mdata *mdata, bool pri_part) +{ + u32 calc_crc32; + void *buf; + + buf = &mdata->version; + calc_crc32 = crc32(0, buf, sizeof(*mdata) - sizeof(u32)); + + if (calc_crc32 != mdata->crc32) { + log_debug("crc32 check failed for %s FWU metadata partition\n", + pri_part ? "primary" : "secondary"); + return -EINVAL; + } + + return 0; +} + +/** + * fwu_check_mdata_validity() - Check for validity of the FWU metadata copies + * + * Read both the metadata copies from the storage media, verify their checksum, + * and ascertain that both copies match. If one of the copies has gone bad, + * restore it from the good copy. + * + * Return: 0 if OK, -ve on error + * + */ +int fwu_check_mdata_validity(void) +{ + int ret; + struct udevice *dev; + struct fwu_mdata pri_mdata; + struct fwu_mdata secondary_mdata; + uint mdata_parts[2]; + uint valid_partitions, invalid_partitions; + + ret = fwu_get_dev_mdata(&dev, NULL); + if (ret) + return ret; + + /* + * Check if the platform has defined its own + * function to check the metadata partitions' + * validity. If so, that takes precedence. + */ + ret = fwu_mdata_check(dev); + if (!ret || ret != -ENOSYS) + return ret; + + /* + * Two FWU metadata partitions are expected. + * If we don't have two, user needs to create + * them first + */ + valid_partitions = 0; + ret = fwu_get_mdata_part_num(dev, mdata_parts); + if (ret < 0) { + log_debug("Error getting the FWU metadata partitions\n"); + return -ENOENT; + } + + ret = fwu_read_mdata_partition(dev, &pri_mdata, mdata_parts[0]); + if (!ret) { + ret = fwu_verify_mdata(&pri_mdata, 1); + if (!ret) + valid_partitions |= PRIMARY_PART; + } + + ret = fwu_read_mdata_partition(dev, &secondary_mdata, mdata_parts[1]); + if (!ret) { + ret = fwu_verify_mdata(&secondary_mdata, 0); + if (!ret) + valid_partitions |= SECONDARY_PART; + } + + if (valid_partitions == (PRIMARY_PART | SECONDARY_PART)) { + /* + * Before returning, check that both the + * FWU metadata copies are the same. If not, + * populate the secondary partition from the + * primary partition copy. + */ + if (!memcmp(&pri_mdata, &secondary_mdata, + sizeof(struct fwu_mdata))) { + ret = 0; + } else { + log_info("Both FWU metadata copies are valid but do not match."); + log_info(" Restoring the secondary partition from the primary\n"); + ret = fwu_write_mdata_partition(dev, &pri_mdata, + mdata_parts[1]); + if (ret) + log_debug("Restoring secondary FWU metadata partition failed\n"); + } + goto out; + } + + if (!(valid_partitions & BOTH_PARTS)) { + log_info("Both FWU metadata partitions invalid\n"); + ret = -EBADMSG; + goto out; + } + + invalid_partitions = valid_partitions ^ BOTH_PARTS; + ret = fwu_write_mdata_partition(dev, + (invalid_partitions == PRIMARY_PART) ? + &secondary_mdata : &pri_mdata, + (invalid_partitions == PRIMARY_PART) ? + mdata_parts[0] : mdata_parts[1]); + + if (ret) + log_debug("Restoring %s FWU metadata partition failed\n", + (invalid_partitions == PRIMARY_PART) ? + "primary" : "secondary"); + +out: + return ret; +} + +/** + * fwu_get_active_index() - Get active_index from the FWU metadata + * @active_idx: active_index value to be read + * + * Read the active_index field from the FWU metadata and place it in + * the variable pointed to be the function argument. + * + * Return: 0 if OK, -ve on error + * + */ +int fwu_get_active_index(uint *active_idx) +{ + int ret; + struct udevice *dev; + struct fwu_mdata mdata = { 0 }; + + ret = fwu_get_dev_mdata(&dev, &mdata); + if (ret) + return ret; + + /* + * Found the FWU metadata partition, now read the active_index + * value + */ + *active_idx = mdata.active_index; + if (*active_idx >= CONFIG_FWU_NUM_BANKS) { + log_debug("Active index value read is incorrect\n"); + ret = -EINVAL; + } + + return ret; +} + +/** + * fwu_set_active_index() - Set active_index in the FWU metadata + * @active_idx: active_index value to be set + * + * Update the active_index field in the FWU metadata + * + * Return: 0 if OK, -ve on error + * + */ +int fwu_set_active_index(uint active_idx) +{ + int ret; + struct udevice *dev; + struct fwu_mdata mdata = { 0 }; + + if (active_idx >= CONFIG_FWU_NUM_BANKS) { + log_debug("Invalid active index value\n"); + return -EINVAL; + } + + ret = fwu_get_dev_mdata(&dev, &mdata); + if (ret) + return ret; + + /* + * Update the active index and previous_active_index fields + * in the FWU metadata + */ + mdata.previous_active_index = mdata.active_index; + mdata.active_index = active_idx; + + /* + * Now write this updated FWU metadata to both the + * FWU metadata partitions + */ + ret = fwu_update_mdata(dev, &mdata); + if (ret) { + log_debug("Failed to update FWU metadata partitions\n"); + ret = -EIO; + } + + return ret; +} + +/** + * fwu_get_image_index() - Get the Image Index to be used for capsule update + * @image_index: The Image Index for the image + * + * The FWU multi bank update feature computes the value of image_index at + * runtime, based on the bank to which the image needs to be written to. + * Derive the image_index value for the image. + * + * Currently, the capsule update driver uses the DFU framework for + * the updates. This function gets the DFU alt number which is to + * be used as the Image Index + * + * Return: 0 if OK, -ve on error + * + */ +int fwu_get_image_index(u8 *image_index) +{ + int ret, i; + u8 alt_num; + uint update_bank; + efi_guid_t *image_guid, image_type_id; + struct udevice *dev; + struct fwu_mdata mdata = { 0 }; + struct fwu_image_entry *img_entry; + struct fwu_image_bank_info *img_bank_info; + + ret = fwu_get_dev_mdata(&dev, &mdata); + if (ret) + return ret; + + ret = fwu_plat_get_update_index(&update_bank); + if (ret) { + log_debug("Failed to get the FWU update bank\n"); + goto out; + } + + ret = fwu_get_image_type_id(image_index, &image_type_id); + if (ret) { + log_debug("Unable to get image_type_id for image_index %u\n", + *image_index); + goto out; + } + + ret = -EINVAL; + /* + * The FWU metadata has been read. Now get the image_uuid for the + * image with the update_bank. + */ + for (i = 0; i < CONFIG_FWU_NUM_IMAGES_PER_BANK; i++) { + if (!guidcmp(&image_type_id, + &mdata.img_entry[i].image_type_uuid)) { + img_entry = &mdata.img_entry[i]; + img_bank_info = &img_entry->img_bank_info[update_bank]; + image_guid = &img_bank_info->image_uuid; + ret = fwu_plat_get_alt_num(dev, image_guid, &alt_num); + if (ret) { + log_debug("alt_num not found for partition with GUID %pUs\n", + image_guid); + } else { + log_debug("alt_num %d for partition %pUs\n", + alt_num, image_guid); + *image_index = alt_num + 1; + } + + goto out; + } + } + + log_debug("Partition with the image type %pUs not found\n", + &image_type_id); + +out: + return ret; +} + +/** + * fwu_revert_boot_index() - Revert the active index in the FWU metadata + * + * Revert the active_index value in the FWU metadata, by swapping the values + * of active_index and previous_active_index in both copies of the + * FWU metadata. + * + * Return: 0 if OK, -ve on error + * + */ +int fwu_revert_boot_index(void) +{ + int ret; + u32 cur_active_index; + struct udevice *dev; + struct fwu_mdata mdata = { 0 }; + + ret = fwu_get_dev_mdata(&dev, &mdata); + if (ret) + return ret; + + /* + * Swap the active index and previous_active_index fields + * in the FWU metadata + */ + cur_active_index = mdata.active_index; + mdata.active_index = mdata.previous_active_index; + mdata.previous_active_index = cur_active_index; + + /* + * Now write this updated FWU metadata to both the + * FWU metadata partitions + */ + ret = fwu_update_mdata(dev, &mdata); + if (ret) { + log_debug("Failed to update FWU metadata partitions\n"); + ret = -EIO; + } + + return ret; +} + +/** + * fwu_clrset_image_accept() - Set or Clear the Acceptance bit for the image + * @img_type_id: GUID of the image type for which the accepted bit is to be + * set or cleared + * @bank: Bank of which the image's Accept bit is to be set or cleared + * @action: Action which specifies whether image's Accept bit is to be set or + * cleared + * + * Set/Clear the accepted bit for the image specified by the img_guid parameter. + * This indicates acceptance or rejection of image for subsequent boots by some + * governing component like OS(or firmware). + * + * Return: 0 if OK, -ve on error + * + */ +static int fwu_clrset_image_accept(efi_guid_t *img_type_id, u32 bank, u8 action) +{ + int ret, i; + struct udevice *dev; + struct fwu_mdata mdata = { 0 }; + struct fwu_image_entry *img_entry; + struct fwu_image_bank_info *img_bank_info; + + ret = fwu_get_dev_mdata(&dev, &mdata); + if (ret) + return ret; + + img_entry = &mdata.img_entry[0]; + for (i = 0; i < CONFIG_FWU_NUM_IMAGES_PER_BANK; i++) { + if (!guidcmp(&img_entry[i].image_type_uuid, img_type_id)) { + img_bank_info = &img_entry[i].img_bank_info[bank]; + if (action == IMAGE_ACCEPT_SET) + img_bank_info->accepted |= FWU_IMAGE_ACCEPTED; + else + img_bank_info->accepted = 0; + + ret = fwu_update_mdata(dev, &mdata); + goto out; + } + } + + /* Image not found */ + ret = -ENOENT; + +out: + return ret; +} + +/** + * fwu_accept_image() - Set the Acceptance bit for the image + * @img_type_id: GUID of the image type for which the accepted bit is to be + * cleared + * @bank: Bank of which the image's Accept bit is to be set + * + * Set the accepted bit for the image specified by the img_guid parameter. This + * indicates acceptance of image for subsequent boots by some governing component + * like OS(or firmware). + * + * Return: 0 if OK, -ve on error + * + */ +int fwu_accept_image(efi_guid_t *img_type_id, u32 bank) +{ + return fwu_clrset_image_accept(img_type_id, bank, + IMAGE_ACCEPT_SET); +} + +/** + * fwu_clear_accept_image() - Clear the Acceptance bit for the image + * @img_type_id: GUID of the image type for which the accepted bit is to be + * cleared + * @bank: Bank of which the image's Accept bit is to be cleared + * + * Clear the accepted bit for the image type specified by the img_type_id parameter. + * This function is called after the image has been updated. The accepted bit is + * cleared to be set subsequently after passing the image acceptance criteria, by + * either the OS(or firmware) + * + * Return: 0 if OK, -ve on error + * + */ +int fwu_clear_accept_image(efi_guid_t *img_type_id, u32 bank) +{ + return fwu_clrset_image_accept(img_type_id, bank, + IMAGE_ACCEPT_CLEAR); +} -- cgit v1.2.3 From 7d6e2c54b7dd057f50bf0116c9e803e214dbe74c Mon Sep 17 00:00:00 2001 From: Sughosh Ganu Date: Fri, 21 Oct 2022 18:15:59 +0530 Subject: FWU: Add helper functions for accessing FWU metadata Add weak functions for getting the update index value and dfu alternate number needed for FWU Multi Bank update functionality. The current implementation for getting the update index value is for platforms with 2 banks. If a platform supports more than 2 banks, it can implement it's own function. The function to get the dfu alternate number has been added for platforms with GPT partitioned storage devices. Platforms with other storage partition scheme need to implement their own function. Signed-off-by: Sughosh Ganu Reviewed-by: Patrick Delaunay Acked-by: Etienne Carriere Reviewed-by: Ilias Apalodimas --- lib/fwu_updates/fwu.c | 27 ++++++++++ lib/fwu_updates/fwu_gpt.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 lib/fwu_updates/fwu_gpt.c (limited to 'lib') diff --git a/lib/fwu_updates/fwu.c b/lib/fwu_updates/fwu.c index e92533ea65c..e73d5f11ff7 100644 --- a/lib/fwu_updates/fwu.c +++ b/lib/fwu_updates/fwu.c @@ -472,3 +472,30 @@ int fwu_clear_accept_image(efi_guid_t *img_type_id, u32 bank) return fwu_clrset_image_accept(img_type_id, bank, IMAGE_ACCEPT_CLEAR); } + +/** + * fwu_plat_get_update_index() - Get the value of the update bank + * @update_idx: Bank number to which images are to be updated + * + * Get the value of the bank(partition) to which the update needs to be + * made. + * + * Note: This is a weak function and platforms can override this with + * their own implementation for selection of the update bank. + * + * Return: 0 if OK, -ve on error + * + */ +__weak int fwu_plat_get_update_index(uint *update_idx) +{ + int ret; + u32 active_idx; + + ret = fwu_get_active_index(&active_idx); + if (ret < 0) + return -1; + + *update_idx = (active_idx + 1) % CONFIG_FWU_NUM_BANKS; + + return ret; +} diff --git a/lib/fwu_updates/fwu_gpt.c b/lib/fwu_updates/fwu_gpt.c new file mode 100644 index 00000000000..21a573c9342 --- /dev/null +++ b/lib/fwu_updates/fwu_gpt.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2022, Linaro Limited + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +static int get_gpt_dfu_identifier(struct blk_desc *desc, efi_guid_t *image_guid) +{ + int i; + struct disk_partition info; + efi_guid_t unique_part_guid; + + for (i = 1; i < MAX_SEARCH_PARTITIONS; i++) { + if (part_get_info(desc, i, &info)) + continue; + uuid_str_to_bin(info.uuid, unique_part_guid.b, + UUID_STR_FORMAT_GUID); + + if (!guidcmp(&unique_part_guid, image_guid)) + return i; + } + + log_err("No partition found with image_guid %pUs\n", image_guid); + return -ENOENT; +} + +static int fwu_alt_num_for_dfu_dev(struct dfu_entity *dfu, int dev_num, + int part, unsigned char dfu_dev, + u8 *alt_num) +{ + int ret; + + switch(dfu_dev) { + case DFU_DEV_MMC: + if (dfu->layout == DFU_RAW_ADDR && + dfu->data.mmc.dev_num == dev_num && + dfu->data.mmc.part == part) { + *alt_num = dfu->alt; + ret = 0; + } else { + ret = -ENOENT; + } + break; + default: + ret = -ENOENT; + } + + return ret; +} + +static int fwu_gpt_get_alt_num(struct blk_desc *desc, efi_guid_t *image_guid, + u8 *alt_num, unsigned char dfu_dev) +{ + int ret = -1; + int i, part, dev_num; + struct dfu_entity *dfu; + + dev_num = desc->devnum; + part = get_gpt_dfu_identifier(desc, image_guid); + if (part < 0) + return -ENOENT; + + ret = dfu_init_env_entities(NULL, NULL); + if (ret) + goto out; + + i = 0; + while (true) { + dfu = dfu_get_entity(i++); + if (!dfu) { + ret = -ENOENT; + break; + } + + if (dfu->dev_type != dfu_dev) + continue; + + ret = fwu_alt_num_for_dfu_dev(dfu, dev_num, part, dfu_dev, + alt_num); + if (!ret) + break; + } + +out: + dfu_free_entities(); + + return ret; +} + +/** + * fwu_plat_get_alt_num() - Get the DFU alt number + * @dev: FWU metadata device + * @image_guid: GUID value of the image for which the alt num is to + * be obtained + * @alt_num: The DFU alt number for the image that is to be updated + * + * Get the DFU alt number for the image that is to be updated. The + * image is identified with the image_guid parameter that is passed + * to the function. + * + * Note: This is a weak function and platforms can override this with + * their own implementation for obtaining the alt number value. + * + * Return: 0 if OK, -ve on error + * + */ +__weak int fwu_plat_get_alt_num(struct udevice *dev, efi_guid_t *image_guid, + u8 *alt_num) +{ + struct fwu_mdata_gpt_blk_priv *priv = dev_get_priv(dev); + + return fwu_gpt_get_alt_num(dev_get_uclass_plat(priv->blk_dev), + image_guid, alt_num, DFU_DEV_MMC); +} -- cgit v1.2.3 From 7e9814cc6c1dcda8cb8028e1d34483387889e149 Mon Sep 17 00:00:00 2001 From: Sughosh Ganu Date: Fri, 21 Oct 2022 18:16:02 +0530 Subject: FWU: Add boot time checks as highlighted by the FWU specification The FWU Multi Bank Update specification requires the Update Agent to carry out certain checks at the time of platform boot. The Update Agent is the component which is responsible for updating the firmware components and maintaining and keeping the metadata in sync. The spec requires that the Update Agent perform the following checks at the time of boot * Sanity check of both the metadata copies maintained by the platform. * Get the boot index passed to U-Boot by the prior stage bootloader and use this value for metadata bookkeeping. * Check if the system is booting in Trial State. If the system boots in the Trial State for more than a specified number of boot counts, change the Active Bank to be booting the platform from. Call these checks through the main loop event at the time of platform boot. Signed-off-by: Sughosh Ganu Reviewed-by: Etienne Carriere Acked-by: Ilias Apalodimas --- lib/fwu_updates/fwu.c | 192 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 191 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/fwu_updates/fwu.c b/lib/fwu_updates/fwu.c index e73d5f11ff7..c9a9022a595 100644 --- a/lib/fwu_updates/fwu.c +++ b/lib/fwu_updates/fwu.c @@ -4,10 +4,19 @@ */ #include +#include #include +#include +#include #include #include -#include +#include + +#include +#include + +static u8 in_trial; +static u8 boottime_check; #include #include @@ -44,6 +53,96 @@ static int fwu_get_dev_mdata(struct udevice **dev, struct fwu_mdata *mdata) return ret; } +static int trial_counter_update(u16 *trial_state_ctr) +{ + bool delete; + u32 var_attr; + efi_status_t status; + efi_uintn_t var_size; + + delete = !trial_state_ctr ? true : false; + var_size = !trial_state_ctr ? 0 : (efi_uintn_t)sizeof(*trial_state_ctr); + var_attr = !trial_state_ctr ? 0 : EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS; + status = efi_set_variable_int(u"TrialStateCtr", + &efi_global_variable_guid, + var_attr, + var_size, trial_state_ctr, false); + + if ((delete && (status != EFI_NOT_FOUND && + status != EFI_SUCCESS)) || + (!delete && status != EFI_SUCCESS)) + return -1; + + return 0; +} + +static int trial_counter_read(u16 *trial_state_ctr) +{ + efi_status_t status; + efi_uintn_t var_size; + + var_size = (efi_uintn_t)sizeof(trial_state_ctr); + status = efi_get_variable_int(u"TrialStateCtr", + &efi_global_variable_guid, + NULL, + &var_size, trial_state_ctr, + NULL); + if (status != EFI_SUCCESS) { + log_err("Unable to read TrialStateCtr variable\n"); + return -1; + } + + return 0; +} + +static int fwu_trial_count_update(void) +{ + int ret; + u16 trial_state_ctr; + + ret = trial_counter_read(&trial_state_ctr); + if (ret) { + log_debug("Unable to read trial_state_ctr\n"); + goto out; + } + + ++trial_state_ctr; + if (trial_state_ctr > CONFIG_FWU_TRIAL_STATE_CNT) { + log_info("Trial State count exceeded. Revert back to previous_active_index\n"); + ret = fwu_revert_boot_index(); + if (ret) + log_err("Unable to revert active_index\n"); + ret = 1; + } else { + ret = trial_counter_update(&trial_state_ctr); + if (ret) + log_err("Unable to increment TrialStateCtr variable\n"); + } + +out: + return ret; +} + +static int in_trial_state(struct fwu_mdata *mdata) +{ + u32 i, active_bank; + struct fwu_image_entry *img_entry; + struct fwu_image_bank_info *img_bank_info; + + active_bank = mdata->active_index; + img_entry = &mdata->img_entry[0]; + for (i = 0; i < CONFIG_FWU_NUM_IMAGES_PER_BANK; i++) { + img_bank_info = &img_entry[i].img_bank_info[active_bank]; + if (!img_bank_info->accepted) { + log_info("System booting in Trial State\n"); + return 1; + } + } + + return 0; +} + static int fwu_get_image_type_id(u8 *image_index, efi_guid_t *image_type_id) { u8 index; @@ -499,3 +598,94 @@ __weak int fwu_plat_get_update_index(uint *update_idx) return ret; } + +/** + * fwu_update_checks_pass() - Check if FWU update can be done + * + * Check if the FWU update can be executed. The updates are + * allowed only when the platform is not in Trial State and + * the boot time checks have passed + * + * Return: 1 if OK, 0 if checks do not pass + * + */ +u8 fwu_update_checks_pass(void) +{ + return !in_trial && boottime_check; +} + +/** + * fwu_empty_capsule_checks_pass() - Check if empty capsule can be processed + * + * Check if the empty capsule can be processed to either accept or revert + * an earlier executed update. The empty capsules need to be processed + * only when the platform is in Trial State and the boot time checks have + * passed + * + * Return: 1 if OK, 0 if not to be allowed + * + */ +u8 fwu_empty_capsule_checks_pass(void) +{ + return in_trial && boottime_check; +} + +static int fwu_boottime_checks(void *ctx, struct event *event) +{ + int ret; + u32 boot_idx, active_idx; + struct udevice *dev; + struct fwu_mdata mdata = { 0 }; + + ret = fwu_check_mdata_validity(); + if (ret) + return 0; + + /* + * Get the Boot Index, i.e. the bank from + * which the platform has booted. This value + * gets passed from the ealier stage bootloader + * which booted u-boot, e.g. tf-a. If the + * boot index is not the same as the + * active_index read from the FWU metadata, + * update the active_index. + */ + fwu_plat_get_bootidx(&boot_idx); + if (boot_idx >= CONFIG_FWU_NUM_BANKS) { + log_err("Received incorrect value of boot_index\n"); + return 0; + } + + ret = fwu_get_active_index(&active_idx); + if (ret) { + log_err("Unable to read active_index\n"); + return 0; + } + + if (boot_idx != active_idx) { + log_info("Boot idx %u is not matching active idx %u, changing active_idx\n", + boot_idx, active_idx); + ret = fwu_set_active_index(boot_idx); + if (!ret) + boottime_check = 1; + + return 0; + } + + if (efi_init_obj_list() != EFI_SUCCESS) + return 0; + + ret = fwu_get_dev_mdata(&dev, &mdata); + if (ret) + return ret; + + in_trial = in_trial_state(&mdata); + if (!in_trial || (ret = fwu_trial_count_update()) > 0) + ret = trial_counter_update(NULL); + + if (!ret) + boottime_check = 1; + + return 0; +} +EVENT_SPY(EVT_MAIN_LOOP, fwu_boottime_checks); -- cgit v1.2.3 From 86794052418b7aa15d94025add3082cd357a0b12 Mon Sep 17 00:00:00 2001 From: Sughosh Ganu Date: Fri, 21 Oct 2022 18:16:03 +0530 Subject: FWU: Add support for the FWU Multi Bank Update feature The FWU Multi Bank Update feature supports updating firmware images to one of multiple sets(also called banks) of images. The firmware images are clubbed together in banks, with the system booting images from the active bank. Information on the images such as which bank they belong to is stored as part of the metadata structure, which is stored on the same storage media as the firmware images on a dedicated partition. At the time of update, the metadata is read to identify the bank to which the images need to be flashed(update bank). On a successful update, the metadata is modified to set the updated bank as active bank to subsequently boot from. Signed-off-by: Sughosh Ganu Acked-by: Ilias Apalodimas --- lib/Kconfig | 6 ++ lib/Makefile | 1 + lib/efi_loader/efi_capsule.c | 210 +++++++++++++++++++++++++++++++++++++++++- lib/efi_loader/efi_firmware.c | 14 +++ lib/fwu_updates/Kconfig | 33 +++++++ lib/fwu_updates/Makefile | 7 ++ lib/fwu_updates/fwu.c | 22 +++++ 7 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 lib/fwu_updates/Kconfig create mode 100644 lib/fwu_updates/Makefile (limited to 'lib') diff --git a/lib/Kconfig b/lib/Kconfig index 6121c80dc81..6abe1d0a863 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -978,3 +978,9 @@ config LMB_RESERVED_REGIONS memory blocks. endmenu + +menu "FWU Multi Bank Updates" + +source lib/fwu_updates/Kconfig + +endmenu diff --git a/lib/Makefile b/lib/Makefile index e3deb152879..f2cfd1e4289 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_EFI) += efi/ obj-$(CONFIG_EFI_LOADER) += efi_driver/ obj-$(CONFIG_EFI_LOADER) += efi_loader/ obj-$(CONFIG_CMD_BOOTEFI_SELFTEST) += efi_selftest/ +obj-$(CONFIG_FWU_MULTI_BANK_UPDATE) += fwu_updates/ obj-$(CONFIG_LZMA) += lzma/ obj-$(CONFIG_BZIP2) += bzip2/ obj-$(CONFIG_FIT) += libfdt/ diff --git a/lib/efi_loader/efi_capsule.c b/lib/efi_loader/efi_capsule.c index 397e393a188..1163a2ee301 100644 --- a/lib/efi_loader/efi_capsule.c +++ b/lib/efi_loader/efi_capsule.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,12 @@ static const efi_guid_t efi_guid_firmware_management_capsule_id = EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID; const efi_guid_t efi_guid_firmware_management_protocol = EFI_FIRMWARE_MANAGEMENT_PROTOCOL_GUID; +const efi_guid_t fwu_guid_os_request_fw_revert = + FWU_OS_REQUEST_FW_REVERT_GUID; +const efi_guid_t fwu_guid_os_request_fw_accept = + FWU_OS_REQUEST_FW_ACCEPT_GUID; + +#define FW_ACCEPT_OS (u32)0x8000 #ifdef CONFIG_EFI_CAPSULE_ON_DISK /* for file system access */ @@ -386,6 +393,132 @@ efi_status_t efi_capsule_authenticate(const void *capsule, efi_uintn_t capsule_s } #endif /* CONFIG_EFI_CAPSULE_AUTHENTICATE */ +static __maybe_unused bool fwu_empty_capsule(struct efi_capsule_header *capsule) +{ + return !guidcmp(&capsule->capsule_guid, + &fwu_guid_os_request_fw_revert) || + !guidcmp(&capsule->capsule_guid, + &fwu_guid_os_request_fw_accept); +} + +static __maybe_unused efi_status_t fwu_to_efi_error(int err) +{ + efi_status_t ret; + + switch(err) { + case 0: + ret = EFI_SUCCESS; + break; + case -ERANGE: + case -EIO: + ret = EFI_DEVICE_ERROR; + break; + case -EINVAL: + ret = EFI_INVALID_PARAMETER; + break; + case -ENODEV: + ret = EFI_NOT_FOUND; + break; + default: + ret = EFI_OUT_OF_RESOURCES; + } + + return ret; +} + +static __maybe_unused efi_status_t fwu_empty_capsule_process( + struct efi_capsule_header *capsule) +{ + int status; + u32 active_idx; + efi_guid_t *image_guid; + efi_status_t ret = EFI_INVALID_PARAMETER; + + if (!guidcmp(&capsule->capsule_guid, + &fwu_guid_os_request_fw_revert)) { + /* + * One of the previously updated image has + * failed the OS acceptance test. OS has + * requested to revert back to the earlier + * boot index + */ + status = fwu_revert_boot_index(); + ret = fwu_to_efi_error(status); + if (ret == EFI_SUCCESS) + log_debug("Reverted the FWU active_index. Recommend rebooting the system\n"); + else + log_err("Failed to revert the FWU boot index\n"); + } else if (!guidcmp(&capsule->capsule_guid, + &fwu_guid_os_request_fw_accept)) { + /* + * Image accepted by the OS. Set the acceptance + * status for the image. + */ + image_guid = (void *)(char *)capsule + + capsule->header_size; + + status = fwu_get_active_index(&active_idx); + ret = fwu_to_efi_error(status); + if (ret != EFI_SUCCESS) { + log_err("Unable to get the active_index from the FWU metadata\n"); + return ret; + } + + status = fwu_accept_image(image_guid, active_idx); + ret = fwu_to_efi_error(status); + if (ret != EFI_SUCCESS) + log_err("Unable to set the Accept bit for the image %pUs\n", + image_guid); + } + + return ret; +} + +static __maybe_unused void fwu_post_update_checks( + struct efi_capsule_header *capsule, + bool *fw_accept_os, bool *capsule_update) +{ + if (fwu_empty_capsule(capsule)) + *capsule_update = false; + else + if (!*fw_accept_os) + *fw_accept_os = + capsule->flags & FW_ACCEPT_OS ? true : false; +} + +static __maybe_unused efi_status_t fwu_post_update_process(bool fw_accept_os) +{ + int status; + uint update_index; + efi_status_t ret; + + status = fwu_plat_get_update_index(&update_index); + if (status < 0) { + log_err("Failed to get the FWU update_index value\n"); + return EFI_DEVICE_ERROR; + } + + /* + * All the capsules have been updated successfully, + * update the FWU metadata. + */ + log_debug("Update Complete. Now updating active_index to %u\n", + update_index); + status = fwu_set_active_index(update_index); + ret = fwu_to_efi_error(status); + if (ret != EFI_SUCCESS) { + log_err("Failed to update FWU metadata index values\n"); + } else { + log_debug("Successfully updated the active_index\n"); + if (fw_accept_os) { + status = fwu_trial_state_ctr_start(); + if (status < 0) + ret = EFI_DEVICE_ERROR; + } + } + + return ret; +} /** * efi_capsule_update_firmware - update firmware from capsule @@ -408,7 +541,32 @@ static efi_status_t efi_capsule_update_firmware( int item; struct efi_firmware_management_protocol *fmp; u16 *abort_reason; + efi_guid_t *image_type_id; efi_status_t ret = EFI_SUCCESS; + int status; + uint update_index; + bool fw_accept_os; + + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + if (fwu_empty_capsule_checks_pass() && + fwu_empty_capsule(capsule_data)) + return fwu_empty_capsule_process(capsule_data); + + if (!fwu_update_checks_pass()) { + log_err("FWU checks failed. Cannot start update\n"); + return EFI_INVALID_PARAMETER; + } + + + /* Obtain the update_index from the platform */ + status = fwu_plat_get_update_index(&update_index); + if (status < 0) { + log_err("Failed to get the FWU update_index value\n"); + return EFI_DEVICE_ERROR; + } + + fw_accept_os = capsule_data->flags & FW_ACCEPT_OS ? 0x1 : 0x0; + } /* sanity check */ if (capsule_data->header_size < sizeof(*capsule) || @@ -495,6 +653,34 @@ static efi_status_t efi_capsule_update_firmware( efi_free_pool(abort_reason); goto out; } + + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + image_type_id = &image->update_image_type_id; + if (!fw_accept_os) { + /* + * The OS will not be accepting the firmware + * images. Set the accept bit of all the + * images contained in this capsule. + */ + status = fwu_accept_image(image_type_id, + update_index); + } else { + status = fwu_clear_accept_image(image_type_id, + update_index); + } + ret = fwu_to_efi_error(status); + if (ret != EFI_SUCCESS) { + log_err("Unable to %s the accept bit for the image %pUs\n", + fw_accept_os ? "clear" : "set", + image_type_id); + goto out; + } + + log_debug("%s the accepted bit for Image %pUs\n", + fw_accept_os ? "Cleared" : "Set", + image_type_id); + } + } out: @@ -1103,6 +1289,9 @@ efi_status_t efi_launch_capsules(void) u16 **files; unsigned int nfiles, index, i; efi_status_t ret; + bool capsule_update = true; + bool update_status = true; + bool fw_accept_os = false; if (check_run_capsules() != EFI_SUCCESS) return EFI_SUCCESS; @@ -1130,12 +1319,19 @@ efi_status_t efi_launch_capsules(void) ret = efi_capsule_read_file(files[i], &capsule); if (ret == EFI_SUCCESS) { ret = efi_capsule_update_firmware(capsule); - if (ret != EFI_SUCCESS) + if (ret != EFI_SUCCESS) { log_err("Applying capsule %ls failed.\n", files[i]); - else + update_status = false; + } else { log_info("Applying capsule %ls succeeded.\n", files[i]); + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + fwu_post_update_checks(capsule, + &fw_accept_os, + &capsule_update); + } + } /* create CapsuleXXXX */ set_capsule_result(index, capsule, ret); @@ -1143,6 +1339,7 @@ efi_status_t efi_launch_capsules(void) free(capsule); } else { log_err("Reading capsule %ls failed\n", files[i]); + update_status = false; } /* delete a capsule either in case of success or failure */ ret = efi_capsule_delete_file(files[i]); @@ -1150,8 +1347,17 @@ efi_status_t efi_launch_capsules(void) log_err("Deleting capsule %ls failed\n", files[i]); } + efi_capsule_scan_done(); + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + if (capsule_update == true && update_status == true) { + ret = fwu_post_update_process(fw_accept_os); + } else if (capsule_update == true && update_status == false) { + log_err("All capsules were not updated. Not updating FWU metadata\n"); + } + } + for (i = 0; i < nfiles; i++) free(files[i]); free(files); diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c index 30cafd15caa..93e2b01c07a 100644 --- a/lib/efi_loader/efi_firmware.c +++ b/lib/efi_loader/efi_firmware.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -389,6 +390,7 @@ efi_status_t EFIAPI efi_firmware_raw_set_image( efi_status_t (*progress)(efi_uintn_t completion), u16 **abort_reason) { + int ret; efi_status_t status; EFI_ENTRY("%p %d %p %zu %p %p %p\n", this, image_index, image, @@ -401,6 +403,18 @@ efi_status_t EFIAPI efi_firmware_raw_set_image( if (status != EFI_SUCCESS) return EFI_EXIT(status); + if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) { + /* + * Based on the value of update bank, derive the + * image index value. + */ + ret = fwu_get_image_index(&image_index); + if (ret) { + log_debug("Unable to get FWU image_index\n"); + return EFI_EXIT(EFI_DEVICE_ERROR); + } + } + if (dfu_write_by_alt(image_index - 1, (void *)image, image_size, NULL, NULL)) return EFI_EXIT(EFI_DEVICE_ERROR); diff --git a/lib/fwu_updates/Kconfig b/lib/fwu_updates/Kconfig new file mode 100644 index 00000000000..78759e6618d --- /dev/null +++ b/lib/fwu_updates/Kconfig @@ -0,0 +1,33 @@ +config FWU_MULTI_BANK_UPDATE + bool "Enable FWU Multi Bank Update Feature" + depends on EFI_CAPSULE_ON_DISK + select PARTITION_TYPE_GUID + select EFI_SETUP_EARLY + imply EFI_CAPSULE_ON_DISK_EARLY + select EVENT + help + Feature for updating firmware images on platforms having + multiple banks(copies) of the firmware images. One of the + bank is selected for updating all the firmware components + +config FWU_NUM_BANKS + int "Number of Banks defined by the platform" + depends on FWU_MULTI_BANK_UPDATE + help + Define the number of banks of firmware images on a platform + +config FWU_NUM_IMAGES_PER_BANK + int "Number of firmware images per bank" + depends on FWU_MULTI_BANK_UPDATE + help + Define the number of firmware images per bank. This value + should be the same for all the banks. + +config FWU_TRIAL_STATE_CNT + int "Number of times system boots in Trial State" + depends on FWU_MULTI_BANK_UPDATE + default 3 + help + With FWU Multi Bank Update feature enabled, number of times + the platform is allowed to boot in Trial State after an + update. diff --git a/lib/fwu_updates/Makefile b/lib/fwu_updates/Makefile new file mode 100644 index 00000000000..1993088e5b1 --- /dev/null +++ b/lib/fwu_updates/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (c) 2022, Linaro Limited +# + +obj-$(CONFIG_FWU_MULTI_BANK_UPDATE) += fwu.o +obj-$(CONFIG_FWU_MDATA_GPT_BLK) += fwu_gpt.o diff --git a/lib/fwu_updates/fwu.c b/lib/fwu_updates/fwu.c index c9a9022a595..6e159c050c5 100644 --- a/lib/fwu_updates/fwu.c +++ b/lib/fwu_updates/fwu.c @@ -630,6 +630,28 @@ u8 fwu_empty_capsule_checks_pass(void) return in_trial && boottime_check; } +/** + * fwu_trial_state_ctr_start() - Start the Trial State counter + * + * Start the counter to identify the platform booting in the + * Trial State. The counter is implemented as an EFI variable. + * + * Return: 0 if OK, -ve on error + * + */ +int fwu_trial_state_ctr_start(void) +{ + int ret; + u16 trial_state_ctr; + + trial_state_ctr = 0; + ret = trial_counter_update(&trial_state_ctr); + if (ret) + log_err("Unable to initialise TrialStateCtr\n"); + + return ret; +} + static int fwu_boottime_checks(void *ctx, struct event *event) { int ret; -- cgit v1.2.3 From 873cf8ac70b620a0c1417d804bd4fb4246c34ebc Mon Sep 17 00:00:00 2001 From: Sughosh Ganu Date: Fri, 21 Oct 2022 18:16:05 +0530 Subject: test: dm: Add test cases for FWU Metadata uclass Add test cases for accessing the FWU Metadata on the sandbox platform. The sandbox platform also uses the metadata access driver for GPT partitioned block devices. The FWU feature will be tested on the sandbox64 variant with a raw capsule. Remove the FIT capsule testing from sandbox64 defconfig -- the FIT capsule test will be run on the sandbox_flattree variant. Signed-off-by: Sughosh Ganu Suggested-by: Heinrich Schuchardt Acked-by: Ilias Apalodimas --- lib/fwu_updates/fwu.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib') diff --git a/lib/fwu_updates/fwu.c b/lib/fwu_updates/fwu.c index 6e159c050c5..5313d073020 100644 --- a/lib/fwu_updates/fwu.c +++ b/lib/fwu_updates/fwu.c @@ -659,6 +659,12 @@ static int fwu_boottime_checks(void *ctx, struct event *event) struct udevice *dev; struct fwu_mdata mdata = { 0 }; + /* Don't have boot time checks on sandbox */ + if (IS_ENABLED(CONFIG_SANDBOX)) { + boottime_check = 1; + return 0; + } + ret = fwu_check_mdata_validity(); if (ret) return 0; -- cgit v1.2.3