From: Varadarajan Narayanan <[email protected]>

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 <[email protected]>
Signed-off-by: Aswin Murugan <[email protected]>
---
 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@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

Reply via email to