On Sat, 23 May 2026 at 12:49, Balaji Selvanathan
<[email protected]> wrote:
>
> Hi Ilias,
>
> On 5/23/2026 1:36 PM, Ilias Apalodimas wrote:
> > Hi Balaji,
> >
> > On Sat, 23 May 2026 at 08:26, Balaji Selvanathan
> > <[email protected]> wrote:
> >> Hi Ilias, Casey,
> >>
> >> On 5/22/2026 7:09 PM, Casey Connolly wrote:
> >>> On 22/05/2026 10:58, Ilias Apalodimas wrote:
> >>>> Hi Balaji,
> >>>>
> >>>> On Fri, 22 May 2026 at 11:46, Balaji Selvanathan
> >>>> <[email protected]> wrote:
> >>>>> Hi Ilias,
> >>>>>
> >>>>> On 5/22/2026 12:18 PM, Ilias Apalodimas wrote:
> >>>>>> Hi Balaji,
> >>>>>>
> >>>>>>
> >>>>>> On Fri, 22 May 2026 at 09:09, Balaji Selvanathan
> >>>>>> <[email protected]> wrote:
> >>>>>>> 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.
> >>>>>> I am not sure I am following this one? What are you trying to achieve
> >>>>>> here? Have a single capsule for all hardware?
> >>>>>>
> >>>>>> Thanks
> >>>>>> /Ilias
> >>>>> Current qcom capsule update codes (in mach-snapdragon/capsule_update.c)
> >>>>> has support to update only U-Boot partition (so the capsule's payload
> >>>>> just has U-boot binary).
> >>>>>
> >>>>> We want to extend that support to update multiple partitions, not just
> >>>>> U-Boot. We want to achieve this by using existing FIT based framework
> >>>>> (in efi_firmware.c).
> >>>>>
> >>>>> So, we create a fit with each node of the FIT containing binaries of a
> >>>>> firmware. This fit we will place in the capsule's payload.
> >>>> Yes but the question is why FIT specifically? The efi capsule code for
> >>>> raw images can do the same thing and they better adhere to the UEFI
> >>>> spec. All you have to do is define those extra partitions and their
> >>>> respective dfu command. We already have examples of boards updating
> >>>> multiple partitions.
> >>> Agreed, this was something I considered when implementing this
> >>> originally. The partition detection code of course needs changes for
> >>> this but there's no reason to use FIT here.
> >> Hi Ilias, Casey,
> >>
> >> Thanks for the feedback. I would like to highlight following advantages
> >> I see with the approach introduced in this series:
> >>
> >> 1. Based on your comments and my analysis of the code, I understand that
> >> RAW capsules can handle multi-partition updates by creating multiple
> >> separate capsule files - one capsule per firmware partition. Each
> >> capsule would have:
> >>
> >> - Its own unique GUID (identifying the target partition)
> >> - A single binary payload
> >> Is this understanding correct? If so, this would require users to manage
> >> and deploy multiple capsule files (e.g., uefi_a.capsule, tz_a.capsule,
> >> xbl_a.capsule, etc.).
> > No that's not correct. A single capsule can contain as many GUIDs you
> > want during the capsule creation
> While the UEFI capsule format theoretically supports multiple images via
> payload_item_count, the current efi_firmware_raw_set_image()
> implementation doesn't parse multiple image headers - it expects a
> single binary payload. Also , using the current mkeficapsule tool, we
> can create a capsule using only 1 firmware image.
>
> To support multi-image RAW capsules, we would need to:
>
> 1. Enhance mkeficapsule to create multi-image capsules?
There's an equivalent tool in EDKII that can produce a capsule with
multiple payloads. There were also patches posted for mkeficapsule,
but need some minor tweaks to merge them
> 2. Enhance efi_firmware_raw_set_image() to parse FMP capsule headers and
> extract multiple binaries?
That's already supported
>
> >
> >> In contrast, the FIT-based capsule approach uses a single capsule file
> >> to update multiple firmware binaries simultaneously, with the FIT image
> >> serving as a container that bundles all firmware components together.
> >>
> >> 2. The FIT approach (introduced in this series) eliminates hardcoded
> >> partition names: the current Qualcomm RAW capsule update code (in
> >> capsule_update.c) hardcodes partition names like "uefi" in the source
> >> and searches for matching partitions on the device, whereas the
> >> FIT-based method stores partition names as FIT node names within the
> >> capsule itself, allowing the same U-Boot binary to work with different
> >> partition naming schemes without code modification.
> > The UEFI spec does't use names during the update. That's an artificial
> > limitation of your current implementation. The capsule code just looks
> > at the index and the DFU command to update the proper partitions.
> You're correct that the capsule code uses index and DFU commands.
> However, in the current Qualcomm implementation, the DFU string itself
> is built by searching for hardcoded partition names like 'uefi' in the code:
> if (!strncmp(info.name, "uefi_", strlen("uefi_"))) {
> // Build DFU string with this partition
> }
I haven't looked at the code but that sounds like an implementation
detail, that's used to *build* the dfu string dynamically for Qualcomm
platforms. You'll still need that regardless of FIT/RAW capsules,
since you have to define the dfu command. OTOH, you can statically
define that dfu string per platform and remove that part.
Cheers
/Ilias
>
> Regards,
>
> Balaji
>
> >
> > Thanks
> > /Ilias
> >> Regards,
> >>
> >> Balaji
> >>
> >>>> Thanks
> >>>> /Ilias
> >>>>> Regards,
> >>>>>
> >>>>> Balaji
> >>>>>
> >>>>>>> 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
> >>>>>>> <[email protected]>
> >>>>>>> ---
> >>>>>>> arch/arm/mach-snapdragon/capsule_update.c | 740
> >>>>>>> ++++++++++++++++++++++++++++--
> >>>>>>> arch/arm/mach-snapdragon/qcom-priv.h | 23 +
> >>>>>>> 2 files changed, 712 insertions(+), 51 deletions(-)
> >>>>>>>
> >>>>>>> diff --git a/arch/arm/mach-snapdragon/capsule_update.c
> >>>>>>> b/arch/arm/mach-snapdragon/capsule_update.c
> >>>>>>> index 586682434b7..d803c46f38d 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 <dm/device.h>
> >>>>>>> -#include <dm/uclass.h>
> >>>>>>> +#include <command.h>
> >>>>>>> #include <efi.h>
> >>>>>>> #include <efi_loader.h>
> >>>>>>> #include <malloc.h>
> >>>>>>> #include <mmc.h>
> >>>>>>> -#include <scsi.h>
> >>>>>>> #include <part.h>
> >>>>>>> +#include <scsi.h>
> >>>>>>> +#include <dm/device.h>
> >>>>>>> +#include <dm/uclass.h>
> >>>>>>> #include <linux/err.h>
> >>>>>>> -
> >>>>>>> #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 @@ found:
> >>>>>>> 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,625 @@ 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 b8bf574e8bb..d664c22ae96 100644
> >>>>>>> --- a/arch/arm/mach-snapdragon/qcom-priv.h
> >>>>>>> +++ b/arch/arm/mach-snapdragon/qcom-priv.h
> >>>>>>> @@ -18,6 +18,29 @@ enum qcom_boot_source {
> >>>>>>> extern enum qcom_boot_source qcom_boot_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) {}
> >>>>>>>
> >>>>>>> --
> >>>>>>> 2.34.1
> >>>>>>>