* [PATCH 1/2] qcom: capsule: build fw_images[] dynamically from the partition table
2026-08-13 5:56 [PATCH 0/2] qcom: capsule: build fw_images[] dynamically from the partition table Balaji Selvanathan via U-Boot
@ 2026-08-13 5:56 ` Balaji Selvanathan via U-Boot
2026-08-13 5:56 ` [PATCH 2/2] configs: qcom: ignore OsIndications for capsule-on-disk Balaji Selvanathan via U-Boot
2026-08-26 9:16 ` [PATCH 0/2] qcom: capsule: build fw_images[] dynamically from the partition table Balaji Selvanathan
2 siblings, 0 replies; 5+ messages in thread
From: Balaji Selvanathan via U-Boot @ 2026-08-13 5:56 UTC (permalink / raw)
To: Sumit Garg, u-boot-qcom, u-boot
Cc: Tom Rini, Ilias Apalodimas, Casey Connolly, Neil Armstrong,
Balaji Selvanathan, Aswin Murugan, Varadarajan Narayanan,
Quentin Schulz, Biswapriyo Nath
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 <balaji.selvanathan@oss.qualcomm.com>
---
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
^ permalink raw reply related [flat|nested] 5+ messages in thread