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-43f034253...@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

Reply via email to