[PATCH 1/2] qcom: capsule: build fw_images[] dynamically from the partition table

Balaji Selvanathan via U-Boot <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <20260813-qcom-capsule-dynamic-fw-images-v1-1-e595ff79496f@oss.qualcomm.com>
Capsule update on Qualcomm boards only ever handled a single image:
U-Boot itself, found by find_target_partition() and hardcoded as the
lone fw_images[] entry. There was no way to update any other firmware
component (xbl, tz, hyp, aop, devcfg, qupfw, ...) through a capsule.

Replace this with a table, qcom_image_map[], that maps each
partition_base to an image_index and fw_name. Scan every partition on
every probed block device; each map row with a matching partition
contributes one entry to a variable-length fw_images[] and to a
matching dfu_alt_info string. num_images is set to whatever was
actually discovered, so a component missing on a given board is simply
left out.

U-Boot's own image keeps image_index 1. Rather than a separate code
path, it is now expressed as three boot-source-gated rows (uefi/xbl/
boot), so find_target_partition()'s uefi-over-xbl priority and A/B slot
selection are reproduced by the same generic scan rather than
duplicated logic.

image_index values are frozen once a capsule references them, so
qcom_image_map[] is meant to only ever grow by appending new rows;
existing indices must never be renumbered.

Since dfu_alt_num can no longer be derived positionally from
image_index once components are missing on a board, this also adds a
strong efi_firmware_get_dfu_alt_num() override that looks up the
dfu_alt_num recorded for each image_index while building the DFU
string, falling back to the generic (image_index - 1) for anything it
doesn't recognize.

Signed-off-by: Balaji Selvanathan <[email protected]>
---
This patch depends on following series:
https://lore.kernel.org/u-boot/20260813-efi-firmware-dfu-alt-num-v1-0-43f034253b21@oss.qualcomm.com/
---
 arch/arm/mach-snapdragon/capsule_update.c | 757 +++++++++++++++++++++---------
 1 file changed, 530 insertions(+), 227 deletions(-)

diff --git a/arch/arm/mach-snapdragon/capsule_update.c b/arch/arm/mach-snapdragon/capsule_update.c
index 586682434b7..9a5b1578a27 100644
--- a/arch/arm/mach-snapdragon/capsule_update.c
+++ b/arch/arm/mach-snapdragon/capsule_update.c
@@ -21,35 +21,109 @@
 #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:
+ * fw_images[] and update_info are built at runtime from the board's GPT
+ * partition table (see qcom_configure_capsule_updates()): the set of updatable
+ * firmware components varies per board, and U-Boot itself may live on one of
+ * several partitions depending on how it was booted.
  *
- * - 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
+ * qcom_image_map[] maps each partition base name to a fixed capsule image_index
+ * and a slot-independent firmware name. U-Boot's own image is image_index 1 in
+ * the same table, split into boot-source-gated rows so the right partition is
+ * picked for the running boot source: uefi/xbl (or the legacy "aboot" alias)
+ * when flashed as XBL, boot when chainloaded from ABL.
+ */
+
+/**
+ * struct qcom_image_map - maps a GPT partition to a capsule FMP image
+ * @partition_base: base partition name, without the _a/_b slot suffix
+ * @fw_name_base: slot-independent firmware name; the FMP GUID is derived
+ *                from this, so it must stay constant across A/B toggles
+ * @image_index: fixed image index for this component, frozen forever once
+ *               a capsule ships referencing it. image_index 1 is reserved for
+ *               U-Boot's own image (the three boot-source-gated rows below);
+ *               exactly one of those is ever selected per boot.
+ * @boot_source: if non-zero, this row is only eligible when qcom_boot_source
+ *               matches. Gates U-Boot's own image: uefi/xbl need
+ *               QCOM_BOOT_SOURCE_XBL, boot needs QCOM_BOOT_SOURCE_ANDROID.
+ *               0 means any boot source (every real firmware component).
+ * @alias_base: optional second base name this row also matches (the legacy
+ *              "aboot" alias for U-Boot on the xbl partition); NULL for none.
+ *              Matched both slotted ("<alias>_a"/"_b") and bare.
+ * @match_nonslotted: also match the bare, non-slotted @partition_base. Some
+ *                    boards don't use A/B for U-Boot's partition (e.g. a
+ *                    single "boot").
  *
- * Which partition actually has U-Boot on it is determined based on the
- * qcom_boot_source variable and additional logic in find_target_partition().
+ * Every row matches "<partition_base>_a" and "<partition_base>_b".
+ * @alias_base and @match_nonslotted reproduce the uefi/xbl/aboot/boot name
+ * handling find_target_partition() used before U-Boot's image joined this
+ * table; the firmware component rows leave both unset.
  */
-struct efi_fw_image fw_images[] = {
-	{
-		.image_index = 1,
-	},
+struct qcom_image_map {
+	const char *partition_base;
+	const u16 *fw_name_base;
+	u8 image_index;
+	enum qcom_boot_source boot_source;
+	const char *alias_base;
+	bool match_nonslotted;
+};
+
+static const struct qcom_image_map qcom_image_map[] = {
+	/*
+	 * U-Boot's own image (image_index 1). Exactly one of these rows is
+	 * selected per boot:
+	 *  - boot_source gating makes the uefi/xbl (XBL) rows and the boot
+	 *    (ANDROID) row mutually exclusive;
+	 *  - uefi is listed before xbl, so it wins image_index 1 when both
+	 *    partitions exist and the generic "xbl" row below stays a normal
+	 *    updatable component;
+	 *  - when U-Boot is on xbl (no uefi), the xbl row here claims that
+	 *    partition first and the component row is skipped, since one
+	 *    partition can't be two capsule images.
+	 * The per-row fw_name makes the FMP GUID differ, so a U-Boot image
+	 * built for xbl can't be flashed to the boot partition.
+	 */
+	{ "uefi", u"UBOOT_UEFI_PARTITION", 1, QCOM_BOOT_SOURCE_XBL,     NULL,    false },
+	{ "xbl",  u"UBOOT_XBL_PARTITION",  1, QCOM_BOOT_SOURCE_XBL,     "aboot", false },
+	{ "boot", u"UBOOT_BOOT_PARTITION", 1, QCOM_BOOT_SOURCE_ANDROID, NULL,    true  },
+
+	{ "xbl",         u"QCOM-XBL",          2, 0, NULL, false },
+	{ "xbl_config",  u"QCOM-XBL-CONFIG",   3, 0, NULL, false },
+	{ "uefisecapp",  u"QCOM-UEFI-SECAPP",  4, 0, NULL, false },
+	{ "tz",          u"QCOM-TZ",           5, 0, NULL, false },
+	{ "hyp",         u"QCOM-HYP",          6, 0, NULL, false },
+	{ "aop",         u"QCOM-AOP",          7, 0, NULL, false },
+	{ "devcfg",      u"QCOM-DEVCFG",       8, 0, NULL, false },
+	{ "qupfw",       u"QCOM-QUPFW",        9, 0, NULL, false },
+	{ "xbl_ramdump", u"QCOM-XBL-RAMDUMP", 10, 0, NULL, false },
+	{ "cpucp",       u"QCOM-CPUCP",       11, 0, NULL, false },
+	{ "shrm",        u"QCOM-SHRM",        12, 0, NULL, false },
+	{ "imagefv",     u"QCOM-IMAGEFV",     13, 0, NULL, false },
+	{ "multiimgoem", u"QCOM-MULTIIMGOEM", 14, 0, NULL, false },
+	{ "apdp",        u"QCOM-APDP",        15, 0, NULL, false },
+	{ "rpm",         u"QCOM-RPM",         16, 0, NULL, false },
 };
 
+/*
+ * Fixed-capacity, no heap allocation:
+ * one slot per qcom_image_map[] row. Only one of the three image_index 1
+ * (U-Boot) rows is ever used, so this is larger than the real maximum.
+ * update_info.num_images is set at runtime to the number actually found, which
+ * varies since not every board has every component.
+ */
+static struct efi_fw_image fw_images[ARRAY_SIZE(qcom_image_map)];
+
 struct efi_capsule_update_info update_info = {
-	/* Filled in by configure_dfu_string() */
 	.dfu_string = NULL,
-	.num_images = ARRAY_SIZE(fw_images),
+	.num_images = 0,
 	.images = fw_images,
 };
 
-enum target_part_type {
-	TARGET_PART_UEFI = 1,
-	TARGET_PART_XBL,
-	TARGET_PART_BOOT,
-};
+/*
+ * Worst case: every fw_images[] entry on its own LUN (each needing an "&scsi
+ * N=" or "&mmc N=" group separator) with a full-length "<name> part <num>"
+ * token. See qcom_build_dfu_string().
+ */
+#define QCOM_DFU_STRING_LEN	(64 * ARRAY_SIZE(qcom_image_map))
 
 /* LSB first */
 struct part_slot_status {
@@ -67,20 +141,6 @@ enum ab_slot {
 	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,206 +158,407 @@ static bool is_sd(struct blk_desc *desc)
 	return IS_SD(mmc) != 0U;
 }
 
+/**
+ * struct qcom_partition_info - a selected partition for one component
+ * @name: partition name as found on disk, e.g. "tz_a"
+ * @part_num: partition number on the block device
+ * @devnum: block device number @name lives on
+ * @uclass_id: uclass (UCLASS_SCSI/UCLASS_MMC) of @devnum. @devnum and
+ *             @uclass_id are carried through instead of a struct blk_desc
+ *             pointer so partitions found on different LUNs (e.g. Kodiak/Lemans
+ *             split xbl/xbl_config onto one LUN, tz/hyp/aop onto another) group
+ *             back into the correct per-device DFU string tokens
+ * @fw_name: slot-independent FMP name (from the matched map row's fw_name_base)
+ * @image_index: fw_images[] image_index (from the matched map row)
+ */
+struct qcom_partition_info {
+	char name[32];
+	u32 part_num;
+	int devnum;
+	enum uclass_id uclass_id;
+	const u16 *fw_name;
+	u8 image_index;
+};
+
+/*
+ * Used only within qcom_configure_capsule_updates() (scan -> resolve -> build).
+ */
+static struct qcom_partition_info qcom_partitions[ARRAY_SIZE(qcom_image_map)];
+
+/**
+ * struct qcom_slot_cand - one candidate partition for a map row's slot
+ * @part_num: partition number, or -1 if no partition filled this slot
+ * @active: A/B "active" flag from the partition's slot status
+ * @devnum: block device number the partition was found on
+ * @uclass_id: uclass of that block device
+ * @name: partition name as found on disk
+ */
+struct qcom_slot_cand {
+	int part_num;
+	bool active;
+	int devnum;
+	enum uclass_id uclass_id;
+	char name[32];
+};
+
+/**
+ * struct qcom_row_cand - candidate partitions for one qcom_image_map[] row
+ * @slot: indexed by enum ab_slot (SLOT_NONE for a non-slotted/alias-bare match)
+ */
+struct qcom_row_cand {
+	struct qcom_slot_cand slot[3];
+};
+
 /*
- * 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.
+ * Candidate table, one entry per qcom_image_map[] row, accumulated across every
+ * scanned block device before a single resolve pass picks one partition per
+ * row.
+ */
+static struct qcom_row_cand qcom_cands[ARRAY_SIZE(qcom_image_map)];
+
+/**
+ * qcom_match_partition() - test a partition name against a map row
+ * @name: partition name as found on disk
+ * @map: candidate map row
+ * @slot: output, which A/B slot (SLOT_NONE for a non-slotted / bare-alias match)
+ *
+ * Every row matches "<partition_base>_a"/"<partition_base>_b". U-Boot's rows
+ * may also match a bare non-slotted name (@match_nonslotted) and a legacy alias
+ * base (@alias_base, both slotted and bare).
+ *
+ * Return: true if @name belongs to @map
+ */
+static bool qcom_match_partition(const char *name,
+				 const struct qcom_image_map *map,
+				 enum ab_slot *slot)
+{
+	char cand[36];
+
+	snprintf(cand, sizeof(cand), "%s_a", map->partition_base);
+	if (!strcmp(name, cand)) {
+		*slot = SLOT_A;
+		return true;
+	}
+	snprintf(cand, sizeof(cand), "%s_b", map->partition_base);
+	if (!strcmp(name, cand)) {
+		*slot = SLOT_B;
+		return true;
+	}
+	if (map->match_nonslotted && !strcmp(name, map->partition_base)) {
+		*slot = SLOT_NONE;
+		return true;
+	}
+
+	if (map->alias_base) {
+		snprintf(cand, sizeof(cand), "%s_a", map->alias_base);
+		if (!strcmp(name, cand)) {
+			*slot = SLOT_A;
+			return true;
+		}
+		snprintf(cand, sizeof(cand), "%s_b", map->alias_base);
+		if (!strcmp(name, cand)) {
+			*slot = SLOT_B;
+			return true;
+		}
+		if (!strcmp(name, map->alias_base)) {
+			*slot = SLOT_NONE;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/**
+ * qcom_scan_device() - record candidate partitions on one block device
+ * @desc: block device to scan
+ * @cands: candidate table, one entry per qcom_image_map[] row
+ *
+ * Single pass over every partition on @desc. Each partition is checked against
+ * every eligible map row (rows whose boot_source doesn't match the live
+ * qcom_boot_source are skipped) and recorded into that row's A/B/NONE slot. One
+ * partition can match several rows ("xbl_a" feeds both the U-Boot xbl row and
+ * the generic xbl component row), so the inner loop doesn't stop at the first
+ * match.
+ *
+ * Candidates from an earlier device aren't overwritten, so the first device
+ * wins when the same partition table is exposed on more than one (e.g. mirrored
+ * UFS boot LUNs). The candidate table is global rather than per-device so the
+ * later qcom_resolve_images() pass can apply uefi-over-xbl priority even when
+ * the two partitions live on different LUNs.
  */
-static int find_target_partition(int *devnum, enum uclass_id *uclass,
-				 enum target_part_type *target_part_type)
+static void qcom_scan_device(struct blk_desc *desc, struct qcom_row_cand *cands)
 {
-	int ret;
-	int partnum, uefi_partnum = -1, xbl_partnum = -1;
 	struct disk_partition info;
 	struct part_slot_status *slot_status;
-	struct udevice *dev = NULL;
-	struct blk_desc *desc = NULL, *xbl_desc = NULL;
-	uchar ptn_name[32] = { 0 };
-	bool have_ufs = false;
+	int partnum, i;
 
-	/*
-	 * Check to see if we have UFS storage, if so U-Boot MUST be on it and we can skip
-	 * all non-UFS block devices
-	 */
-	uclass_foreach_dev_probe(UCLASS_UFS, dev) {
-		have_ufs = true;
-		break;
-	}
+	for (partnum = 1; !part_get_info(desc, partnum, &info); partnum++) {
+		slot_status = (struct part_slot_status *)&info.type_flags;
 
-	uclass_foreach_dev_probe(UCLASS_BLK, dev) {
-		if (device_get_uclass_id(dev) != UCLASS_BLK)
-			continue;
+		for (i = 0; i < ARRAY_SIZE(qcom_image_map); i++) {
+			const struct qcom_image_map *m = &qcom_image_map[i];
+			struct qcom_slot_cand *c;
+			enum ab_slot slot;
 
-		desc = dev_get_uclass_plat(dev);
+			if (m->boot_source && m->boot_source != qcom_boot_source)
+				continue;
+			if (!qcom_match_partition((char *)info.name, m, &slot))
+				continue;
 
-		/* If we have a UFS then don't look at any other block devices */
-		if (have_ufs) {
-			if (device_get_uclass_id(dev->parent->parent) != UCLASS_UFS)
+			c = &cands[i].slot[slot];
+			if (c->part_num >= 0)	/* first device wins */
 				continue;
+
+			c->part_num = partnum;
+			c->active = !!slot_status->active;
+			c->devnum = desc->devnum;
+			c->uclass_id = desc->uclass_id;
+			strlcpy(c->name, (char *)info.name, sizeof(c->name));
 		}
-		/*
-		 * If we don't have UFS, then U-Boot must be on the eMMC
-		 */
-		else if (IS_ENABLED(CONFIG_MMC) && is_sd(desc)) {
-			log_debug("skipped SD-Card (devnum %d)\n", desc->devnum);
+	}
+}
+
+/**
+ * qcom_pick_slot() - choose the best candidate slot for a map row
+ * @rc: candidate slots for one row
+ *
+ * Priority: active A > active B > non-slotted > inactive A > inactive B.
+ *
+ * Return: the chosen enum ab_slot, or -1 if no candidate was recorded
+ */
+static int qcom_pick_slot(const struct qcom_row_cand *rc)
+{
+	if (rc->slot[SLOT_A].part_num >= 0 && rc->slot[SLOT_A].active)
+		return SLOT_A;
+	if (rc->slot[SLOT_B].part_num >= 0 && rc->slot[SLOT_B].active)
+		return SLOT_B;
+	if (rc->slot[SLOT_NONE].part_num >= 0)
+		return SLOT_NONE;
+	if (rc->slot[SLOT_A].part_num >= 0)
+		return SLOT_A;
+	if (rc->slot[SLOT_B].part_num >= 0)
+		return SLOT_B;
+
+	return -1;
+}
+
+/**
+ * qcom_resolve_images() - pick one partition per map row from the candidates
+ * @cands: candidate table filled by qcom_scan_device() across every device
+ * @partitions: output array; room for ARRAY_SIZE(qcom_image_map) entries
+ *
+ * Walks qcom_image_map[] in order, so U-Boot's own rows (image_index 1) resolve
+ * first and land in partitions[0]. Two dedup rules:
+ *
+ *  1. One selected partition per image_index. The three U-Boot rows share
+ *     image_index 1, so once one is picked the others are skipped -- this is
+ *     the uefi-over-xbl priority (uefi is listed first).
+ *  2. Never claim the same physical partition twice. When U-Boot is on xbl (no
+ *     uefi), the U-Boot xbl row claims that partition and the generic xbl
+ *     component row, matching the same partition, is skipped.
+ *
+ * A row with no candidate on this board is left out, so num_images varies per
+ * board.
+ *
+ * Return: number of partitions written to @partitions
+ */
+static u32 qcom_resolve_images(struct qcom_row_cand *cands,
+			       struct qcom_partition_info *partitions)
+{
+	u32 count = 0;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(qcom_image_map); i++) {
+		const struct qcom_image_map *m = &qcom_image_map[i];
+		struct qcom_slot_cand *c;
+		struct qcom_partition_info *p;
+		bool dup = false;
+		int sel;
+		u32 j;
+
+		if (m->boot_source && m->boot_source != qcom_boot_source)
 			continue;
-		}
 
-		if (!desc || desc->part_type == PART_TYPE_UNKNOWN)
+		sel = qcom_pick_slot(&cands[i]);
+		if (sel < 0)
 			continue;
-		for (partnum = 1;; partnum++) {
-			ret = part_get_info(desc, partnum, &info);
-			if (ret)
+		c = &cands[i].slot[sel];
+
+		/* rule 1: one selected partition per image_index */
+		for (j = 0; j < count; j++) {
+			if (partitions[j].image_index == m->image_index) {
+				dup = true;
 				break;
+			}
+		}
+		if (dup)
+			continue;
 
-			slot_status = (struct part_slot_status *)&info.type_flags;
-
-			/*
-			 * Qualcomm Linux devices have a "uefi" partition, it's A/B but the
-			 * 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 U-Boot was chainloaded somehow we can't be flashed to
-				 * the uefi partition
-				 */
-				if (qcom_boot_source != QCOM_BOOT_SOURCE_XBL)
-					continue;
-
-				*target_part_type = TARGET_PART_UEFI;
-				/*
-				 * Found an active UEFI partition, this is where U-Boot is
-				 * flashed.
-				 */
-				if (slot_status->active)
-					goto found;
-
-				/* Prefer A slot if it's not marked active */
-				if (get_part_slot(info.name) == SLOT_A) {
-					/*
-					 * If we found the A slot after the B slot (both
-					 * inactive) then we assume U-Boot is on the A slot.
-					 */
-					if (uefi_partnum >= 0)
-						goto found;
-
-					/* Didn't find the B slot yet */
-					uefi_partnum = partnum;
-					strlcpy(ptn_name, info.name, 32);
-				} else {
-					/*
-					 * Found inactive B slot after inactive A slot, return
-					 * the A slot
-					 */
-					if (uefi_partnum >= 0) {
-						partnum = uefi_partnum;
-						goto found;
-					}
-
-					/*
-					 * Didn't find the A slot yet. Record that we found the
-					 * B slot
-					 */
-					uefi_partnum = partnum;
-					strlcpy(ptn_name, info.name, 32);
-				}
-				/* xbl and aboot are effectively the same */
-			} else if ((!strncmp(info.name, "xbl", strlen("xbl")) &&
-				    strlen(info.name) == 5) ||
-				    !strncmp(info.name, "aboot", strlen("aboot"))) {
-				/*
-				 * If U-Boot was booted via ABL, we can't be flashed to the
-				 * XBL partition
-				 */
-				if (qcom_boot_source != QCOM_BOOT_SOURCE_XBL)
-					continue;
-
-				/*
-				 * ignore xbl partition if we have uefi partitions, U-Boot will
-				 * always be on the UEFI partition in this case.
-				 */
-				if (*target_part_type == TARGET_PART_UEFI)
-					continue;
-
-				/* Either non-A/B or find the active XBL partition */
-				if (slot_status->active || !get_part_slot(info.name)) {
-					/*
-					 * No quick return since we might find a uefi partition
-					 * later
-					 */
-					xbl_partnum = partnum;
-					*target_part_type = TARGET_PART_XBL;
-					xbl_desc = desc;
-					strlcpy(ptn_name, info.name, 32);
-				}
-
-				/*
-				 * No fast return since we might also have a uefi partition which
-				 * will take priority.
-				 */
-			} else if (!strncmp(info.name, "boot", strlen("boot"))) {
-				/* We can only be flashed to boot if we were chainloaded */
-				if (qcom_boot_source != QCOM_BOOT_SOURCE_ANDROID)
-					continue;
-
-				/*
-				 * Either non-A/B or find the active partition. We can return
-				 * immediately here since we've narrowed it down to a single option
-				 */
-				if (slot_status->active || !get_part_slot(info.name)) {
-					*target_part_type = TARGET_PART_BOOT;
-					goto found;
-				}
+		/* rule 2: never claim the same physical partition twice */
+		for (j = 0; j < count; j++) {
+			if (partitions[j].devnum == c->devnum &&
+			    partitions[j].uclass_id == c->uclass_id &&
+			    partitions[j].part_num == (u32)c->part_num) {
+				dup = true;
+				break;
 			}
 		}
-	}
+		if (dup)
+			continue;
 
-	/*
-	 * Now we've exhausted all options, if we didn't find a uefi partition
-	 * then we are indeed flashed to the xbl partition.
-	 */
-	if (*target_part_type == TARGET_PART_XBL) {
-		partnum = xbl_partnum;
-		desc = xbl_desc;
-		goto found;
+		p = &partitions[count++];
+		strlcpy(p->name, c->name, sizeof(p->name));
+		p->part_num = c->part_num;
+		p->devnum = c->devnum;
+		p->uclass_id = c->uclass_id;
+		p->fw_name = m->fw_name_base;
+		p->image_index = m->image_index;
+
+		log_debug("qcom capsule: %s -> image %u (part %u, dev %d)\n",
+			  p->name, p->image_index, p->part_num, p->devnum);
 	}
 
-	/* Found no candidate partitions */
-	return -1;
+	return count;
+}
 
-found:
-	if (desc) {
-		*devnum = desc->devnum;
-		*uclass = desc->uclass_id;
+/**
+ * qcom_build_fw_images() - populate fw_images[] from the discovered partitions
+ * @partitions: partitions from qcom_resolve_images(); entry 0 is U-Boot's own
+ *              image (image_index 1), the rest are components
+ * @num_partitions: number of valid entries in @partitions
+ *
+ * One fw_images[] entry per partition, in the same order, so fw_images[] and
+ * @partitions stay index-aligned. .image_type_id is left zeroed:
+ * efi_gen_capsule_guids() derives it from CONFIG_EFI_CAPSULE_NAMESPACE_GUID +
+ * the DT compatible + .fw_name, as it does for every other board.
+ *
+ * Return: number of images written (equal to @num_partitions)
+ */
+static u32 qcom_build_fw_images(const struct qcom_partition_info *partitions,
+				u32 num_partitions)
+{
+	u32 i;
+
+	for (i = 0; i < num_partitions; i++) {
+		fw_images[i].fw_name = (u16 *)partitions[i].fw_name;
+		fw_images[i].image_index = partitions[i].image_index;
 	}
 
-	/* info won't match for XBL hence the copy. */
-	log_info("Capsule update target: %s (disk %d:%d)\n",
-		 *target_part_type == TARGET_PART_BOOT ? info.name : ptn_name,
-		 *devnum, partnum);
-	return partnum;
+	return num_partitions;
 }
 
 /**
- * qcom_configure_capsule_updates() - Configure the DFU string for capsule updates
+ * qcom_build_dfu_string() - build the dfu_alt_info string for the partitions
+ * @partitions: partitions accumulated across every scanned block device
+ * @num_partitions: number of valid entries in @partitions; also the number of
+ *                  valid fw_images[] entries, with matching indices
+ * @buf: output buffer
+ * @buf_size: size of @buf
+ *
+ * Builds an "interface devstring=alt;alt&interface devstring=alt" string (see
+ * dfu_config_interfaces() in drivers/dfu/dfu.c for the grammar), grouping
+ * partitions by originating device (devnum+uclass_id) with '&' so partitions
+ * split across LUNs (e.g. Kodiak/Lemans has xbl/xbl_config on one UFS LUN and
+ * the rest on another) still resolve. efi_firmware_raw_set_image() always calls
+ * dfu_write_by_alt() with a NULL interface/devstring, which forces the
+ * multi-interface '&'-grouped parser, so a single-group string would silently
+ * drop every partition on a LUN other than the first.
  *
- * 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.
+ * dfu_alt_add() assigns each token's dfu_alt_num by its position in the parsed
+ * string, so fw_images[i].dfu_alt_num is recorded here as tokens are appended,
+ * not derived from image_index (which isn't contiguous or string-ordered once
+ * components are missing). Relies on qcom_build_fw_images() having already
+ * filled fw_images[0..num_partitions) from this same @partitions array.
+ *
+ * Return: true on success, false if @buf was too small
+ */
+static bool qcom_build_dfu_string(struct qcom_partition_info *partitions,
+				  u32 num_partitions, char *buf, size_t buf_size)
+{
+	int prev_devnum = -1;
+	enum uclass_id prev_uclass_id = UCLASS_INVALID;
+	u32 i;
+	size_t len;
+
+	buf[0] = '\0';
+	len = 0;
+
+	for (i = 0; i < num_partitions; i++) {
+		struct qcom_partition_info *p = &partitions[i];
+		char token[48];
+		bool new_group = p->devnum != prev_devnum ||
+				  p->uclass_id != prev_uclass_id;
+
+		if (new_group) {
+			char prefix[24];
+
+			if (i != 0)
+				len = strlcat(buf, "&", buf_size);
+
+			switch (p->uclass_id) {
+			case UCLASS_SCSI:
+				snprintf(prefix, sizeof(prefix), "scsi %d=", p->devnum);
+				break;
+			case UCLASS_MMC:
+				snprintf(prefix, sizeof(prefix), "mmc %d=", p->devnum);
+				break;
+			default:
+				log_err("qcom capsule: unsupported storage uclass %d for %s\n",
+					p->uclass_id, p->name);
+				return false;
+			}
+			len = strlcat(buf, prefix, buf_size);
+		} else {
+			len = strlcat(buf, ";", buf_size);
+		}
+
+		if (p->uclass_id == UCLASS_MMC)
+			snprintf(token, sizeof(token), "%s part %u %u",
+				 p->name, p->devnum, p->part_num);
+		else
+			snprintf(token, sizeof(token), "%s part %u",
+				 p->name, p->part_num);
+		len = strlcat(buf, token, buf_size);
+
+		if (len >= buf_size) {
+			log_err("qcom capsule: dfu_alt_info string truncated\n");
+			return false;
+		}
+
+		fw_images[i].dfu_alt_num = i;
+		prev_devnum = p->devnum;
+		prev_uclass_id = p->uclass_id;
+	}
+
+	return true;
+}
+
+/**
+ * qcom_configure_capsule_updates() - Configure the DFU string and fw_images[]
+ * for capsule updates
  *
- * 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.
+ * Scans every probed block device, recording candidates for every qcom_image_map[]
+ * row, and builds fw_images[] and the matching dfu_alt_info string. 
  */
 void qcom_configure_capsule_updates(void)
 {
-	int ret = 0, partnum = -1, devnum;
-	static char dfu_string[32] = { 0 };
-	enum target_part_type target_part_type = 0;
-	enum uclass_id dev_uclass;
+	static char dfu_string[QCOM_DFU_STRING_LEN];
+	struct qcom_partition_info *partitions = qcom_partitions;
+	u32 num_partitions;
+	struct udevice *dev;
+	bool have_ufs = false;
+	int i, ret;
+
+	memset(qcom_cands, 0, sizeof(qcom_cands));
+	for (i = 0; i < ARRAY_SIZE(qcom_image_map); i++) {
+		qcom_cands[i].slot[SLOT_NONE].part_num = -1;
+		qcom_cands[i].slot[SLOT_A].part_num = -1;
+		qcom_cands[i].slot[SLOT_B].part_num = -1;
+	}
+	memset(partitions, 0, sizeof(qcom_partitions));
 
 	if (IS_ENABLED(CONFIG_SCSI)) {
-		/* Scan for SCSI devices */
 		ret = scsi_scan(false);
 		if (ret) {
 			debug("Failed to scan SCSI devices: %d\n", ret);
@@ -305,41 +566,83 @@ void qcom_configure_capsule_updates(void)
 		}
 	}
 
-	partnum = find_target_partition(&devnum, &dev_uclass, &target_part_type);
-	if (partnum < 0) {
-		log_err("Failed to find boot partition\n");
-		return;
-	}
-
 	/*
-	 * Set the fw_name based on the partition type. This causes the GUID to be different
-	 * so we will never accidentally flash a U-Boot image intended for XBL to the boot
-	 * partition.
+	 * Check to see if we have UFS storage, if so firmware MUST be on it and
+	 * we can skip all non-UFS block devices.
 	 */
-	switch (target_part_type) {
-	case TARGET_PART_UEFI:
-		fw_images[0].fw_name = u"UBOOT_UEFI_PARTITION";
-		break;
-	case TARGET_PART_XBL:
-		fw_images[0].fw_name = u"UBOOT_XBL_PARTITION";
-		break;
-	case TARGET_PART_BOOT:
-		fw_images[0].fw_name = u"UBOOT_BOOT_PARTITION";
+	uclass_foreach_dev_probe(UCLASS_UFS, dev) {
+		have_ufs = true;
 		break;
 	}
 
-	switch (dev_uclass) {
-	case UCLASS_SCSI:
-		snprintf(dfu_string, 32, "scsi %d=u-boot.bin part %d", devnum, partnum);
-		break;
-	case UCLASS_MMC:
-		snprintf(dfu_string, 32, "mmc 0=u-boot.bin part %d %d", devnum, partnum);
-		break;
-	default:
-		debug("Unsupported storage uclass: %d\n", dev_uclass);
+	uclass_foreach_dev_probe(UCLASS_BLK, dev) {
+		struct blk_desc *desc;
+
+		if (device_get_uclass_id(dev) != UCLASS_BLK)
+			continue;
+
+		desc = dev_get_uclass_plat(dev);
+
+		/* If we have a UFS then don't look at any other block devices */
+		if (have_ufs) {
+			if (device_get_uclass_id(dev->parent->parent) != UCLASS_UFS)
+				continue;
+		} else if (IS_ENABLED(CONFIG_MMC) && is_sd(desc)) {
+			/* If we don't have UFS, then firmware is on the eMMC */
+			log_debug("skipped SD-Card (devnum %d)\n", desc->devnum);
+			continue;
+		}
+
+		if (!desc || desc->part_type == PART_TYPE_UNKNOWN)
+			continue;
+
+		qcom_scan_device(desc, qcom_cands);
+	}
+
+	num_partitions = qcom_resolve_images(qcom_cands, partitions);
+	if (!num_partitions) {
+		log_err("qcom capsule: no updatable partitions found\n");
 		return;
 	}
-	log_debug("DFU string: '%s'\n", dfu_string);
+	if (partitions[0].image_index != 1)
+		log_warning("qcom capsule: U-Boot's own partition not found; configuring components only\n");
+
+	qcom_build_fw_images(partitions, num_partitions);
+	if (!qcom_build_dfu_string(partitions, num_partitions, dfu_string,
+				   sizeof(dfu_string)))
+		return;
+
+	log_debug("dfu_alt_info: %s\n", dfu_string);
 
+	update_info.num_images = num_partitions;
 	update_info.dfu_string = dfu_string;
 }
+
+/**
+ * efi_firmware_get_dfu_alt_num() - resolve an image_index to its DFU alt number
+ * @image_index: fw_images[].image_index to resolve
+ *
+ * Strong override of the __weak default in lib/efi_loader/efi_firmware.c.
+ * Qualcomm's fw_images[] is built at runtime and its image_index values aren't
+ * guaranteed contiguous (a board may lack some components), so dfu_alt_num
+ * can't be derived positionally -- look up the value recorded by
+ * qcom_build_dfu_string() for the matching image_index instead, mirroring the
+ * scan efi_firmware_get_image_type_id() already does.
+ *
+ * Falls back to the weak default's image_index - 1 if not found, which
+ * shouldn't happen since every image_index passed in comes from fw_images[].
+ *
+ * Return: the DFU alt setting number for @image_index
+ */
+u8 efi_firmware_get_dfu_alt_num(u8 image_index)
+{
+	struct efi_fw_image *fw_array = update_info.images;
+	int i;
+
+	for (i = 0; i < update_info.num_images; i++) {
+		if (fw_array[i].image_index == image_index)
+			return fw_array[i].dfu_alt_num;
+	}
+
+	return image_index - 1;
+}

-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.