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