From 08ef42b57a4e7649b314b269f8b129722f1f119b Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 13 May 2026 17:07:47 +0530 Subject: [PATCH 1/7] qcom: capsule: add FIT capsule support with multi-partition Add comprehensive FIT capsule update support for Qualcomm platforms alongside existing RAW capsule implementation. The new FIT support enables multi-partition firmware updates with automatic partition discovery. Refactor qcom_configure_capsule_updates() to use compile-time mutual exclusivity between CONFIG_EFI_CAPSULE_FIRMWARE_FIT and CONFIG_EFI_CAPSULE_FIRMWARE_RAW using #elif preprocessor directives. Add board-specific FIT capsule GUIDs for QCS615, QCS6490, and Lemans platforms with automatic board detection from device tree compatible strings. Each board uses a unique GUID to prevent cross-board flashing accidents. The FIT implementation discovers all SCSI/eMMC partitions across multiple devices, applies A/B selection logic based on GPT vendor attributes, and generates a comprehensive DFU string for multi-partition updates. A single ESRT entry represents all partitions for simplified firmware management. Signed-off-by: Balaji Selvanathan --- arch/arm/mach-snapdragon/capsule_update.c | 739 ++++++++++++++++++++-- arch/arm/mach-snapdragon/qcom-priv.h | 23 + 2 files changed, 711 insertions(+), 51 deletions(-) diff --git a/arch/arm/mach-snapdragon/capsule_update.c b/arch/arm/mach-snapdragon/capsule_update.c index 586682434b73..eb900d80b421 100644 --- a/arch/arm/mach-snapdragon/capsule_update.c +++ b/arch/arm/mach-snapdragon/capsule_update.c @@ -8,30 +8,55 @@ #define pr_fmt(fmt) "QCOM-FMP: " fmt -#include -#include +#include #include #include #include #include -#include #include +#include +#include +#include #include - #include "qcom-priv.h" /* - * To handle different variants like chainloaded U-Boot here we need to - * build the fw_images array dynamically at runtime. These are the possible - * implementations: - * - * - Devices with U-Boot on the uefi_a/b partition - * - Devices with U-Boot on the boot (a/b) partition - * - Devices with U-Boot on the xbl (a/b) partition - * - * Which partition actually has U-Boot on it is determined based on the - * qcom_boot_source variable and additional logic in find_target_partition(). + * Capsule update support with conditional FIT vs RAW implementation: + * - FIT capsules: Comprehensive partition discovery with dynamic fw_images + * - RAW capsules: Existing single-partition approach with static fw_images */ + +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_FIT +#define MAX_DFU_STRING_SIZE 2048 +#define MAX_PARTITION_GROUPS 64 +#define MAX_PARTITIONS_PER_LUN 64 +#define MAX_PARTITIONS_TO_SCAN 128 +#define MAX_LUN_GROUPS 16 + +struct qcom_partition_info { + char name[32]; /* "uefi_a", "boot_b", etc. */ + char base_name[32]; /* "uefi", "boot", etc. */ + char slot_suffix[4]; /* "_a", "_b", or "" */ + int lun; /* SCSI LUN number */ + int partition_num; /* Partition number within LUN */ + bool is_active; /* From GPT vendor attributes */ + bool is_bootable; /* From GPT vendor attributes */ +}; + +struct partition_group { + char base_name[32]; + struct qcom_partition_info *a_slot; + struct qcom_partition_info *b_slot; + struct qcom_partition_info *no_slot; +}; + +struct lun_group { + int lun_number; + struct qcom_partition_info *partitions[MAX_PARTITIONS_PER_LUN]; /* Max partitions per LUN */ + int partition_count; +}; +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_FIT */ + struct efi_fw_image fw_images[] = { { .image_index = 1, @@ -39,18 +64,26 @@ struct efi_fw_image fw_images[] = { }; struct efi_capsule_update_info update_info = { - /* Filled in by configure_dfu_string() */ + /* Filled in by qcom_configure_capsule_updates() */ .dfu_string = NULL, .num_images = ARRAY_SIZE(fw_images), .images = fw_images, }; +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_RAW enum target_part_type { TARGET_PART_UEFI = 1, TARGET_PART_XBL, TARGET_PART_BOOT, }; +enum ab_slot { + SLOT_NONE, + SLOT_A, + SLOT_B, +}; +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_RAW */ + /* LSB first */ struct part_slot_status { u16: 2; @@ -61,26 +94,6 @@ struct part_slot_status { u16 tries_remaining : 4; }; -enum ab_slot { - SLOT_NONE, - SLOT_A, - SLOT_B, -}; - -static enum ab_slot get_part_slot(const char *partname) -{ - int len = strlen(partname); - - if (partname[len - 2] != '_') - return SLOT_NONE; - if (partname[len - 1] == 'a') - return SLOT_A; - if (partname[len - 1] == 'b') - return SLOT_B; - - return SLOT_NONE; -} - /* Shamelessly copied from lib/efi_loader/efi_device_path.c @ 33 */ /* * Determine if an MMC device is an SD card. @@ -98,6 +111,25 @@ static bool is_sd(struct blk_desc *desc) return IS_SD(mmc) != 0U; } +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_RAW +/* + * RAW Capsule Support + */ + +static enum ab_slot get_part_slot(const char *partname) +{ + int len = strlen(partname); + + if (partname[len - 2] != '_') + return SLOT_NONE; + if (partname[len - 1] == 'a') + return SLOT_A; + if (partname[len - 1] == 'b') + return SLOT_B; + + return SLOT_NONE; +} + /* * Determine which partition U-Boot is flashed to based on the boot source (ABL/XBL), * the slot status, and prioritizing the uefi partition over xbl if found. @@ -156,7 +188,7 @@ static int find_target_partition(int *devnum, enum uclass_id *uclass, * flags might not be set so we assume the A partition unless the B * partition is active. */ - if (!strncmp(info.name, "uefi", strlen("uefi"))) { + if (!strncmp(info.name, "uefi_", strlen("uefi_"))) { /* * If U-Boot was chainloaded somehow we can't be flashed to * the uefi partition @@ -263,7 +295,7 @@ static int find_target_partition(int *devnum, enum uclass_id *uclass, } /* Found no candidate partitions */ - return -1; + return -ENOENT; found: if (desc) { @@ -278,18 +310,7 @@ static int find_target_partition(int *devnum, enum uclass_id *uclass, return partnum; } -/** - * qcom_configure_capsule_updates() - Configure the DFU string for capsule updates - * - * U-Boot is flashed to the boot partition on Qualcomm boards. In most cases there - * are two boot partitions, boot_a and boot_b. As we don't currently support doing - * full A/B updates, we only support updating the currently active boot partition. - * - * So we need to find the current slot suffix and the associated boot partition. - * We do this by looking for the boot partition that has the 'active' flag set - * in the GPT partition vendor attribute bits. - */ -void qcom_configure_capsule_updates(void) +static void configure_raw_capsule_updates(void) { int ret = 0, partnum = -1, devnum; static char dfu_string[32] = { 0 }; @@ -297,7 +318,6 @@ void qcom_configure_capsule_updates(void) enum uclass_id dev_uclass; if (IS_ENABLED(CONFIG_SCSI)) { - /* Scan for SCSI devices */ ret = scsi_scan(false); if (ret) { debug("Failed to scan SCSI devices: %d\n", ret); @@ -339,7 +359,624 @@ void qcom_configure_capsule_updates(void) debug("Unsupported storage uclass: %d\n", dev_uclass); return; } - log_debug("DFU string: '%s'\n", dfu_string); + log_debug("RAW DFU string: '%s'\n", dfu_string); + + /* Set RAW configuration state */ update_info.dfu_string = dfu_string; + update_info.images = fw_images; + update_info.num_images = ARRAY_SIZE(fw_images); + + log_info("RAW capsule update configured (single partition: %s)\n", + target_part_type == TARGET_PART_UEFI ? "uefi" : + target_part_type == TARGET_PART_XBL ? "xbl" : "boot"); +} +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_RAW */ + +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_FIT +/* + * FIT Capsule Support - Implementation + */ + +static void parse_partition_name(const char *full_name, char *base_name, char *slot_suffix) +{ + char *underscore = strrchr(full_name, '_'); + + if (underscore && (strcmp(underscore, "_a") == 0 || strcmp(underscore, "_b") == 0)) { + /* Has A/B suffix */ + size_t base_len = underscore - full_name; + + strlcpy(base_name, full_name, base_len + 1); + strcpy(slot_suffix, underscore); + } else { + /* No A/B suffix */ + strcpy(base_name, full_name); + slot_suffix[0] = '\0'; + } +} + +static void parse_partition_info(struct qcom_partition_info *part, + struct disk_partition *info, + int lun, int partnum) +{ + struct part_slot_status *slot_status; + + strlcpy(part->name, info->name, sizeof(part->name)); + part->lun = lun; + part->partition_num = partnum; + + /* Parse slot status from GPT vendor attributes */ + slot_status = (struct part_slot_status *)&info->type_flags; + part->is_active = slot_status->active; + part->is_bootable = !slot_status->unbootable; + + /* Extract base name and slot suffix */ + parse_partition_name(part->name, part->base_name, part->slot_suffix); +} + +static struct partition_group *find_or_create_group(struct partition_group *groups, + int *group_count, + const char *base_name) +{ + /* Find existing group */ + for (int i = 0; i < *group_count; i++) { + if (strcmp(groups[i].base_name, base_name) == 0) + return &groups[i]; + } + + /* Create new group */ + if (*group_count >= MAX_PARTITION_GROUPS) { + log_err("Too many partition groups\n"); + return NULL; + } + + struct partition_group *new_group = &groups[*group_count]; + + strcpy(new_group->base_name, base_name); + new_group->a_slot = NULL; + new_group->b_slot = NULL; + new_group->no_slot = NULL; + + (*group_count)++; + return new_group; +} + +static struct qcom_partition_info *select_ab_target(struct qcom_partition_info *a_slot, + struct qcom_partition_info *b_slot) +{ + /* Priority: Active slot > A slot (fallback) */ + + if (a_slot && a_slot->is_active) { + log_debug("Selected %s (active)\n", a_slot->name); + return a_slot; + } + if (b_slot && b_slot->is_active) { + log_debug("Selected %s (active)\n", b_slot->name); + return b_slot; + } + + /* Both inactive - prefer A slot as fallback */ + struct qcom_partition_info *fallback = a_slot ? a_slot : b_slot; + + if (fallback) + log_debug("Selected %s (fallback - both inactive)\n", fallback->name); + return fallback; +} + +static int discover_all_partitions(struct qcom_partition_info **all_parts, int *all_count) +{ + struct udevice *dev; + struct blk_desc *desc; + struct qcom_partition_info *partition_list; + int partition_count = 0; + int max_partitions = 256; + bool have_ufs = false; + + /* Allocate partition list */ + partition_list = calloc(max_partitions, sizeof(struct qcom_partition_info)); + if (!partition_list) { + log_err("Failed to allocate partition list\n"); + return -ENOMEM; + } + + if (IS_ENABLED(CONFIG_SCSI)) { + if (scsi_scan(false)) { + log_debug("Failed to scan SCSI devices\n"); + free(partition_list); + return -EIO; + } + } + + /* + * Check to see if we have UFS storage, if so firmware MUST be on it and we can skip + * all non-UFS block devices + */ + uclass_foreach_dev_probe(UCLASS_UFS, dev) { + have_ufs = true; + break; + } + + /* Discover partitions with UFS-priority logic */ + uclass_foreach_dev_probe(UCLASS_BLK, dev) { + if (device_get_uclass_id(dev) != UCLASS_BLK) + continue; + + desc = dev_get_uclass_plat(dev); + if (!desc) + continue; + + if (have_ufs) { + if (device_get_uclass_id(dev->parent->parent) != UCLASS_UFS) + continue; + } else { + /* If we don't have UFS, look at eMMC (but skip SD cards) */ + if (desc->uclass_id == UCLASS_MMC) { + if (IS_ENABLED(CONFIG_MMC) && is_sd(desc)) { + log_debug("Skipped SD-Card (devnum %d)\n", desc->devnum); + continue; + } + } else if (desc->uclass_id != UCLASS_SCSI) { + /* Not MMC and not SCSI, skip it */ + continue; + } + } + + int lun = desc->devnum; + + /* Scan all partitions on this device */ + for (int partnum = 1; partnum <= MAX_PARTITIONS_TO_SCAN; partnum++) { + struct disk_partition info; + + if (part_get_info(desc, partnum, &info) != 0) + break; + + if (partition_count >= max_partitions) { + log_warning("Too many partitions discovered, truncating at %d\n", + max_partitions); + break; + } + + /* Parse and store partition info */ + parse_partition_info(&partition_list[partition_count], &info, lun, partnum); + partition_count++; + } + } + + *all_parts = partition_list; + *all_count = partition_count; + + log_debug("Discovered %d partitions across all %s devices\n", + partition_count, have_ufs ? "UFS" : "eMMC"); + return 0; +} + +static int select_target_partitions(struct qcom_partition_info *all_parts, int all_count, + struct qcom_partition_info **selected_parts, + int *selected_count) +{ + struct partition_group groups[MAX_PARTITION_GROUPS]; + struct qcom_partition_info *target_list; + int group_count = 0; + int target_count = 0; + + memset(groups, 0, sizeof(groups)); + + /* Allocate target list */ + target_list = calloc(all_count, sizeof(struct qcom_partition_info)); + if (!target_list) { + log_err("Failed to allocate target partition list\n"); + return -ENOMEM; + } + + /* Group partitions by base name */ + for (int i = 0; i < all_count; i++) { + struct qcom_partition_info *part = &all_parts[i]; + struct partition_group *group = find_or_create_group(groups, &group_count, + part->base_name); + + if (!group) { + log_err("Failed to create group for %s\n", part->base_name); + continue; + } + + if (strcmp(part->slot_suffix, "_a") == 0) { + if (!group->a_slot) { + group->a_slot = part; + } else { + log_info("Duplicate A-slot partition detected\n"); + log_info(" Keeping: %s (LUN %d, partition %d) [first discovered]\n", + group->a_slot->name, group->a_slot->lun, + group->a_slot->partition_num); + log_info(" Ignoring: %s (LUN %d, partition %d) [duplicate]\n", + part->name, part->lun, part->partition_num); + } + } else if (strcmp(part->slot_suffix, "_b") == 0) { + if (!group->b_slot) { + group->b_slot = part; + } else { + log_info("Duplicate B-slot partition detected\n"); + log_info(" Keeping: %s (LUN %d, partition %d) [first discovered]\n", + group->b_slot->name, group->b_slot->lun, + group->b_slot->partition_num); + log_info(" Ignoring: %s (LUN %d, partition %d) [duplicate]\n", + part->name, part->lun, part->partition_num); + } + } else { + if (!group->no_slot) { + group->no_slot = part; + } else { + log_info("Duplicate non-A/B partition detected\n"); + log_info(" Keeping: %s (LUN %d, partition %d) [first discovered]\n", + group->no_slot->name, group->no_slot->lun, + group->no_slot->partition_num); + log_info(" Ignoring: %s (LUN %d, partition %d) [duplicate]\n", + part->name, part->lun, part->partition_num); + } + } + } + + log_debug("Created %d partition groups for selection\n", group_count); + + /* Select target partition for each group */ + for (int i = 0; i < group_count; i++) { + struct partition_group *group = &groups[i]; + struct qcom_partition_info *target = NULL; + + if (group->no_slot) { + /* Non-A/B partition */ + target = group->no_slot; + log_debug("Group %s: selected non-A/B partition %s\n", + group->base_name, target->name); + } else { + /* A/B partition - apply selection logic */ + target = select_ab_target(group->a_slot, group->b_slot); + if (target) { + log_debug("Group %s: selected %s from A/B pair\n", + group->base_name, target->name); + } + } + + if (target) { + /* Copy selected partition to target list */ + memcpy(&target_list[target_count], target, + sizeof(struct qcom_partition_info)); + target_count++; + } else { + log_info("No target selected for group %s\n", group->base_name); + } + } + + *selected_parts = target_list; + *selected_count = target_count; + + log_debug("Selected %d target partitions from %d discovered\n", target_count, all_count); + return 0; +} + +static int group_partitions_by_lun(struct qcom_partition_info *selected_parts, int selected_count, + struct lun_group **lun_groups, int *group_count) +{ + struct lun_group *groups; + int max_groups = MAX_LUN_GROUPS; + int current_groups = 0; + + /* Allocate LUN groups array */ + groups = calloc(max_groups, sizeof(struct lun_group)); + if (!groups) { + log_err("Failed to allocate LUN groups array\n"); + return -ENOMEM; + } + + /* Group partitions by LUN */ + for (int i = 0; i < selected_count; i++) { + struct qcom_partition_info *part = &selected_parts[i]; + struct lun_group *target_group = NULL; + + /* Find existing group for this LUN */ + for (int j = 0; j < current_groups; j++) { + if (groups[j].lun_number == part->lun) { + target_group = &groups[j]; + break; + } + } + + /* Create new group if not found */ + if (!target_group) { + if (current_groups >= max_groups) { + log_err("Too many LUN groups (max %d)\n", max_groups); + free(groups); + return -ENOSPC; + } + + target_group = &groups[current_groups]; + target_group->lun_number = part->lun; + target_group->partition_count = 0; + current_groups++; + } + + /* Add partition to group */ + if (target_group->partition_count >= 64) { + log_err("Too many partitions in LUN %d (max 64)\n", part->lun); + free(groups); + return -ENOSPC; + } + + target_group->partitions[target_group->partition_count] = part; + target_group->partition_count++; + } + + /* Sort groups by LUN number for consistent output */ + for (int i = 0; i < current_groups - 1; i++) { + for (int j = i + 1; j < current_groups; j++) { + if (groups[i].lun_number > groups[j].lun_number) { + struct lun_group temp = groups[i]; + + groups[i] = groups[j]; + groups[j] = temp; + } + } + } + + *lun_groups = groups; + *group_count = current_groups; + + log_debug("Grouped %d partitions into %d LUN groups\n", selected_count, current_groups); + return 0; +} + +static int generate_dfu_string(struct qcom_partition_info *selected_parts, int selected_count, + char *dfu_string, size_t buffer_size) +{ + struct lun_group *lun_groups = NULL; + struct udevice *dev; + struct blk_desc *desc; + int group_count = 0; + char *dfu_ptr = dfu_string; + int remaining = buffer_size; + int ret; + bool is_mmc = false; + + /* Clear the buffer */ + memset(dfu_string, 0, buffer_size); + + /* Determine storage type by checking the first partition's device */ + if (selected_count > 0) { + uclass_foreach_dev_probe(UCLASS_BLK, dev) { + if (device_get_uclass_id(dev) != UCLASS_BLK) + continue; + + desc = dev_get_uclass_plat(dev); + if (!desc) + continue; + + if (desc->devnum == selected_parts[0].lun) { + if (desc->uclass_id == UCLASS_MMC) { + is_mmc = true; + log_debug("Detected MMC/eMMC storage for DFU string generation\n"); + } else if (desc->uclass_id == UCLASS_SCSI) { + is_mmc = false; + log_debug("Detected SCSI/UFS storage for DFU string generation\n"); + } + break; + } + } + } + + /* Group partitions by LUN/device */ + ret = group_partitions_by_lun(selected_parts, selected_count, &lun_groups, &group_count); + if (ret != 0) { + log_err("Failed to group partitions by LUN: %d\n", ret); + return ret; + } + + /* Generate DFU string with appropriate format for storage type */ + for (int i = 0; i < group_count; i++) { + struct lun_group *group = &lun_groups[i]; + int written; + + /* Add device group separator for non-first groups */ + if (i > 0) { + written = snprintf(dfu_ptr, remaining, "&"); + dfu_ptr += written; + remaining -= written; + } + + if (is_mmc) { + /* MMC format: "mmc X=" */ + written = snprintf(dfu_ptr, remaining, "mmc %d=", group->lun_number); + } else { + /* SCSI format: "scsi X=" */ + written = snprintf(dfu_ptr, remaining, "scsi %d=", group->lun_number); + } + dfu_ptr += written; + remaining -= written; + + /* Add partitions within this device group */ + for (int j = 0; j < group->partition_count; j++) { + struct qcom_partition_info *part = group->partitions[j]; + + /* Add partition separator for non-first partitions in group */ + if (j > 0) { + written = snprintf(dfu_ptr, remaining, ";"); + dfu_ptr += written; + remaining -= written; + } + + if (is_mmc) { + /* MMC format: "partition_name part dev_num partition_num" */ + written = snprintf(dfu_ptr, remaining, "%s part %d %d", + part->name, group->lun_number, part->partition_num); + } else { + /* SCSI format: "partition_name part partition_num" */ + written = snprintf(dfu_ptr, remaining, "%s part %d", + part->name, part->partition_num); + } + dfu_ptr += written; + remaining -= written; + + if (remaining <= 10) { + log_err("DFU string buffer overflow at partition %s\n", part->name); + free(lun_groups); + return -ENOSPC; + } + } + } + + /* Clean up */ + free(lun_groups); + + log_debug("Generated %s DFU string (%zu chars): %s\n", + is_mmc ? "MMC" : "SCSI", strlen(dfu_string), dfu_string); + return 0; +} + +/** + * get_board_fit_capsule_guid - Get board-specific FIT capsule GUID + * + * Detect the board type from device tree and return the appropriate GUID + * for FIT capsule updates. + * + * @guid: Pointer to store the GUID + * Return: 0 on success, negative error code on failure + */ +static int get_board_fit_capsule_guid(efi_guid_t *guid) +{ + const char *compatible; + + if (!guid) + return -EINVAL; + + compatible = ofnode_read_string(ofnode_root(), "compatible"); + if (!compatible) { + log_err("Failed to read board compatible string\n"); + return -ENODEV; + } + + /* Check for QCS615 or Talos */ + if (strstr(compatible, "qcs615") || strstr(compatible, "talos")) { + log_debug("Detected QCS615/Talos board\n"); + *guid = (efi_guid_t)QCOM_QCS615_FIT_CAPSULE_GUID; + return 0; + } + + /* Check for QCS6490 */ + if (strstr(compatible, "qcs6490")) { + log_debug("Detected QCS6490 board\n"); + *guid = (efi_guid_t)QCOM_QCS6490_FIT_CAPSULE_GUID; + return 0; + } + + /* Check for Lemans */ + if (strstr(compatible, "lemans") || strstr(compatible, "qcs9100")) { + log_debug("Detected Lemans board\n"); + *guid = (efi_guid_t)QCOM_LEMANS_FIT_CAPSULE_GUID; + return 0; + } + + log_err("Unsupported board for capsule updates: %s\n", compatible); + return -EINVAL; +} + +/* + * For creating FIT-based capsule images from FvUpdate.xml files, see: + * - Tool: tools/fvupdate_to_fit.py + * - Documentation: doc/develop/fvupdate_to_fit.rst + */ +static void configure_fit_capsule_updates(void) +{ + struct qcom_partition_info *all_partitions = NULL; + struct qcom_partition_info *selected_partitions = NULL; + int all_count = 0, selected_count = 0; + static char dfu_string[MAX_DFU_STRING_SIZE] = { 0 }; + static struct efi_fw_image single_fw_image; + efi_guid_t board_guid; + int ret; + + /* Step 1: Discover all partitions across all SCSI LUNs */ + ret = discover_all_partitions(&all_partitions, &all_count); + if (ret != 0) { + log_err("Failed to discover SCSI partitions: %d\n", ret); + return; + } + + if (all_count == 0) { + log_warning("No SCSI partitions discovered\n"); + goto cleanup; + } + + /* Step 2: Apply A/B selection logic to choose target partitions */ + ret = select_target_partitions(all_partitions, all_count, + &selected_partitions, &selected_count); + if (ret != 0) { + log_err("Failed to select target partitions: %d\n", ret); + goto cleanup; + } + + if (selected_count == 0) { + log_warning("No target partitions selected\n"); + goto cleanup; + } + + /* Step 3: Generate DFU string from selected partitions */ + ret = generate_dfu_string(selected_partitions, selected_count, + dfu_string, sizeof(dfu_string)); + if (ret != 0) { + log_err("Failed to generate DFU string: %d\n", ret); + goto cleanup; + } + + /* Step 4: Get board-specific GUID */ + ret = get_board_fit_capsule_guid(&board_guid); + if (ret != 0) { + log_err("Failed to get board-specific GUID: %d\n", ret); + goto cleanup; + } + + /* Step 5: Create SINGLE fw_image entry for ESRT */ + memset(&single_fw_image, 0, sizeof(single_fw_image)); + single_fw_image.fw_name = QCOM_FIT_CAPSULE_NAME; /* Same name for all boards */ + single_fw_image.image_index = 1; + single_fw_image.image_type_id = board_guid; + + /* Step 6: Configure update_info */ + update_info.dfu_string = dfu_string; + update_info.images = &single_fw_image; + update_info.num_images = 1; + + log_info("FIT capsule configured successfully:\n"); + log_info(" Name: %ls\n", QCOM_FIT_CAPSULE_NAME); + log_info(" GUID: %pUl\n", &board_guid); + log_info(" Partitions in DFU string: %d\n", selected_count); + log_info(" ESRT entries: 1 (single entry for all partitions)\n"); + +cleanup: + free(all_partitions); + free(selected_partitions); +} +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_FIT */ + +/** + * qcom_configure_capsule_updates() - Configure capsule updates + * + * Configures either FIT or RAW capsule updates based on compile-time configuration. + */ +void qcom_configure_capsule_updates(void) +{ +#if defined(CONFIG_EFI_CAPSULE_FIRMWARE_FIT) + log_info("Configuring FIT capsule updates\n"); + configure_fit_capsule_updates(); +#elif defined(CONFIG_EFI_CAPSULE_FIRMWARE_RAW) + log_info("Configuring RAW capsule updates\n"); + configure_raw_capsule_updates(); +#else + log_warning("No capsule firmware configuration enabled\n"); +#endif + + /* Final state logging */ + if (update_info.dfu_string) { + log_info("Capsule update configured successfully with %d image(s)\n", + update_info.num_images); + } else { + log_warning("Capsule update configuration failed\n"); + } } diff --git a/arch/arm/mach-snapdragon/qcom-priv.h b/arch/arm/mach-snapdragon/qcom-priv.h index 39dc8fcc76ad..c06e22fd2390 100644 --- a/arch/arm/mach-snapdragon/qcom-priv.h +++ b/arch/arm/mach-snapdragon/qcom-priv.h @@ -33,6 +33,29 @@ enum qcom_memmap_source { extern enum qcom_memmap_source qcom_memmap_source; #if IS_ENABLED(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) +/* + * Capsule Update GUIDs for FIT capsules + * Each board has a unique GUID to prevent cross-board flashing + */ + +/* QCS615 FIT Capsule GUID: 9fd379d2-670e-4bb3-86a1-40497e6e17b0 */ +#define QCOM_QCS615_FIT_CAPSULE_GUID \ + EFI_GUID(0x9fd379d2, 0x670e, 0x4bb3, 0x86, 0xa1, \ + 0x40, 0x49, 0x7e, 0x6e, 0x17, 0xb0) + +/* QCS6490 FIT Capsule GUID: 6f25bfd2-a165-468b-980f-ac51a0a45c52 */ +#define QCOM_QCS6490_FIT_CAPSULE_GUID \ + EFI_GUID(0x6f25bfd2, 0xa165, 0x468b, 0x98, 0x0f, \ + 0xac, 0x51, 0xa0, 0xa4, 0x5c, 0x52) + +/* Lemans FIT Capsule GUID: 78462415-6133-431c-9fae-48f2bafd5c71 */ +#define QCOM_LEMANS_FIT_CAPSULE_GUID \ + EFI_GUID(0x78462415, 0x6133, 0x431c, 0x9f, 0xae, \ + 0x48, 0xf2, 0xba, 0xfd, 0x5c, 0x71) + +/* Common name for FIT capsule (same for all boards) */ +#define QCOM_FIT_CAPSULE_NAME u"QCOM_FIT_CAPSULE" + void qcom_configure_capsule_updates(void); #else void qcom_configure_capsule_updates(void) {} From ba2d992013ca736313b395667087a8eb51dcbde7 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 13 May 2026 17:54:43 +0530 Subject: [PATCH 2/7] efi_loader: firmware: track last attempt status for ESRT reporting Add proper tracking of last attempt version and status for firmware updates to enable accurate ESRT (EFI System Resource Table) reporting. This allows the OS to query firmware update history and status. Introduce helper functions to manage last attempt information and map internal error codes to UEFI-compliant status codes. The fw_version is now only updated on successful updates, preserving the previous version on failures to maintain accurate ESRT state. Update last attempt information at all failure points in the update flow, including verification failures, resource allocation errors, and device errors. This ensures the OS always has visibility into the most recent update attempt and its outcome. Signed-off-by: Balaji Selvanathan --- lib/efi_loader/efi_firmware.c | 129 +++++++++++++++++++++++++++++++--- 1 file changed, 118 insertions(+), 11 deletions(-) diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c index b41969c70fde..dff8de8cf8f5 100644 --- a/lib/efi_loader/efi_firmware.c +++ b/lib/efi_loader/efi_firmware.c @@ -52,9 +52,9 @@ struct fmp_payload_header { */ struct fmp_state { u32 fw_version; - u32 lowest_supported_version; /* not used */ - u32 last_attempt_version; /* not used */ - u32 last_attempt_status; /* not used */ + u32 lowest_supported_version; /* not used - read from DTB for security */ + u32 last_attempt_version; /* used for esrt tracking */ + u32 last_attempt_status; /* used for esrt tracking */ }; /** @@ -190,6 +190,67 @@ static void efi_firmware_get_lsv_from_dtb(u8 image_index, } } +/** + * efi_firmware_set_last_attempt - set last attempt information + * @state: Pointer to fmp state + * @attempt_version: Version that was attempted + * @attempt_status: Status of the attempt + * + * Set the last attempt version and status in the fmp_state structure. + */ +static void efi_firmware_set_last_attempt(struct fmp_state *state, + u32 attempt_version, + u32 attempt_status) +{ + state->last_attempt_version = attempt_version; + state->last_attempt_status = attempt_status; +} + +/** + * efi_firmware_get_last_attempt - get last attempt information + * @state: Pointer to fmp state + * @attempt_version: Pointer to store last attempt version + * @attempt_status: Pointer to store last attempt status + * + * Retrieve the last attempt version and status from fmp_state structure. + */ +static void efi_firmware_get_last_attempt(const struct fmp_state *state, + u32 *attempt_version, + u32 *attempt_status) +{ + if (attempt_version) + *attempt_version = state->last_attempt_version; + if (attempt_status) + *attempt_status = state->last_attempt_status; +} + +/** + * efi_firmware_map_error_to_status - map internal errors to UEFI status codes + * @error: Internal error code + * + * Map U-Boot internal error codes to UEFI-compliant last attempt status codes. + * + * Return: UEFI last attempt status code + */ +static u32 efi_firmware_map_error_to_status(efi_status_t error) +{ + switch (error) { + case EFI_SUCCESS: + return LAST_ATTEMPT_STATUS_SUCCESS; + case EFI_OUT_OF_RESOURCES: + return LAST_ATTEMPT_STATUS_ERROR_INSUFFICIENT_RESOURCES; + case EFI_INVALID_PARAMETER: + return LAST_ATTEMPT_STATUS_ERROR_INCORRECT_VERSION; + case EFI_SECURITY_VIOLATION: + return LAST_ATTEMPT_STATUS_ERROR_AUTH_ERROR; + case EFI_UNSUPPORTED: + return LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT; + case EFI_DEVICE_ERROR: + default: + return LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL; + } +} + /** * efi_firmware_fill_version_info - fill the version information * @image_info: Image information @@ -237,8 +298,15 @@ void efi_firmware_fill_version_info(struct efi_firmware_image_descriptor *image_ ret = efi_get_variable_int(varname, &fw_array->image_type_id, NULL, &size, var_state, NULL); - if (ret == EFI_SUCCESS && expected_size == size) + if (ret == EFI_SUCCESS && expected_size == size) { image_info->version = var_state[active_index].fw_version; + image_info->last_attempt_version = var_state[active_index].last_attempt_version; + image_info->last_attempt_status = var_state[active_index].last_attempt_status; + } else { + /* Default values if no previous state exists */ + image_info->last_attempt_version = 0; + image_info->last_attempt_status = LAST_ATTEMPT_STATUS_SUCCESS; + } free(var_state); } @@ -427,6 +495,10 @@ efi_status_t efi_firmware_capsule_authenticate(const void **p_image, * @image_index: image index * * Update the FmpStateXXXX variable with the firmware update state. + * On successful update (last_attempt_status == LAST_ATTEMPT_STATUS_SUCCESS), + * updates fw_version to the new version. + * On failed update, preserves the old fw_version. + * Always updates last_attempt_version and last_attempt_status. * * Return: status code */ @@ -471,11 +543,19 @@ efi_status_t efi_firmware_set_fmp_state_var(struct fmp_state *state, u8 image_in memset(var_state, 0, num_banks * sizeof(*var_state)); /* - * Only the fw_version is set here. + * Set fw_version and last attempt information. * lowest_supported_version in FmpState variable is ignored since * it can be tampered if the file based EFI variable storage is used. + * + * Only update fw_version if the update succeeded. + * On failure, preserve the old fw_version to maintain accurate ESRT state. */ - var_state[update_bank].fw_version = state->fw_version; + if (state->last_attempt_status == LAST_ATTEMPT_STATUS_SUCCESS) + var_state[update_bank].fw_version = state->fw_version; + /* else: keep existing fw_version (don't update on failure) */ + + var_state[update_bank].last_attempt_version = state->last_attempt_version; + var_state[update_bank].last_attempt_status = state->last_attempt_status; size = num_banks * sizeof(*var_state); ret = efi_set_variable_int(varname, image_type_id, @@ -661,21 +741,40 @@ efi_status_t EFIAPI efi_firmware_fit_set_image( status = efi_firmware_verify_image(&image, &image_size, image_index, &state); - if (status != EFI_SUCCESS) + if (status != EFI_SUCCESS) { + /* Set last attempt information for failed verification */ + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); return EFI_EXIT(status); + } + + /* Set last attempt version before starting the update */ + efi_firmware_set_last_attempt(&state, state.fw_version, + LAST_ATTEMPT_STATUS_SUCCESS); orig_dfu_env = env_get("dfu_alt_info"); + if (orig_dfu_env) { orig_dfu_env = strdup(orig_dfu_env); if (!orig_dfu_env) { log_err("strdup() failed!\n"); - return EFI_EXIT(EFI_OUT_OF_RESOURCES); + status = EFI_OUT_OF_RESOURCES; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); } } + if (env_set("dfu_alt_info", update_info.dfu_string)) { log_err("Unable to set env variable \"dfu_alt_info\"!\n"); free(orig_dfu_env); - return EFI_EXIT(EFI_DEVICE_ERROR); + status = EFI_DEVICE_ERROR; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); } /* Make sure the update fitImage is properly aligned to 8-bytes */ @@ -698,9 +797,17 @@ efi_status_t EFIAPI efi_firmware_fit_set_image( free(orig_dfu_env); - if (ret) - return EFI_EXIT(EFI_DEVICE_ERROR); + if (ret) { + status = EFI_DEVICE_ERROR; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); + } + /* Update successful - set success status */ + efi_firmware_set_last_attempt(&state, state.fw_version, + LAST_ATTEMPT_STATUS_SUCCESS); efi_firmware_set_fmp_state_var(&state, image_index); return EFI_EXIT(EFI_SUCCESS); From c8f7dc6e221238de9e44b8a891f91cb1c0088912 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 13 May 2026 17:58:50 +0530 Subject: [PATCH 3/7] efi_loader: firmware: add last attempt tracking for RAW capsules Add last attempt status tracking to RAW capsule updates to enable proper ESRT reporting. This allows the OS to query firmware update history and determine the outcome of previous update attempts. Track last attempt information at all failure points in the update flows. Update the FMP state variable with both success and failure status to maintain accurate firmware state information. Signed-off-by: Balaji Selvanathan --- lib/efi_loader/efi_firmware.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c index dff8de8cf8f5..c30283cc9e4c 100644 --- a/lib/efi_loader/efi_firmware.c +++ b/lib/efi_loader/efi_firmware.c @@ -869,8 +869,17 @@ efi_status_t EFIAPI efi_firmware_raw_set_image( status = efi_firmware_verify_image(&image, &image_size, image_index, &state); - if (status != EFI_SUCCESS) + if (status != EFI_SUCCESS) { + /* Set last attempt information for failed verification */ + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); return EFI_EXIT(status); + } + + /* Set last attempt version before starting the update */ + efi_firmware_set_last_attempt(&state, state.fw_version, + LAST_ATTEMPT_STATUS_SUCCESS); /* * dfu_alt_num is assigned from 0 while image_index starts from 1. @@ -895,13 +904,21 @@ efi_status_t EFIAPI efi_firmware_raw_set_image( orig_dfu_env = strdup(orig_dfu_env); if (!orig_dfu_env) { log_err("strdup() failed!\n"); - return EFI_EXIT(EFI_OUT_OF_RESOURCES); + status = EFI_OUT_OF_RESOURCES; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); } } if (env_set("dfu_alt_info", update_info.dfu_string)) { log_err("Unable to set env variable \"dfu_alt_info\"!\n"); free(orig_dfu_env); - return EFI_EXIT(EFI_DEVICE_ERROR); + status = EFI_DEVICE_ERROR; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); } ret = dfu_write_by_alt(dfu_alt_num, (void *)image, image_size, @@ -912,9 +929,17 @@ efi_status_t EFIAPI efi_firmware_raw_set_image( free(orig_dfu_env); - if (ret) - return EFI_EXIT(EFI_DEVICE_ERROR); + if (ret) { + status = EFI_DEVICE_ERROR; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); + } + /* Update successful - set success status */ + efi_firmware_set_last_attempt(&state, state.fw_version, + LAST_ATTEMPT_STATUS_SUCCESS); efi_firmware_set_fmp_state_var(&state, image_index); return EFI_EXIT(EFI_SUCCESS); From c0ff6d6fcd743b146109cac363789a0f41a98917 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 13 May 2026 18:02:24 +0530 Subject: [PATCH 4/7] efi_loader: Extract FW version even on authentication failure When capsule authentication fails, update the image pointers to skip past the authentication wrapper before returning. This allows the caller to access the FMP payload header and extract firmware version information even when authentication check fails. Additionally, call efi_firmware_get_fw_version() in the error path of efi_firmware_verify_image() to ensure version information is retrieved regardless of authentication status. This corrects error reporting by providing version details for ESRT, even for rejected capsules. Signed-off-by: Balaji Selvanathan --- lib/efi_loader/efi_firmware.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c index c30283cc9e4c..ce6ca04ac691 100644 --- a/lib/efi_loader/efi_firmware.c +++ b/lib/efi_loader/efi_firmware.c @@ -471,6 +471,15 @@ efi_status_t efi_firmware_capsule_authenticate(const void **p_image, if (status == EFI_SECURITY_VIOLATION) { printf("Capsule authentication check failed. Aborting update\n"); + /* + * Even though authentication failed, update the pointers + * to skip past the auth wrapper so the caller can read + * the FMP payload header for version information. + */ + image = capsule_payload; + image_size = capsule_payload_size; + *p_image = image; + *p_image_size = image_size; return status; } else if (status != EFI_SUCCESS) { return status; @@ -620,11 +629,10 @@ efi_status_t efi_firmware_verify_image(const void **p_image, efi_guid_t *image_type_id; ret = efi_firmware_capsule_authenticate(p_image, p_image_size); + efi_firmware_get_fw_version(p_image, p_image_size, state); if (ret != EFI_SUCCESS) return ret; - efi_firmware_get_fw_version(p_image, p_image_size, state); - image_type_id = efi_firmware_get_image_type_id(image_index); if (!image_type_id) return EFI_INVALID_PARAMETER; From 94792effb3e28b410ad227f15e008d7cc99f7bf9 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 13 May 2026 18:03:33 +0530 Subject: [PATCH 5/7] configs: qcom: enable FIT capsule support by default Switch from CONFIG_EFI_CAPSULE_FIRMWARE_RAW to CONFIG_EFI_CAPSULE_FIRMWARE_FIT in the default Qualcomm configuration. Signed-off-by: Balaji Selvanathan --- configs/qcom_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/qcom_defconfig b/configs/qcom_defconfig index 2f3c8f0cee51..9826161c562d 100644 --- a/configs/qcom_defconfig +++ b/configs/qcom_defconfig @@ -9,7 +9,7 @@ CONFIG_SYS_LOAD_ADDR=0xA0000000 # CONFIG_EFI_HAVE_RUNTIME_RESET is not set CONFIG_EFI_RUNTIME_UPDATE_CAPSULE=y CONFIG_EFI_CAPSULE_ON_DISK=y -CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y +CONFIG_EFI_CAPSULE_FIRMWARE_FIT=y CONFIG_BUTTON_CMD=y CONFIG_FIT=y CONFIG_FIT_VERBOSE=y From e7d46e1d2d1be6e15ceea283398c3c0a9a7a7244 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 13 May 2026 18:05:15 +0530 Subject: [PATCH 6/7] tools: add fvupdate_to_fit.py for FIT capsule generation Add Python tool to convert Qualcomm FvUpdate.xml files into FIT capsule images compatible with U-Boot capsule update system. The tool provides end-to-end conversion from XML firmware manifests to deployable capsule files. The converter parses FvUpdate.xml UPDATE operations, validates binary files, generates ITS descriptors, compiles FIT images using mkimage, and creates final capsules using mkeficapsule. It supports board-specific GUIDs, firmware versioning, and capsule signing. Signed-off-by: Balaji Selvanathan --- tools/fvupdate_to_fit.py | 605 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 605 insertions(+) create mode 100755 tools/fvupdate_to_fit.py diff --git a/tools/fvupdate_to_fit.py b/tools/fvupdate_to_fit.py new file mode 100755 index 000000000000..bfd9cc631d79 --- /dev/null +++ b/tools/fvupdate_to_fit.py @@ -0,0 +1,605 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +""" +FvUpdate.xml to Capsule-Compatible FIT Image and Capsule Converter + +Complete end-to-end converter that: +1. Processes FvUpdate.xml files +2. Creates FIT images compatible with Qualcomm U-Boot capsule update system +3. Generates GUIDs using mkeficapsule guidgen +4. Creates final capsule files ready for deployment + +Usage: ./fvupdate_to_fit.py +""" + +import xml.etree.ElementTree as ET +import os +import sys +import subprocess +import shutil +import re +from pathlib import Path +import argparse + +class FvUpdateToFitConverter: + """ + Complete FvUpdate.xml to Capsule converter. + + This converter provides end-to-end functionality: + 1. Converts FvUpdate.xml to FIT images + 2. Generates GUIDs using mkeficapsule guidgen + 3. Creates final capsule files + 4. Auto-installs missing tools + 5. Ensures compatibility with Qualcomm U-Boot capsule update system + """ + + def __init__(self, xml_path, mkeficapsule_path=None): + self.xml_path = Path(xml_path) + self.mkeficapsule_path = mkeficapsule_path or "mkeficapsule" + self.base_dir = self.xml_path.parent + self.images_dir = self.base_dir / "Images" + self.fw_entries = [] + + def parse_xml(self): + """ + Parse FvUpdate.xml and extract UPDATE operations only. + + Only processes FwEntry elements with Operation="UPDATE" to ensure + compatibility with capsule update requirements. + """ + print(f"Parsing XML file: {self.xml_path}") + + try: + tree = ET.parse(self.xml_path) + root = tree.getroot() + except ET.ParseError as e: + raise ValueError(f"Invalid XML format: {e}") + except FileNotFoundError: + raise FileNotFoundError(f"XML file not found: {self.xml_path}") + + # Find all FwEntry elements + fw_entries = root.findall('.//FwEntry') + if not fw_entries: + raise ValueError("No FwEntry elements found in XML") + + print(f"Found {len(fw_entries)} FwEntry elements") + + for fw_entry in fw_entries: + # Check operation type + operation_elem = fw_entry.find('Operation') + if operation_elem is None: + print("Warning: FwEntry missing Operation element, skipping") + continue + + operation = operation_elem.text + if operation != "UPDATE": + print(f"Skipping FwEntry with operation: {operation}") + continue + + # Extract required elements + try: + dest = fw_entry.find('.//Dest') + if dest is None: + raise ValueError("FwEntry missing Dest element") + + partition_name_elem = dest.find('PartitionName') + if partition_name_elem is None: + raise ValueError("Dest missing PartitionName element") + partition_name = partition_name_elem.text + + binary_file_elem = fw_entry.find('InputBinary') + if binary_file_elem is None: + raise ValueError("FwEntry missing InputBinary element") + binary_file = binary_file_elem.text + + # Validate partition name + if not partition_name or not partition_name.strip(): + raise ValueError("Empty partition name") + + # Validate binary file name + if not binary_file or not binary_file.strip(): + raise ValueError("Empty binary file name") + + self.fw_entries.append({ + 'partition_name': partition_name.strip(), + 'binary_file': binary_file.strip(), + 'binary_path': self.images_dir / binary_file.strip() + }) + + print(f" Added: {partition_name} -> {binary_file}") + + except ValueError as e: + raise ValueError(f"Invalid FwEntry: {e}") + + if not self.fw_entries: + raise ValueError("No valid UPDATE FwEntry elements found") + + print(f"Successfully parsed {len(self.fw_entries)} UPDATE entries") + + def validate_files(self): + """ + Validate all binary files exist. + + Fails completely if any binary file is missing, as requested. + """ + print("Validating binary files...") + + if not self.images_dir.exists(): + raise FileNotFoundError(f"Images directory not found: {self.images_dir}") + + missing_files = [] + for entry in self.fw_entries: + if not entry['binary_path'].exists(): + missing_files.append(str(entry['binary_path'])) + else: + # Check file size + size = entry['binary_path'].stat().st_size + print(f" {entry['binary_file']}: {size} bytes") + + if missing_files: + raise FileNotFoundError(f"Missing binary files: {missing_files}") + + print("All binary files validated successfully") + + def generate_its(self, its_path): + """ + Generate capsule-compatible ITS file. + + Creates ITS content that works with the Qualcomm capsule update flow: + - Node names match partition names for DFU mapping + - All images marked as "firmware" type + - SHA256 hashes for integrity verification + - Single configuration referencing all firmware + """ + print(f"Generating ITS file: {its_path}") + + its_content = self._build_its_content() + + try: + with open(its_path, 'w') as f: + f.write(its_content) + except IOError as e: + raise IOError(f"Failed to write ITS file: {e}") + + print(f"ITS file generated successfully") + + def _build_its_content(self): + """ + Build ITS content compatible with capsule update flow. + + Format ensures compatibility with: + - qcom_configure_capsule_updates() partition discovery + - fit_update() processing requirements + - fit_image_verify() hash validation + - dfu_write_by_name() partition targeting + """ + images_section = "" + firmware_list = [] + + for entry in self.fw_entries: + # Node name format: partition_name (without @1 to avoid reg issues) + # This MUST match the partition name discovered by U-Boot + node_name = entry['partition_name'] + firmware_list.append(f'"{node_name}"') + + # Generate image node without reg properties to avoid DTC warnings + images_section += f''' + {node_name} {{ + description = "{entry['partition_name']} Firmware"; + data = /incbin/("Images/{entry['binary_file']}"); + type = "firmware"; /* Required for fit_update() */ + arch = "arm64"; /* Target architecture */ + compression = "none"; /* No compression */ + load = <0x00000000>; /* Required by fit_image_get_load() */ + + hash-1 {{ + algo = "sha256"; /* For fit_image_verify() */ + }}; + }};''' + + # Build firmware reference list for configuration + firmware_refs = ", ".join(firmware_list) + + # Complete ITS content + return f'''/dts-v1/; + +/ {{ + description = "Qualcomm Firmware Update Package"; + + images {{{images_section} + }}; + + configurations {{ + default = "config-1"; + config-1 {{ + description = "Qualcomm Multi-Partition Update"; + firmware = {firmware_refs}; + }}; + }}; +}}; +''' + + def compile_fit(self, its_path, fit_path): + """ + Compile ITS to FIT using mkimage. + + Uses U-Boot's mkimage tool to create the final FIT image. + """ + print(f"Compiling FIT image: {fit_path}") + + # Check if mkimage is available + try: + subprocess.run(["mkimage", "-V"], capture_output=True, check=True) + except (subprocess.CalledProcessError, FileNotFoundError): + raise RuntimeError("mkimage tool not found. Please install U-Boot tools.") + + # Compile ITS to FIT + cmd = ["mkimage", "-f", str(its_path), str(fit_path)] + print(f"Running: {' '.join(cmd)}") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, + cwd=self.base_dir, check=True) + + if result.stdout: + print("mkimage output:") + print(result.stdout) + + except subprocess.CalledProcessError as e: + error_msg = f"mkimage compilation failed (exit code {e.returncode})" + if e.stderr: + error_msg += f": {e.stderr}" + raise RuntimeError(error_msg) + + # Verify output file was created + if not fit_path.exists(): + raise RuntimeError("FIT image was not created") + + fit_size = fit_path.stat().st_size + print(f"FIT image compiled successfully: {fit_size} bytes") + + def check_required_tools(self): + """Check if required tools are available, auto-install if missing""" + required_tools = { + 'mkimage': 'u-boot-tools', + 'mkeficapsule': 'u-boot-tools' + } + + missing_tools = [] + for tool, package in required_tools.items(): + if not self.tool_available(tool): + missing_tools.append((tool, package)) + + if missing_tools: + print("Installing missing tools...") + packages_to_install = set() + for tool, package in missing_tools: + packages_to_install.add(package) + + for package in packages_to_install: + self.install_package(package) + print(f" Installed {package} ✓") + + def tool_available(self, tool_name): + """Check if a tool is available in PATH""" + return shutil.which(tool_name) is not None + + def install_package(self, package): + """Install package using system package manager""" + # Detect package manager and install + if shutil.which('apt-get'): + cmd = ['sudo', 'apt-get', 'install', '-y', package] + elif shutil.which('yum'): + cmd = ['sudo', 'yum', 'install', '-y', package] + elif shutil.which('dnf'): + cmd = ['sudo', 'dnf', 'install', '-y', package] + elif shutil.which('pacman'): + cmd = ['sudo', 'pacman', '-S', '--noconfirm', package] + else: + raise RuntimeError(f"Cannot auto-install {package}. Please install manually.") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + except subprocess.CalledProcessError as e: + raise RuntimeError(f"Failed to install {package}: {e.stderr}") + + def get_primary_partition_name(self): + """Get partition name from first FwEntry with Operation='UPDATE'""" + if not self.fw_entries: + raise ValueError("No UPDATE FwEntry found in XML") + + # Return first UPDATE partition found + primary_partition = self.fw_entries[0]['partition_name'] + print(f"Primary partition selected: {primary_partition} (first UPDATE entry)") + return primary_partition + + def is_valid_guid(self, guid): + """Validate GUID format""" + # GUID format: 8-4-4-4-12 hex digits + guid_pattern = r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' + return re.match(guid_pattern, guid) is not None + + def create_capsule(self, fit_path, guid, capsule_path, fw_version=None, monotonic_count=None, private_key=None, certificate=None): + """Create capsule using mkeficapsule with optional firmware version and signing""" + print(f"Creating capsule: {capsule_path}") + + cmd = [ + self.mkeficapsule_path, + '--guid', guid, + '--index', '1', + ] + + # Add firmware version if provided + if fw_version is not None: + version_parts = fw_version.split('.') + try: + major = int(version_parts[2]) + minor = int(version_parts[3]) + encoded_version = (major << 16) | minor + cmd.extend(['--fw-version', str(encoded_version)]) + print(f" Encoded Firmware version: {encoded_version} (from {fw_version})") + except (ValueError, IndexError): + raise ValueError(f"Invalid version string: {fw_version}") + + # Add signing parameters if provided + if private_key and certificate and monotonic_count is not None: + print("Signing capsule...") + cmd.extend([ + '--monotonic-count', str(monotonic_count), + '--private-key', str(private_key), + '--certificate', str(certificate), + ]) + elif any([private_key, certificate, monotonic_count is not None]): + raise ValueError("All signing parameters (--private-key, --certificate, --monotonic-count) must be provided together.") + + cmd.extend([str(fit_path), str(capsule_path)]) + + print(f" Command: {' '.join(cmd)}") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, + check=True, timeout=60) + + if result.stdout: + print("mkeficapsule output:") + print(result.stdout) + + except subprocess.CalledProcessError as e: + error_msg = f"Capsule creation failed (exit code {e.returncode})" + if e.stderr: + error_msg += f": {e.stderr}" + raise RuntimeError(error_msg) + except subprocess.TimeoutExpired: + raise RuntimeError("Capsule creation timed out") + + # Verify output file was created + if not capsule_path.exists(): + raise RuntimeError("Capsule file was not created") + + capsule_size = capsule_path.stat().st_size + print(f"Capsule created successfully: {capsule_size} bytes ✓") + return capsule_size + + def convert_complete(self, fit_output_name="system.fit", capsule_output_name="firmware.capsule", fw_version=None, guid=None, monotonic_count=None, private_key=None, certificate=None): + """ + Complete conversion workflow: XML → FIT → Capsule + + Performs the full end-to-end conversion: + 1. Check and install required tools + 2. XML parsing and validation + 3. Binary file validation + 4. ITS generation + 5. FIT compilation + 6. Capsule creation with provided GUID and optional firmware version + 7. Keep all intermediate files + """ + print(f"Converting {self.xml_path} to capsule...") + print("=" * 60) + + # Step 1: Check and install required tools + self.check_required_tools() + + # Step 2-5: Generate FIT + self.parse_xml() + self.validate_files() + + # Generate file paths + its_path = self.base_dir / "system.its" + fit_path = self.base_dir / fit_output_name + capsule_path = self.base_dir / capsule_output_name + + self.generate_its(its_path) + self.compile_fit(its_path, fit_path) + + # Step 6: Create capsule with provided GUID and optional firmware version + if not guid: + raise ValueError("GUID is required for capsule creation. Use --guid option.") + + print(f"Using GUID: {guid}") + capsule_size = self.create_capsule(fit_path, guid, capsule_path, fw_version, monotonic_count, private_key, certificate) + + # Step 7: Summary (keep all files) + print("=" * 60) + print("SUCCESS: Complete capsule workflow completed!") + print() + print("Files created:") + print(f" ITS file: {its_path}") + print(f" FIT file: {fit_path}") + print(f" Capsule file: {capsule_path} ({capsule_size / (1024*1024):.1f} MB)") + print() + print(f"Capsule GUID: {guid}") + print() + print("Ready for deployment:") + print(" 1. Copy firmware.capsule to boot partition") + print(" 2. In U-Boot: fatload mmc 0:1 $loadaddr firmware.capsule") + print(" 3. In U-Boot: efidebug capsule update $loadaddr") + + return capsule_path + + def convert(self, output_name="system.fit"): + """ + Main conversion workflow. + + Converts FvUpdate.xml to capsule-compatible FIT image through: + 1. XML parsing and validation + 2. Binary file validation + 3. ITS generation + 4. FIT compilation + """ + print(f"Converting {self.xml_path} to FIT image...") + print("=" * 60) + + # Parse and validate XML + self.parse_xml() + + # Validate binary files + self.validate_files() + + # Generate ITS file + its_path = self.base_dir / "system.its" + self.generate_its(its_path) + + # Compile FIT image + fit_path = self.base_dir / output_name + self.compile_fit(its_path, fit_path) + + print("=" * 60) + print("CONVERSION SUMMARY:") + print(f" Input XML: {self.xml_path}") + print(f" Generated ITS: {its_path}") + print(f" Output FIT: {fit_path}") + print(f" Partitions: {len(self.fw_entries)}") + for entry in self.fw_entries: + print(f" - {entry['partition_name']} ({entry['binary_file']})") + + return fit_path + +def main(): + """Main entry point with command line argument parsing.""" + parser = argparse.ArgumentParser( + description="Convert FvUpdate.xml to capsule-compatible FIT image and capsule", + epilog=""" +Examples: + # Generate FIT image only: + %(prog)s FvUpdate.xml + + # Generate FIT image and create capsule: + %(prog)s FvUpdate.xml --mkeficapsule --guid --fw-version + +The script can operate in two modes: +1. FIT-only mode: Creates FIT image from FvUpdate.xml +2. Complete mode: Creates FIT image and capsule (requires --guid and --mkeficapsule) + +Workflow: +1. Auto-install missing tools (mkimage, mkeficapsule) +2. Parse FvUpdate.xml for UPDATE operations +3. Validate all binary files in Images/ directory +4. Generate system.its file +5. Compile system.fit image using mkimage +6. Create firmware.capsule using mkeficapsule (if --guid provided) + +The generated capsule is ready for deployment in Qualcomm U-Boot systems. + """, + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument('xml_file', + help='Path to FvUpdate.xml file') + parser.add_argument('--guid', + help='Capsule GUID for capsule generation. Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. ' + 'Board-specific GUIDs: ' + 'qcs615: 9FD379D2-670E-4BB3-86A1-40497E6E17B0, ' + 'qcs6490-rb3gen2: 6f25bfd2-a165-468b-980f-ac51a0a45c52, ' + 'lemans-evk: 78462415-6133-431c-9fae-48f2bafd5c71') + parser.add_argument('--mkeficapsule', + help='Path to mkeficapsule binary (default: use system PATH)') + parser.add_argument('-o', '--output', + default='system.fit', + help='Output FIT image name (default: %(default)s)') + parser.add_argument('-c', '--capsule-output', + default='firmware.capsule', + help='Output capsule file name (default: %(default)s)') + parser.add_argument('--fw-version', + help='Firmware version for capsule in "0.0.A.B" format (e.g., "0.0.1.0"). This version will be stored in ESRT.') + parser.add_argument('--monotonic-count', + type=int, + help='Monotonic count for capsule signing.') + parser.add_argument('--private-key', + help='Path to the private key for signing.') + parser.add_argument('--certificate', + help='Path to the certificate for signing.') + parser.add_argument('-v', '--verbose', + action='store_true', + help='Enable verbose output') + + args = parser.parse_args() + + # Validate input file + xml_path = Path(args.xml_file) + if not xml_path.exists(): + print(f"ERROR: XML file not found: {xml_path}") + sys.exit(1) + + if not xml_path.is_file(): + print(f"ERROR: Path is not a file: {xml_path}") + sys.exit(1) + + # Validate GUID format if provided + if args.guid: + guid_pattern = r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' + if not re.match(guid_pattern, args.guid): + print(f"ERROR: Invalid GUID format: {args.guid}") + print("Expected format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") + sys.exit(1) + + # Validate fw-version format if provided + if args.fw_version: + version_pattern = r'^\d+\.\d+\.\d+\.\d+$' + if not re.match(version_pattern, args.fw_version): + print(f"ERROR: Invalid firmware version format: {args.fw_version}") + print('Expected format: "0.0.A.B" (e.g., "0.0.1.0")') + sys.exit(1) + + # Create converter + converter = FvUpdateToFitConverter(xml_path, args.mkeficapsule) + + try: + if args.guid: + # Complete capsule workflow + capsule_path = converter.convert_complete( + args.output, + args.capsule_output, + args.fw_version, + args.guid, + args.monotonic_count, + args.private_key, + args.certificate + ) + + print("\n" + "=" * 60) + print("SUCCESS: Complete capsule workflow completed!") + print(f"Final capsule: {capsule_path}") + if args.fw_version: + print(f"Firmware version: {args.fw_version}") + + else: + # FIT-only mode + fit_path = converter.convert(args.output) + + print("\n" + "=" * 60) + print("SUCCESS: FIT image created successfully!") + print(f"Output: {fit_path}") + print("\nTo create capsule, run again with:") + print(f" {sys.argv[0]} {args.xml_file} --mkeficapsule --guid --fw-version ") + + except Exception as e: + print(f"\nERROR: {e}") + if args.verbose: + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() From 1a39c6e04c590e8a052caf06180993de34fbd586 Mon Sep 17 00:00:00 2001 From: Balaji Selvanathan Date: Wed, 13 May 2026 18:07:35 +0530 Subject: [PATCH 7/7] doc: add fvupdate_to_fit.py documentation and example XML Add comprehensive documentation for the fvupdate_to_fit.py tool that converts Qualcomm FvUpdate.xml files to FIT capsule images. The documentation covers usage, command-line options, deployment workflow, and troubleshooting guidance. Include an example FvUpdate.xml file demonstrating the expected XML structure with UPDATE operations for multiple firmware partitions. The documentation provides complete end-to-end guidance from XML conversion through capsule deployment in U-Boot, including board- specific GUIDs for QCS615, QCS6490, and Lemans platforms. Signed-off-by: Balaji Selvanathan --- doc/develop/fvupdate_to_fit.rst | 487 ++++++++++++++++++ .../fvupdate_to_fit/example_FvUpdate.xml | 43 ++ 2 files changed, 530 insertions(+) create mode 100644 doc/develop/fvupdate_to_fit.rst create mode 100644 doc/develop/fvupdate_to_fit/example_FvUpdate.xml diff --git a/doc/develop/fvupdate_to_fit.rst b/doc/develop/fvupdate_to_fit.rst new file mode 100644 index 000000000000..ef29587198f7 --- /dev/null +++ b/doc/develop/fvupdate_to_fit.rst @@ -0,0 +1,487 @@ +.. SPDX-License-Identifier: GPL-2.0+ +.. Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + +FvUpdate.xml to Capsule Converter +================================== + +This Python script provides complete end-to-end conversion from Qualcomm FvUpdate.xml files to ready-to-deploy EFI capsule files for U-Boot capsule updates. + +Overview +-------- + +The script offers two modes of operation: + +1. **FIT-only mode**: Converts FvUpdate.xml to FIT images +2. **Complete mode**: Full workflow from XML to final capsule file + +The complete workflow includes: + +- FvUpdate.xml parsing and validation +- FIT image generation +- Final capsule creation with provided GUID +- Auto-installation of missing tools + +Features +-------- + +Core Functionality +~~~~~~~~~~~~~~~~~~ + +- **XML Parsing**: Extracts UPDATE operations from FvUpdate.xml +- **Binary Validation**: Ensures all referenced binary files exist +- **ITS Generation**: Creates Image Tree Source files with proper node naming +- **FIT Compilation**: Uses mkimage to generate FIT images +- **Capsule Creation**: Creates final capsule files using mkeficapsule with provided GUID +- **Tool Management**: Auto-installs missing tools (mkimage, mkeficapsule) + +Compatibility +~~~~~~~~~~~~~ + +- Qualcomm U-Boot capsule update system +- Multi-partition firmware updates +- FIT-based capsule payloads +- EFI Firmware Management Protocol (FMP) +- DFU backend integration + +Usage +----- + +FIT Image Only +~~~~~~~~~~~~~~ + +Creates only system.fit:: + + ./fvupdate_to_fit.py FvUpdate.xml + +Complete Capsule Workflow +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Creates system.fit and firmware.capsule:: + + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule \ + --guid \ + --fw-version + +Additional Examples +~~~~~~~~~~~~~~~~~~~ + +With custom output names:: + + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule /path/to/mkeficapsule \ + --guid 12345678-1234-5678-9abc-123456789abc \ + --fw-version 0.0.1.0 \ + --output custom.fit \ + --capsule-output custom.capsule + +With verbose output:: + + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule /path/to/mkeficapsule \ + --guid 12345678-1234-5678-9abc-123456789abc \ + --fw-version 0.0.1.0 \ + --verbose + +With Capsule Signing +~~~~~~~~~~~~~~~~~~~~ + +Creates a signed capsule:: + + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule /path/to/mkeficapsule \ + --guid 12345678-1234-5678-9abc-123456789abc \ + --fw-version 0.0.1.0 \ + --monotonic-count 1 \ + --private-key keys/CRT.key \ + --certificate keys/CRT.crt + +Command Line Options +-------------------- + +.. list-table:: + :header-rows: 1 + :widths: 20 50 30 + + * - Option + - Description + - Default + * - ``xml_file`` + - Path to FvUpdate.xml file + - Required + * - ``--guid`` + - GUID value of the board (enables complete mode). Board-specific GUIDs: qcs615: ``9FD379D2-670E-4BB3-86A1-40497E6E17B0``, qcs6490-rb3gen2: ``6f25bfd2-a165-468b-980f-ac51a0a45c52``, lemans-evk: ``78462415-6133-431c-9fae-48f2bafd5c71`` + - Required + * - ``--mkeficapsule`` + - Path to mkeficapsule binary + - Required + * - ``--fw-version`` + - Firmware version in "0.0.A.B" format (e.g., "0.0.1.0") + - Optional + * - ``--monotonic-count`` + - Monotonic count for capsule signing + - Optional + * - ``--private-key`` + - Path to the private key for signing + - Optional + * - ``--certificate`` + - Path to the certificate for signing + - Optional + * - ``-o, --output`` + - Output FIT image name + - system.fit + * - ``-c, --capsule-output`` + - Output capsule file name + - firmware.capsule + * - ``-v, --verbose`` + - Enable verbose output + - Disabled + * - ``-h, --help`` + - Show help message + - \- + +Requirements +------------ + +System Requirements +~~~~~~~~~~~~~~~~~~~ + +- Python 3.6+ +- Linux system with package manager (apt, yum, dnf, or pacman) +- sudo access for tool installation + +Required Tools (Auto-installed) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- U-Boot tools (mkimage, mkeficapsule) + +Required Files +~~~~~~~~~~~~~~ + +- FvUpdate.xml file +- Binary files in Images/ directory +- GUID value for the target board (for complete mode) + +Installation +------------ + +1. Make the script executable:: + + chmod +x fvupdate_to_fit.py + +2. The script will auto-install missing tools on first run + +File Structure +-------------- + +The script expects the following directory structure:: + + project/ + ├── FvUpdate.xml + ├── Images/ + │ ├── xbl.elf + │ ├── uefi.bin + │ └── boot.img + └── fvupdate_to_fit.py + +Example FvUpdate.xml +~~~~~~~~~~~~~~~~~~~~ + +An example FvUpdate.xml file is provided for reference: + +.. literalinclude:: fvupdate_to_fit/example_FvUpdate.xml + :language: xml + +This example demonstrates the XML structure with UPDATE operations for +multiple firmware partitions. + +XML Structure Requirements +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The tool only processes the following XML elements: + +**Required Elements:** + +- ```` - Container for each firmware entry +- ```` - Binary filename (must exist in Images/ directory) +- ```` - Must be "UPDATE" to be processed +- ``/`` - Target partition name (case-sensitive) + +**XML Structure:** + +.. code-block:: xml + + + + + firmware.bin + UPDATE + + partition_name + + + + +**Notes:** + +- The tool hardcodes the Images/ directory for binary files +- Only entries with ``UPDATE`` are processed +- Partition names are case-sensitive and must match U-Boot partition discovery + +Output Files +------------ + +Complete Mode +~~~~~~~~~~~~~ + +- ``system.its`` - Image Tree Source file (kept) +- ``system.fit`` - FIT image (kept) +- ``firmware.capsule`` - Final capsule file (ready for deployment) + +FIT-only Mode +~~~~~~~~~~~~~ + +- ``system.its`` - Image Tree Source file +- ``system.fit`` - FIT image + +Deployment Workflow +------------------- + +After generating the capsule: + +1. **Copy capsule to boot partition**:: + + cp firmware.capsule /boot/ + +2. **Deploy in U-Boot**:: + + => fatload mmc 0:1 $loadaddr firmware.capsule + => efidebug capsule update $loadaddr + +Technical Details +----------------- + +FIT Image Structure +~~~~~~~~~~~~~~~~~~~ + +- **Node Names**: Match partition names (e.g., ``xbl_a``, ``uefi_a``) +- **Image Type**: All images marked as "firmware" +- **Hash Algorithm**: SHA256 for integrity verification +- **Configuration**: Single config referencing all firmware images + +Capsule Structure +~~~~~~~~~~~~~~~~~ + +- **GUID**: Provided via --guid option +- **Index**: Always 1 (required for FIT capsules) +- **Payload**: FIT image containing all firmware +- **Version**: Optional firmware version (in 0.0.A.B format) via --fw-version, encoded as (A << 16 | B) +- **Signing**: Optional signing with monotonic count, private key, and certificate + +FMP Driver Compatibility +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The provided GUID must match what the FMP (Firmware Management Protocol) driver expects: + +- Uses actual partition names from FvUpdate.xml +- Compatible with Qualcomm capsule update flow +- Works with ``fit_update()`` processing + +Error Handling +-------------- + +Comprehensive error handling with fail-fast approach: + +- Missing XML files or invalid format +- Missing binary files in Images/ directory +- Tool installation failures +- Capsule creation failures +- Invalid GUID format + +Example Output +-------------- + +Complete Mode +~~~~~~~~~~~~~ + +:: + + Converting FvUpdate.xml to capsule... + ============================================================ + Installing missing tools... + Installed u-boot-tools ✓ + Parsing XML file: FvUpdate.xml + Found 3 FwEntry elements + Added: xbl_a -> xbl.elf + Added: uefi_a -> uefi.bin + Added: boot_a -> boot.img + Successfully parsed 3 UPDATE entries + Validating binary files... + xbl.elf: 524288 bytes + uefi.bin: 1048576 bytes + boot.img: 2097152 bytes + All binary files validated successfully + Generating ITS file: system.its + ITS file generated successfully + Compiling FIT image: system.fit + Running: mkimage -f system.its system.fit + FIT image compiled successfully: 3670016 bytes + Using GUID: 12345678-1234-5678-9abc-123456789abc + Creating capsule: firmware.capsule + Encoded Firmware version: 65536 (from 0.0.1.0) + Command: mkeficapsule -g 12345678-1234-5678-9abc-123456789abc -i 1 -v 65536 system.fit firmware.capsule + Capsule created successfully: 3670144 bytes ✓ + ============================================================ + SUCCESS: Complete capsule workflow completed! + + Files created: + ITS file: system.its + FIT file: system.fit + Capsule file: firmware.capsule (3.5 MB) + + Capsule GUID: 12345678-1234-5678-9abc-123456789abc + + Ready for deployment: + 1. Copy firmware.capsule to boot partition + 2. In U-Boot: fatload mmc 0:1 $loadaddr firmware.capsule + 3. In U-Boot: efidebug capsule update $loadaddr + +FIT-only Mode +~~~~~~~~~~~~~ + +:: + + Converting FvUpdate.xml to FIT image... + ============================================================ + Parsing XML file: FvUpdate.xml + Found 3 FwEntry elements + Added: xbl_a -> xbl.elf + Added: uefi_a -> uefi.bin + Added: boot_a -> boot.img + Successfully parsed 3 UPDATE entries + Validating binary files... + xbl.elf: 524288 bytes + uefi.bin: 1048576 bytes + boot.img: 2097152 bytes + All binary files validated successfully + Generating ITS file: system.its + ITS file generated successfully + Compiling FIT image: system.fit + FIT image compiled successfully: 3670016 bytes + ============================================================ + CONVERSION SUMMARY: + Input XML: FvUpdate.xml + Generated ITS: system.its + Output FIT: system.fit + Partitions: 3 + - xbl_a (xbl.elf) + - uefi_a (uefi.bin) + - boot_a (boot.img) + + ============================================================ + SUCCESS: FIT image created successfully! + Output: system.fit + + To create capsule, run again with: + ./fvupdate_to_fit.py FvUpdate.xml --mkeficapsule --guid --fw-version + +Viewing Generated File Contents +-------------------------------- + +FIT Image Contents +~~~~~~~~~~~~~~~~~~ + +You can inspect the generated ``system.fit`` file using U-Boot tools:: + + # View FIT image structure and metadata + mkimage -l system.fit + + # List all images in the FIT + dumpimage -l system.fit + +Capsule Contents +~~~~~~~~~~~~~~~~ + +You can inspect the generated ``firmware.capsule`` file using mkeficapsule:: + + # View capsule header and metadata + mkeficapsule --dump-capsule firmware.capsule + + # Or using local mkeficapsule binary + /path/to/local/mkeficapsule --dump-capsule firmware.capsule + +Troubleshooting +--------------- + +mkimage not found +~~~~~~~~~~~~~~~~~ + +Install U-Boot tools:: + + # Ubuntu/Debian + sudo apt-get install u-boot-tools + + # CentOS/RHEL + sudo yum install uboot-tools + +mkeficapsule not found +~~~~~~~~~~~~~~~~~~~~~~ + +If you get an error about mkeficapsule not being found, use a locally compiled version:: + + # Use local mkeficapsule binary + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule /path/to/local/u-boot/tools/mkeficapsule \ + --guid 12345678-1234-5678-9abc-123456789abc \ + --fw-version 0.0.1.0 + + # Example with U-Boot build directory + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule /local/mnt/workspace/bselvana/k2c_le/u-boot_upstream/u-boot_v2025_upstream/tools/mkeficapsule \ + --guid 12345678-1234-5678-9abc-123456789abc \ + --fw-version 0.0.1.0 + +Invalid GUID format +~~~~~~~~~~~~~~~~~~~ + +Ensure the GUID follows the format: ``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`` + +Supported Boards and GUIDs +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following Qualcomm boards are supported with their respective GUIDs: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Board + - GUID + * - **qcs615** + - ``9FD379D2-670E-4BB3-86A1-40497E6E17B0`` + * - **qcs6490-rb3gen2** + - ``6f25bfd2-a165-468b-980f-ac51a0a45c52`` + * - **lemans-evk** + - ``78462415-6133-431c-9fae-48f2bafd5c71`` + + +Example valid GUID format: ``12345678-1234-5678-9abc-123456789abc`` + +Missing binary files +~~~~~~~~~~~~~~~~~~~~ + +Ensure all files referenced in FvUpdate.xml exist in the Images/ directory with correct names. + +XML parsing errors +~~~~~~~~~~~~~~~~~~ + +Verify FvUpdate.xml is well-formed XML with proper FwEntry structure. + +mkeficapsule binary not found +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you specify a custom mkeficapsule path, ensure: + +- The file exists and is executable +- The path is correct (absolute or relative to current directory) +- The binary supports capsule creation diff --git a/doc/develop/fvupdate_to_fit/example_FvUpdate.xml b/doc/develop/fvupdate_to_fit/example_FvUpdate.xml new file mode 100644 index 000000000000..c1dea8db4a26 --- /dev/null +++ b/doc/develop/fvupdate_to_fit/example_FvUpdate.xml @@ -0,0 +1,43 @@ + + + + + + + + + u-boot.mbn + UPDATE + + uefi_a + + + + + + tz.mbn + UPDATE + + tz_a + + + + + + \ No newline at end of file