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.

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

Reply via email to