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

