[PATCH v1 3/3] mach-snapdragon: Implement Qualcomm multi-DTB selection

Aswin Murugan aswin.murugan at oss.qualcomm.com
Tue Jan 6 13:21:34 CET 2026


From: Varadarajan Narayanan <varadarajan.narayanan at oss.qualcomm.com>

Add Qualcomm-specific implementation of multi-DTB selection by
overriding the weak efi_load_platform_fdt() function. This enables
automatic device tree selection based on SoC information read from
SMEM (Shared Memory).

The implementation:
- Reads combined-dtb.dtb from the boot partition
- Parses qcom,msm-id, qcom,board-id, and qcom,pmic-id properties
- Matches against runtime SoC info (platform ID, board variant, PMIC)
- Selects the appropriate DTB for the current hardware configuration

Signed-off-by: Varadarajan Narayanan <varadarajan.narayanan at oss.qualcomm.com>
Signed-off-by: Aswin Murugan <aswin.murugan at oss.qualcomm.com>
---
 arch/arm/mach-snapdragon/Makefile       |   1 +
 arch/arm/mach-snapdragon/efi_fdt_qcom.c | 337 ++++++++++++++++++++++++
 2 files changed, 338 insertions(+)
 create mode 100644 arch/arm/mach-snapdragon/efi_fdt_qcom.c

diff --git a/arch/arm/mach-snapdragon/Makefile b/arch/arm/mach-snapdragon/Makefile
index 343e825c6fd..70f36c8ec19 100644
--- a/arch/arm/mach-snapdragon/Makefile
+++ b/arch/arm/mach-snapdragon/Makefile
@@ -5,3 +5,4 @@
 obj-y += board.o
 obj-$(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) += capsule_update.o
 obj-$(CONFIG_OF_LIVE) += of_fixup.o
+obj-$(CONFIG_EFI_LOADER) += efi_fdt_qcom.o
diff --git a/arch/arm/mach-snapdragon/efi_fdt_qcom.c b/arch/arm/mach-snapdragon/efi_fdt_qcom.c
new file mode 100644
index 00000000000..f465fea71d8
--- /dev/null
+++ b/arch/arm/mach-snapdragon/efi_fdt_qcom.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Qualcomm multi-DTB selection for EFI boot
+ *
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc.
+ */
+
+#define LOG_CATEGORY LOGC_EFI
+
+#include <efi_device_path.h>
+#include <efi_loader.h>
+#include <log.h>
+#include <string.h>
+#include <fdt_support.h>
+#include <smem.h>
+#include <dm.h>
+#include <soc/qcom/socinfo.h>
+
+struct qcom_board_id {
+	u32 variant;
+	u32 sub_type;
+};
+
+struct qcom_pmic_id {
+	u32 pmic_ver[4];
+};
+
+struct qcom_plat_id {
+	u32 plat_id;
+	u32 soc_rev;
+};
+
+struct qcom_smem_pmic_info {
+	u32 model;
+	u32 version;
+};
+
+struct qcom_dt_entry {
+	u32 plat_id;
+	u32 variant;
+	u32 sub_type;
+	u32 soc_rev;
+	u32 pmic_ver[4];
+	void *fdt;
+	u32 size;
+	u32 idx;
+};
+
+struct qcom_dt_entry qcom_dt_entries[64];
+
+#define PLAT_ID_SIZE    sizeof(struct qcom_plat_id)
+#define BOARD_ID_SIZE   sizeof(struct qcom_board_id)
+#define PMIC_ID_SIZE    sizeof(struct qcom_pmic_id)
+#define DEV_TREE_VERSION_V1 1
+#define DEV_TREE_VERSION_V2 2
+#define DEV_TREE_VERSION_V3 3
+#define DT_ENTRY_V1_SIZE 0xC
+#define PMIC_EXT_VERSION 0x0000000000010003
+
+#define fdt_board_variant(_b, _i)	\
+	(fdt32_to_cpu(((struct qcom_board_id *)_b)[_i].variant) & 0xff)
+#define fdt_board_sub_type(_b, _i)	\
+	(fdt32_to_cpu(((struct qcom_board_id *)_b)[_i].sub_type) & 0xff)
+#define fdt_plat_plat_id(_b, _i)	\
+	(fdt32_to_cpu(((struct qcom_plat_id *)_b)[_i].plat_id) & 0xffff)
+#define fdt_plat_soc_rev(_b, _i)	\
+	fdt32_to_cpu(((struct qcom_plat_id *)_b)[_i].soc_rev)
+#define fdt_pmic_pmic_ver(_b, _i, _x)	\
+	fdt32_to_cpu(((struct qcom_pmic_id *)_b)[_i].pmic_ver[_x])
+
+static int qcom_parse_one_dtb(void *fdt, struct socinfo *socinfo, void **match)
+{
+	const char *model, *pmic, *board, *plat;
+	int pmiclen, boardlen, platlen, minplatlen;
+	struct qcom_smem_pmic_info *smem_pmic_info;
+	int dtbver, i, j, k;
+
+	*match = NULL;
+
+	model = fdt_getprop(fdt, 0, "model", NULL);
+	pmic = fdt_getprop(fdt, 0, "qcom,pmic-id", &pmiclen);
+	board = fdt_getprop(fdt, 0, "qcom,board-id", &boardlen);
+	plat = fdt_getprop(fdt, 0, "qcom,msm-id", &platlen);
+
+	if (pmic && pmiclen > 0 && board && boardlen > 0) {
+		if ((pmiclen % PMIC_ID_SIZE) || (boardlen % BOARD_ID_SIZE)) {
+			log_err("qcom,pmic-id (%d) or qcom,board-id (%d) not a multiple of (%lu, %lu)\n",
+				pmiclen, boardlen, PMIC_ID_SIZE, BOARD_ID_SIZE);
+			return -1;
+		}
+		dtbver = DEV_TREE_VERSION_V3;
+		minplatlen = PLAT_ID_SIZE;
+	} else if (board && boardlen > 0) {
+		if (boardlen % BOARD_ID_SIZE) {
+			log_err("qcom,board-id (%d) not a multiple of %lu\n",
+				boardlen, BOARD_ID_SIZE);
+			return -1;
+		}
+		dtbver = DEV_TREE_VERSION_V2;
+		minplatlen = PLAT_ID_SIZE;
+	} else {
+		dtbver = DEV_TREE_VERSION_V1;
+		minplatlen = DT_ENTRY_V1_SIZE;
+	}
+
+	if (!plat || platlen < 0) {
+		log_err("qcom,msm-id not found\n");
+		return -1;
+	} else if (platlen % minplatlen) {
+		log_err("qcom,msm-id (%d) not a multiple of %d\n",
+			platlen, minplatlen);
+		return -1;
+	}
+
+	smem_pmic_info = ((void *)socinfo) + socinfo->pmic_array_offset;
+
+	for (i = 0; i < (boardlen / BOARD_ID_SIZE); i++)
+		for (j = 0; j < (platlen / PLAT_ID_SIZE); j++)
+			if (pmic) {
+				for (k = 0; k < (pmiclen / PMIC_ID_SIZE); k++) {
+					if ((socinfo->id & 0x0000ffff) ==
+					    fdt_plat_plat_id(plat, j) &&
+					    socinfo->hw_plat ==
+					    fdt_board_variant(board, i) &&
+					    socinfo->hw_plat_subtype ==
+					    fdt_board_sub_type(board, i) &&
+					    socinfo->plat_ver ==
+					    fdt_plat_soc_rev(plat, j) &&
+					    smem_pmic_info[0].model ==
+					    fdt_pmic_pmic_ver(pmic, k, 0) &&
+					    smem_pmic_info[1].model ==
+					    fdt_pmic_pmic_ver(pmic, k, 1) &&
+					    smem_pmic_info[2].model ==
+					    fdt_pmic_pmic_ver(pmic, k, 2) &&
+					    smem_pmic_info[3].model ==
+					    fdt_pmic_pmic_ver(pmic, k, 3)) {
+						*match = fdt;
+						log_info("%8x| %8x| %8x| %4d.%03d| %8x| %8x| %8x| %8x| %s\n",
+							 fdt_board_variant(board, i),
+							 fdt_board_sub_type(board, i),
+							 fdt_plat_plat_id(plat, j),
+							 SOCINFO_MAJOR(fdt_plat_soc_rev(plat, j)),
+							 SOCINFO_MINOR(fdt_plat_soc_rev(plat, j)),
+							 fdt_pmic_pmic_ver(pmic, k, 0),
+							 fdt_pmic_pmic_ver(pmic, k, 1),
+							 fdt_pmic_pmic_ver(pmic, k, 2),
+							 fdt_pmic_pmic_ver(pmic, k, 3), model);
+						return 0;
+					}
+				}
+			} else {
+				if ((socinfo->id & 0x0000ffff) ==
+				    fdt_plat_plat_id(plat, j) &&
+				    socinfo->hw_plat ==
+				    fdt_board_variant(board, i) &&
+				    socinfo->hw_plat_subtype ==
+				    fdt_board_sub_type(board, i) &&
+				    socinfo->plat_ver ==
+				    fdt_plat_soc_rev(plat, j)) {
+					*match = fdt;
+					log_info("%8x| %8x| %8x| %4d.%03d| %8s| %8s| %8s| %8s| %s\n",
+						 fdt_board_variant(board, i),
+						 fdt_board_sub_type(board, i),
+						 fdt_plat_plat_id(plat, j),
+						 SOCINFO_MAJOR(fdt_plat_soc_rev(plat, j)),
+						 SOCINFO_MINOR(fdt_plat_soc_rev(plat, j)),
+						 "--", "--", "--", "--", model);
+					return 0;
+				}
+			}
+
+	return 0;
+}
+
+static struct fdt_header *qcom_parse_combined_dtb(struct fdt_header *fdt)
+{
+	int fdt_count = 0;
+	struct udevice *dev;
+	size_t size;
+	struct socinfo *socinfo;
+	void *match;
+	struct fdt_header *fdt_blob = fdt;
+
+	if (!fdt_valid(&fdt)) {
+		log_err("%s: fdt not valid!\n", __func__);
+		return NULL;
+	}
+
+	uclass_get_device(UCLASS_SMEM, 0, &dev);
+
+	socinfo = smem_get(dev, 0, SMEM_HW_SW_BUILD_ID, &size);
+	log_debug("%s: socinfo: %p\n", __func__, socinfo);
+
+	log_info("%-8s| %-8s| %-8s| %-8s| %-8s| %-8s| %-8s| %-8s| %s\n"
+		 "--------+---------+---------+---------+---------+---------+---------+---------+------\n",
+		 "Variant", "Sub Type", "Plat Id", "SoC Rev.",
+		 "pmic[0]", "pmic[1]", "pmic[2]", "pmic[3]", "Model");
+
+	while (fdt_valid(&fdt)) {
+		qcom_parse_one_dtb(fdt, socinfo, &match);
+
+		if (match)
+			return match;
+
+		fdt = ((void *)fdt) + fdt_totalsize(fdt);
+		fdt_count++;
+	}
+
+	if (fdt_count == 1)
+		return fdt_blob;
+
+	return NULL;
+}
+
+static void efi_load_qcom_fdt(efi_handle_t handle, void **_fdt,
+			      efi_uintn_t *_fdt_size)
+{
+	u32 i;
+	efi_uintn_t count;
+	u64 fdt_addr, tmp_addr;
+	efi_status_t ret;
+	u16 *fdt_name = u"/combined-dtb.dtb";
+	struct efi_handler *handler;
+	efi_handle_t *volume_handles = NULL;
+	struct efi_simple_file_system_protocol *v;
+	struct efi_file_handle *f = NULL;
+	void *match;
+
+	*_fdt = NULL;
+	*_fdt_size = 0;
+
+	ret = efi_locate_handle_buffer_int(BY_PROTOCOL,
+					   &efi_simple_file_system_protocol_guid,
+					   NULL, &count,
+					   (efi_handle_t **)&volume_handles);
+	if (ret != EFI_SUCCESS) {
+		log_err("%s: No block device found! (0x%lx)\n", __func__, ret);
+		return;
+	}
+
+	log_info("Using device tree: %ls\n", fdt_name);
+
+	/* Try to find combined-dtb.dtb on available volumes */
+	for (i = 0; i < count; i++) {
+		struct efi_file_handle *root;
+
+		ret = efi_search_protocol(volume_handles[i],
+					  &efi_simple_file_system_protocol_guid,
+					  &handler);
+		if (ret != EFI_SUCCESS)
+			continue;
+		ret = efi_protocol_open(handler, (void **)&v, efi_root, NULL,
+					EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+		if (ret != EFI_SUCCESS)
+			continue;
+
+		ret = EFI_CALL(v->open_volume(v, &root));
+		if (ret != EFI_SUCCESS)
+			goto out;
+
+		ret = EFI_CALL(root->open(root, &f, fdt_name,
+					  EFI_FILE_MODE_READ, 0));
+		if (ret != EFI_SUCCESS) {
+			log_warning("%s: Reading volume failed! (0x%lx)\n",
+				    __func__, ret);
+			continue;
+		} else {
+			log_debug("%s: %ls found!\n", __func__, fdt_name);
+			break;
+		}
+	}
+
+	if (!f)
+		goto out;
+
+	ret = efi_file_size(f, &count);
+	if (ret != EFI_SUCCESS)
+		goto out;
+
+	ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES,
+				 EFI_BOOT_SERVICES_DATA,
+				 efi_size_in_pages(count), &fdt_addr);
+	if (ret != EFI_SUCCESS) {
+		log_err("%s: Out of memory! (0x%lx bytes) (0x%lx)\n",
+			__func__, count, ret);
+		goto out;
+	}
+	ret = EFI_CALL(f->read(f, &count, (void *)(uintptr_t)fdt_addr));
+	if (ret != EFI_SUCCESS) {
+		log_err("%s: Can't read fdt! (0x%lx)\n", __func__, ret);
+		goto out;
+	}
+
+	match = qcom_parse_combined_dtb((void *)(uintptr_t)fdt_addr);
+	if (match) {
+		*_fdt_size = fdt_totalsize(match);
+
+		ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES,
+					 EFI_BOOT_SERVICES_DATA,
+					 efi_size_in_pages(*_fdt_size), &tmp_addr);
+		if (ret != EFI_SUCCESS) {
+			log_err("%s: Out of memory! (0x%lx bytes) (0x%lx)\n",
+				__func__, *_fdt_size, ret);
+			goto out;
+		}
+		/*
+		 * The memory allocated for reading the fdt is eventually freed
+		 * by the caller using the address we return. Hence copy it out
+		 */
+		memcpy((void *)(uintptr_t)tmp_addr, match, *_fdt_size);
+		*_fdt = (void *)(uintptr_t)tmp_addr;
+
+		log_debug("%s: fdt at 0x%p %lu\n", __func__, *_fdt, *_fdt_size);
+	} else {
+		log_warning("%s: No FDT matched!!\n", __func__);
+	}
+out:
+	efi_free_pages(fdt_addr, efi_size_in_pages(count));
+
+	efi_free_pool(volume_handles);
+}
+
+/**
+ * efi_load_platform_fdt() - Platform-specific FDT loading (Qualcomm override)
+ *
+ * @handle:	handle of loaded image
+ * @fdt:	on return device-tree, must be freed via efi_free_pages()
+ * @fdt_size:	buffer size
+ *
+ * This function overrides the weak function in lib/efi_loader/efi_fdt.c
+ * to provide Qualcomm-specific multi-DTB selection based on SoC info.
+ */
+void efi_load_platform_fdt(efi_handle_t handle, void **fdt,
+			   efi_uintn_t *fdt_size)
+{
+	efi_load_qcom_fdt(handle, fdt, fdt_size);
+}
-- 
2.34.1



More information about the U-Boot mailing list