diff --git a/MAINTAINERS b/MAINTAINERS index 0dcc7243124e..4d54a61327c7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -688,7 +688,6 @@ F: drivers/phy/msm8916-usbh-phy.c F: drivers/phy/qcom/ F: drivers/serial/serial_msm.c F: drivers/serial/serial_msm_geni.c -F: drivers/smem/msm_smem.c F: drivers/spmi/spmi-msm.c F: drivers/usb/host/ehci-msm.c N: qcom diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index f624675eadf8..1da0677d85b3 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1140,9 +1140,8 @@ config ARCH_SNAPDRAGON select DM_RESET select POWER_DOMAIN select GPIO_EXTRA_HEADER - select MSM_SMEM select OF_CONTROL - select SMEM + select QCOM_SMEM select SPMI select BOARD_LATE_INIT select OF_BOARD diff --git a/arch/arm/cpu/armv8/cache_v8.c b/arch/arm/cpu/armv8/cache_v8.c index 39479df7b21f..72c6eb200f2e 100644 --- a/arch/arm/cpu/armv8/cache_v8.c +++ b/arch/arm/cpu/armv8/cache_v8.c @@ -163,7 +163,7 @@ u64 get_tcr(u64 *pips, u64 *pva_bits) static int pte_type(u64 *pte) { - return *pte & PTE_TYPE_MASK; + return *pte & PTE_TYPE_VALID ? *pte & PTE_TYPE_MASK : PTE_TYPE_FAULT; } /* Returns the LSB number for a PTE on level */ @@ -388,13 +388,18 @@ static void map_range(u64 virt, u64 phys, u64 size, int level, } } -void mmu_map_region(phys_addr_t addr, u64 size, bool emergency) +void mmu_map_region(phys_addr_t addr, u64 size, bool cached, bool emergency) { u64 va_bits; int level = 0; - u64 attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) | PTE_BLOCK_INNER_SHARE; + u64 attrs; - attrs |= PTE_TYPE_BLOCK | PTE_BLOCK_AF; + if (cached) + attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL); + else + attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL_NC); + + attrs |= PTE_BLOCK_INNER_SHARE | PTE_TYPE_BLOCK | PTE_BLOCK_AF; get_tcr(NULL, &va_bits); if (va_bits < 39) @@ -940,6 +945,18 @@ static u64 set_one_region(u64 start, u64 size, u64 attrs, bool flag, int level) u64 levelsize = 1ULL << levelshift; u64 *pte = find_pte(start, level); + /* + * If we're trying to unmap a region then check if it's already unmapped or if it's bigger + * then the PTE we're looking at right now, in the first case we can do nothing and in the + * second case we just need to unmap this page/block. + * Otherwise we will needlessly create new tables until we have traversed every single page + * in the region. + */ + if (attrs == PTE_TYPE_FAULT && (pte_type(pte) == PTE_TYPE_FAULT || size >= levelsize)) { + *pte &= ~(PMD_ATTRMASK | PTE_TYPE_MASK); + return levelsize; + } + /* Can we can just modify the current level block PTE? */ if (is_aligned(start, size, levelsize)) { if (flag) { @@ -1082,6 +1099,10 @@ void mmu_change_region_attr(phys_addr_t addr, size_t siz, u64 attrs) gd->arch.tlb_addr + gd->arch.tlb_size); __asm_invalidate_tlb_all(); + /* If we were unmapping a region then we're done! */ + if (attrs == PTE_TYPE_FAULT) + return; + mmu_change_region_attr_nobreak(addr, siz, attrs); } diff --git a/arch/arm/dts/qcs615-ride-u-boot.dtsi b/arch/arm/dts/qcs615-ride-u-boot.dtsi index 68fffc70fcb4..687e9a67a9f0 100644 --- a/arch/arm/dts/qcs615-ride-u-boot.dtsi +++ b/arch/arm/dts/qcs615-ride-u-boot.dtsi @@ -11,4 +11,30 @@ <0x0 0xc0000000 0x0 0xc0000000>, <0x1 0x80000000 0x1 0x00000000>; }; + + reboot-mode { + compatible = "nvmem-reboot-mode"; + nvmem-cells = <&reboot_reason>; + nvmem-cell-names = "reboot-mode"; + + mode-bootloader = <0x02>; + mode-recovery = <0x01>; + }; +}; + +&pm8150_0 { + /* Virtual NVMEM node for PON-based reboot reason storage */ + nvram@800 { + compatible = "qcom,spmi-sdam"; + reg = <0x800>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x00 0x800 0x100>; + + /* Reboot reason cell at PON_SOFT_RB_SPARE (0x88F) */ + reboot_reason: reboot-reason@8f { + reg = <0x8f 0x1>; + bits = <1 7>; + }; + }; }; diff --git a/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi b/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi index c3ec4a317f7c..3c0d61861c75 100644 --- a/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi +++ b/arch/arm/dts/qcs6490-rb3gen2-u-boot.dtsi @@ -3,21 +3,29 @@ * Copyright (c) 2024 Linaro Ltd. */ / { - /* When running as the primary bootloader there is no prior - * stage to populate the memory layout for us. We *should* - * have two nodes here, but ABL does NOT like that. - * sooo we're stuck with this. - */ - memory@80000000 { - device_type = "memory"; - reg = <0 0x80000000 0 0x3A800000>, - <0 0xC0000000 0 0x01800000>, - <0 0xC3400000 0 0x3CC00000>, - <1 0x00000000 1 0x00000000>; + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + axon_dma_mem: axon-dma@80600000 { + reg = <0x0 0x80600000 0x0 0x100000>; + no-map; + }; + + xbl_dt_mem: xbl-dt@80894000 { + reg = <0x0 0x80894000 0x0 0x40000>; + no-map; + }; + + pil_reserved_mem: pil-reserved@84300000 { + reg = <0x0 0x84300000 0x0 0x16b00000>; + no-map; + }; + + display_mem: display@e1000000 { + reg = <0x0 0xe1000000 0x0 0x2400000>; + no-map; + }; }; }; - -// RAM Entry 0 : Base 0x0080000000 Size 0x003A800000 -// RAM Entry 1 : Base 0x00C0000000 Size 0x0001800000 -// RAM Entry 2 : Base 0x00C3400000 Size 0x003CC00000 -// RAM Entry 3 : Base 0x0100000000 Size 0x0100000000 diff --git a/arch/arm/dts/talos-evk-u-boot.dtsi b/arch/arm/dts/talos-evk-u-boot.dtsi new file mode 100644 index 000000000000..f51d58b26ff5 --- /dev/null +++ b/arch/arm/dts/talos-evk-u-boot.dtsi @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +/ { + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + hyp_mem: hyp@80000000 { + reg = <0x0 0x80000000 0x0 0x600000>; + no-map; + }; + + xbl_boot_mem: xbl-boot@85d00000 { + reg = <0x0 0x85d00000 0x0 0x200000>; + no-map; + }; + + aop_mem: aop@85f00000 { + reg = <0x0 0x85f00000 0x0 0x20000>; + no-map; + }; + + xbl_dt_mem: xbl-dt@85f40000 { + reg = <0x0 0x85f40000 0x0 0x30000>; + no-map; + }; + + tz_stat_mem: tz-stat@86200000 { + reg = <0x0 0x86200000 0x0 0x100000>; + no-map; + }; + + tags_mem: tags@86300000 { + reg = <0x0 0x86300000 0x0 0x1200000>; + no-map; + }; + + tz_mem: tz@87500000 { + reg = <0x0 0x87500000 0x0 0x500000>; + no-map; + }; + + tzapps_mem: tzapps@87a00000 { + reg = <0x0 0x87a00000 0x0 0x1c00000>; + no-map; + }; + + pil_mem: pil@8ab00000 { + reg = <0x0 0x8ab00000 0x0 0xcc17000>; + no-map; + }; + + secure_dsp_mem: secure-dsp@a0000000 { + reg = <0x0 0xa0000000 0x0 0x1600000>; + no-map; + }; + }; +}; diff --git a/arch/arm/include/asm/system.h b/arch/arm/include/asm/system.h index 5ed6833c155c..52ab86112fb1 100644 --- a/arch/arm/include/asm/system.h +++ b/arch/arm/include/asm/system.h @@ -306,9 +306,10 @@ void flush_l3_cache(void); * * @start: Start address of the region * @size: Size of the region + * @cached: Map memory as cacheable or not * @emerg: Also map the region in the emergency table */ -void mmu_map_region(phys_addr_t start, u64 size, bool emerg); +void mmu_map_region(phys_addr_t start, u64 size, bool cached, bool emerg); /** * mmu_change_region_attr() - change a mapped region attributes diff --git a/arch/arm/mach-snapdragon/Kconfig b/arch/arm/mach-snapdragon/Kconfig index d3de8693b5a8..6e90376007e5 100644 --- a/arch/arm/mach-snapdragon/Kconfig +++ b/arch/arm/mach-snapdragon/Kconfig @@ -42,4 +42,35 @@ config SYS_CONFIG_NAME Based on this option include/configs/.h header will be used for board configuration. +config QCOM_FIT_MULTIDTB + bool "Enable FIT multi-DTB selection for Qualcomm platforms" + depends on FIT + help + Enable FIT multi-DTB selection for Qualcomm platforms. + This allows U-Boot to select the appropriate device tree + from a FIT image. + +choice + prompt "Qualcomm boot0.h workaround" + optional + help + While U-Boot on Qualcomm platforms doesn't generally need compile-time + adjustments based on the target SoC, workarounds in boot0.h can't + practically detect the SoC at runtime. Enable one of these workarounds + if you know you need it. + +config BOOT0_MSM8916_PSCI_WORKAROUND + bool "boot0.h workaround for buggy PSCI on the msm8916 SoC" + help + Select this if you are building U-Boot proper for an msm8916 board that + uses the buggy PSCI implementation. + +config QCOM_EL2_GUNYAH_EXIT_SUPPORT + bool "Enable early EL2 switch by exiting Gunyah" + help + Exit Gunyah hypervisor and switch to EL2 during early boot. This must + happen before EL register configuration in start.S so that U-Boot can + run properly at EL2. +endchoice + endif diff --git a/arch/arm/mach-snapdragon/Makefile b/arch/arm/mach-snapdragon/Makefile index 343e825c6fdd..63b6db05552f 100644 --- a/arch/arm/mach-snapdragon/Makefile +++ b/arch/arm/mach-snapdragon/Makefile @@ -2,6 +2,7 @@ # # (C) Copyright 2015 Mateusz Kulikowski -obj-y += board.o +obj-y += board.o dram.o obj-$(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) += capsule_update.o obj-$(CONFIG_OF_LIVE) += of_fixup.o +obj-$(CONFIG_QCOM_FIT_MULTIDTB) += qcom_fit_multidtb.o diff --git a/arch/arm/mach-snapdragon/board.c b/arch/arm/mach-snapdragon/board.c index 829a0109ac78..6acbdbb1f6a1 100644 --- a/arch/arm/mach-snapdragon/board.c +++ b/arch/arm/mach-snapdragon/board.c @@ -19,7 +19,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -30,134 +32,23 @@ #include #include #include +#include #include +#include #include +#include "qcom_fit_multidtb.h" #include "qcom-priv.h" DECLARE_GLOBAL_DATA_PTR; enum qcom_boot_source qcom_boot_source __section(".data") = 0; +enum qcom_memmap_source qcom_memmap_source __section(".data") = 0; static struct mm_region rbx_mem_map[CONFIG_NR_DRAM_BANKS + 2] = { { 0 } }; struct mm_region *mem_map = rbx_mem_map; -static struct { - phys_addr_t start; - phys_size_t size; -} prevbl_ddr_banks[CONFIG_NR_DRAM_BANKS] __section(".data") = { 0 }; - -int dram_init(void) -{ - /* - * gd->ram_base / ram_size have been setup already - * in qcom_parse_memory(). - */ - return 0; -} - -static int ddr_bank_cmp(const void *v1, const void *v2) -{ - const struct { - phys_addr_t start; - phys_size_t size; - } *res1 = v1, *res2 = v2; - - if (!res1->size) - return 1; - if (!res2->size) - return -1; - - return (res1->start >> 24) - (res2->start >> 24); -} - -/* This has to be done post-relocation since gd->bd isn't preserved */ -static void qcom_configure_bi_dram(void) -{ - int i; - - for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { - gd->bd->bi_dram[i].start = prevbl_ddr_banks[i].start; - gd->bd->bi_dram[i].size = prevbl_ddr_banks[i].size; - } -} - -int dram_init_banksize(void) -{ - qcom_configure_bi_dram(); - - return 0; -} - -/** - * The generic memory parsing code in U-Boot lacks a few things that we - * need on Qualcomm: - * - * 1. It sets gd->ram_size and gd->ram_base to represent a single memory block - * 2. setup_dest_addr() later relocates U-Boot to ram_base + ram_size, the end - * of that first memory block. - * - * This results in all memory beyond U-Boot being unusable in Linux when booting - * with EFI. - * - * Since the ranges in the memory node may be out of order, the only way for us - * to correctly determine the relocation address for U-Boot is to parse all - * memory regions and find the highest valid address. - * - * We can't use fdtdec_setup_memory_banksize() since it stores the result in - * gd->bd, which is not yet allocated. - * - * @fdt: FDT blob to parse /memory node from - * - * Return: 0 on success or -ENODATA if /memory node is missing or incomplete - */ -static int qcom_parse_memory(const void *fdt) -{ - int offset; - const fdt64_t *memory; - int memsize; - phys_addr_t ram_end = 0; - int i, j, banks; - - offset = fdt_path_offset(fdt, "/memory"); - if (offset < 0) - return -ENODATA; - - memory = fdt_getprop(fdt, offset, "reg", &memsize); - if (!memory) - return -ENODATA; - - banks = min(memsize / (2 * sizeof(u64)), (ulong)CONFIG_NR_DRAM_BANKS); - - if (memsize / sizeof(u64) > CONFIG_NR_DRAM_BANKS * 2) - log_err("Provided more than the max of %d memory banks\n", CONFIG_NR_DRAM_BANKS); - - if (banks > CONFIG_NR_DRAM_BANKS) - log_err("Provided more memory banks than we can handle\n"); - - for (i = 0, j = 0; i < banks * 2; i += 2, j++) { - prevbl_ddr_banks[j].start = get_unaligned_be64(&memory[i]); - prevbl_ddr_banks[j].size = get_unaligned_be64(&memory[i + 1]); - if (!prevbl_ddr_banks[j].size) { - j--; - continue; - } - ram_end = max(ram_end, prevbl_ddr_banks[j].start + prevbl_ddr_banks[j].size); - } - - if (!banks || !prevbl_ddr_banks[0].size) - return -ENODATA; - - /* Sort our RAM banks -_- */ - qsort(prevbl_ddr_banks, banks, sizeof(prevbl_ddr_banks[0]), ddr_bank_cmp); - - gd->ram_base = prevbl_ddr_banks[0].start; - gd->ram_size = ram_end - gd->ram_base; - - return 0; -} - static void show_psci_version(void) { struct arm_smccc_res res; @@ -225,7 +116,7 @@ int board_fdt_blob_setup(void **fdtp) /* Prefer memory information from internal DT if it's present */ if (internal_valid) - ret = qcom_parse_memory(internal_fdt); + ret = qcom_parse_memory(internal_fdt, true); if (ret < 0 && external_valid) { /* No internal FDT or it lacks a proper /memory node. @@ -234,7 +125,7 @@ int board_fdt_blob_setup(void **fdtp) if (internal_valid) debug("No memory info in internal FDT, falling back to external\n"); - ret = qcom_parse_memory(external_fdt); + ret = qcom_parse_memory(external_fdt, false); } if (ret < 0) @@ -365,9 +256,14 @@ static const char *get_cmdline(void) void qcom_set_serialno(void) { - const char *cmdline = get_cmdline(); + const char *cmdline; char serial[32]; + if (!qcom_socinfo_init()) + return; + + cmdline = get_cmdline(); + if (!cmdline) { log_debug("Failed to get bootargs\n"); return; @@ -489,21 +385,37 @@ static void configure_env(void) qcom_set_serialno(); } -void qcom_show_boot_source(void) +static void qcom_show_boot_context(void) { - const char *name = "UNKNOWN"; + const char *boot_source = "UNKNOWN"; + const char *memmap_source = "UNKNOWN"; switch (qcom_boot_source) { case QCOM_BOOT_SOURCE_ANDROID: - name = "ABL"; + boot_source = "ABL"; break; case QCOM_BOOT_SOURCE_XBL: - name = "XBL"; + boot_source = "XBL"; break; } - log_info("U-Boot loaded from %s\n", name); - env_set("boot_source", name); + log_info("U-Boot loaded from %s\n", boot_source); + env_set("boot_source", boot_source); + + switch (qcom_memmap_source) { + case QCOM_MEMMAP_SOURCE_INTERNAL_FDT: + memmap_source = "INTERNAL_FDT"; + break; + case QCOM_MEMMAP_SOURCE_EXTERNAL_FDT: + memmap_source = "EXTERNAL_FDT"; + break; + case QCOM_MEMMAP_SOURCE_SMEM: + memmap_source = "SMEM"; + break; + } + + log_info("Memory map loaded from %s\n", memmap_source); + env_set("memmap_source", memmap_source); } void __weak qcom_late_init(void) @@ -571,13 +483,22 @@ int board_late_init(void) memcpy((void *)addr, (void *)gd->fdt_blob, fdt32_to_cpu(fdt_blob->totalsize)); + /* Initialise SMEM if it wasn't done already and ensure it's memory is mapped */ + qcom_smem_init(); + configure_env(); qcom_late_init(); - qcom_show_boot_source(); + qcom_show_boot_context(); /* Configure the dfu_string for capsule updates */ qcom_configure_capsule_updates(); + /* Try FIT multi-DTB selection if enabled */ + if (IS_ENABLED(CONFIG_QCOM_FIT_MULTIDTB)) { + if (qcom_fit_multidtb_setup()) + log_debug("FIT multi-DTB selection not available or failed\n"); + } + return 0; } @@ -623,25 +544,34 @@ u64 get_page_table_size(void) return SZ_1M; } +struct mem_resource_attrs { + fdt_addr_t start; + fdt_addr_t size; + u64 attrs; +}; + static int fdt_cmp_res(const void *v1, const void *v2) { - const struct fdt_resource *res1 = v1, *res2 = v2; + const struct mem_resource_attrs *res1 = v1, *res2 = v2; return res1->start - res2->start; } -#define N_RESERVED_REGIONS 32 +#define N_RESERVED_REGIONS 64 -/* Mark all no-map regions as PTE_TYPE_FAULT to prevent speculative access. +/* Map and unmap reserved memory regions as appropriate. + * Mark all no-map regions as PTE_TYPE_FAULT to prevent speculative access. * On some platforms this is enough to trigger a security violation and trap * to EL3. + * Regions that may be accessed by drivers get mapped explicitly. */ -static void carve_out_reserved_memory(void) +static void configure_reserved_memory(void) { - static struct fdt_resource res[N_RESERVED_REGIONS] = { 0 }; + static struct mem_resource_attrs res[N_RESERVED_REGIONS] = { 0 }; int parent, rmem, count, i = 0; phys_addr_t start; size_t size; + u64 attrs; /* Some reserved nodes must be carved out, as the cache-prefetcher may otherwise * attempt to access them, causing a security exception. @@ -652,12 +582,17 @@ static void carve_out_reserved_memory(void) return; } - /* Collect the reserved memory regions */ + /* Collect the reserved memory regions and appropriate attrs */ fdt_for_each_subnode(rmem, gd->fdt_blob, parent) { const fdt32_t *ptr; - int len; + attrs = PTE_TYPE_FAULT; + /* If the no-map property isn't set then the region is valid */ if (!fdt_getprop(gd->fdt_blob, rmem, "no-map", NULL)) - continue; + attrs = PTE_TYPE_VALID | PTE_BLOCK_MEMTYPE(MT_NORMAL); + /* If the compatible property is set then this region may be accessed by drivers and should + * be marked valid too. */ + if (fdt_getprop(gd->fdt_blob, rmem, "compatible", NULL)) + attrs = PTE_TYPE_VALID | PTE_BLOCK_MEMTYPE(MT_NORMAL); if (i == N_RESERVED_REGIONS) { log_err("Too many reserved regions!\n"); @@ -668,48 +603,109 @@ static void carve_out_reserved_memory(void) * fdt_get_resource() takes ~70ms on SDM845, but open-coding the happy path here * takes <1ms... Oh the woes of no dcache. */ - ptr = fdt_getprop(gd->fdt_blob, rmem, "reg", &len); + ptr = fdt_getprop(gd->fdt_blob, rmem, "reg", NULL); if (ptr) { /* Qualcomm devices use #address/size-cells = <2> but all reserved regions are within * the 32-bit address space. So we can cheat here for speed. */ res[i].start = fdt32_to_cpu(ptr[1]); - res[i].end = res[i].start + fdt32_to_cpu(ptr[3]); + res[i].size = fdt32_to_cpu(ptr[3]); + res[i].attrs = attrs; i++; } } /* Sort the reserved memory regions by address */ count = i; - qsort(res, count, sizeof(struct fdt_resource), fdt_cmp_res); + qsort(res, count, sizeof(res[0]), fdt_cmp_res); + debug("Mapping %d regions!\n", count); /* Now set the right attributes for them. Often a lot of the regions are tightly packed together - * so we can optimise the number of calls to mmu_change_region_attr() by combining adjacent + * so we can optimise the number of calls to mmu_change_region_attr_nobreak() by combining adjacent * regions. */ - start = ALIGN_DOWN(res[0].start, SZ_2M); - size = ALIGN(res[0].end - start, SZ_2M); + start = res[0].start; + size = res[0].size; + attrs = res[0].attrs; + /* For each region after the first one, either increase the `size` to eventually be mapped or + * map the region we have and start a new one, this allows us to reduce the number of calls to + * mmu_map_region(). The loop is therefore "lagging" behind by one iteration. */ for (i = 1; i <= count; i++) { - /* We ideally want to 2M align everything for more efficient pagetables, but we must avoid - * overwriting reserved memory regions which shouldn't be mapped as FAULT (like those with - * compatible properties). - * If within 2M of the previous region, bump the size to include this region. Otherwise - * start a new region. - */ - if (i == count || start + size < res[i].start - SZ_2M) { - debug(" 0x%016llx - 0x%016llx: reserved\n", - start, start + size); - mmu_change_region_attr(start, size, PTE_TYPE_FAULT); - /* If this is the final region then quit here before we index - * out of bounds... - */ + /* If i == count we are done, just map the last region. If the last region is + * too far away or the attrs don't match then map the meta-region we have and + * start a new one. */ + if (i == count || start + size < res[i].start - SZ_8K || attrs != res[i].attrs) { + debug(" 0x%016llx - 0x%016llx: %s\n", + start, start + size, attrs == PTE_TYPE_FAULT ? "FAULT" : "VALID"); + /* No need to break-before-make since dcache is disabled */ + mmu_change_region_attr_nobreak(start, size, attrs); + /* We have now mapped all the regions */ if (i == count) break; - start = ALIGN_DOWN(res[i].start, SZ_2M); - size = ALIGN(res[i].end - start, SZ_2M); + /* Start a new meta-region */ + start = res[i].start; + size = res[i].size; + attrs = res[i].attrs; } else { - /* Bump size if this region is immediately after the previous one */ - size = ALIGN(res[i].end - start, SZ_2M); + /* This region is next to (<8K) the previous one so combine them. + * Accounting for any small (<8K) gap. */ + size = (res[i].start - start) + res[i].size; + } + } +} + +/** + * efi_add_known_memory() - Add platform-specific reserved memory to EFI map + * + * This function is called by the EFI memory initialization code to allow + * platforms to add their reserved memory regions to the EFI memory map. + * For Qualcomm platforms, this parses the reserved-memory nodes from the + * device tree and adds them to the EFI memory map. + */ +void efi_add_known_memory(void) +{ + ofnode parent, node; + fdt_addr_t addr; + fdt_size_t size; + const char *name; + + if (!IS_ENABLED(CONFIG_EFI_LOADER)) + return; + + /* Parse reserved-memory nodes from device tree */ + parent = ofnode_path("/reserved-memory"); + if (!ofnode_valid(parent)) { + log_debug("No reserved-memory node found in device tree\n"); + return; + } + + log_debug("Adding reserved-memory regions to EFI memory map\n"); + + ofnode_for_each_subnode(node, parent) { + if (!ofnode_is_enabled(node)) + continue; + + addr = ofnode_get_addr_size_index(node, 0, &size); + if (addr != FDT_ADDR_T_NONE) { + efi_status_t ret; + + name = ofnode_get_name(node); + ret = efi_add_memory_map(addr, size, + EFI_RESERVED_MEMORY_TYPE); + + if (ret != EFI_SUCCESS) { + log_err("Failed to reserve %s (0x%llx-0x%llx): %lu\n", + name ? name : "unknown", + (unsigned long long)addr, + (unsigned long long)(addr + size), + ret & ~EFI_ERROR_MASK); + } else { + log_debug("Reserved %s: 0x%llx-0x%llx (%llu KB)\n", + name ? name : "unknown", + (unsigned long long)addr, + (unsigned long long)(addr + size), + (unsigned long long)(size / 1024)); + } } } } @@ -745,11 +741,15 @@ void enable_caches(void) gd->arch.tlb_addr = tlb_addr; gd->arch.tlb_size = tlb_size; - /* We do the carveouts only for QCS404, for now. */ - if (fdt_node_check_compatible(gd->fdt_blob, 0, "qcom,qcs404") == 0) { + /* + * On some boards speculative access may trigger a NOC or XPU violation so explicitly mark + * reserved regions as inacessible (PTE_TYPE_FAULT) + */ + if (qcom_memmap_source == QCOM_MEMMAP_SOURCE_SMEM || + fdt_node_check_compatible(gd->fdt_blob, 0, "qcom,qcs404") == 0) { carveout_start = get_timer(0); /* Takes ~20-50ms on SDM845 */ - carve_out_reserved_memory(); + configure_reserved_memory(); debug("carveout time: %lums\n", get_timer(carveout_start)); } dcache_enable(); diff --git a/arch/arm/mach-snapdragon/capsule_update.c b/arch/arm/mach-snapdragon/capsule_update.c index 586682434b73..eb900d80b421 100644 --- a/arch/arm/mach-snapdragon/capsule_update.c +++ b/arch/arm/mach-snapdragon/capsule_update.c @@ -8,30 +8,55 @@ #define pr_fmt(fmt) "QCOM-FMP: " fmt -#include -#include +#include #include #include #include #include -#include #include +#include +#include +#include #include - #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: - * - * - 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 - * - * Which partition actually has U-Boot on it is determined based on the - * qcom_boot_source variable and additional logic in find_target_partition(). + * Capsule update support with conditional FIT vs RAW implementation: + * - FIT capsules: Comprehensive partition discovery with dynamic fw_images + * - RAW capsules: Existing single-partition approach with static fw_images */ + +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_FIT +#define MAX_DFU_STRING_SIZE 2048 +#define MAX_PARTITION_GROUPS 64 +#define MAX_PARTITIONS_PER_LUN 64 +#define MAX_PARTITIONS_TO_SCAN 128 +#define MAX_LUN_GROUPS 16 + +struct qcom_partition_info { + char name[32]; /* "uefi_a", "boot_b", etc. */ + char base_name[32]; /* "uefi", "boot", etc. */ + char slot_suffix[4]; /* "_a", "_b", or "" */ + int lun; /* SCSI LUN number */ + int partition_num; /* Partition number within LUN */ + bool is_active; /* From GPT vendor attributes */ + bool is_bootable; /* From GPT vendor attributes */ +}; + +struct partition_group { + char base_name[32]; + struct qcom_partition_info *a_slot; + struct qcom_partition_info *b_slot; + struct qcom_partition_info *no_slot; +}; + +struct lun_group { + int lun_number; + struct qcom_partition_info *partitions[MAX_PARTITIONS_PER_LUN]; /* Max partitions per LUN */ + int partition_count; +}; +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_FIT */ + struct efi_fw_image fw_images[] = { { .image_index = 1, @@ -39,18 +64,26 @@ struct efi_fw_image fw_images[] = { }; struct efi_capsule_update_info update_info = { - /* Filled in by configure_dfu_string() */ + /* Filled in by qcom_configure_capsule_updates() */ .dfu_string = NULL, .num_images = ARRAY_SIZE(fw_images), .images = fw_images, }; +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_RAW enum target_part_type { TARGET_PART_UEFI = 1, TARGET_PART_XBL, TARGET_PART_BOOT, }; +enum ab_slot { + SLOT_NONE, + SLOT_A, + SLOT_B, +}; +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_RAW */ + /* LSB first */ struct part_slot_status { u16: 2; @@ -61,26 +94,6 @@ struct part_slot_status { u16 tries_remaining : 4; }; -enum ab_slot { - SLOT_NONE, - SLOT_A, - 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,6 +111,25 @@ static bool is_sd(struct blk_desc *desc) return IS_SD(mmc) != 0U; } +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_RAW +/* + * RAW Capsule Support + */ + +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; +} + /* * 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. @@ -156,7 +188,7 @@ static int find_target_partition(int *devnum, enum uclass_id *uclass, * 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 (!strncmp(info.name, "uefi_", strlen("uefi_"))) { /* * If U-Boot was chainloaded somehow we can't be flashed to * the uefi partition @@ -263,7 +295,7 @@ static int find_target_partition(int *devnum, enum uclass_id *uclass, } /* Found no candidate partitions */ - return -1; + return -ENOENT; found: if (desc) { @@ -278,18 +310,7 @@ static int find_target_partition(int *devnum, enum uclass_id *uclass, return partnum; } -/** - * qcom_configure_capsule_updates() - Configure the DFU string for capsule updates - * - * 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. - * - * 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. - */ -void qcom_configure_capsule_updates(void) +static void configure_raw_capsule_updates(void) { int ret = 0, partnum = -1, devnum; static char dfu_string[32] = { 0 }; @@ -297,7 +318,6 @@ void qcom_configure_capsule_updates(void) enum uclass_id dev_uclass; if (IS_ENABLED(CONFIG_SCSI)) { - /* Scan for SCSI devices */ ret = scsi_scan(false); if (ret) { debug("Failed to scan SCSI devices: %d\n", ret); @@ -339,7 +359,624 @@ void qcom_configure_capsule_updates(void) debug("Unsupported storage uclass: %d\n", dev_uclass); return; } - log_debug("DFU string: '%s'\n", dfu_string); + log_debug("RAW DFU string: '%s'\n", dfu_string); + + /* Set RAW configuration state */ update_info.dfu_string = dfu_string; + update_info.images = fw_images; + update_info.num_images = ARRAY_SIZE(fw_images); + + log_info("RAW capsule update configured (single partition: %s)\n", + target_part_type == TARGET_PART_UEFI ? "uefi" : + target_part_type == TARGET_PART_XBL ? "xbl" : "boot"); +} +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_RAW */ + +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_FIT +/* + * FIT Capsule Support - Implementation + */ + +static void parse_partition_name(const char *full_name, char *base_name, char *slot_suffix) +{ + char *underscore = strrchr(full_name, '_'); + + if (underscore && (strcmp(underscore, "_a") == 0 || strcmp(underscore, "_b") == 0)) { + /* Has A/B suffix */ + size_t base_len = underscore - full_name; + + strlcpy(base_name, full_name, base_len + 1); + strcpy(slot_suffix, underscore); + } else { + /* No A/B suffix */ + strcpy(base_name, full_name); + slot_suffix[0] = '\0'; + } +} + +static void parse_partition_info(struct qcom_partition_info *part, + struct disk_partition *info, + int lun, int partnum) +{ + struct part_slot_status *slot_status; + + strlcpy(part->name, info->name, sizeof(part->name)); + part->lun = lun; + part->partition_num = partnum; + + /* Parse slot status from GPT vendor attributes */ + slot_status = (struct part_slot_status *)&info->type_flags; + part->is_active = slot_status->active; + part->is_bootable = !slot_status->unbootable; + + /* Extract base name and slot suffix */ + parse_partition_name(part->name, part->base_name, part->slot_suffix); +} + +static struct partition_group *find_or_create_group(struct partition_group *groups, + int *group_count, + const char *base_name) +{ + /* Find existing group */ + for (int i = 0; i < *group_count; i++) { + if (strcmp(groups[i].base_name, base_name) == 0) + return &groups[i]; + } + + /* Create new group */ + if (*group_count >= MAX_PARTITION_GROUPS) { + log_err("Too many partition groups\n"); + return NULL; + } + + struct partition_group *new_group = &groups[*group_count]; + + strcpy(new_group->base_name, base_name); + new_group->a_slot = NULL; + new_group->b_slot = NULL; + new_group->no_slot = NULL; + + (*group_count)++; + return new_group; +} + +static struct qcom_partition_info *select_ab_target(struct qcom_partition_info *a_slot, + struct qcom_partition_info *b_slot) +{ + /* Priority: Active slot > A slot (fallback) */ + + if (a_slot && a_slot->is_active) { + log_debug("Selected %s (active)\n", a_slot->name); + return a_slot; + } + if (b_slot && b_slot->is_active) { + log_debug("Selected %s (active)\n", b_slot->name); + return b_slot; + } + + /* Both inactive - prefer A slot as fallback */ + struct qcom_partition_info *fallback = a_slot ? a_slot : b_slot; + + if (fallback) + log_debug("Selected %s (fallback - both inactive)\n", fallback->name); + return fallback; +} + +static int discover_all_partitions(struct qcom_partition_info **all_parts, int *all_count) +{ + struct udevice *dev; + struct blk_desc *desc; + struct qcom_partition_info *partition_list; + int partition_count = 0; + int max_partitions = 256; + bool have_ufs = false; + + /* Allocate partition list */ + partition_list = calloc(max_partitions, sizeof(struct qcom_partition_info)); + if (!partition_list) { + log_err("Failed to allocate partition list\n"); + return -ENOMEM; + } + + if (IS_ENABLED(CONFIG_SCSI)) { + if (scsi_scan(false)) { + log_debug("Failed to scan SCSI devices\n"); + free(partition_list); + return -EIO; + } + } + + /* + * Check to see if we have UFS storage, if so firmware MUST be on it and we can skip + * all non-UFS block devices + */ + uclass_foreach_dev_probe(UCLASS_UFS, dev) { + have_ufs = true; + break; + } + + /* Discover partitions with UFS-priority logic */ + uclass_foreach_dev_probe(UCLASS_BLK, dev) { + if (device_get_uclass_id(dev) != UCLASS_BLK) + continue; + + desc = dev_get_uclass_plat(dev); + if (!desc) + continue; + + if (have_ufs) { + if (device_get_uclass_id(dev->parent->parent) != UCLASS_UFS) + continue; + } else { + /* If we don't have UFS, look at eMMC (but skip SD cards) */ + if (desc->uclass_id == UCLASS_MMC) { + if (IS_ENABLED(CONFIG_MMC) && is_sd(desc)) { + log_debug("Skipped SD-Card (devnum %d)\n", desc->devnum); + continue; + } + } else if (desc->uclass_id != UCLASS_SCSI) { + /* Not MMC and not SCSI, skip it */ + continue; + } + } + + int lun = desc->devnum; + + /* Scan all partitions on this device */ + for (int partnum = 1; partnum <= MAX_PARTITIONS_TO_SCAN; partnum++) { + struct disk_partition info; + + if (part_get_info(desc, partnum, &info) != 0) + break; + + if (partition_count >= max_partitions) { + log_warning("Too many partitions discovered, truncating at %d\n", + max_partitions); + break; + } + + /* Parse and store partition info */ + parse_partition_info(&partition_list[partition_count], &info, lun, partnum); + partition_count++; + } + } + + *all_parts = partition_list; + *all_count = partition_count; + + log_debug("Discovered %d partitions across all %s devices\n", + partition_count, have_ufs ? "UFS" : "eMMC"); + return 0; +} + +static int select_target_partitions(struct qcom_partition_info *all_parts, int all_count, + struct qcom_partition_info **selected_parts, + int *selected_count) +{ + struct partition_group groups[MAX_PARTITION_GROUPS]; + struct qcom_partition_info *target_list; + int group_count = 0; + int target_count = 0; + + memset(groups, 0, sizeof(groups)); + + /* Allocate target list */ + target_list = calloc(all_count, sizeof(struct qcom_partition_info)); + if (!target_list) { + log_err("Failed to allocate target partition list\n"); + return -ENOMEM; + } + + /* Group partitions by base name */ + for (int i = 0; i < all_count; i++) { + struct qcom_partition_info *part = &all_parts[i]; + struct partition_group *group = find_or_create_group(groups, &group_count, + part->base_name); + + if (!group) { + log_err("Failed to create group for %s\n", part->base_name); + continue; + } + + if (strcmp(part->slot_suffix, "_a") == 0) { + if (!group->a_slot) { + group->a_slot = part; + } else { + log_info("Duplicate A-slot partition detected\n"); + log_info(" Keeping: %s (LUN %d, partition %d) [first discovered]\n", + group->a_slot->name, group->a_slot->lun, + group->a_slot->partition_num); + log_info(" Ignoring: %s (LUN %d, partition %d) [duplicate]\n", + part->name, part->lun, part->partition_num); + } + } else if (strcmp(part->slot_suffix, "_b") == 0) { + if (!group->b_slot) { + group->b_slot = part; + } else { + log_info("Duplicate B-slot partition detected\n"); + log_info(" Keeping: %s (LUN %d, partition %d) [first discovered]\n", + group->b_slot->name, group->b_slot->lun, + group->b_slot->partition_num); + log_info(" Ignoring: %s (LUN %d, partition %d) [duplicate]\n", + part->name, part->lun, part->partition_num); + } + } else { + if (!group->no_slot) { + group->no_slot = part; + } else { + log_info("Duplicate non-A/B partition detected\n"); + log_info(" Keeping: %s (LUN %d, partition %d) [first discovered]\n", + group->no_slot->name, group->no_slot->lun, + group->no_slot->partition_num); + log_info(" Ignoring: %s (LUN %d, partition %d) [duplicate]\n", + part->name, part->lun, part->partition_num); + } + } + } + + log_debug("Created %d partition groups for selection\n", group_count); + + /* Select target partition for each group */ + for (int i = 0; i < group_count; i++) { + struct partition_group *group = &groups[i]; + struct qcom_partition_info *target = NULL; + + if (group->no_slot) { + /* Non-A/B partition */ + target = group->no_slot; + log_debug("Group %s: selected non-A/B partition %s\n", + group->base_name, target->name); + } else { + /* A/B partition - apply selection logic */ + target = select_ab_target(group->a_slot, group->b_slot); + if (target) { + log_debug("Group %s: selected %s from A/B pair\n", + group->base_name, target->name); + } + } + + if (target) { + /* Copy selected partition to target list */ + memcpy(&target_list[target_count], target, + sizeof(struct qcom_partition_info)); + target_count++; + } else { + log_info("No target selected for group %s\n", group->base_name); + } + } + + *selected_parts = target_list; + *selected_count = target_count; + + log_debug("Selected %d target partitions from %d discovered\n", target_count, all_count); + return 0; +} + +static int group_partitions_by_lun(struct qcom_partition_info *selected_parts, int selected_count, + struct lun_group **lun_groups, int *group_count) +{ + struct lun_group *groups; + int max_groups = MAX_LUN_GROUPS; + int current_groups = 0; + + /* Allocate LUN groups array */ + groups = calloc(max_groups, sizeof(struct lun_group)); + if (!groups) { + log_err("Failed to allocate LUN groups array\n"); + return -ENOMEM; + } + + /* Group partitions by LUN */ + for (int i = 0; i < selected_count; i++) { + struct qcom_partition_info *part = &selected_parts[i]; + struct lun_group *target_group = NULL; + + /* Find existing group for this LUN */ + for (int j = 0; j < current_groups; j++) { + if (groups[j].lun_number == part->lun) { + target_group = &groups[j]; + break; + } + } + + /* Create new group if not found */ + if (!target_group) { + if (current_groups >= max_groups) { + log_err("Too many LUN groups (max %d)\n", max_groups); + free(groups); + return -ENOSPC; + } + + target_group = &groups[current_groups]; + target_group->lun_number = part->lun; + target_group->partition_count = 0; + current_groups++; + } + + /* Add partition to group */ + if (target_group->partition_count >= 64) { + log_err("Too many partitions in LUN %d (max 64)\n", part->lun); + free(groups); + return -ENOSPC; + } + + target_group->partitions[target_group->partition_count] = part; + target_group->partition_count++; + } + + /* Sort groups by LUN number for consistent output */ + for (int i = 0; i < current_groups - 1; i++) { + for (int j = i + 1; j < current_groups; j++) { + if (groups[i].lun_number > groups[j].lun_number) { + struct lun_group temp = groups[i]; + + groups[i] = groups[j]; + groups[j] = temp; + } + } + } + + *lun_groups = groups; + *group_count = current_groups; + + log_debug("Grouped %d partitions into %d LUN groups\n", selected_count, current_groups); + return 0; +} + +static int generate_dfu_string(struct qcom_partition_info *selected_parts, int selected_count, + char *dfu_string, size_t buffer_size) +{ + struct lun_group *lun_groups = NULL; + struct udevice *dev; + struct blk_desc *desc; + int group_count = 0; + char *dfu_ptr = dfu_string; + int remaining = buffer_size; + int ret; + bool is_mmc = false; + + /* Clear the buffer */ + memset(dfu_string, 0, buffer_size); + + /* Determine storage type by checking the first partition's device */ + if (selected_count > 0) { + uclass_foreach_dev_probe(UCLASS_BLK, dev) { + if (device_get_uclass_id(dev) != UCLASS_BLK) + continue; + + desc = dev_get_uclass_plat(dev); + if (!desc) + continue; + + if (desc->devnum == selected_parts[0].lun) { + if (desc->uclass_id == UCLASS_MMC) { + is_mmc = true; + log_debug("Detected MMC/eMMC storage for DFU string generation\n"); + } else if (desc->uclass_id == UCLASS_SCSI) { + is_mmc = false; + log_debug("Detected SCSI/UFS storage for DFU string generation\n"); + } + break; + } + } + } + + /* Group partitions by LUN/device */ + ret = group_partitions_by_lun(selected_parts, selected_count, &lun_groups, &group_count); + if (ret != 0) { + log_err("Failed to group partitions by LUN: %d\n", ret); + return ret; + } + + /* Generate DFU string with appropriate format for storage type */ + for (int i = 0; i < group_count; i++) { + struct lun_group *group = &lun_groups[i]; + int written; + + /* Add device group separator for non-first groups */ + if (i > 0) { + written = snprintf(dfu_ptr, remaining, "&"); + dfu_ptr += written; + remaining -= written; + } + + if (is_mmc) { + /* MMC format: "mmc X=" */ + written = snprintf(dfu_ptr, remaining, "mmc %d=", group->lun_number); + } else { + /* SCSI format: "scsi X=" */ + written = snprintf(dfu_ptr, remaining, "scsi %d=", group->lun_number); + } + dfu_ptr += written; + remaining -= written; + + /* Add partitions within this device group */ + for (int j = 0; j < group->partition_count; j++) { + struct qcom_partition_info *part = group->partitions[j]; + + /* Add partition separator for non-first partitions in group */ + if (j > 0) { + written = snprintf(dfu_ptr, remaining, ";"); + dfu_ptr += written; + remaining -= written; + } + + if (is_mmc) { + /* MMC format: "partition_name part dev_num partition_num" */ + written = snprintf(dfu_ptr, remaining, "%s part %d %d", + part->name, group->lun_number, part->partition_num); + } else { + /* SCSI format: "partition_name part partition_num" */ + written = snprintf(dfu_ptr, remaining, "%s part %d", + part->name, part->partition_num); + } + dfu_ptr += written; + remaining -= written; + + if (remaining <= 10) { + log_err("DFU string buffer overflow at partition %s\n", part->name); + free(lun_groups); + return -ENOSPC; + } + } + } + + /* Clean up */ + free(lun_groups); + + log_debug("Generated %s DFU string (%zu chars): %s\n", + is_mmc ? "MMC" : "SCSI", strlen(dfu_string), dfu_string); + return 0; +} + +/** + * get_board_fit_capsule_guid - Get board-specific FIT capsule GUID + * + * Detect the board type from device tree and return the appropriate GUID + * for FIT capsule updates. + * + * @guid: Pointer to store the GUID + * Return: 0 on success, negative error code on failure + */ +static int get_board_fit_capsule_guid(efi_guid_t *guid) +{ + const char *compatible; + + if (!guid) + return -EINVAL; + + compatible = ofnode_read_string(ofnode_root(), "compatible"); + if (!compatible) { + log_err("Failed to read board compatible string\n"); + return -ENODEV; + } + + /* Check for QCS615 or Talos */ + if (strstr(compatible, "qcs615") || strstr(compatible, "talos")) { + log_debug("Detected QCS615/Talos board\n"); + *guid = (efi_guid_t)QCOM_QCS615_FIT_CAPSULE_GUID; + return 0; + } + + /* Check for QCS6490 */ + if (strstr(compatible, "qcs6490")) { + log_debug("Detected QCS6490 board\n"); + *guid = (efi_guid_t)QCOM_QCS6490_FIT_CAPSULE_GUID; + return 0; + } + + /* Check for Lemans */ + if (strstr(compatible, "lemans") || strstr(compatible, "qcs9100")) { + log_debug("Detected Lemans board\n"); + *guid = (efi_guid_t)QCOM_LEMANS_FIT_CAPSULE_GUID; + return 0; + } + + log_err("Unsupported board for capsule updates: %s\n", compatible); + return -EINVAL; +} + +/* + * For creating FIT-based capsule images from FvUpdate.xml files, see: + * - Tool: tools/fvupdate_to_fit.py + * - Documentation: doc/develop/fvupdate_to_fit.rst + */ +static void configure_fit_capsule_updates(void) +{ + struct qcom_partition_info *all_partitions = NULL; + struct qcom_partition_info *selected_partitions = NULL; + int all_count = 0, selected_count = 0; + static char dfu_string[MAX_DFU_STRING_SIZE] = { 0 }; + static struct efi_fw_image single_fw_image; + efi_guid_t board_guid; + int ret; + + /* Step 1: Discover all partitions across all SCSI LUNs */ + ret = discover_all_partitions(&all_partitions, &all_count); + if (ret != 0) { + log_err("Failed to discover SCSI partitions: %d\n", ret); + return; + } + + if (all_count == 0) { + log_warning("No SCSI partitions discovered\n"); + goto cleanup; + } + + /* Step 2: Apply A/B selection logic to choose target partitions */ + ret = select_target_partitions(all_partitions, all_count, + &selected_partitions, &selected_count); + if (ret != 0) { + log_err("Failed to select target partitions: %d\n", ret); + goto cleanup; + } + + if (selected_count == 0) { + log_warning("No target partitions selected\n"); + goto cleanup; + } + + /* Step 3: Generate DFU string from selected partitions */ + ret = generate_dfu_string(selected_partitions, selected_count, + dfu_string, sizeof(dfu_string)); + if (ret != 0) { + log_err("Failed to generate DFU string: %d\n", ret); + goto cleanup; + } + + /* Step 4: Get board-specific GUID */ + ret = get_board_fit_capsule_guid(&board_guid); + if (ret != 0) { + log_err("Failed to get board-specific GUID: %d\n", ret); + goto cleanup; + } + + /* Step 5: Create SINGLE fw_image entry for ESRT */ + memset(&single_fw_image, 0, sizeof(single_fw_image)); + single_fw_image.fw_name = QCOM_FIT_CAPSULE_NAME; /* Same name for all boards */ + single_fw_image.image_index = 1; + single_fw_image.image_type_id = board_guid; + + /* Step 6: Configure update_info */ + update_info.dfu_string = dfu_string; + update_info.images = &single_fw_image; + update_info.num_images = 1; + + log_info("FIT capsule configured successfully:\n"); + log_info(" Name: %ls\n", QCOM_FIT_CAPSULE_NAME); + log_info(" GUID: %pUl\n", &board_guid); + log_info(" Partitions in DFU string: %d\n", selected_count); + log_info(" ESRT entries: 1 (single entry for all partitions)\n"); + +cleanup: + free(all_partitions); + free(selected_partitions); +} +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_FIT */ + +/** + * qcom_configure_capsule_updates() - Configure capsule updates + * + * Configures either FIT or RAW capsule updates based on compile-time configuration. + */ +void qcom_configure_capsule_updates(void) +{ +#if defined(CONFIG_EFI_CAPSULE_FIRMWARE_FIT) + log_info("Configuring FIT capsule updates\n"); + configure_fit_capsule_updates(); +#elif defined(CONFIG_EFI_CAPSULE_FIRMWARE_RAW) + log_info("Configuring RAW capsule updates\n"); + configure_raw_capsule_updates(); +#else + log_warning("No capsule firmware configuration enabled\n"); +#endif + + /* Final state logging */ + if (update_info.dfu_string) { + log_info("Capsule update configured successfully with %d image(s)\n", + update_info.num_images); + } else { + log_warning("Capsule update configuration failed\n"); + } } diff --git a/arch/arm/mach-snapdragon/dram.c b/arch/arm/mach-snapdragon/dram.c new file mode 100644 index 000000000000..af5f3c84882c --- /dev/null +++ b/arch/arm/mach-snapdragon/dram.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Memory layout parsing for Qualcomm. + */ + +#define LOG_CATEGORY LOGC_BOARD +#define pr_fmt(fmt) "QCOM-DRAM: " fmt + +#include +#include +#include +#include +#include + +#include "qcom-priv.h" +#include "rampart.h" + +static struct { + phys_addr_t start; + phys_size_t size; +} prevbl_ddr_banks[CONFIG_NR_DRAM_BANKS] __section(".data") = { 0 }; + +int dram_init(void) +{ + /* + * gd->ram_base / ram_size have been setup already + * in qcom_parse_memory(). + */ + return 0; +} + +static int ddr_bank_cmp(const void *v1, const void *v2) +{ + const struct { + phys_addr_t start; + phys_size_t size; + } *res1 = v1, *res2 = v2; + + if (!res1->size) + return 1; + if (!res2->size) + return -1; + + return (res1->start >> 24) - (res2->start >> 24); +} + +/* This has to be done post-relocation since gd->bd isn't preserved */ +static void qcom_configure_bi_dram(void) +{ + int i; + + for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { + gd->bd->bi_dram[i].start = prevbl_ddr_banks[i].start; + gd->bd->bi_dram[i].size = prevbl_ddr_banks[i].size; + debug("Bank[%d]: start = %#011llx, size = %#011llx\n", + i, gd->bd->bi_dram[i].start, gd->bd->bi_dram[i].size); + if (!prevbl_ddr_banks[i].size) + break; + } +} + +int dram_init_banksize(void) +{ + qcom_configure_bi_dram(); + + return 0; +} + +#define entry_field(v, e, field) (v == 0 ? ((struct ram_partition_entry_v0 *)(e))->field : \ + (v == 1 ? ((struct ram_partition_entry_v1 *)(e))->field : \ + ((struct ram_partition_entry_v3 *)(e))->field)) +#define entry_start(v, e) entry_field(v, e, start_address) +#define entry_length(v, e) entry_field(v, e, length) +#define entry_category(v, e) entry_field(v, e, partition_category) +#define entry_domain(v, e) entry_field(v, e, partition_domain) +#define entry_type(v, e) entry_field(v, e, partition_type) + +/* Parse memory map from SMEM, return the number of entries */ +static int qcom_parse_memory_smem(phys_addr_t *ram_end) +{ + size_t size; + int i, j = 0, ret; + struct usable_ram_partition_table_header *header; + u32 ver; + void *entry; + u32 entry_size; // Size of each RAM partition entry (version dependent) + + ret = qcom_smem_init(); + if (ret) { + debug("Failed to initialize SMEM: %d.\n", ret); + return ret; + } + + header = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_USABLE_RAM_PARTITION_TABLE, &size); + if (!header) { + debug("Failed to find SMEM partition.\n"); + return -ENODEV; + } + + ver = header->version; + debug("SMEM RAM partition table version %u. %u entries\n", ver, header->num_partitions); + + switch (ver) { + case 0: + entry_size = sizeof(struct ram_partition_entry_v0); + entry = ((struct usable_ram_partition_table_v0 *)header)->entries; + break; + case 1: + entry_size = sizeof(struct ram_partition_entry_v1); + entry = ((struct usable_ram_partition_table_v1 *)header)->entries; + break; + default: + pr_warn("Unknown SMEM ram partition table version!\n"); + case 2: + case 3: + entry_size = sizeof(struct ram_partition_entry_v3); + entry = ((struct usable_ram_partition_table_v3 *)header)->entries; + break; + } + + debug("SMEM RAM partition entry size: %u bytes\n", entry_size); + + /* Check validy of RAM */ + for (i = 0; i < header->num_partitions && j < CONFIG_NR_DRAM_BANKS; i++, entry += entry_size) { + debug("Entry %d: [%#010llx - %#010llx]\n", i, entry_start(ver, entry), + (u64)entry_start(ver, entry) + entry_length(ver, entry)); + debug(" cat: %#04x type: %#04x domain: %#04x\n", entry_category(ver, entry), + entry_type(ver, entry), entry_domain(ver, entry)); + + + if (entry_category(ver, entry) != RAM_PARTITION_SDRAM || + entry_type(ver, entry) != RAM_PARTITION_SYS_MEMORY) + continue; + if (!entry_length(ver, entry) && !entry_start(ver, entry)) + break; + + prevbl_ddr_banks[j].start = entry_start(ver, entry); + prevbl_ddr_banks[j].size = entry_length(ver, entry); + *ram_end = max(*ram_end, prevbl_ddr_banks[j].start + prevbl_ddr_banks[j].size); + j++; + } + + if (j == CONFIG_NR_DRAM_BANKS) + pr_err("SMEM: More than CONFIG_NR_DRAM_BANKS (%u) entries!", CONFIG_NR_DRAM_BANKS); + + return j; +} + +static void qcom_parse_memory_dt(const fdt64_t *fdt, int *banks, phys_addr_t *ram_end) +{ + int offset; + const fdt64_t *memory; + int memsize; + int i, j; + + *ram_end = 0; + + offset = fdt_path_offset(fdt, "/memory"); + if (offset < 0) + return; + + memory = fdt_getprop(fdt, offset, "reg", &memsize); + if (!memory) + return; + + *banks = min(memsize / (2 * sizeof(u64)), (ulong)CONFIG_NR_DRAM_BANKS); + + if (memsize / sizeof(u64) > CONFIG_NR_DRAM_BANKS * 2) + log_err("Provided more than the max of %d memory banks\n", CONFIG_NR_DRAM_BANKS); + + if (*banks > CONFIG_NR_DRAM_BANKS) + log_err("Provided more memory banks than we can handle\n"); + + for (i = 0, j = 0; i < *banks * 2; i += 2, j++) { + prevbl_ddr_banks[j].start = get_unaligned_be64(&memory[i]); + prevbl_ddr_banks[j].size = get_unaligned_be64(&memory[i + 1]); + if (!prevbl_ddr_banks[j].size) { + j--; + continue; + } + *ram_end = max(*ram_end, prevbl_ddr_banks[j].start + prevbl_ddr_banks[j].size); + } +} + +/** + * The generic memory parsing code in U-Boot lacks a few things that we + * need on Qualcomm: + * + * 1. It sets gd->ram_size and gd->ram_base to represent a single memory block + * 2. setup_dest_addr() later relocates U-Boot to ram_base + ram_size, the end + * of that first memory block. + * + * This results in all memory beyond U-Boot being unusable in Linux when booting + * with EFI. + * + * Since the ranges in the memory node may be out of order, the only way for us + * to correctly determine the relocation address for U-Boot is to parse all + * memory regions and find the highest valid address. + * + * We can't use fdtdec_setup_memory_banksize() since it stores the result in + * gd->bd, which is not yet allocated. + * + * @fdt: FDT blob to parse /memory node from + * @fdt_is_internal: is the FDT one embedded into U-Boot or was it provided by a prior + * bootloader stage? This determined if we should try to rely on SMEM + * (internal FDT) or error. We currently assume that if we are passed + * an external FDT then it already has the memory map populated. + * + * Return: 0 on success or -ENODATA if /memory node is missing or incomplete + */ +int qcom_parse_memory(const void *fdt, bool fdt_is_internal) +{ + phys_addr_t ram_end = 0; + int banks; + + qcom_memmap_source = fdt_is_internal ? QCOM_MEMMAP_SOURCE_INTERNAL_FDT + : QCOM_MEMMAP_SOURCE_EXTERNAL_FDT; + + qcom_parse_memory_dt(fdt, &banks, &ram_end); + + /* + * If using an internal FDT but the memory node is empty + * then fall back to SMEM. + */ + if (!prevbl_ddr_banks[0].size && fdt_is_internal) { + banks = qcom_parse_memory_smem(&ram_end); + if (banks < 0) + panic("Couldn't find a valid memory map!\n"); + qcom_memmap_source = QCOM_MEMMAP_SOURCE_SMEM; + } + + /* Sort our RAM banks -_- */ + qsort(prevbl_ddr_banks, banks, sizeof(prevbl_ddr_banks[0]), ddr_bank_cmp); + + gd->ram_base = prevbl_ddr_banks[0].start; + gd->ram_size = ram_end - gd->ram_base; + + return 0; +} diff --git a/arch/arm/mach-snapdragon/include/mach/boot0.h b/arch/arm/mach-snapdragon/include/mach/boot0.h index 953cccad7908..032a9fbdb12b 100644 --- a/arch/arm/mach-snapdragon/include/mach/boot0.h +++ b/arch/arm/mach-snapdragon/include/mach/boot0.h @@ -1,54 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0+ */ -/* - * Workaround for "PSCI bug" on DragonBoard 410c - * Copyright (C) 2021 Stephan Gerhold - * - * Syscall parameters taken from Qualcomm's LK fork (scm.h): - * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. - * - * The PSCI implementation in the TrustZone/tz firmware on DragonBoard 410c has - * a bug that starts all other CPU cores in 32-bit mode unless the TZ syscall - * that switches from 32-bit to 64-bit mode is executed at least once. - * - * Normally this happens inside Qualcomm's LK bootloader which runs in 32-bit - * mode and uses the TZ syscall to boot a kernel in 64-bit mode. However, if - * U-Boot is installed to the "aboot" partition (replacing LK) the switch to - * 64-bit mode never happens since U-Boot is already running in 64-bit mode. - * - * A workaround for this "PSCI bug" is to execute the TZ syscall when entering - * U-Boot. That way PSCI is made aware of the 64-bit switch and starts all other - * CPU cores in 64-bit mode as well. - */ -#include -#define ARM_SMCCC_SIP32_FAST_CALL \ - ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, ARM_SMCCC_OWNER_SIP, 0) - - /* - * U-Boot might be started in EL2 or EL3 with custom firmware. - * In that case, we assume that the workaround is not necessary or is - * handled already by the alternative firmware. Using the syscall in EL2 - * would demote U-Boot to EL1; in EL3 it would probably just crash. - */ - mrs x0, CurrentEL - cmp x0, #(1 << 2) /* EL1 */ - bne reset - - /* Prepare TZ syscall parameters */ - mov x0, #ARM_SMCCC_SIP32_FAST_CALL - movk x0, #0x10f /* SCM_SVC_MILESTONE_CMD_ID */ - mov x1, #0x12 /* MAKE_SCM_ARGS(0x2, SMC_PARAM_TYPE_BUFFER_READ) */ - adr x2, el1_system_param - mov x3, el1_system_param_end - el1_system_param - - /* Switch PSCI to 64-bit mode. Resets CPU and returns at el1_elr */ - smc #0 - - /* Something went wrong, perhaps PSCI is already in 64-bit mode? */ +#if defined(CONFIG_SPL_BUILD) b reset - - .align 3 -el1_system_param: - .quad 0, 0, 0, 0, 0, 0, 0, 0, 0 /* el1_x0-x8 */ - .quad reset /* el1_elr */ -el1_system_param_end: +#else +#if defined(CONFIG_BOOT0_MSM8916_PSCI_WORKAROUND) +#include "msm8916_boot0.h" +#elif defined(CONFIG_QCOM_EL2_GUNYAH_EXIT_SUPPORT) +#include "gunyah_exit_boot0.h" +#else + b reset +#endif +#endif diff --git a/arch/arm/mach-snapdragon/include/mach/gunyah_exit_boot0.h b/arch/arm/mach-snapdragon/include/mach/gunyah_exit_boot0.h new file mode 100644 index 000000000000..5c5bd3b66919 --- /dev/null +++ b/arch/arm/mach-snapdragon/include/mach/gunyah_exit_boot0.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Early Gunyah Hypervisor Exit + * + * Exit Gunyah hypervisor and switch to EL2 during early boot. This must + * happen before EL register configuration in start.S so that U-Boot can + * run properly at EL2. + */ + +#include +#include + +/* TrustZone SMC IDs for hypervisor configuration */ +#define TZ_EL2_SWITCH_SMC_ID 0x02000121 +#define TZ_EL2_SWITCH_PARAM_ID 0x00000023 +#define TZ_EL2_SWITCH_PARAM2_EXIT_GUNYAH 0x1 + + /* Only perform hypervisor switch if we're at EL1 */ + switch_el x9, 3f, 2f, 1f + + /* Save FDT address before we modify x0 */ +1: mov x9, x0 + + /* Switch to EL2 (exit Gunyah) */ + ldr w0, =TZ_EL2_SWITCH_SMC_ID + ldr w1, =TZ_EL2_SWITCH_PARAM_ID + mov w2, wzr + mov w3, wzr + ldr w4, =TZ_EL2_SWITCH_PARAM2_EXIT_GUNYAH + smc #0 + + /* Restore FDT address */ + mov x0, x9 +2: +3: b reset diff --git a/arch/arm/mach-snapdragon/include/mach/msm8916_boot0.h b/arch/arm/mach-snapdragon/include/mach/msm8916_boot0.h new file mode 100644 index 000000000000..953cccad7908 --- /dev/null +++ b/arch/arm/mach-snapdragon/include/mach/msm8916_boot0.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Workaround for "PSCI bug" on DragonBoard 410c + * Copyright (C) 2021 Stephan Gerhold + * + * Syscall parameters taken from Qualcomm's LK fork (scm.h): + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * + * The PSCI implementation in the TrustZone/tz firmware on DragonBoard 410c has + * a bug that starts all other CPU cores in 32-bit mode unless the TZ syscall + * that switches from 32-bit to 64-bit mode is executed at least once. + * + * Normally this happens inside Qualcomm's LK bootloader which runs in 32-bit + * mode and uses the TZ syscall to boot a kernel in 64-bit mode. However, if + * U-Boot is installed to the "aboot" partition (replacing LK) the switch to + * 64-bit mode never happens since U-Boot is already running in 64-bit mode. + * + * A workaround for this "PSCI bug" is to execute the TZ syscall when entering + * U-Boot. That way PSCI is made aware of the 64-bit switch and starts all other + * CPU cores in 64-bit mode as well. + */ +#include + +#define ARM_SMCCC_SIP32_FAST_CALL \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32, ARM_SMCCC_OWNER_SIP, 0) + + /* + * U-Boot might be started in EL2 or EL3 with custom firmware. + * In that case, we assume that the workaround is not necessary or is + * handled already by the alternative firmware. Using the syscall in EL2 + * would demote U-Boot to EL1; in EL3 it would probably just crash. + */ + mrs x0, CurrentEL + cmp x0, #(1 << 2) /* EL1 */ + bne reset + + /* Prepare TZ syscall parameters */ + mov x0, #ARM_SMCCC_SIP32_FAST_CALL + movk x0, #0x10f /* SCM_SVC_MILESTONE_CMD_ID */ + mov x1, #0x12 /* MAKE_SCM_ARGS(0x2, SMC_PARAM_TYPE_BUFFER_READ) */ + adr x2, el1_system_param + mov x3, el1_system_param_end - el1_system_param + + /* Switch PSCI to 64-bit mode. Resets CPU and returns at el1_elr */ + smc #0 + + /* Something went wrong, perhaps PSCI is already in 64-bit mode? */ + b reset + + .align 3 +el1_system_param: + .quad 0, 0, 0, 0, 0, 0, 0, 0, 0 /* el1_x0-x8 */ + .quad reset /* el1_elr */ +el1_system_param_end: diff --git a/arch/arm/mach-snapdragon/qcom-priv.h b/arch/arm/mach-snapdragon/qcom-priv.h index b8bf574e8bbb..c06e22fd2390 100644 --- a/arch/arm/mach-snapdragon/qcom-priv.h +++ b/arch/arm/mach-snapdragon/qcom-priv.h @@ -3,6 +3,8 @@ #ifndef __QCOM_PRIV_H__ #define __QCOM_PRIV_H__ +#include + /** * enum qcom_boot_source - Track where we got loaded from. * Used for capsule update logic. @@ -17,10 +19,48 @@ enum qcom_boot_source { extern enum qcom_boot_source qcom_boot_source; +/* + * enum qcom_memmap_source - Track where we got the memory map from. + * used for debugging and validation. + */ +enum qcom_memmap_source { + QCOM_MEMMAP_SOURCE_INTERNAL_FDT = 1, + QCOM_MEMMAP_SOURCE_EXTERNAL_FDT, + QCOM_MEMMAP_SOURCE_SMEM, +}; + +/* Set by qcom_parse_memory() */ +extern enum qcom_memmap_source qcom_memmap_source; + #if IS_ENABLED(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) +/* + * Capsule Update GUIDs for FIT capsules + * Each board has a unique GUID to prevent cross-board flashing + */ + +/* QCS615 FIT Capsule GUID: 9fd379d2-670e-4bb3-86a1-40497e6e17b0 */ +#define QCOM_QCS615_FIT_CAPSULE_GUID \ + EFI_GUID(0x9fd379d2, 0x670e, 0x4bb3, 0x86, 0xa1, \ + 0x40, 0x49, 0x7e, 0x6e, 0x17, 0xb0) + +/* QCS6490 FIT Capsule GUID: 6f25bfd2-a165-468b-980f-ac51a0a45c52 */ +#define QCOM_QCS6490_FIT_CAPSULE_GUID \ + EFI_GUID(0x6f25bfd2, 0xa165, 0x468b, 0x98, 0x0f, \ + 0xac, 0x51, 0xa0, 0xa4, 0x5c, 0x52) + +/* Lemans FIT Capsule GUID: 78462415-6133-431c-9fae-48f2bafd5c71 */ +#define QCOM_LEMANS_FIT_CAPSULE_GUID \ + EFI_GUID(0x78462415, 0x6133, 0x431c, 0x9f, 0xae, \ + 0x48, 0xf2, 0xba, 0xfd, 0x5c, 0x71) + +/* Common name for FIT capsule (same for all boards) */ +#define QCOM_FIT_CAPSULE_NAME u"QCOM_FIT_CAPSULE" + void qcom_configure_capsule_updates(void); #else void qcom_configure_capsule_updates(void) {} #endif /* EFI_HAVE_CAPSULE_SUPPORT */ +int qcom_parse_memory(const void *fdt, bool fdt_is_internal); + #endif /* __QCOM_PRIV_H__ */ diff --git a/arch/arm/mach-snapdragon/qcom_fit_multidtb.c b/arch/arm/mach-snapdragon/qcom_fit_multidtb.c new file mode 100644 index 000000000000..cc0321201a11 --- /dev/null +++ b/arch/arm/mach-snapdragon/qcom_fit_multidtb.c @@ -0,0 +1,1132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm FIT Multi-DTB Selection + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * Automatic DTB selection from FIT images based on hardware detection via SMEM. + * Loads qclinux_fit.img from dtb partition, detects hardware parameters, + * and selects the best matching DTB configuration. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qcom_fit_multidtb.h" + +/* FIT image filename on FAT partition and raw partition name */ +#define QCOM_FIT_FILENAME "qclinux_fit.img" +#define QCOM_FIT_PARTNAME "dtb_a" + +#define TCSR_SOC_HW_VERSION 0x01fc8000 +#define TCSR_MAJOR_VERSION_MASK 0x0000ff00 +#define TCSR_MAJOR_VERSION_SHIFT 8 +#define TCSR_MINOR_VERSION_MASK 0x000000ff +#define TCSR_MINOR_VERSION_SHIFT 0 + +#define lmb_alloc(size, addr) lmb_alloc_mem(LMB_MEM_ALLOC_ANY, SZ_2M, addr, size, LMB_NONE) + +/* Maximum values to match (SOC needs 2) */ +#define MAX_MATCH_VALUES 2 + +/* Metadata DTB node names */ +#define META_NODE_OEM "oem" +#define META_NODE_SOC "soc" +#define META_NODE_SOCVER "socver" +#define META_NODE_BOARD "board" +#define META_NODE_BOARDREV "boardrev" +#define META_NODE_SOC_SKU "soc-sku" +#define META_NODE_BOARD_SUBTYPE_PERIPHERAL "board-subtype-peripheral-subtype" +#define META_NODE_BOARD_SUBTYPE_STORAGE "board-subtype-storage-type" +#define META_NODE_BOARD_SUBTYPE_DDR_SIZE "board-subtype-memory-size" +#define META_NODE_SOFTSKU "softsku" + +/* Property names */ +#define PROP_OEM_ID "oem-id" +#define PROP_MSM_ID "msm-id" +#define PROP_SOCVER_ID "socver-id" +#define PROP_BOARD_ID "board-id" +#define PROP_BOARDREV_ID "boardrev-id" +#define PROP_BOARD_SUBTYPE "board-subtype" +#define PROP_SOFTSKU_ID "softsku-id" +#define PROP_COMPATIBLE "compatible" +#define PROP_FDT "fdt" +#define PROP_DATA "data" + +/** + * add_to_bucket() - Add a node name to the bucket list + * @name: Node name to add + * @name_len: Length of the name + * @bucket_head: Head of the bucket list + * + * Return: 0 on success, negative error code on failure + */ +static int add_to_bucket(const char *name, int name_len, struct list_head *bucket_head) +{ + struct bucket_node *node; + + node = malloc(sizeof(*node)); + if (!node) + return -ENOMEM; + + node->name = malloc(name_len + 1); + if (!node->name) { + free(node); + return -ENOMEM; + } + + strlcpy(node->name, name, name_len + 1); + + list_add_tail(&node->list, bucket_head); + + return 0; +} + +/** + * search_in_bucket() - Check if a name exists in the bucket list + * @name: Name to search for + * @bucket_head: Head of the bucket list + * + * Return: true if found, false otherwise + */ +static bool search_in_bucket(const char *name, struct list_head *bucket_head) +{ + struct bucket_node *node; + + list_for_each_entry(node, bucket_head, list) { + if (!strcmp(node->name, name)) + return true; + } + + return false; +} + +/** + * free_bucket_list() - Free all nodes in the bucket list + * @bucket_head: Head of the bucket list + */ +static void free_bucket_list(struct list_head *bucket_head) +{ + struct bucket_node *node, *tmp; + + list_for_each_entry_safe(node, tmp, bucket_head, list) { + list_del(&node->list); + free(node->name); + free(node); + } +} + +/** + * qcom_get_ddr_size_type() - Get DDR size type from gd->ram_size + * @ddr_type: Pointer to store DDR type + * + * Uses gd->ram_size which is already calculated by dram.c from SMEM/FDT + * during early boot (same value U-Boot prints as "DRAM: X GiB"). + * Avoids redundant SMEM re-parsing. + * + * Return: 0 on success + */ +static int qcom_get_ddr_size_type(u32 *ddr_type) +{ + u64 total_ddr_size = gd->ram_size; + + log_info("Total DDR Size: 0x%llx (%llu MB)\n", + total_ddr_size, total_ddr_size / SZ_1M); + + *ddr_type = 0; + if (total_ddr_size <= DDR_128MB) + *ddr_type = DDRTYPE_128MB; + else if (total_ddr_size <= DDR_256MB) + *ddr_type = DDRTYPE_256MB; + else if (total_ddr_size <= DDR_512MB) + *ddr_type = DDRTYPE_512MB; + else if (total_ddr_size <= DDR_1024MB) + *ddr_type = DDRTYPE_1024MB; + else if (total_ddr_size <= DDR_2048MB) + *ddr_type = DDRTYPE_2048MB; + else if (total_ddr_size <= DDR_3072MB) + *ddr_type = DDRTYPE_3072MB; + else if (total_ddr_size <= DDR_4096MB) + *ddr_type = DDRTYPE_4096MB; + + log_debug("DDR Type: %u\n", *ddr_type); + + return 0; +} + +/** + * qcom_get_imem_address() - Look up shared IMEM cookie address for this SoC + * + * Reads the root node compatible string from the device tree and searches + * the qcom_imem_table for a matching entry. + * + * Return: Physical address of shared IMEM cookie, or 0 if not found + */ +static uintptr_t qcom_get_imem_address(void) +{ + const struct qcom_imem_info *entry; + const char *soc_compat; + int len; + + soc_compat = fdt_getprop(gd->fdt_blob, 0, "compatible", &len); + if (!soc_compat) { + log_warning("Cannot read SoC compatible from DT\n"); + return 0; + } + + for (entry = qcom_imem_table; entry->compatible; entry++) { + if (fdt_stringlist_contains(soc_compat, len, entry->compatible)) + return entry->shared_imem_addr; + } + + log_warning("SoC not found in IMEM table\n"); + return 0; +} + +/** + * qcom_get_storage_type() - Detect storage type (UFS/EMMC/NAND) + * + * Reads the boot device type from the shared IMEM cookie structure populated + * by the bootloader. The shared IMEM address is looked up from a per-SoC + * table (qcom_imem_table) using the device tree compatible string. + * + * Falls back to UFS if the SoC is not in the table or if the IMEM cookie + * magic/version validation fails. + * + * Return: mem_card_type enum value (UFS/EMMC/NAND), or UFS as fallback + */ +static enum mem_card_type qcom_get_storage_type(void) +{ + struct boot_imem_cookie *imem; + uintptr_t shared_imem_addr; + + shared_imem_addr = qcom_get_imem_address(); + if (!shared_imem_addr) { + log_warning("SoC not in IMEM table, defaulting to UFS\n"); + return UFS; + } + + imem = (struct boot_imem_cookie *)shared_imem_addr; + + if (imem->shared_imem_magic != BOOT_SHARED_IMEM_MAGIC_NUM) { + log_warning("Invalid shared IMEM magic: 0x%x, defaulting to UFS\n", + imem->shared_imem_magic); + return UFS; + } + + if (imem->shared_imem_version < BOOT_SHARED_IMEM_VERSION_NUM) { + log_warning("Invalid shared IMEM version: %u, defaulting to UFS\n", + imem->shared_imem_version); + return UFS; + } + + log_info("Shared IMEM: magic=0x%x, version=%u, boot_device_type=%u\n", + imem->shared_imem_magic, imem->shared_imem_version, + imem->boot_device_type); + + switch (imem->boot_device_type) { + case UFS_FLASH: + return UFS; + case MMC_FLASH: + case SDC_FLASH: + return EMMC; + case NAND_FLASH: + return NAND; + default: + log_warning("Unknown shared IMEM boot device: %u, defaulting to UFS\n", + imem->boot_device_type); + return UFS; + } +} + +static u32 qcom_read_tcsr_soc_version(void) +{ + u32 reg_val = readl(TCSR_SOC_HW_VERSION); + u32 major = (reg_val & TCSR_MAJOR_VERSION_MASK) >> TCSR_MAJOR_VERSION_SHIFT; + u32 minor = (reg_val & TCSR_MINOR_VERSION_MASK) >> TCSR_MINOR_VERSION_SHIFT; + + return (major << 4) | minor; +} + +/** + * qcom_detect_hardware_params() - Detect all hardware parameters from SMEM + * @params: Pointer to hardware parameters structure + * + * Reads hardware information directly from SMEM using qcom_smem_get() and + * populates the qcom_hw_params structure with all data needed for DTB selection. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_detect_hardware_params(struct qcom_hw_params *params) +{ + struct socinfo *soc_info; + size_t size; + int ret; + u32 raw_version; + + memset(params, 0, sizeof(*params)); + + soc_info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HW_SW_BUILD_ID, &size); + if (IS_ERR_OR_NULL(soc_info)) { + log_err("Failed to get SOC info from SMEM\n"); + return -ENODEV; + } + + params->chip_id = le32_to_cpu(soc_info->id) & 0xffff; + + raw_version = le32_to_cpu(soc_info->plat_ver); + params->board_version = (SOCINFO_MAJOR(raw_version) << 4) | SOCINFO_MINOR(raw_version); + + params->chip_version = qcom_read_tcsr_soc_version(); + + params->platform = le32_to_cpu(soc_info->hw_plat); + params->subtype = le32_to_cpu(soc_info->hw_plat_subtype); + + if (le32_to_cpu(soc_info->fmt) >= 17) + params->oem_variant_id = le32_to_cpu(soc_info->oem_variant); + + if (le32_to_cpu(soc_info->fmt) >= 9) + params->foundry_id = le32_to_cpu(soc_info->foundry_id); + + ret = qcom_get_ddr_size_type(¶ms->ddr_size_type); + if (ret) + log_warning("Failed to get DDR size, defaulting to 0\n"); + + params->storage_type = qcom_get_storage_type(); + + log_info("Hardware Parameters:\n"); + log_info(" Chip ID: 0x%x\n", params->chip_id); + log_info(" Chip Version: 0x%x\n", params->chip_version); + log_info(" Board Version: 0x%x\n", params->board_version); + log_info(" Platform: 0x%x\n", params->platform); + log_info(" Subtype: 0x%x\n", params->subtype); + log_info(" OEM Variant ID: 0x%x\n", params->oem_variant_id); + log_info(" DDR Size Type: %u\n", params->ddr_size_type); + log_info(" Storage Type: %u\n", params->storage_type); + log_info(" Foundry ID: 0x%x\n", params->foundry_id); + + return 0; +} + +/** + * log_match_values() - Log matched hardware parameter values + * @log_type: Type label for logging (e.g., "SOC", "Board") + * @subnode_name: Name of the matched node + * @num_match_values: Number of values to log + * @match_values: Array of matched values + */ +static void log_match_values(const char *log_type, const char *subnode_name, + int num_match_values, const u32 *match_values) +{ + int i; + + log_info("Matched %s: %s (", log_type, subnode_name); + + for (i = 0; i < num_match_values; i++) { + if (i > 0) + log_info(", "); + log_info("val%d=0x%x", i + 1, match_values[i]); + } + + log_info(")\n"); +} + +/** + * process_node() - Generic metadata node processor + * @type: Type of node to process + * @metadata: Metadata DTB pointer + * @root_offset: Root node offset + * @params: Hardware parameters + * @bucket_head: Bucket list head + * + * Processes different types of nodes in the metadata DTB. Handles matching + * hardware parameters against DTB properties, with support for bit masking/shifting + * and fallback values. + * + * Return: 0 on success, -ENOENT if no match, other negative on error + */ +static int process_node(enum node_process_type type, + void *metadata, + int root_offset, + struct qcom_hw_params *params, + struct list_head *bucket_head) +{ + const char *node_name, *prop_name, *log_type; + const char *fallback; + const char *subnode_name; + int node_offset, subnode, len, name_len; + int num_match_values, i; + u32 match_values[MAX_MATCH_VALUES]; + u32 masks[MAX_MATCH_VALUES]; + int shifts[MAX_MATCH_VALUES]; + bool all_match; + + fallback = NULL; + num_match_values = 1; + memset(shifts, 0, sizeof(shifts)); + memset(masks, 0xff, sizeof(masks)); + + switch (type) { + case NODE_TYPE_OEM: + node_name = META_NODE_OEM; + prop_name = PROP_OEM_ID; + match_values[0] = params->oem_variant_id; + log_type = "OEM"; + fallback = "qcom"; + break; + case NODE_TYPE_SOC: + node_name = META_NODE_SOC; + prop_name = PROP_MSM_ID; + match_values[0] = params->chip_id; + masks[0] = 0xffff; + num_match_values = 1; + log_type = "SOC"; + break; + case NODE_TYPE_SOCVER: + node_name = META_NODE_SOCVER; + prop_name = PROP_SOCVER_ID; + match_values[0] = params->chip_version; + num_match_values = 1; + log_type = "SOCVER"; + break; + case NODE_TYPE_BOARD: + node_name = META_NODE_BOARD; + prop_name = PROP_BOARD_ID; + match_values[0] = params->platform; + log_type = "Board"; + break; + case NODE_TYPE_BOARDREV: + node_name = META_NODE_BOARDREV; + prop_name = PROP_BOARDREV_ID; + match_values[0] = params->board_version; + num_match_values = 1; + log_type = "BoardRev"; + break; + case NODE_TYPE_PERIPHERAL: + node_name = META_NODE_BOARD_SUBTYPE_PERIPHERAL; + prop_name = PROP_BOARD_SUBTYPE; + match_values[0] = params->subtype; + log_type = "Peripheral Subtype"; + break; + case NODE_TYPE_STORAGE: + node_name = META_NODE_BOARD_SUBTYPE_STORAGE; + prop_name = PROP_BOARD_SUBTYPE; + match_values[0] = params->storage_type; + masks[0] = 0x7000; + shifts[0] = 12; + log_type = "Storage"; + break; + case NODE_TYPE_DDR_SIZE: + node_name = META_NODE_BOARD_SUBTYPE_DDR_SIZE; + prop_name = PROP_BOARD_SUBTYPE; + match_values[0] = params->ddr_size_type; + masks[0] = 0xf00; + shifts[0] = 8; + log_type = "DDR Size"; + break; + case NODE_TYPE_SOFTSKU: + node_name = META_NODE_SOFTSKU; + prop_name = PROP_SOFTSKU_ID; + match_values[0] = params->softsku_id; + log_type = "SoftSKU"; + break; + default: + return -EINVAL; + } + + node_offset = fdt_subnode_offset(metadata, root_offset, node_name); + if (node_offset < 0) { + log_debug("%s node not found\n", log_type); + return node_offset; + } + + fdt_for_each_subnode(subnode, metadata, node_offset) { + const u32 *prop = fdt_getprop(metadata, subnode, prop_name, &len); + + if (!prop || len < (int)(num_match_values * sizeof(u32))) + continue; + + all_match = true; + for (i = 0; i < num_match_values; i++) { + u32 dtb_value = fdt32_to_cpu(prop[i]); + + dtb_value = (dtb_value & masks[i]) >> shifts[i]; + + if (dtb_value != match_values[i]) { + all_match = false; + break; + } + } + + if (!all_match) + continue; + + subnode_name = fdt_get_name(metadata, subnode, &name_len); + if (subnode_name) { + log_match_values(log_type, subnode_name, num_match_values, + match_values); + return add_to_bucket(subnode_name, name_len, bucket_head); + } + } + + if (fallback) { + log_info("No %s match, using fallback '%s'\n", log_type, fallback); + return add_to_bucket(fallback, strlen(fallback), bucket_head); + } + + log_debug("No %s match\n", log_type); + + return -ENOENT; +} + +/** + * qcom_build_bucket_list() - Build bucket list from metadata DTB + * @metadata: Metadata DTB pointer + * @params: Hardware parameters + * @bucket_head: Bucket list head + * + * This function parses the metadata DTB and builds a list of matching + * node names based on the detected hardware parameters. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_build_bucket_list(void *metadata, + struct qcom_hw_params *params, + struct list_head *bucket_head) +{ + int root_offset; + int ret; + struct bucket_node *node; + + log_debug("Building bucket list from hardware parameters\n"); + + root_offset = fdt_path_offset(metadata, "/"); + if (root_offset < 0) { + log_err("Failed to find root node in metadata DTB\n"); + return root_offset; + } + + ret = process_node(NODE_TYPE_OEM, metadata, root_offset, + params, bucket_head); + if (ret < 0 && ret != -ENOENT) + return ret; + + ret = process_node(NODE_TYPE_SOC, metadata, root_offset, + params, bucket_head); + if (ret < 0 && ret != -ENOENT) + return ret; + + ret = process_node(NODE_TYPE_SOCVER, metadata, root_offset, + params, bucket_head); + if (ret < 0 && ret != -ENOENT) + return ret; + + ret = process_node(NODE_TYPE_BOARD, metadata, root_offset, + params, bucket_head); + if (ret < 0 && ret != -ENOENT) + return ret; + + ret = process_node(NODE_TYPE_BOARDREV, metadata, root_offset, + params, bucket_head); + if (ret < 0 && ret != -ENOENT) + return ret; + + process_node(NODE_TYPE_PERIPHERAL, metadata, root_offset, + params, bucket_head); + + process_node(NODE_TYPE_STORAGE, metadata, root_offset, + params, bucket_head); + + process_node(NODE_TYPE_DDR_SIZE, metadata, root_offset, + params, bucket_head); + + process_node(NODE_TYPE_SOFTSKU, metadata, root_offset, + params, bucket_head); + + log_debug("Bucket list: "); + list_for_each_entry(node, bucket_head, list) + log_debug("%s ", node->name); + log_debug("\n"); + + return 0; +} + +/** + * qcom_find_fat_file() - Find a file on any FAT partition across all block devices + * @filename: Filename to search for + * @descp: Returns block descriptor of the partition containing the file + * @part_info: Returns partition info of the found partition + * + * Iterates through all block devices and their partitions, mounting each as + * FAT and checking if the file exists. + * + * Return: 0 on success, -ENOENT if not found + */ +static int qcom_find_fat_file(const char *filename, struct blk_desc **descp, + struct disk_partition *part_info) +{ + struct udevice *blk_dev; + struct blk_desc *desc; + loff_t file_size; + int partnum; + + blk_foreach_probe(BLKF_BOTH, blk_dev) { + desc = dev_get_uclass_plat(blk_dev); + if (!desc) + continue; + + for (partnum = 1; partnum <= MAX_SEARCH_PARTITIONS; partnum++) { + if (part_get_info(desc, partnum, part_info)) + break; + if (fat_set_blk_dev(desc, part_info)) + continue; + if (!fat_size(filename, &file_size)) { + *descp = desc; + return 0; + } + } + } + + return -ENOENT; +} + +/** + * qcom_load_raw_partition() - Load FIT image from a raw partition by name + * @partname: GPT partition name to search for + * @fitp: Pointer to store FIT image address + * @fit_sizep: Pointer to store FIT image size + * + * Searches all block devices for a GPT partition with the given name and + * reads the entire partition into a buffer. The caller is responsible for + * validating the content (fdt_check_header, fit_check_format). + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_load_raw_partition(const char *partname, void **fitp, + size_t *fit_sizep) +{ + struct udevice *blk_dev; + struct blk_desc *desc = NULL; + struct disk_partition part_info; + void *part_buf; + size_t part_size; + + blk_foreach_probe(BLKF_BOTH, blk_dev) { + desc = dev_get_uclass_plat(blk_dev); + if (!desc) + continue; + if (part_get_info_by_name(desc, partname, &part_info) >= 0) + break; + desc = NULL; + } + + if (!desc) { + log_err("Partition '%s' not found on any block device\n", partname); + return -ENOENT; + } + + part_size = (size_t)(part_info.size * part_info.blksz); + part_buf = malloc(part_size); + if (!part_buf) + return -ENOMEM; + + if (blk_dread(desc, part_info.start, part_info.size, part_buf) != part_info.size) { + log_err("Failed to read partition '%s'\n", partname); + free(part_buf); + return -EIO; + } + + *fitp = part_buf; + *fit_sizep = part_size; + + log_info("Loaded raw partition '%s': %zu bytes\n", partname, part_size); + return 0; +} + +/** + * qcom_load_fit_image() - Load FIT image from FAT partition or raw partition + * @filename: Filename to search for on FAT partitions + * @partname: GPT partition name to use as fallback (raw image) + * @fitp: Pointer to store FIT image address + * @fit_sizep: Pointer to store FIT image size + * + * First tries to find @filename on any FAT partition across all block devices. + * If not found, falls back to reading the raw partition named @partname. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_load_fit_image(const char *filename, const char *partname, + void **fitp, size_t *fit_sizep) +{ + struct blk_desc *desc; + struct disk_partition part_info; + loff_t file_size; + void *fit_buf; + int ret; + + log_info("%s: Loading FIT image\n", __func__); + + /* Try FAT first: search for filename on any FAT partition */ + ret = qcom_find_fat_file(filename, &desc, &part_info); + if (!ret) { + fat_size(filename, &file_size); + + fit_buf = malloc(file_size); + if (!fit_buf) + return -ENOMEM; + + ret = file_fat_read(filename, fit_buf, file_size); + if (ret >= 0) { + log_info("Loaded '%s' from FAT partition: %lld bytes\n", + filename, file_size); + *fitp = fit_buf; + *fit_sizep = file_size; + return 0; + } + + log_debug("FAT read of '%s' failed (%d), trying raw partition\n", + filename, ret); + free(fit_buf); + } + + /* Fallback: try raw partition by name */ + return qcom_load_raw_partition(partname, fitp, fit_sizep); +} + +/** + * qcom_extract_metadata_dtb() - Extract metadata DTB from FIT image + * @fit: FIT image pointer + * @metadata: Pointer to store metadata DTB address + * @metadata_size: Pointer to store metadata DTB size + * + * The metadata DTB is the first image in the FIT (fdt-0). + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_extract_metadata_dtb(void *fit, void **metadata, + size_t *metadata_size) +{ + int images_node, first_image; + const void *data; + size_t size; + int ret; + + images_node = fdt_path_offset(fit, FIT_IMAGES_PATH); + if (images_node < 0) { + log_err("Cannot find /images node in FIT\n"); + return images_node; + } + + first_image = fdt_first_subnode(fit, images_node); + if (first_image < 0) { + log_err("Cannot find first image in FIT\n"); + return first_image; + } + + ret = fit_image_get_data(fit, first_image, &data, &size); + if (ret) { + log_err("Failed to get metadata DTB data\n"); + return ret; + } + + *metadata = malloc(size); + if (!*metadata) { + log_err("Failed to allocate memory for metadata DTB\n"); + return -ENOMEM; + } + + memcpy(*metadata, data, size); + *metadata_size = size; + + log_info("Extracted metadata DTB: %zu bytes\n", size); + + return 0; +} + +/** + * qcom_count_compatible_matches() - Count matching tokens in compatible string + * @compatible: Compatible string from FIT configuration + * @compat_len: Length of compatible string + * @bucket_head: Bucket list head + * + * Parses the compatible string and counts how many tokens match entries + * in the bucket list. The compatible string format is typically: + * "vendor,device-variant-subtype" where tokens are separated by commas and dashes. + * + * Return: Number of matching tokens + */ +static int qcom_count_compatible_matches(const char *compatible, int compat_len, + struct list_head *bucket_head) +{ + char *compat_copy; + char *str_ptr; + char *token; + int match_count = 0; + + compat_copy = malloc(compat_len + 1); + if (!compat_copy) + return 0; + + memcpy(compat_copy, compatible, compat_len); + compat_copy[compat_len] = '\0'; + + str_ptr = compat_copy; + + /* First split by comma to get vendor prefix (e.g., "qcom") */ + token = strsep(&str_ptr, ","); + if (token && search_in_bucket(token, bucket_head)) + match_count++; + + /* Then split remaining parts by dash */ + token = strsep(&str_ptr, "-"); + while (token) { + if (search_in_bucket(token, bucket_head)) + match_count++; + token = strsep(&str_ptr, "-"); + } + + free(compat_copy); + return match_count; +} + +/** + * qcom_find_matching_config() - Find matching FIT configuration + * @fit: FIT image pointer + * @bucket_head: Bucket list head + * @config_node: Pointer to store matching configuration node offset + * + * This function iterates through all FIT configurations and finds the one + * with the most matching tokens in its compatible string against the bucket list. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_find_matching_config(void *fit, struct list_head *bucket_head, + int *config_node) +{ + int configs_node, cfg; + const char *compatible; + int compat_len; + const char *cfg_name; + int name_len; + int best_match_count = 0; + int best_config = -1; + int match_count; + + configs_node = fdt_path_offset(fit, FIT_CONFS_PATH); + if (configs_node < 0) { + log_err("Cannot find /configurations node in FIT\n"); + return configs_node; + } + + fdt_for_each_subnode(cfg, fit, configs_node) { + cfg_name = fdt_get_name(fit, cfg, &name_len); + compatible = fdt_getprop(fit, cfg, PROP_COMPATIBLE, &compat_len); + + if (!compatible || compat_len <= 0) { + log_debug("Config %s has no compatible property\n", cfg_name); + continue; + } + + log_debug("Checking config: %s, compatible: %s\n", + cfg_name, compatible); + + match_count = qcom_count_compatible_matches(compatible, compat_len, + bucket_head); + + log_debug("Config %s: %d matches\n", cfg_name, match_count); + + if (match_count > best_match_count) { + best_match_count = match_count; + best_config = cfg; + } + } + + if (best_config < 0) { + log_err("No matching configuration found\n"); + return -ENOENT; + } + + cfg_name = fdt_get_name(fit, best_config, &name_len); + compatible = fdt_getprop(fit, best_config, PROP_COMPATIBLE, &compat_len); + log_info("Selected configuration: %s (compatible: %s, matches: %d)\n", + cfg_name, compatible, best_match_count); + + *config_node = best_config; + return 0; +} + +/** + * qcom_get_fdt_image_data() - Get FDT image data from FIT + * @fit: FIT image pointer + * @images_node: Images node offset + * @fdt_name: FDT image name to load + * @fdt_datap: Pointer to store FDT data address + * @fdt_sizep: Pointer to store FDT data size + * + * Helper function to load an FDT image from the FIT by name. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_get_fdt_image_data(void *fit, int images_node, + const char *fdt_name, + const void **fdt_datap, size_t *fdt_sizep) +{ + int fdt_node; + int ret; + + fdt_node = fdt_subnode_offset(fit, images_node, fdt_name); + if (fdt_node < 0) { + log_err("Cannot find FDT node: %s\n", fdt_name); + return fdt_node; + } + + ret = fit_image_get_data(fit, fdt_node, fdt_datap, fdt_sizep); + if (ret) { + log_err("Failed to get FDT data for %s\n", fdt_name); + return ret; + } + + return 0; +} + +/** + * qcom_load_dtb_with_overlays() - Load DTB and apply overlays + * @fit: FIT image pointer + * @config_node: Configuration node offset + * @final_dtb: Pointer to store final DTB address + * @final_dtb_size: Pointer to store final DTB size + * + * This function loads the base DTB and applies all DTBOs specified in the + * configuration's "fdt" property. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_load_dtb_with_overlays(void *fit, int config_node, + void **final_dtb, + size_t *final_dtb_size) +{ + int images_node; + const char *fdt_name; + int fdt_name_len; + const void *fdt_data; + size_t fdt_size; + void *base_dtb = NULL; + size_t base_dtb_size = 0; + phys_addr_t dtb_addr; + int i, ret; + int fixups_offset; + + images_node = fdt_path_offset(fit, FIT_IMAGES_PATH); + if (images_node < 0) + return images_node; + + fdt_name = fdt_stringlist_get(fit, config_node, PROP_FDT, 0, &fdt_name_len); + if (!fdt_name) { + log_err("No fdt property in configuration\n"); + return -EINVAL; + } + + log_info("DTB: %s\n", fdt_name); + + ret = qcom_get_fdt_image_data(fit, images_node, fdt_name, + &fdt_data, &fdt_size); + if (ret) + return ret; + + /* Allocate base DTB with extra space for overlays using LMB */ + base_dtb_size = fdt_size + (8 * 1024); /* Add 8KB for overlays */ + ret = lmb_alloc(base_dtb_size, &dtb_addr); + if (ret) { + log_err("Failed to allocate LMB memory for base DTB: %zu bytes\n", base_dtb_size); + return -ENOMEM; + } + base_dtb = (void *)dtb_addr; + + memcpy(base_dtb, fdt_data, fdt_size); + ret = fdt_open_into(base_dtb, base_dtb, base_dtb_size); + if (ret) { + log_err("Failed to open DTB: %d\n", ret); + return ret; + } + + /* Apply overlays (remaining fdt entries) */ + for (i = 1; ; i++) { + fdt_name = fdt_stringlist_get(fit, config_node, PROP_FDT, i, + &fdt_name_len); + if (!fdt_name) + break; + + log_info("Applying overlay: %s\n", fdt_name); + + ret = qcom_get_fdt_image_data(fit, images_node, fdt_name, + &fdt_data, &fdt_size); + if (ret) + continue; + + fixups_offset = fdt_path_offset(fdt_data, "/__fixups__"); + if (fixups_offset == -FDT_ERR_NOTFOUND) { + log_warning("%s is not a valid overlay (no __fixups__)\n", fdt_name); + continue; + } + + ret = fdt_overlay_apply_verbose(base_dtb, (void *)fdt_data); + if (ret) + log_err("Failed to apply overlay %s: %d\n", fdt_name, ret); + } + + ret = fdt_pack(base_dtb); + if (ret) { + log_err("Failed to pack DTB: %d\n", ret); + return ret; + } + + *final_dtb = base_dtb; + *final_dtb_size = fdt_totalsize(base_dtb); + + log_info("Final DTB size: %zu bytes\n", *final_dtb_size); + + return 0; +} + +/** + * qcom_fit_multidtb_setup() - Main entry point for FIT multi-DTB selection + * + * This is the main function that orchestrates the entire DTB selection process: + * 1. Load qclinux_fit.img from EFI partition + * 2. Extract metadata DTB + * 3. Detect hardware parameters from SMEM + * 4. Build bucket list from metadata + * 5. Find matching FIT configuration + * 6. Load DTB and apply overlays + * 7. Install FDT for EFI + * + * Return: 0 on success, negative error code on failure + */ +int qcom_fit_multidtb_setup(void) +{ + void *fit = NULL; + size_t fit_size = 0; + void *metadata = NULL; + size_t metadata_size = 0; + struct qcom_hw_params hw_params; + LIST_HEAD(bucket_list); + int config_node; + void *final_dtb = NULL; + size_t final_dtb_size = 0; + int ret; + + log_debug("=== FIT Multi-DTB Selection ===\n"); + + log_debug("Loading FIT image\n"); + ret = qcom_load_fit_image(QCOM_FIT_FILENAME, QCOM_FIT_PARTNAME, + &fit, &fit_size); + if (ret) { + log_err("Failed to load FIT image\n"); + goto cleanup_fit; + } + + ret = fdt_check_header(fit); + if (ret) { + log_err("Invalid FIT header\n"); + ret = -EINVAL; + goto cleanup_fit; + } + + ret = fit_check_format(fit, IMAGE_SIZE_INVAL); + if (ret) { + log_err("Invalid FIT format\n"); + ret = -EINVAL; + goto cleanup_fit; + } + + log_debug("Extracting metadata DTB\n"); + ret = qcom_extract_metadata_dtb(fit, &metadata, &metadata_size); + if (ret) { + log_err("Failed to extract metadata DTB\n"); + goto cleanup_metadata; + } + + log_debug("Detecting hardware parameters\n"); + ret = qcom_detect_hardware_params(&hw_params); + if (ret) { + log_err("Failed to detect hardware parameters\n"); + goto cleanup_metadata; + } + + log_debug("Building bucket list\n"); + ret = qcom_build_bucket_list(metadata, &hw_params, &bucket_list); + if (ret) { + log_err("Failed to build bucket list\n"); + goto cleanup_bucket; + } + + log_debug("Finding matching configuration\n"); + ret = qcom_find_matching_config(fit, &bucket_list, &config_node); + if (ret) { + log_err("Failed to find matching configuration\n"); + goto cleanup_bucket; + } + + log_debug("Loading DTB and applying overlays\n"); + ret = qcom_load_dtb_with_overlays(fit, config_node, &final_dtb, + &final_dtb_size); + if (ret) { + log_err("Failed to load DTB with overlays\n"); + goto cleanup_dtb; + } + + log_debug("Setting fdt_addr to selected DTB address\n"); + + ret = fdt_check_header(final_dtb); + if (ret) { + log_err("Invalid final DTB header: %d\n", ret); + ret = -EINVAL; + goto cleanup_dtb; + } + + /* Update fdt_addr environment variable to point to our DTB */ + env_set_hex("fdt_addr", (ulong)final_dtb); + log_info("Updated fdt_addr=0x%lx, DTB size=%zu bytes\n", (ulong)final_dtb, final_dtb_size); + log_info("EFI boot flow will use DTB directly from this address\n"); + + /* Don't free final_dtb - LMB manages memory and EFI boot flow will use it */ + final_dtb = NULL; + + log_debug("=== FIT Multi-DTB Selection Complete ===\n"); + + ret = 0; + goto cleanup_success; + +cleanup_dtb: + if (ret && final_dtb) + final_dtb = NULL; + +cleanup_success: +cleanup_bucket: + free_bucket_list(&bucket_list); + +cleanup_metadata: + if (metadata) + free(metadata); + +cleanup_fit: + if (fit) + free(fit); + + return ret; +} diff --git a/arch/arm/mach-snapdragon/qcom_fit_multidtb.h b/arch/arm/mach-snapdragon/qcom_fit_multidtb.h new file mode 100644 index 000000000000..802eb749fe80 --- /dev/null +++ b/arch/arm/mach-snapdragon/qcom_fit_multidtb.h @@ -0,0 +1,190 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Qualcomm FIT Multi-DTB Selection + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + * This implements automatic DTB selection from FIT images based on hardware + * detection via SMEM. + */ + +#ifndef __QCOM_FIT_MULTIDTB_H__ +#define __QCOM_FIT_MULTIDTB_H__ + +#include +#include +#include + +/* DDR size thresholds */ +#define DDR_128MB SZ_128M +#define DDR_256MB SZ_256M +#define DDR_512MB SZ_512M +#define DDR_1024MB SZ_1G +#define DDR_2048MB SZ_2G +#define DDR_3072MB (SZ_2G + SZ_1G) +#define DDR_4096MB SZ_4G + +/* DDR type enum */ +enum ddr_type { + DDRTYPE_256MB = 1, + DDRTYPE_512MB, /* 2 */ + DDRTYPE_1024MB, /* 3 */ + DDRTYPE_2048MB, /* 4 */ + DDRTYPE_3072MB, /* 5 */ + DDRTYPE_4096MB, /* 6 */ + DDRTYPE_128MB, /* 7 */ +}; + +/* Storage type enum */ +enum mem_card_type { + UFS = 0, + EMMC = 1, + NAND = 2, + STORAGE_UNKNOWN, +}; + +/* Boot device types from shared IMEM */ +enum boot_media_type { + NO_FLASH = 0, + NOR_FLASH = 1, + NAND_FLASH = 2, + ONENAND_FLASH = 3, + SDC_FLASH = 4, + MMC_FLASH = 5, + SPI_FLASH = 6, + PCIE_FLASHLESS = 7, + UFS_FLASH = 8, + RESERVED_0_FLASH = 9, + RESERVED_1_FLASH = 10, + USB_FLASHLESS = 11 +}; + +/* Shared IMEM constants */ +#define BOOT_SHARED_IMEM_MAGIC_NUM 0xc1f8db40 +#define BOOT_SHARED_IMEM_VERSION_NUM 0x3 + +/** + * struct qcom_imem_info - SoC-specific shared IMEM cookie address mapping + * @compatible: SoC compatible string (e.g., "qcom,qcs6490") + * @shared_imem_addr: Physical address of the boot shared IMEM cookie + * + * The shared IMEM cookie is populated by the bootloader and contains + * boot device type and other boot parameters. Its location varies per SoC + * and is calculated as: SCL_IMEM_BASE + IMEM_SIZE - 0x1000 (4KB cookie). + * Only SoCs with CONFIG_QCOM_FIT_MULTIDTB support are listed here. + */ +struct qcom_imem_info { + const char *compatible; + uintptr_t shared_imem_addr; +}; + +/* + * Per-SoC shared IMEM cookie address table. + * Address = SCL_IMEM_BASE(0x14680000) + IMEM_SIZE - 0x1000 + * QCM6490/QCS6490/QCS615: IMEM_SIZE=0x2B000 -> 0x146aa000 + * QCS9100: IMEM_SIZE=0x59000 -> 0x146d8000 + */ +static const struct qcom_imem_info qcom_imem_table[] = { + { "qcom,qcm6490", 0x146aa000 }, + { "qcom,qcs6490", 0x146aa000 }, + { "qcom,qcs615", 0x146aa000 }, + { "qcom,qcs9100", 0x146d8000 }, + { } +}; + +/* Boot shared IMEM cookie structure */ +struct boot_imem_cookie { + u32 shared_imem_magic; + u32 shared_imem_version; + u64 etb_buf_addr; + u64 l2_cache_dump_buff_addr; + u32 a64_pointer_padding; + u32 uefi_ram_dump_magic; + u32 ddr_training_cookie; + u32 abnormal_reset_occurred; + u32 reset_status_register; + u32 rpm_sync_cookie; + u32 debug_config; + u64 boot_log_addr; + u32 boot_log_size; + u32 boot_fail_count; + u32 sbl1_error_type; + u32 uefi_image_magic; + u32 boot_device_type; + u64 boot_devtree_addr; + u64 boot_devtree_size; +}; + +/** + * struct qcom_hw_params - Hardware parameters detected from SMEM + * @chip_id: SoC chip ID (from socinfo->id) + * @chip_version: SoC version (from TCSR register) + * @platform: Hardware platform ID (from socinfo->hw_plat) + * @board_version: Board version (from socinfo->plat_ver) + * @subtype: Hardware platform subtype (from socinfo->hw_plat_subtype) + * @oem_variant_id: OEM variant ID (from socinfo->oem_variant) + * @ddr_size_type: DDR size type (0-10, calculated from RAM partitions) + * @storage_type: Storage type (UFS=0, EMMC=1, NAND=2) + * @foundry_id: Foundry ID (from socinfo->foundry_id) + * @softsku_id: Software SKU ID (if available) + * + * This structure holds all hardware parameters needed for DTB selection. + */ +struct qcom_hw_params { + u32 chip_id; + u32 chip_version; + u32 platform; + u32 board_version; + u32 subtype; + u32 oem_variant_id; + u32 ddr_size_type; + u32 storage_type; + u32 foundry_id; + u32 softsku_id; +}; + +/** + * struct bucket_node - Node in the bucket list + * @list: List head for linking nodes + * @name: Node name string (e.g., "qcom", "sa8775p-v2", "ride", "ufs", "8gb") + * + * The bucket list contains all matching node names from the metadata DTB. + * These are used to match against FIT configuration compatible strings. + */ +struct bucket_node { + struct list_head list; + char *name; +}; + +/* Node processing types for metadata DTB parsing */ +enum node_process_type { + NODE_TYPE_OEM, + NODE_TYPE_SOC, + NODE_TYPE_SOCVER, + NODE_TYPE_BOARD, + NODE_TYPE_BOARDREV, + NODE_TYPE_PERIPHERAL, + NODE_TYPE_STORAGE, + NODE_TYPE_DDR_SIZE, + NODE_TYPE_SOFTSKU, +}; + +/* Function prototypes */ + +/** + * qcom_fit_multidtb_setup() - Main entry point for FIT multi-DTB selection + * + * This function: + * 1. Loads qclinux_fit.img from EFI partition + * 2. Extracts metadata DTB + * 3. Detects hardware parameters from SMEM + * 4. Builds bucket list from metadata + * 5. Finds matching FIT configuration + * 6. Loads DTB and applies overlays + * 7. Sets FDT for EFI + * + * Return: 0 on success, negative error code on failure + */ +int qcom_fit_multidtb_setup(void); + +#endif /* __QCOM_FIT_MULTIDTB_H__ */ diff --git a/arch/arm/mach-snapdragon/rampart.h b/arch/arm/mach-snapdragon/rampart.h new file mode 100644 index 000000000000..57f3dbb7eec0 --- /dev/null +++ b/arch/arm/mach-snapdragon/rampart.h @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RAM partition table definitions + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * + */ + +#ifndef __QCOM_SMEM_RAMPART_H__ +#define __QCOM_SMEM_RAMPART_H__ + +#include + +#define SMEM_USABLE_RAM_PARTITION_TABLE 402 + +#define RAM_PARTITION_H_MAJOR 03 +#define RAM_PARTITION_H_MINOR 00 + +/** + * Total length of zero filled name string. This is not a C + * string, as it can occupy the total number of bytes, and if + * it does, it does not require a zero terminator. It cannot + * be manipulated with standard string handling library functions. + */ +#define RAM_PART_NAME_LENGTH 16 + +/** + * Number of RAM partition entries which are usable by APPS. + */ +#define RAM_NUM_PART_ENTRIES 32 + +/** + * @name: Magic numbers + * Used in identifying valid RAM partition table. + */ +#define RAM_PART_MAGIC1 0x9da5e0a8 +#define RAM_PART_MAGIC2 0xaf9ec4e2 + +/** + * RAM partition attributes. + */ +enum ram_partition_attribute_t { + RAM_PARTITION_READ_ONLY = 0, /* Read-only RAM partition */ + RAM_PARTITION_READWRITE, /* Read/write RAM partition */ +}; + +/** + * RAM partition categories. + */ +enum ram_partition_category_t { + RAM_PARTITION_IRAM = 4, /* IRAM RAM partition */ + RAM_PARTITION_IMEM = 5, /* IMEM RAM partition */ + RAM_PARTITION_SDRAM = 14, /* SDRAM type without specific bus information**/ +}; + +/** + * RAM Partition domains. + * @note: For shared RAM partition, domain value would be 0b11:\n + * RAM_PARTITION_APPS_DOMAIN | RAM_PARTITION_MODEM_DOMAIN. + */ +enum ram_partition_domain_t { + RAM_PARTITION_DEFAULT_DOMAIN = 0, /* 0b00: No specific domain definition */ + RAM_PARTITION_APPS_DOMAIN = 1, /* 0b01: APPS RAM partition */ + RAM_PARTITION_MODEM_DOMAIN = 2, /* 0b10: MODEM RAM partition */ +}; + +/** + * RAM Partition types. + * @note: The RAM_PARTITION_SYS_MEMORY type represents DDR rams that are attached + * to the current system. + */ +enum ram_partition_type_t { + RAM_PARTITION_SYS_MEMORY = 1, /* system memory */ + RAM_PARTITION_BOOT_REGION_MEMORY1, /* boot loader memory 1 */ + RAM_PARTITION_BOOT_REGION_MEMORY2, /* boot loader memory 2, reserved */ + RAM_PARTITION_APPSBL_MEMORY, /* apps boot loader memory */ + RAM_PARTITION_APPS_MEMORY, /* apps usage memory */ + RAM_PARTITION_TOOLS_FV_MEMORY, /* tools usage memory */ + RAM_PARTITION_QUANTUM_FV_MEMORY, /* quantum usage memory */ + RAM_PARTITION_QUEST_FV_MEMORY, /* quest usage memory */ +}; + +/* Common table header between versions */ +struct usable_ram_partition_table_header { + u32 magic1; /* Magic number to identify valid RAM partition table */ + u32 magic2; /* Magic number to identify valid RAM partition table */ + u32 version; /* Version number to track structure definition changes */ + u32 reserved1; /* Reserved for future use */ + + u32 num_partitions; /* Number of RAM partition table entries */ +}; + +/* Holds information for an entry in the RAM partition table */ +struct ram_partition_entry_v3 { + char name[RAM_PART_NAME_LENGTH]; /* Partition name, unused for now */ + u64 start_address; /* Partition start address in RAM */ + u64 length; /* Partition length in RAM in Bytes */ + u32 partition_attribute; /* Partition attribute */ + u32 partition_category; /* Partition category */ + u32 partition_domain; /* Partition domain */ + u32 partition_type; /* Partition type */ + u32 num_partitions; /* Number of partitions on device */ + u32 hw_info; /* hw information such as type and frequency */ + u8 highest_bank_bit; /* Highest bit corresponding to a bank */ + u8 reserve0; /* Reserved for future use */ + u8 reserve1; /* Reserved for future use */ + u8 reserve2; /* Reserved for future use */ + u32 min_pasr_size; /* Minimum PASR size in MB */ + u64 available_length; /* Available Partition length in RAM in Bytes */ +}; + +/* + * Defines the RAM partition table structure + * + * Do not change the placement of the first four elements so that future + * compatibility will always be guaranteed at least for the identifiers. + * + * The other portion of the structure may be changed as necessary to accommodate + * new features. Be sure to increment version number if you change it. + */ +struct usable_ram_partition_table_v3 { + struct usable_ram_partition_table_header header; + + u32 reserved2; /* Added for 8 bytes alignment of header */ + + /* RAM partition table entries */ + struct ram_partition_entry_v3 entries[RAM_NUM_PART_ENTRIES]; +}; + +/* Version 1 structure 32 Bit - Holds information for an entry in the RAM partition table */ +struct ram_partition_entry_v1 { + char name[RAM_PART_NAME_LENGTH]; /* Partition name, unused for now */ + u64 start_address; /* Partition start address in RAM */ + u64 length; /* Partition length in RAM in Bytes */ + u32 partition_attribute; /* Partition attribute */ + u32 partition_category; /* Partition category */ + u32 partition_domain; /* Partition domain */ + u32 partition_type; /* Partition type */ + u32 num_partitions; /* Number of partitions on device */ + u32 hw_info; /* hw information such as type and frequency */ + u32 reserved4; /* Reserved for future use */ + u32 reserved5; /* Reserved for future use */ +}; + +/* + * Defines the RAM partition table structure (Version 1) + * + * Do not change the placement of the first four elements so that future + * compatibility will always be guaranteed at least for the identifiers. + * + * The other portion of the structure may be changed as necessary to accommodate + * new features. Be sure to increment version number if you change it. + */ +struct usable_ram_partition_table_v1 { + struct usable_ram_partition_table_header header; + + u32 reserved2; /* Added for 8 bytes alignment of header */ + + /* RAM partition table entries */ + struct ram_partition_entry_v1 entries[RAM_NUM_PART_ENTRIES]; +}; + +/* Version 0 structure 32 Bit - Holds information for an entry in the RAM partition table */ +struct ram_partition_entry_v0 { + char name[RAM_PART_NAME_LENGTH]; /* Partition name, unused for now */ + u32 start_address; /* Partition start address in RAM */ + u32 length; /* Partition length in RAM in Bytes */ + u32 partition_attribute; /* Partition attribute */ + u32 partition_category; /* Partition category */ + u32 partition_domain; /* Partition domain */ + u32 partition_type; /* Partition type */ + u32 num_partitions; /* Number of partitions on device */ + u32 reserved3; /* Reserved for future use */ + u32 reserved4; /* Reserved for future use */ + u32 reserved5; /* Reserved for future use */ +}; + +/* + * Defines the RAM partition table structure (Version 0) + * + * Do not change the placement of the first four elements so that future + * compatibility will always be guaranteed at least for the identifiers. + * + * The other portion of the structure may be changed as necessary to accommodate + * new features. Be sure to increment version number if you change it. + */ +struct usable_ram_partition_table_v0 { + struct usable_ram_partition_table_header header; + + /* RAM partition table entries */ + struct ram_partition_entry_v0 entries[RAM_NUM_PART_ENTRIES]; +}; + +#endif // __QCOM_SMEM_RAMPART_H__ diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 0887de4333b0..beba425888c5 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -1021,6 +1021,11 @@ sandbox,filename = "i2c.bin"; sandbox,size = <256>; }; + emul_nvmem_test: emul-nvmem-test { + compatible = "sandbox,i2c-eeprom"; + sandbox,filename = "nvmem-test.bin"; + sandbox,size = <256>; + }; emul0: emul0 { compatible = "sandbox,i2c-rtc-emul"; }; @@ -1038,6 +1043,13 @@ reg = <0x41>; sandbox,emul = <&emul_pmic1>; }; + + /* Mock NVMEM device for bit field testing */ + nvmem-test@50 { + reg = <0x50>; + compatible = "i2c-eeprom"; + sandbox,emul = <&emul_nvmem_test>; + }; }; i3c0 { @@ -1582,10 +1594,6 @@ sandbox,filepath = "scsi.img"; }; - smem@0 { - compatible = "sandbox,smem"; - }; - sound { compatible = "sandbox,sound"; cpu { diff --git a/board/qualcomm/debug-sm8650.config b/board/qualcomm/debug-sm8650.config new file mode 100644 index 000000000000..8323bf25e9c1 --- /dev/null +++ b/board/qualcomm/debug-sm8650.config @@ -0,0 +1,5 @@ +CONFIG_DEBUG_UART=y +CONFIG_DEBUG_UART_ANNOUNCE=y +CONFIG_DEBUG_UART_BASE=0x89c000 +CONFIG_DEBUG_UART_MSM_GENI=y +CONFIG_DEBUG_UART_CLOCK=14745600 diff --git a/board/qualcomm/default.env b/board/qualcomm/default.env index dbf6f4e72600..e61c99870e6a 100644 --- a/board/qualcomm/default.env +++ b/board/qualcomm/default.env @@ -1,9 +1,9 @@ stdin=serial,button-kbd stdout=serial,vidconsole stderr=serial,vidconsole -preboot=scsi scan; usb start +preboot=scsi scan; usb start; if test "${reboot-mode}" = "bootloader"; then run fastboot; fi fastboot=fastboot -l $fastboot_addr_r usb 0 -do_boot=bootefi bootmgr +do_boot=bootflow scan -lb bootmenu_0=Boot first available device=run do_boot bootmenu_1=Enable fastboot mode=run fastboot bootmenu_2=Reset device=reset diff --git a/configs/dragonboard410c_defconfig b/configs/dragonboard410c_defconfig index 51ad265688c4..645f08167010 100644 --- a/configs/dragonboard410c_defconfig +++ b/configs/dragonboard410c_defconfig @@ -11,6 +11,7 @@ CONFIG_ENV_OFFSET=0x0 CONFIG_DEFAULT_DEVICE_TREE="qcom/apq8016-sbc" CONFIG_OF_LIBFDT_OVERLAY=y CONFIG_SYS_LOAD_ADDR=0x80080000 +CONFIG_BOOT0_MSM8916_PSCI_WORKAROUND=y CONFIG_IDENT_STRING="\nQualcomm-DragonBoard 410C" CONFIG_REMAKE_ELF=y CONFIG_BUTTON_CMD=y diff --git a/configs/qcm6490_defconfig b/configs/qcm6490_defconfig index b088367f86c0..cafd9233c19b 100644 --- a/configs/qcm6490_defconfig +++ b/configs/qcm6490_defconfig @@ -16,3 +16,11 @@ CONFIG_DEFAULT_DEVICE_TREE="qcom/qcs6490-rb3gen2" CONFIG_FASTBOOT_BUF_ADDR=0xd8800000 CONFIG_PHY_QCOM_QMP_COMBO=y + +CONFIG_ENV_IS_IN_SCSI=y +CONFIG_ENV_SCSI_PART_USE_TYPE_GUID=y +# SCSI partition type GUID for logfs partition +CONFIG_ENV_SCSI_PART_TYPE_GUID="bc0330eb-3410-4951-a617-03898dbe3372" + +CONFIG_QCOM_EL2_GUNYAH_EXIT_SUPPORT=y +CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK=y diff --git a/configs/qcom_defconfig b/configs/qcom_defconfig index d0d54ec5a70b..9826161c562d 100644 --- a/configs/qcom_defconfig +++ b/configs/qcom_defconfig @@ -9,10 +9,11 @@ CONFIG_SYS_LOAD_ADDR=0xA0000000 # CONFIG_EFI_HAVE_RUNTIME_RESET is not set CONFIG_EFI_RUNTIME_UPDATE_CAPSULE=y CONFIG_EFI_CAPSULE_ON_DISK=y -CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y +CONFIG_EFI_CAPSULE_FIRMWARE_FIT=y CONFIG_BUTTON_CMD=y CONFIG_FIT=y CONFIG_FIT_VERBOSE=y +CONFIG_QCOM_FIT_MULTIDTB=y CONFIG_BOOTSTD_FULL=y # CONFIG_BOOTMETH_VBE is not set CONFIG_BOOTDELAY=1 @@ -49,6 +50,7 @@ CONFIG_CMD_RNG=y CONFIG_CMD_REGULATOR=y CONFIG_CMD_LOG=y CONFIG_OF_LIVE=y +CONFIG_OF_LIBFDT_OVERLAY=y CONFIG_OF_UPSTREAM_BUILD_VENDOR=y CONFIG_ENV_USE_DEFAULT_ENV_TEXT_FILE=y CONFIG_ENV_DEFAULT_ENV_TEXT_FILE="board/qualcomm/default.env" @@ -79,8 +81,9 @@ CONFIG_SYS_DFU_DATA_BUF_SIZE=0x200000 CONFIG_USB_FUNCTION_FASTBOOT=y CONFIG_FASTBOOT_BUF_ADDR=0x0 CONFIG_FASTBOOT_FLASH=y -CONFIG_FASTBOOT_FLASH_MMC_DEV=0 -CONFIG_FASTBOOT_MMC_USER_SUPPORT=y +CONFIG_FASTBOOT_FLASH_BLOCK=y +CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME="scsi" +CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID=4 CONFIG_MSM_GPIO=y CONFIG_QCOM_PMIC_GPIO=y CONFIG_DM_I2C=y @@ -98,6 +101,7 @@ CONFIG_MISC=y CONFIG_NVMEM=y CONFIG_QCOM_GENI=y CONFIG_I2C_EEPROM=y +CONFIG_SYS_MMC_MAX_BLK_COUNT=16384 CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_ADMA=y CONFIG_MMC_SDHCI_MSM=y @@ -119,6 +123,7 @@ CONFIG_PINCTRL_QCOM_GENERIC=y CONFIG_QCOM_RPMH_POWER_DOMAIN=y CONFIG_DM_PMIC=y CONFIG_PMIC_QCOM=y +CONFIG_DM_REBOOT_MODE=y CONFIG_DM_REGULATOR=y CONFIG_DM_REGULATOR_FIXED=y CONFIG_DM_REGULATOR_QCOM_RPMH=y @@ -130,6 +135,9 @@ CONFIG_MSM_GENI_SERIAL=y CONFIG_SOC_QCOM=y CONFIG_QCOM_COMMAND_DB=y CONFIG_QCOM_RPMH=y +CONFIG_QCOM_SPMI_SDAM=y +CONFIG_REBOOT_MODE_ENV_UPDATE=y +CONFIG_REBOOT_MODE_NVMEM=y CONFIG_SPMI_MSM=y CONFIG_SYSINFO=y CONFIG_SYSINFO_SMBIOS=y diff --git a/configs/qcom_qcs9100_defconfig b/configs/qcom_lemans_defconfig similarity index 62% rename from configs/qcom_qcs9100_defconfig rename to configs/qcom_lemans_defconfig index 082106157bbc..c604c9989474 100644 --- a/configs/qcom_qcs9100_defconfig +++ b/configs/qcom_lemans_defconfig @@ -9,8 +9,13 @@ CONFIG_TEXT_BASE=0xaf000000 CONFIG_REMAKE_ELF=y CONFIG_FASTBOOT_BUF_ADDR=0xdb300000 -CONFIG_DEFAULT_DEVICE_TREE="qcom/qcs9100-ride-r3" +CONFIG_DEFAULT_DEVICE_TREE="qcom/lemans-evk" CONFIG_ENV_IS_IN_SCSI=y -CONFIG_ENV_SCSI_PART_UUID="71cb9cd0-acf1-b6cb-ad91-be9572fe11a9" +CONFIG_ENV_SCSI_PART_USE_TYPE_GUID=y +# SCSI partition type GUID for logfs partition +CONFIG_ENV_SCSI_PART_TYPE_GUID="bc0330eb-3410-4951-a617-03898dbe3372" # CONFIG_ENV_IS_DEFAULT is not set # CONFIG_ENV_IS_NOWHERE is not set + +CONFIG_QCOM_EL2_GUNYAH_EXIT_SUPPORT=y +CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK=y diff --git a/configs/qcom_qcs615_defconfig b/configs/qcom_qcs615_defconfig index 27666a8129d7..c90fa8bafb19 100644 --- a/configs/qcom_qcs615_defconfig +++ b/configs/qcom_qcs615_defconfig @@ -14,7 +14,7 @@ CONFIG_DEBUG_UART_BASE=0x880000 CONFIG_DEBUG_UART_MSM_GENI=y CONFIG_DEBUG_UART_CLOCK=7372800 -CONFIG_DEFAULT_DEVICE_TREE="qcom/qcs615-ride" +CONFIG_DEFAULT_DEVICE_TREE="qcom/talos-evk" CONFIG_REMAKE_ELF=y @@ -22,3 +22,19 @@ CONFIG_REMAKE_ELF=y CONFIG_TEXT_BASE=0x9fc00000 CONFIG_FASTBOOT_BUF_ADDR=0xa1600000 + +# QCS615 has eMMC only, not UFS/SCSI - override qcom_defconfig settings +CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME="mmc" +CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID=0 + +# CONFIG_SCSI is not set + +# Environment stored in eMMC logfs partition +CONFIG_ENV_IS_IN_MMC=y +CONFIG_ENV_MMC_USE_SW_PARTITION=y +CONFIG_ENV_MMC_SW_PARTITION="logfs" +CONFIG_ENV_OFFSET=0x0 +CONFIG_ENV_SIZE=0x4000 + +CONFIG_QCOM_EL2_GUNYAH_EXIT_SUPPORT=y +CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK=y diff --git a/configs/qcom_sm8650_defconfig b/configs/qcom_sm8650_defconfig new file mode 100644 index 000000000000..e0a82c460337 --- /dev/null +++ b/configs/qcom_sm8650_defconfig @@ -0,0 +1,16 @@ +# Configuration for building U-Boot to be flashed +# to the uefi partition of QCM6490 dev boards with +# the "Linux Embedded" partition layout (which have +# a dedicated "uefi" partition for edk2/U-Boot) + +#include "qcom_defconfig" + +# Otherwise buildman thinks this isn't an ARM platform +CONFIG_ARM=y + +# Address where U-Boot will be loaded +CONFIG_TEXT_BASE=0xa7000000 +CONFIG_REMAKE_ELF=y + +CONFIG_DEFAULT_DEVICE_TREE="qcom/sm8650-hdk" + diff --git a/configs/sandbox64_defconfig b/configs/sandbox64_defconfig index 5bf6146b1d0c..3cc1e7f77f64 100644 --- a/configs/sandbox64_defconfig +++ b/configs/sandbox64_defconfig @@ -232,8 +232,6 @@ CONFIG_REMOTEPROC_SANDBOX=y CONFIG_DM_RTC=y CONFIG_RTC_RV8803=y CONFIG_SANDBOX_SERIAL=y -CONFIG_SMEM=y -CONFIG_SANDBOX_SMEM=y CONFIG_SOUND_SANDBOX=y CONFIG_SOC_DEVICE=y CONFIG_SANDBOX_SPI=y diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index ba800f7d19da..42dc7b07b117 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -325,8 +325,6 @@ CONFIG_RTC_RV8803=y CONFIG_RTC_HT1380=y CONFIG_SANDBOX_SERIAL=y CONFIG_SANDBOX_SM=y -CONFIG_SMEM=y -CONFIG_SANDBOX_SMEM=y CONFIG_SOUND_DA7219=y CONFIG_SOUND_MAX98357A=y CONFIG_SOUND_SANDBOX=y diff --git a/disk/part.c b/disk/part.c index 4923dc44593c..4cb3204ac6e0 100644 --- a/disk/part.c +++ b/disk/part.c @@ -731,6 +731,43 @@ int part_get_info_by_uuid(struct blk_desc *desc, const char *uuid, return -ENOENT; } +int part_get_info_by_type_guid(struct blk_desc *desc, const char *type_guid, + struct disk_partition *info) +{ + struct part_driver *part_drv; + int ret; + int i; + + if (!CONFIG_IS_ENABLED(PARTITION_TYPE_GUID)) + return -ENOENT; + + part_drv = part_driver_lookup_type(desc); + if (!part_drv) + return -ENOENT; + + for (i = 1; i <= part_drv->max_entries; i++) { + ret = part_driver_get_info(part_drv, desc, i, info); + if (ret) { + /* -ENOSYS means no ->get_info method. */ + if (ret == -ENOSYS) + return ret; + /* + * Partition with this index can't be obtained, but + * further partitions might be, so keep checking. + */ + continue; + } + + if (!strncasecmp(type_guid, disk_partition_type_guid(info), + UUID_STR_LEN)) { + /* matched */ + return i; + } + } + + return -ENOENT; +} + /** * Get partition info from device number and partition name. * diff --git a/doc/android/fastboot.rst b/doc/android/fastboot.rst index 19e2ee9d4074..ce3a030e701b 100644 --- a/doc/android/fastboot.rst +++ b/doc/android/fastboot.rst @@ -147,6 +147,38 @@ The Fastboot implementation in U-Boot allows to write images into disk partitions. Target partitions are referred on the host computer by their names. +Device Selection for Block Devices +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When using ``FASTBOOT_FLASH_BLOCK``, you can specify which block device to +target by prefixing the partition name with the device number and a colon. + +Syntax:: + + : + +Examples:: + + fastboot flash 0:boot boot.img # Flash to device 0, partition "boot" + fastboot flash 1:system system.img # Flash to device 1, partition "system" + fastboot flash 0:gpt gpt.img # Write GPT to device 0 + fastboot flash 1:mbr mbr.img # Write MBR to device 1 + +If no device number is specified, the default device from +``CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID`` is used:: + + fastboot flash boot boot.img # Uses default device + +This syntax is supported for: + +* Regular partition flashing +* GPT partition table updates (``gpt`` or ``N:gpt``) +* MBR partition table updates (``mbr`` or ``N:mbr``) +* Partition erasing operations + +Partition Name Formats +^^^^^^^^^^^^^^^^^^^^^^ + For GPT/EFI the respective partition name is used. For MBR the partitions are referred by generic names according to the @@ -186,6 +218,15 @@ configuration options: CONFIG_FASTBOOT_GPT_NAME CONFIG_FASTBOOT_MBR_NAME +When using block devices (``FASTBOOT_FLASH_BLOCK``), you can specify the target +device by prefixing with the device number:: + + fastboot flash 0:gpt gpt.img # Write GPT to device 0 + fastboot flash 1:mbr mbr.img # Write MBR to device 1 + +If no device number is specified, the default device from +``CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID`` is used. + In Action --------- diff --git a/doc/develop/fvupdate_to_fit.rst b/doc/develop/fvupdate_to_fit.rst new file mode 100644 index 000000000000..ef29587198f7 --- /dev/null +++ b/doc/develop/fvupdate_to_fit.rst @@ -0,0 +1,487 @@ +.. SPDX-License-Identifier: GPL-2.0+ +.. Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + +FvUpdate.xml to Capsule Converter +================================== + +This Python script provides complete end-to-end conversion from Qualcomm FvUpdate.xml files to ready-to-deploy EFI capsule files for U-Boot capsule updates. + +Overview +-------- + +The script offers two modes of operation: + +1. **FIT-only mode**: Converts FvUpdate.xml to FIT images +2. **Complete mode**: Full workflow from XML to final capsule file + +The complete workflow includes: + +- FvUpdate.xml parsing and validation +- FIT image generation +- Final capsule creation with provided GUID +- Auto-installation of missing tools + +Features +-------- + +Core Functionality +~~~~~~~~~~~~~~~~~~ + +- **XML Parsing**: Extracts UPDATE operations from FvUpdate.xml +- **Binary Validation**: Ensures all referenced binary files exist +- **ITS Generation**: Creates Image Tree Source files with proper node naming +- **FIT Compilation**: Uses mkimage to generate FIT images +- **Capsule Creation**: Creates final capsule files using mkeficapsule with provided GUID +- **Tool Management**: Auto-installs missing tools (mkimage, mkeficapsule) + +Compatibility +~~~~~~~~~~~~~ + +- Qualcomm U-Boot capsule update system +- Multi-partition firmware updates +- FIT-based capsule payloads +- EFI Firmware Management Protocol (FMP) +- DFU backend integration + +Usage +----- + +FIT Image Only +~~~~~~~~~~~~~~ + +Creates only system.fit:: + + ./fvupdate_to_fit.py FvUpdate.xml + +Complete Capsule Workflow +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Creates system.fit and firmware.capsule:: + + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule \ + --guid \ + --fw-version + +Additional Examples +~~~~~~~~~~~~~~~~~~~ + +With custom output names:: + + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule /path/to/mkeficapsule \ + --guid 12345678-1234-5678-9abc-123456789abc \ + --fw-version 0.0.1.0 \ + --output custom.fit \ + --capsule-output custom.capsule + +With verbose output:: + + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule /path/to/mkeficapsule \ + --guid 12345678-1234-5678-9abc-123456789abc \ + --fw-version 0.0.1.0 \ + --verbose + +With Capsule Signing +~~~~~~~~~~~~~~~~~~~~ + +Creates a signed capsule:: + + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule /path/to/mkeficapsule \ + --guid 12345678-1234-5678-9abc-123456789abc \ + --fw-version 0.0.1.0 \ + --monotonic-count 1 \ + --private-key keys/CRT.key \ + --certificate keys/CRT.crt + +Command Line Options +-------------------- + +.. list-table:: + :header-rows: 1 + :widths: 20 50 30 + + * - Option + - Description + - Default + * - ``xml_file`` + - Path to FvUpdate.xml file + - Required + * - ``--guid`` + - GUID value of the board (enables complete mode). Board-specific GUIDs: qcs615: ``9FD379D2-670E-4BB3-86A1-40497E6E17B0``, qcs6490-rb3gen2: ``6f25bfd2-a165-468b-980f-ac51a0a45c52``, lemans-evk: ``78462415-6133-431c-9fae-48f2bafd5c71`` + - Required + * - ``--mkeficapsule`` + - Path to mkeficapsule binary + - Required + * - ``--fw-version`` + - Firmware version in "0.0.A.B" format (e.g., "0.0.1.0") + - Optional + * - ``--monotonic-count`` + - Monotonic count for capsule signing + - Optional + * - ``--private-key`` + - Path to the private key for signing + - Optional + * - ``--certificate`` + - Path to the certificate for signing + - Optional + * - ``-o, --output`` + - Output FIT image name + - system.fit + * - ``-c, --capsule-output`` + - Output capsule file name + - firmware.capsule + * - ``-v, --verbose`` + - Enable verbose output + - Disabled + * - ``-h, --help`` + - Show help message + - \- + +Requirements +------------ + +System Requirements +~~~~~~~~~~~~~~~~~~~ + +- Python 3.6+ +- Linux system with package manager (apt, yum, dnf, or pacman) +- sudo access for tool installation + +Required Tools (Auto-installed) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- U-Boot tools (mkimage, mkeficapsule) + +Required Files +~~~~~~~~~~~~~~ + +- FvUpdate.xml file +- Binary files in Images/ directory +- GUID value for the target board (for complete mode) + +Installation +------------ + +1. Make the script executable:: + + chmod +x fvupdate_to_fit.py + +2. The script will auto-install missing tools on first run + +File Structure +-------------- + +The script expects the following directory structure:: + + project/ + ├── FvUpdate.xml + ├── Images/ + │ ├── xbl.elf + │ ├── uefi.bin + │ └── boot.img + └── fvupdate_to_fit.py + +Example FvUpdate.xml +~~~~~~~~~~~~~~~~~~~~ + +An example FvUpdate.xml file is provided for reference: + +.. literalinclude:: fvupdate_to_fit/example_FvUpdate.xml + :language: xml + +This example demonstrates the XML structure with UPDATE operations for +multiple firmware partitions. + +XML Structure Requirements +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The tool only processes the following XML elements: + +**Required Elements:** + +- ```` - Container for each firmware entry +- ```` - Binary filename (must exist in Images/ directory) +- ```` - Must be "UPDATE" to be processed +- ``/`` - Target partition name (case-sensitive) + +**XML Structure:** + +.. code-block:: xml + + + + + firmware.bin + UPDATE + + partition_name + + + + +**Notes:** + +- The tool hardcodes the Images/ directory for binary files +- Only entries with ``UPDATE`` are processed +- Partition names are case-sensitive and must match U-Boot partition discovery + +Output Files +------------ + +Complete Mode +~~~~~~~~~~~~~ + +- ``system.its`` - Image Tree Source file (kept) +- ``system.fit`` - FIT image (kept) +- ``firmware.capsule`` - Final capsule file (ready for deployment) + +FIT-only Mode +~~~~~~~~~~~~~ + +- ``system.its`` - Image Tree Source file +- ``system.fit`` - FIT image + +Deployment Workflow +------------------- + +After generating the capsule: + +1. **Copy capsule to boot partition**:: + + cp firmware.capsule /boot/ + +2. **Deploy in U-Boot**:: + + => fatload mmc 0:1 $loadaddr firmware.capsule + => efidebug capsule update $loadaddr + +Technical Details +----------------- + +FIT Image Structure +~~~~~~~~~~~~~~~~~~~ + +- **Node Names**: Match partition names (e.g., ``xbl_a``, ``uefi_a``) +- **Image Type**: All images marked as "firmware" +- **Hash Algorithm**: SHA256 for integrity verification +- **Configuration**: Single config referencing all firmware images + +Capsule Structure +~~~~~~~~~~~~~~~~~ + +- **GUID**: Provided via --guid option +- **Index**: Always 1 (required for FIT capsules) +- **Payload**: FIT image containing all firmware +- **Version**: Optional firmware version (in 0.0.A.B format) via --fw-version, encoded as (A << 16 | B) +- **Signing**: Optional signing with monotonic count, private key, and certificate + +FMP Driver Compatibility +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The provided GUID must match what the FMP (Firmware Management Protocol) driver expects: + +- Uses actual partition names from FvUpdate.xml +- Compatible with Qualcomm capsule update flow +- Works with ``fit_update()`` processing + +Error Handling +-------------- + +Comprehensive error handling with fail-fast approach: + +- Missing XML files or invalid format +- Missing binary files in Images/ directory +- Tool installation failures +- Capsule creation failures +- Invalid GUID format + +Example Output +-------------- + +Complete Mode +~~~~~~~~~~~~~ + +:: + + Converting FvUpdate.xml to capsule... + ============================================================ + Installing missing tools... + Installed u-boot-tools ✓ + Parsing XML file: FvUpdate.xml + Found 3 FwEntry elements + Added: xbl_a -> xbl.elf + Added: uefi_a -> uefi.bin + Added: boot_a -> boot.img + Successfully parsed 3 UPDATE entries + Validating binary files... + xbl.elf: 524288 bytes + uefi.bin: 1048576 bytes + boot.img: 2097152 bytes + All binary files validated successfully + Generating ITS file: system.its + ITS file generated successfully + Compiling FIT image: system.fit + Running: mkimage -f system.its system.fit + FIT image compiled successfully: 3670016 bytes + Using GUID: 12345678-1234-5678-9abc-123456789abc + Creating capsule: firmware.capsule + Encoded Firmware version: 65536 (from 0.0.1.0) + Command: mkeficapsule -g 12345678-1234-5678-9abc-123456789abc -i 1 -v 65536 system.fit firmware.capsule + Capsule created successfully: 3670144 bytes ✓ + ============================================================ + SUCCESS: Complete capsule workflow completed! + + Files created: + ITS file: system.its + FIT file: system.fit + Capsule file: firmware.capsule (3.5 MB) + + Capsule GUID: 12345678-1234-5678-9abc-123456789abc + + Ready for deployment: + 1. Copy firmware.capsule to boot partition + 2. In U-Boot: fatload mmc 0:1 $loadaddr firmware.capsule + 3. In U-Boot: efidebug capsule update $loadaddr + +FIT-only Mode +~~~~~~~~~~~~~ + +:: + + Converting FvUpdate.xml to FIT image... + ============================================================ + Parsing XML file: FvUpdate.xml + Found 3 FwEntry elements + Added: xbl_a -> xbl.elf + Added: uefi_a -> uefi.bin + Added: boot_a -> boot.img + Successfully parsed 3 UPDATE entries + Validating binary files... + xbl.elf: 524288 bytes + uefi.bin: 1048576 bytes + boot.img: 2097152 bytes + All binary files validated successfully + Generating ITS file: system.its + ITS file generated successfully + Compiling FIT image: system.fit + FIT image compiled successfully: 3670016 bytes + ============================================================ + CONVERSION SUMMARY: + Input XML: FvUpdate.xml + Generated ITS: system.its + Output FIT: system.fit + Partitions: 3 + - xbl_a (xbl.elf) + - uefi_a (uefi.bin) + - boot_a (boot.img) + + ============================================================ + SUCCESS: FIT image created successfully! + Output: system.fit + + To create capsule, run again with: + ./fvupdate_to_fit.py FvUpdate.xml --mkeficapsule --guid --fw-version + +Viewing Generated File Contents +-------------------------------- + +FIT Image Contents +~~~~~~~~~~~~~~~~~~ + +You can inspect the generated ``system.fit`` file using U-Boot tools:: + + # View FIT image structure and metadata + mkimage -l system.fit + + # List all images in the FIT + dumpimage -l system.fit + +Capsule Contents +~~~~~~~~~~~~~~~~ + +You can inspect the generated ``firmware.capsule`` file using mkeficapsule:: + + # View capsule header and metadata + mkeficapsule --dump-capsule firmware.capsule + + # Or using local mkeficapsule binary + /path/to/local/mkeficapsule --dump-capsule firmware.capsule + +Troubleshooting +--------------- + +mkimage not found +~~~~~~~~~~~~~~~~~ + +Install U-Boot tools:: + + # Ubuntu/Debian + sudo apt-get install u-boot-tools + + # CentOS/RHEL + sudo yum install uboot-tools + +mkeficapsule not found +~~~~~~~~~~~~~~~~~~~~~~ + +If you get an error about mkeficapsule not being found, use a locally compiled version:: + + # Use local mkeficapsule binary + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule /path/to/local/u-boot/tools/mkeficapsule \ + --guid 12345678-1234-5678-9abc-123456789abc \ + --fw-version 0.0.1.0 + + # Example with U-Boot build directory + ./fvupdate_to_fit.py FvUpdate.xml \ + --mkeficapsule /local/mnt/workspace/bselvana/k2c_le/u-boot_upstream/u-boot_v2025_upstream/tools/mkeficapsule \ + --guid 12345678-1234-5678-9abc-123456789abc \ + --fw-version 0.0.1.0 + +Invalid GUID format +~~~~~~~~~~~~~~~~~~~ + +Ensure the GUID follows the format: ``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`` + +Supported Boards and GUIDs +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following Qualcomm boards are supported with their respective GUIDs: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Board + - GUID + * - **qcs615** + - ``9FD379D2-670E-4BB3-86A1-40497E6E17B0`` + * - **qcs6490-rb3gen2** + - ``6f25bfd2-a165-468b-980f-ac51a0a45c52`` + * - **lemans-evk** + - ``78462415-6133-431c-9fae-48f2bafd5c71`` + + +Example valid GUID format: ``12345678-1234-5678-9abc-123456789abc`` + +Missing binary files +~~~~~~~~~~~~~~~~~~~~ + +Ensure all files referenced in FvUpdate.xml exist in the Images/ directory with correct names. + +XML parsing errors +~~~~~~~~~~~~~~~~~~ + +Verify FvUpdate.xml is well-formed XML with proper FwEntry structure. + +mkeficapsule binary not found +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you specify a custom mkeficapsule path, ensure: + +- The file exists and is executable +- The path is correct (absolute or relative to current directory) +- The binary supports capsule creation diff --git a/doc/develop/fvupdate_to_fit/example_FvUpdate.xml b/doc/develop/fvupdate_to_fit/example_FvUpdate.xml new file mode 100644 index 000000000000..c1dea8db4a26 --- /dev/null +++ b/doc/develop/fvupdate_to_fit/example_FvUpdate.xml @@ -0,0 +1,43 @@ + + + + + + + + + u-boot.mbn + UPDATE + + uefi_a + + + + + + tz.mbn + UPDATE + + tz_a + + + + + + \ No newline at end of file diff --git a/drivers/Kconfig b/drivers/Kconfig index 47606ddc6c8b..aa293df37c57 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -124,8 +124,6 @@ source "drivers/serial/Kconfig" source "drivers/sm/Kconfig" -source "drivers/smem/Kconfig" - source "drivers/sound/Kconfig" source "drivers/soc/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 43d0ba332818..ca3e87ffb102 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -119,7 +119,6 @@ obj-y += reset/ obj-y += input/ obj-y += iommu/ # SOC specific infrastructure drivers. -obj-y += smem/ obj-y += thermal/ obj-$(CONFIG_TEE) += tee/ obj-$(CONFIG_ARM_FFA_TRANSPORT) += firmware/arm-ffa/ diff --git a/drivers/clk/clk-stub.c b/drivers/clk/clk-stub.c index 117266ac7789..4a6c71016da2 100644 --- a/drivers/clk/clk-stub.c +++ b/drivers/clk/clk-stub.c @@ -49,11 +49,13 @@ static struct clk_ops stub_clk_ops = { }; static const struct udevice_id stub_clk_ids[] = { + { .compatible = "qcom,qcs615-rpmh-clk" }, { .compatible = "qcom,rpmcc" }, - { .compatible = "qcom,sdm670-rpmh-clk" }, - { .compatible = "qcom,sdm845-rpmh-clk" }, + { .compatible = "qcom,sa8775p-rpmh-clk" }, { .compatible = "qcom,sc7180-rpmh-clk" }, { .compatible = "qcom,sc7280-rpmh-clk" }, + { .compatible = "qcom,sdm670-rpmh-clk" }, + { .compatible = "qcom,sdm845-rpmh-clk" }, { .compatible = "qcom,sm6350-rpmh-clk" }, { .compatible = "qcom,sm8150-rpmh-clk" }, { .compatible = "qcom,sm8250-rpmh-clk" }, diff --git a/drivers/clk/qcom/clock-qcom.h b/drivers/clk/qcom/clock-qcom.h index 3a4550d85366..9899cd28aade 100644 --- a/drivers/clk/qcom/clock-qcom.h +++ b/drivers/clk/qcom/clock-qcom.h @@ -14,6 +14,8 @@ #define CFG_CLK_SRC_GPLL0_AUX2 (2 << 8) #define CFG_CLK_SRC_GPLL2 (2 << 8) #define CFG_CLK_SRC_GPLL2_MAIN (2 << 8) +#define CFG_CLK_SRC_GPLL6_OUT_MAIN (2 << 8) +#define CFG_CLK_SRC_GPLL8 (2 << 8) #define CFG_CLK_SRC_GPLL9 (2 << 8) #define CFG_CLK_SRC_GPLL0_ODD (3 << 8) #define CFG_CLK_SRC_GPLL6 (4 << 8) diff --git a/drivers/clk/qcom/clock-qcs615.c b/drivers/clk/qcom/clock-qcs615.c index 2087fc38f63d..76f5f8191b5e 100644 --- a/drivers/clk/qcom/clock-qcs615.c +++ b/drivers/clk/qcom/clock-qcs615.c @@ -19,6 +19,39 @@ #define USB30_PRIM_MASTER_CLK_CMD_RCGR 0xf01c #define USB3_PRIM_PHY_AUX_CMD_RCGR 0xf060 +#define UFS_PHY_AXI_CLK_CMD_RCGR 0x77020 +#define UFS_PHY_ICE_CORE_CLK_CMD_RCGR 0x77048 +#define UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR 0x77060 +#define UFS_PHY_PHY_AUX_CLK_CMD_RCGR 0x7707c + +#define SDCC1_APPS_CLK_CMD_RCGR 0x12028 +#define SDCC2_APPS_CLK_CMD_RCGR 0x1400c + +/* + * Frequency tables for SDCC clocks + */ +static const struct freq_tbl ftbl_gcc_sdcc1_apps_clk_src[] = { + F(144000, CFG_CLK_SRC_CXO, 16, 3, 25), + F(400000, CFG_CLK_SRC_CXO, 12, 1, 4), + F(20000000, CFG_CLK_SRC_GPLL0_AUX2, 5, 1, 3), + F(25000000, CFG_CLK_SRC_GPLL0_AUX2, 6, 1, 2), + F(50000000, CFG_CLK_SRC_GPLL0_AUX2, 6, 0, 0), + F(100000000, CFG_CLK_SRC_GPLL0_AUX2, 3, 0, 0), + F(192000000, CFG_CLK_SRC_GPLL6_OUT_MAIN, 2, 0, 0), + F(384000000, CFG_CLK_SRC_GPLL6_OUT_MAIN, 1, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_gcc_sdcc2_apps_clk_src[] = { + F(400000, CFG_CLK_SRC_CXO, 12, 1, 4), + F(19200000, CFG_CLK_SRC_CXO, 1, 0, 0), + F(25000000, CFG_CLK_SRC_GPLL0_AUX2, 12, 0, 0), + F(50000000, CFG_CLK_SRC_GPLL0_AUX2, 6, 0, 0), + F(100000000, CFG_CLK_SRC_GPLL0_AUX2, 3, 0, 0), + F(202000000, CFG_CLK_SRC_GPLL8, 2, 0, 0), + { } +}; + #define GCC_QUPV3_WRAP0_S0_CLK_ENA_BIT BIT(10) #define GCC_QUPV3_WRAP0_S1_CLK_ENA_BIT BIT(11) #define GCC_QUPV3_WRAP0_S2_CLK_ENA_BIT BIT(12) @@ -33,9 +66,37 @@ #define GCC_QUPV3_WRAP1_S4_CLK_ENA_BIT BIT(26) #define GCC_QUPV3_WRAP1_S5_CLK_ENA_BIT BIT(27) +/* UFS PHY AXI clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_axi_clk_src[] = { + F(25000000, CFG_CLK_SRC_GPLL0_EVEN, 12, 0, 0), + F(50000000, CFG_CLK_SRC_GPLL0_EVEN, 6, 0, 0), + F(100000000, CFG_CLK_SRC_GPLL0, 6, 0, 0), + F(200000000, CFG_CLK_SRC_GPLL0, 3, 0, 0), + F(240000000, CFG_CLK_SRC_GPLL0, 2.5, 0, 0), + { } +}; + +/* UFS PHY ICE CORE clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_ice_core_clk_src[] = { + F(37500000, CFG_CLK_SRC_GPLL0_EVEN, 8, 0, 0), + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0, 4, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0, 2, 0, 0), + { } +}; + +/* UFS PHY UNIPRO CORE clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_unipro_core_clk_src[] = { + F(37500000, CFG_CLK_SRC_GPLL0_EVEN, 8, 0, 0), + F(75000000, CFG_CLK_SRC_GPLL0, 8, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0, 4, 0, 0), + { } +}; + static ulong qcs615_set_rate(struct clk *clk, ulong rate) { struct msm_clk_priv *priv = dev_get_priv(clk->dev); + const struct freq_tbl *freq; if (clk->id < priv->data->num_clks) debug("%s: %s, requested rate=%ld\n", __func__, @@ -52,6 +113,34 @@ static ulong qcs615_set_rate(struct clk *clk, ulong rate) 5, 0, 0, CFG_CLK_SRC_GPLL0, 8); clk_rcg_set_rate(priv->base, USB3_PRIM_PHY_AUX_CMD_RCGR, 0, 0); return rate; + case GCC_SDCC1_APPS_CLK: + freq = qcom_find_freq(ftbl_gcc_sdcc1_apps_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, SDCC1_APPS_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_SDCC2_APPS_CLK: + freq = qcom_find_freq(ftbl_gcc_sdcc2_apps_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, SDCC2_APPS_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_AXI_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_axi_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_AXI_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_UNIPRO_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_unipro_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_ICE_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_ice_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_ICE_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_PHY_AUX_CLK: + clk_rcg_set_rate(priv->base, UFS_PHY_PHY_AUX_CLK_CMD_RCGR, 0, CFG_CLK_SRC_CXO); + return 19200000; default: return 0; } @@ -81,7 +170,21 @@ static const struct gate_clk qcs615_clks[] = { GATE_CLK(GCC_QUPV3_WRAP1_S4_CLK, 0x5200c, GCC_QUPV3_WRAP1_S4_CLK_ENA_BIT), GATE_CLK(GCC_QUPV3_WRAP1_S5_CLK, 0x5200c, GCC_QUPV3_WRAP1_S5_CLK_ENA_BIT), GATE_CLK(GCC_DISP_HF_AXI_CLK, 0xb038, BIT(0)), - GATE_CLK(GCC_DISP_AHB_CLK, 0xb032, BIT(0)) + GATE_CLK(GCC_DISP_AHB_CLK, 0xb032, BIT(0)), + GATE_CLK(GCC_SDCC1_AHB_CLK, 0x12008, BIT(0)), + GATE_CLK(GCC_SDCC1_APPS_CLK, 0x12004, BIT(0)), + GATE_CLK(GCC_SDCC1_ICE_CORE_CLK, 0x1200c, BIT(0)), + GATE_CLK(GCC_SDCC2_AHB_CLK, 0x14008, BIT(0)), + GATE_CLK(GCC_SDCC2_APPS_CLK, 0x14004, BIT(0)), + GATE_CLK(GCC_UFS_PHY_AXI_CLK, 0x77010, BIT(0)), + GATE_CLK(GCC_AGGRE_UFS_PHY_AXI_CLK, 0x770c0, BIT(0)), + GATE_CLK(GCC_UFS_PHY_AHB_CLK, 0x77014, BIT(0)), + GATE_CLK(GCC_UFS_PHY_UNIPRO_CORE_CLK, 0x77040, BIT(0)), + GATE_CLK(GCC_UFS_PHY_ICE_CORE_CLK, 0x77044, BIT(0)), + GATE_CLK(GCC_UFS_PHY_TX_SYMBOL_0_CLK, 0x77018, BIT(0)), + GATE_CLK(GCC_UFS_PHY_RX_SYMBOL_0_CLK, 0x7701c, BIT(0)), + GATE_CLK(GCC_UFS_PHY_PHY_AUX_CLK, 0x77078, BIT(0)), + GATE_CLK(GCC_UFS_MEM_CLKREF_CLK, 0x8c000, BIT(0)) }; static int qcs615_enable(struct clk *clk) diff --git a/drivers/clk/qcom/clock-sa8775p.c b/drivers/clk/qcom/clock-sa8775p.c index 4957abf6f589..7eec4aeae489 100644 --- a/drivers/clk/qcom/clock-sa8775p.c +++ b/drivers/clk/qcom/clock-sa8775p.c @@ -19,6 +19,11 @@ #define USB30_PRIM_MASTER_CLK_CMD_RCGR 0x1b028 #define USB3_PRIM_PHY_AUX_CMD_RCGR 0x1b06c +#define UFS_PHY_AXI_CLK_CMD_RCGR 0x8302c +#define UFS_PHY_ICE_CORE_CLK_CMD_RCGR 0x83074 +#define UFS_PHY_PHY_AUX_CLK_CMD_RCGR 0x830a8 +#define UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR 0x8308c + #define GCC_QUPV3_WRAP0_S0_CLK_ENA_BIT BIT(10) #define GCC_QUPV3_WRAP0_S1_CLK_ENA_BIT BIT(11) #define GCC_QUPV3_WRAP0_S2_CLK_ENA_BIT BIT(12) @@ -44,9 +49,35 @@ #define GCC_QUPV3_WRAP3_S0_CLK_ENA_BIT BIT(25) +/* UFS AXI clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_axi_clk_src[] = { + F(25000000, CFG_CLK_SRC_GPLL0_EVEN, 12, 0, 0), + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0, 4, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0, 2, 0, 0), + { } +}; + +/* UFS ICE CORE clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_ice_core_clk_src[] = { + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0, 4, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0, 2, 0, 0), + { } +}; + +/* UFS UNIPRO CORE clock frequency table */ +static const struct freq_tbl ftbl_gcc_ufs_phy_unipro_core_clk_src[] = { + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0, 4, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0, 2, 0, 0), + { } +}; + static ulong sa8775p_set_rate(struct clk *clk, ulong rate) { struct msm_clk_priv *priv = dev_get_priv(clk->dev); + const struct freq_tbl *freq; if (clk->id < priv->data->num_clks) debug("%s: %s, requested rate=%ld\n", __func__, @@ -63,6 +94,24 @@ static ulong sa8775p_set_rate(struct clk *clk, ulong rate) 5, 0, 0, CFG_CLK_SRC_GPLL0, 8); clk_rcg_set_rate(priv->base, USB3_PRIM_PHY_AUX_CMD_RCGR, 0, 0); return rate; + case GCC_UFS_PHY_AXI_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_axi_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_AXI_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_UNIPRO_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_unipro_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_ICE_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_ice_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_ICE_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_PHY_AUX_CLK: + clk_rcg_set_rate(priv->base, UFS_PHY_PHY_AUX_CLK_CMD_RCGR, 0, CFG_CLK_SRC_CXO); + return 19200000; default: return 0; } @@ -106,6 +155,20 @@ static const struct gate_clk sa8775p_clks[] = { /* QUP Wrapper 3 clocks */ GATE_CLK(GCC_QUPV3_WRAP3_S0_CLK, 0x4b000, GCC_QUPV3_WRAP3_S0_CLK_ENA_BIT), + + /* UFS PHY clocks */ + GATE_CLK(GCC_UFS_PHY_AXI_CLK, 0x83018, 1), + GATE_CLK(GCC_AGGRE_UFS_PHY_AXI_CLK, 0x830d4, 1), + GATE_CLK(GCC_UFS_PHY_AHB_CLK, 0x83020, 1), + GATE_CLK(GCC_UFS_PHY_UNIPRO_CORE_CLK, 0x83064, 1), + GATE_CLK(GCC_UFS_PHY_TX_SYMBOL_0_CLK, 0x83024, 1), + GATE_CLK(GCC_UFS_PHY_RX_SYMBOL_0_CLK, 0x83028, 1), + GATE_CLK(GCC_UFS_PHY_RX_SYMBOL_1_CLK, 0x830c0, 1), + GATE_CLK(GCC_UFS_PHY_PHY_AUX_CLK, 0x830a4, 1), + GATE_CLK(GCC_UFS_PHY_ICE_CORE_CLK, 0x8306c, 1), + + /* EDP reference clock (used by UFS PHY) */ + GATE_CLK(GCC_EDP_REF_CLKREF_EN, 0x97448, 1), }; static int sa8775p_enable(struct clk *clk) diff --git a/drivers/clk/qcom/clock-sc7280.c b/drivers/clk/qcom/clock-sc7280.c index 01c8587ac398..91e3fcc27cb2 100644 --- a/drivers/clk/qcom/clock-sc7280.c +++ b/drivers/clk/qcom/clock-sc7280.c @@ -23,6 +23,10 @@ #define PCIE_1_AUX_CLK_CMD_RCGR 0x8d058 #define PCIE1_PHY_RCHNG_CMD_RCGR 0x8d03c #define PCIE_1_PIPE_CLK_PHY_MUX 0x8d054 +#define UFS_PHY_AXI_CLK_CMD_RCGR 0x77024 +#define UFS_PHY_ICE_CORE_CLK_CMD_RCGR 0x7706c +#define UFS_PHY_PHY_AUX_CLK_CMD_RCGR 0x770a0 +#define UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR 0x77084 static const struct freq_tbl ftbl_gcc_usb30_prim_master_clk_src[] = { F(66666667, CFG_CLK_SRC_GPLL0_EVEN, 4.5, 0, 0), @@ -54,6 +58,33 @@ static const struct freq_tbl ftbl_gcc_qupv3_wrap0_s2_clk_src[] = { { } }; +static const struct freq_tbl ftbl_gcc_ufs_phy_axi_clk_src[] = { + F(25000000, CFG_CLK_SRC_GPLL0_EVEN, 12, 0, 0), + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0_EVEN, 2, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0_EVEN, 1, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_gcc_ufs_phy_ice_core_clk_src[] = { + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0_EVEN, 2, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0_EVEN, 1, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_gcc_ufs_phy_phy_aux_clk_src[] = { + F(19200000, CFG_CLK_SRC_CXO, 1, 0, 0), + { } +}; + +static const struct freq_tbl ftbl_gcc_ufs_phy_unipro_core_clk_src[] = { + F(75000000, CFG_CLK_SRC_GPLL0_EVEN, 4, 0, 0), + F(150000000, CFG_CLK_SRC_GPLL0_EVEN, 2, 0, 0), + F(300000000, CFG_CLK_SRC_GPLL0_EVEN, 1, 0, 0), + { } +}; + static ulong sc7280_set_rate(struct clk *clk, ulong rate) { struct msm_clk_priv *priv = dev_get_priv(clk->dev); @@ -103,6 +134,26 @@ static ulong sc7280_set_rate(struct clk *clk, ulong rate) case GCC_PCIE1_PHY_RCHNG_CLK: clk_rcg_set_rate(priv->base, PCIE1_PHY_RCHNG_CMD_RCGR, 5, CFG_CLK_SRC_GPLL0_EVEN); return 100000000; + case GCC_UFS_PHY_AXI_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_axi_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_AXI_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_ICE_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_ice_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_ICE_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_PHY_AUX_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_phy_aux_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_PHY_AUX_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; + case GCC_UFS_PHY_UNIPRO_CORE_CLK: + freq = qcom_find_freq(ftbl_gcc_ufs_phy_unipro_core_clk_src, rate); + clk_rcg_set_rate_mnd(priv->base, UFS_PHY_UNIPRO_CORE_CLK_CMD_RCGR, + freq->pre_div, freq->m, freq->n, freq->src, 8); + return freq->freq; default: return rate; } @@ -148,6 +199,7 @@ static const struct gate_clk sc7280_clks[] = { GATE_CLK(GCC_UFS_PHY_AXI_CLK, 0x77010, BIT(0)), GATE_CLK(GCC_AGGRE_UFS_PHY_AXI_CLK, 0x770cc, BIT(0)), GATE_CLK(GCC_UFS_PHY_AHB_CLK, 0x77018, BIT(0)), + GATE_CLK(GCC_UFS_PHY_ICE_CORE_CLK, 0x77064, BIT(0)), GATE_CLK(GCC_UFS_PHY_UNIPRO_CORE_CLK, 0x7705c, BIT(0)), GATE_CLK(GCC_UFS_PHY_PHY_AUX_CLK, 0x7709c, BIT(0)), GATE_CLK(GCC_UFS_PHY_TX_SYMBOL_0_CLK, 0x7701c, BIT(0)), diff --git a/drivers/fastboot/Kconfig b/drivers/fastboot/Kconfig index 90212fcf9efd..a4539844d898 100644 --- a/drivers/fastboot/Kconfig +++ b/drivers/fastboot/Kconfig @@ -227,7 +227,7 @@ config FASTBOOT_FLASH_BLOCK_DEVICE_ID config FASTBOOT_GPT_NAME string "Target name for updating GPT" - depends on FASTBOOT_FLASH_MMC && EFI_PARTITION + depends on (FASTBOOT_FLASH_MMC || FASTBOOT_FLASH_BLOCK) && EFI_PARTITION default "gpt" help The fastboot "flash" command supports writing the downloaded @@ -240,7 +240,7 @@ config FASTBOOT_GPT_NAME config FASTBOOT_MBR_NAME string "Target name for updating MBR" - depends on FASTBOOT_FLASH_MMC && DOS_PARTITION + depends on (FASTBOOT_FLASH_MMC || FASTBOOT_FLASH_BLOCK) && DOS_PARTITION default "mbr" help The fastboot "flash" command allows to write the downloaded image diff --git a/drivers/fastboot/fb_block.c b/drivers/fastboot/fb_block.c index 51d1abb18c77..a8c74e35d0d1 100644 --- a/drivers/fastboot/fb_block.c +++ b/drivers/fastboot/fb_block.c @@ -11,6 +11,7 @@ #include #include #include +#include /** * FASTBOOT_MAX_BLOCKS_ERASE - maximum blocks to erase per derase call @@ -124,6 +125,65 @@ static lbaint_t fb_block_sparse_reserve(struct sparse_storage *info, return blkcnt; } +/** + * parse_device_partition() - Parse and validate device:partition format + * @part_name: Input string in format "N:partition" or "partition" + * @device: Output device number + * @partition_name: Output partition name pointer (can be NULL) + * + * Parses the input string to extract device number and partition name. + * If no device is specified, uses the default from config. + * Returns: 0 on success, -EINVAL if format is invalid + */ +static int parse_device_partition(const char *part_name, int *device, + const char **partition_name) +{ + const char *colon_pos; + + *device = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, + CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID, -1); + + /* Check for colon in partition name */ + colon_pos = strchr(part_name, ':'); + + /* Reject invalid format like ":partition" */ + if (colon_pos && colon_pos == part_name) + return -EINVAL; + + /* Override if device:partition format detected */ + if (colon_pos && colon_pos > part_name) { + *device = simple_strtoul(part_name, NULL, 10); + if (partition_name) + *partition_name = colon_pos + 1; + } else { + if (partition_name) + *partition_name = part_name; + } + + return 0; +} + +/** + * is_partition_table_name() - Check if name matches partition table target + * @part_name: Partition name to check + * @table_name: Config name for partition table (e.g., "gpt", "mbr") + * + * Returns: true if part_name matches table_name (with or without device prefix) + */ +static bool is_partition_table_name(const char *part_name, const char *table_name) +{ + const char *colon_pos; + + if (strcmp(part_name, table_name) == 0) + return true; + + colon_pos = strchr(part_name, ':'); + if (colon_pos && colon_pos > part_name && strcmp(colon_pos + 1, table_name) == 0) + return true; + + return false; +} + int fastboot_block_get_part_info(const char *part_name, struct blk_desc **dev_desc, struct disk_partition *part_info, @@ -133,25 +193,31 @@ int fastboot_block_get_part_info(const char *part_name, const char *interface = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME, NULL); - const int device = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, - CONFIG_FASTBOOT_FLASH_BLOCK_DEVICE_ID, -1); + int device; + const char *partition_name; if (!part_name || !strcmp(part_name, "")) { fastboot_fail("partition not given", response); return -ENOENT; } + if (!interface || !strcmp(interface, "")) { fastboot_fail("block interface isn't provided", response); return -EINVAL; } + if (parse_device_partition(part_name, &device, &partition_name) < 0) { + fastboot_fail("invalid partition name format", response); + return -EINVAL; + } + *dev_desc = blk_get_dev(interface, device); - if (!dev_desc) { + if (!*dev_desc) { fastboot_fail("no such device", response); return -ENODEV; } - ret = part_get_info_by_name(*dev_desc, part_name, part_info); + ret = part_get_info_by_name(*dev_desc, partition_name, part_info); if (ret < 0) fastboot_fail("failed to get partition info", response); @@ -316,6 +382,34 @@ void fastboot_block_flash_write(const char *part_name, void *download_buffer, struct blk_desc *dev_desc; struct disk_partition part_info; +#if CONFIG_IS_ENABLED(EFI_PARTITION) + if (is_partition_table_name(part_name, CONFIG_FASTBOOT_GPT_NAME)) { + int device; + const char *interface = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, + CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME, + NULL); + + parse_device_partition(part_name, &device, NULL); + fastboot_flash_gpt_partition_table(interface, device, + download_buffer, response); + return; + } +#endif + +#if CONFIG_IS_ENABLED(DOS_PARTITION) + if (is_partition_table_name(part_name, CONFIG_FASTBOOT_MBR_NAME)) { + int device; + const char *interface = config_opt_enabled(CONFIG_FASTBOOT_FLASH_BLOCK, + CONFIG_FASTBOOT_FLASH_BLOCK_INTERFACE_NAME, + NULL); + + parse_device_partition(part_name, &device, NULL); + fastboot_flash_mbr_partition_table(interface, device, + download_buffer, response); + return; + } +#endif + if (fastboot_block_get_part_info(part_name, &dev_desc, &part_info, response) < 0) return; diff --git a/drivers/fastboot/fb_common.c b/drivers/fastboot/fb_common.c index 9c52e0045881..2ff6e6567cf0 100644 --- a/drivers/fastboot/fb_common.c +++ b/drivers/fastboot/fb_common.c @@ -245,3 +245,97 @@ void fastboot_init(void *buf_addr, u32 buf_size) fastboot_set_progress_callback(NULL); } + +#if CONFIG_IS_ENABLED(EFI_PARTITION) +/** + * fastboot_flash_gpt_partition_table() - Flash GPT partition table + * @interface: Block interface name (e.g., "mmc", "scsi") + * @device: Device number + * @download_buffer: Buffer containing GPT data + * @response: Fastboot response buffer + */ +void fastboot_flash_gpt_partition_table(const char *interface, + int device, + void *download_buffer, + char *response) +{ + struct blk_desc *dev_desc; + + if (!interface || !strcmp(interface, "")) { + fastboot_fail("block interface isn't provided", response); + return; + } + + dev_desc = blk_get_dev(interface, device); + if (!dev_desc) { + fastboot_fail("no such device", response); + return; + } + + printf("%s: updating MBR, Primary and Backup GPT(s) on %s device %d\n", + __func__, interface, dev_desc->devnum); + + if (is_valid_gpt_buf(dev_desc, download_buffer)) { + printf("%s: invalid GPT - refusing to write to flash\n", __func__); + fastboot_fail("invalid GPT partition", response); + return; + } + + if (write_mbr_and_gpt_partitions(dev_desc, download_buffer)) { + printf("%s: writing GPT partitions failed\n", __func__); + fastboot_fail("writing GPT partitions failed", response); + return; + } + + part_init(dev_desc); + printf("........ success\n"); + fastboot_okay(NULL, response); +} +#endif + +#if CONFIG_IS_ENABLED(DOS_PARTITION) +/** + * fastboot_flash_mbr_partition_table() - Flash MBR partition table + * @interface: Block interface name (e.g., "mmc", "scsi") + * @device: Device number + * @download_buffer: Buffer containing MBR data + * @response: Fastboot response buffer + */ +void fastboot_flash_mbr_partition_table(const char *interface, + int device, + void *download_buffer, + char *response) +{ + struct blk_desc *dev_desc; + + if (!interface || !strcmp(interface, "")) { + fastboot_fail("block interface isn't provided", response); + return; + } + + dev_desc = blk_get_dev(interface, device); + if (!dev_desc) { + fastboot_fail("no such device", response); + return; + } + + printf("%s: updating MBR on %s device %d\n", __func__, interface, + dev_desc->devnum); + + if (is_valid_dos_buf(download_buffer)) { + printf("%s: invalid MBR - refusing to write to flash\n", __func__); + fastboot_fail("invalid MBR partition", response); + return; + } + + if (write_mbr_sector(dev_desc, download_buffer)) { + printf("%s: writing MBR partition failed\n", __func__); + fastboot_fail("writing MBR partition failed", response); + return; + } + + part_init(dev_desc); + printf("........ success\n"); + fastboot_okay(NULL, response); +} +#endif diff --git a/drivers/fastboot/fb_mmc.c b/drivers/fastboot/fb_mmc.c index 11d9c8e84602..c85853915c88 100644 --- a/drivers/fastboot/fb_mmc.c +++ b/drivers/fastboot/fb_mmc.c @@ -343,7 +343,7 @@ int fastboot_mmc_get_part_info(const char *part_name, return ret; } -static struct blk_desc *fastboot_mmc_get_dev(char *response) +static struct blk_desc __maybe_unused *fastboot_mmc_get_dev(char *response) { struct blk_desc *ret = blk_get_dev("mmc", CONFIG_FASTBOOT_FLASH_MMC_DEV); @@ -389,53 +389,18 @@ void fastboot_mmc_flash_write(const char *cmd, void *download_buffer, #if CONFIG_IS_ENABLED(EFI_PARTITION) if (strcmp(cmd, CONFIG_FASTBOOT_GPT_NAME) == 0) { - dev_desc = fastboot_mmc_get_dev(response); - if (!dev_desc) - return; - - printf("%s: updating MBR, Primary and Backup GPT(s)\n", - __func__); - if (is_valid_gpt_buf(dev_desc, download_buffer)) { - printf("%s: invalid GPT - refusing to write to flash\n", - __func__); - fastboot_fail("invalid GPT partition", response); - return; - } - if (write_mbr_and_gpt_partitions(dev_desc, download_buffer)) { - printf("%s: writing GPT partitions failed\n", __func__); - fastboot_fail("writing GPT partitions failed", - response); - return; - } - part_init(dev_desc); - printf("........ success\n"); - fastboot_okay(NULL, response); + fastboot_flash_gpt_partition_table("mmc", + CONFIG_FASTBOOT_FLASH_MMC_DEV, + download_buffer, response); return; } #endif #if CONFIG_IS_ENABLED(DOS_PARTITION) if (strcmp(cmd, CONFIG_FASTBOOT_MBR_NAME) == 0) { - dev_desc = fastboot_mmc_get_dev(response); - if (!dev_desc) - return; - - printf("%s: updating MBR\n", __func__); - if (is_valid_dos_buf(download_buffer)) { - printf("%s: invalid MBR - refusing to write to flash\n", - __func__); - fastboot_fail("invalid MBR partition", response); - return; - } - if (write_mbr_sector(dev_desc, download_buffer)) { - printf("%s: writing MBR partition failed\n", __func__); - fastboot_fail("writing MBR partition failed", - response); - return; - } - part_init(dev_desc); - printf("........ success\n"); - fastboot_okay(NULL, response); + fastboot_flash_mbr_partition_table("mmc", + CONFIG_FASTBOOT_FLASH_MMC_DEV, + download_buffer, response); return; } #endif diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index ea785793d18b..c00aa11d65d5 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -93,6 +93,14 @@ config QCOM_GENI for providing a common interface for various peripherals like UART, I2C, SPI, etc. +config QCOM_SPMI_SDAM + bool "Qualcomm SPMI SDAM NVMEM driver" + depends on MISC && NVMEM && SPMI + help + Enable support for Qualcomm SPMI SDAM (Shared Direct Access Memory) blocks + as NVMEM providers. This driver support accessing SDAM blocks in PMICs + for reboot reason functionality and other NVMEM use cases. + config ROCKCHIP_EFUSE bool "Rockchip e-fuse support" depends on MISC diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index e2170212e5ad..13800cc8b7fd 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_QFW_SMBIOS) += qfw_smbios.o obj-$(CONFIG_SANDBOX) += qfw_sandbox.o endif obj-$(CONFIG_QCOM_GENI) += qcom_geni.o +obj-$(CONFIG_QCOM_SPMI_SDAM) += qcom-spmi-sdam.o obj-$(CONFIG_$(PHASE_)ROCKCHIP_EFUSE) += rockchip-efuse.o obj-$(CONFIG_$(PHASE_)ROCKCHIP_OTP) += rockchip-otp.o obj-$(CONFIG_$(PHASE_)ROCKCHIP_IODOMAIN) += rockchip-io-domain.o diff --git a/drivers/misc/nvmem.c b/drivers/misc/nvmem.c index 33e808585657..03538343a29b 100644 --- a/drivers/misc/nvmem.c +++ b/drivers/misc/nvmem.c @@ -12,55 +12,163 @@ #include #include #include +#include +#include +#include -int nvmem_cell_read(struct nvmem_cell *cell, void *buf, size_t size) +/* Maximum supported NVMEM cell size */ +#define MAX_NVMEM_CELL_SIZE sizeof(u32) /* 4 bytes */ + +/** + * nvmem_cell_read_raw() - Read raw bytes from NVMEM cell without bit field extraction + * @cell: NVMEM cell to read from + * @buf: Buffer to store read data + * @size: Size of buffer + * + * This is an internal helper that reads raw bytes from hardware without applying + * bit field extraction. Used by both nvmem_cell_read() and nvmem_cell_write(). + * Caller must validate buffer size before calling this function. + * + * Return: Number of bytes read on success, negative error code on failure + */ +static int nvmem_cell_read_raw(struct nvmem_cell *cell, void *buf, size_t size) { - dev_dbg(cell->nvmem, "%s: off=%u size=%zu\n", __func__, cell->offset, size); - if (size != cell->size) - return -EINVAL; + int ret; + + memset(buf, 0, size); switch (cell->nvmem->driver->id) { case UCLASS_I2C_EEPROM: - return i2c_eeprom_read(cell->nvmem, cell->offset, buf, size); - case UCLASS_MISC: { - int ret = misc_read(cell->nvmem, cell->offset, buf, size); - + ret = i2c_eeprom_read(cell->nvmem, cell->offset, buf, cell->size); + break; + case UCLASS_MISC: + ret = misc_read(cell->nvmem, cell->offset, buf, cell->size); if (ret < 0) return ret; - if (ret != size) + if (ret != cell->size) return -EIO; - return 0; - } + ret = 0; + break; case UCLASS_RTC: - return dm_rtc_read(cell->nvmem, cell->offset, buf, size); + ret = dm_rtc_read(cell->nvmem, cell->offset, buf, cell->size); + break; default: return -ENOSYS; } + + if (ret) + return ret; + + return cell->size; +} + +int nvmem_cell_read(struct nvmem_cell *cell, void *buf, size_t size) +{ + int ret, bytes_needed; + u32 value; + + dev_dbg(cell->nvmem, "%s: off=%u size=%zu\n", __func__, cell->offset, size); + + if (cell->nbits) { + if (size != MAX_NVMEM_CELL_SIZE) { + dev_dbg(cell->nvmem, "bit field requires buffer size %zu, got %zu\n", + MAX_NVMEM_CELL_SIZE, size); + return -EINVAL; + } + + bytes_needed = DIV_ROUND_UP(cell->nbits + cell->bit_offset, BITS_PER_BYTE); + if (bytes_needed > cell->size || bytes_needed > MAX_NVMEM_CELL_SIZE) { + dev_dbg(cell->nvmem, "bit field requires %d bytes, cell size %zu\n", + bytes_needed, cell->size); + return -EINVAL; + } + } else { + if (size != cell->size) { + dev_dbg(cell->nvmem, "buffer size %zu must match cell size %zu\n", + size, cell->size); + return -EINVAL; + } + } + + ret = nvmem_cell_read_raw(cell, buf, size); + if (ret < 0) + return ret; + + if (cell->nbits) { + value = le32_to_cpu(*((__le32 *)buf)); + value >>= cell->bit_offset; + value &= GENMASK(cell->nbits - 1, 0); + *(u32 *)buf = value; + } + + return 0; } int nvmem_cell_write(struct nvmem_cell *cell, const void *buf, size_t size) { + int ret, bytes_needed; + u32 current, value, mask; + dev_dbg(cell->nvmem, "%s: off=%u size=%zu\n", __func__, cell->offset, size); - if (size != cell->size) - return -EINVAL; + + if (cell->nbits) { + if (size != MAX_NVMEM_CELL_SIZE) { + dev_dbg(cell->nvmem, "bit field requires buffer size %zu, got %zu\n", + MAX_NVMEM_CELL_SIZE, size); + return -EINVAL; + } + + bytes_needed = DIV_ROUND_UP(cell->nbits + cell->bit_offset, BITS_PER_BYTE); + if (bytes_needed > cell->size || bytes_needed > MAX_NVMEM_CELL_SIZE) { + dev_dbg(cell->nvmem, "bit field requires %d bytes, cell size %zu\n", + bytes_needed, cell->size); + return -EINVAL; + } + + ret = nvmem_cell_read_raw(cell, ¤t, sizeof(current)); + if (ret < 0) + return ret; + + current = le32_to_cpu(*((__le32 *)¤t)); + value = *(const u32 *)buf; + value &= GENMASK(cell->nbits - 1, 0); + value <<= cell->bit_offset; + + mask = GENMASK(cell->nbits - 1, 0) << cell->bit_offset; + + current = (current & ~mask) | value; + buf = ¤t; + } else { + if (size != cell->size) { + dev_dbg(cell->nvmem, "buffer size %zu must match cell size %zu\n", + size, cell->size); + return -EINVAL; + } + } switch (cell->nvmem->driver->id) { case UCLASS_I2C_EEPROM: - return i2c_eeprom_write(cell->nvmem, cell->offset, buf, size); - case UCLASS_MISC: { - int ret = misc_write(cell->nvmem, cell->offset, buf, size); - + ret = i2c_eeprom_write(cell->nvmem, cell->offset, buf, cell->size); + break; + case UCLASS_MISC: + ret = misc_write(cell->nvmem, cell->offset, buf, cell->size); if (ret < 0) return ret; - if (ret != size) + if (ret != cell->size) return -EIO; - return 0; - } + ret = 0; + break; case UCLASS_RTC: - return dm_rtc_write(cell->nvmem, cell->offset, buf, size); + ret = dm_rtc_write(cell->nvmem, cell->offset, buf, cell->size); + break; default: return -ENOSYS; } + + if (ret) + return ret; + + return 0; } /** @@ -128,6 +236,20 @@ int nvmem_cell_get_by_index(struct udevice *dev, int index, cell->offset = offset; cell->size = size; + + ret = ofnode_read_u32_index(args.node, "bits", 0, &cell->bit_offset); + if (ret) { + cell->bit_offset = 0; + cell->nbits = 0; + } else { + ret = ofnode_read_u32_index(args.node, "bits", 1, &cell->nbits); + if (ret) + return -EINVAL; + + if (cell->bit_offset + cell->nbits > cell->size * 8) + return -EINVAL; + } + return 0; } diff --git a/drivers/misc/qcom-spmi-sdam.c b/drivers/misc/qcom-spmi-sdam.c new file mode 100644 index 000000000000..f987c19deb45 --- /dev/null +++ b/drivers/misc/qcom-spmi-sdam.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Qualcomm SPMI SDAM NVMEM driver + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include +#include +#include +#include +#include + +#define PID_SHIFT 8 +#define PID_MASK (0xFF << PID_SHIFT) +#define REG_MASK 0xFF +#define SDAM_SIZE 0x100 + +struct qcom_sdam_priv { + u32 base; + u32 size; + u32 pmic_usid; + struct udevice *spmi_dev; +}; + +/** + * qcom_sdam_find_spmi_pmic() - Find SPMI controller and PMIC USID + * @dev: SDAM device + * @spmi_dev: Returns SPMI controller device + * @pmic_usid: Returns PMIC USID for SPMI access + * + * Walks up the device tree to find the PMIC parent and SPMI controller. + * Supports both direct SDAM under PMIC and virtual NVMEM under PON. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_sdam_find_spmi_pmic(struct udevice *dev, + struct udevice **spmi_dev, + u32 *pmic_usid) +{ + struct udevice *pmic_dev = dev->parent; + int ret; + + if (!pmic_dev) { + dev_err(dev, "No parent device found\n"); + return -ENODEV; + } + + ret = dev_read_u32_index(pmic_dev, "reg", 0, pmic_usid); + if (ret) { + dev_err(dev, "Could not read PMIC USID: %d\n", ret); + return ret; + } + + *spmi_dev = pmic_dev->parent; + if (!*spmi_dev || (*spmi_dev)->uclass->uc_drv->id != UCLASS_SPMI) { + dev_err(dev, "Could not find SPMI controller\n"); + return -ENODEV; + } + + dev_dbg(dev, "Found PMIC USID=%d, SPMI controller=%s\n", + *pmic_usid, (*spmi_dev)->name); + + return 0; +} + +/** + * qcom_sdam_read() - Read data from SDAM/NVMEM region + * @dev: MISC device (SDAM) + * @offset: Offset within SDAM/NVMEM region + * @buf: Buffer to read data into + * @size: Number of bytes to read + * + * Uses the same SPMI register access pattern as pmic_qcom.c driver + * for consistency and reliability. This function is called by the + * NVMEM subsystem via misc_read(). + * + * Return: number of bytes read on success, negative error code on failure + */ +static int qcom_sdam_read(struct udevice *dev, int offset, + void *buf, int size) +{ + struct qcom_sdam_priv *priv = dev_get_priv(dev); + u8 *buffer = buf; + int ret; + + if (offset + size > priv->size) + return -EINVAL; + + for (size_t i = 0; i < size; i++) { + u32 reg = priv->base + offset + i; + + ret = spmi_reg_read(priv->spmi_dev, priv->pmic_usid, + (reg & PID_MASK) >> PID_SHIFT, + reg & REG_MASK); + if (ret < 0) { + dev_err(dev, "SPMI read failed at 0x%x: %d\n", reg, ret); + return ret; + } + buffer[i] = ret; + + dev_dbg(dev, "Read 0x%02x from 0x%x (PID=0x%02x REG=0x%02x)\n", + buffer[i], reg, (reg & PID_MASK) >> PID_SHIFT, reg & REG_MASK); + } + + return size; +} + +/** + * qcom_sdam_write() - Write data to SDAM/NVMEM region + * @dev: MISC device (SDAM) + * @offset: Offset within SDAM/NVMEM region + * @buf: Buffer containing data to write + * @size: Number of bytes to write + * + * Uses the same SPMI register access pattern as pmic_qcom.c driver + * for consistency and reliability. This function is called by the + * NVMEM subsystem via misc_write(). + * + * Return: number of bytes written on success, negative error code on failure + */ +static int qcom_sdam_write(struct udevice *dev, int offset, + const void *buf, int size) +{ + struct qcom_sdam_priv *priv = dev_get_priv(dev); + const u8 *buffer = buf; + int ret; + + if (offset + size > priv->size) + return -EINVAL; + + for (size_t i = 0; i < size; i++) { + u32 reg = priv->base + offset + i; + + ret = spmi_reg_write(priv->spmi_dev, priv->pmic_usid, + (reg & PID_MASK) >> PID_SHIFT, + reg & REG_MASK, + buffer[i]); + if (ret < 0) { + dev_err(dev, "SPMI write failed at 0x%x: %d\n", reg, ret); + return ret; + } + + dev_dbg(dev, "Wrote 0x%02x to 0x%x (PID=0x%02x REG=0x%02x)\n", + buffer[i], reg, (reg & PID_MASK) >> PID_SHIFT, reg & REG_MASK); + } + + return size; +} + +static const struct misc_ops qcom_sdam_ops = { + .read = qcom_sdam_read, + .write = qcom_sdam_write, +}; + +/** + * qcom_sdam_probe() - Probe SDAM device and register as NVMEM provider + * @dev: SDAM device + * + * Handles both real SDAM blocks and virtual NVMEM under PON blocks. + * For virtual NVMEM, adds the parent PON base address to the offset. + * + * Return: 0 on success, negative error code on failure + */ +static int qcom_sdam_probe(struct udevice *dev) +{ + struct qcom_sdam_priv *priv = dev_get_priv(dev); + fdt_addr_t base; + int ret; + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) { + dev_err(dev, "Could not read base address\n"); + return -EINVAL; + } + + priv->base = base; + priv->size = SDAM_SIZE; + + ret = qcom_sdam_find_spmi_pmic(dev, &priv->spmi_dev, &priv->pmic_usid); + if (ret) + return ret; + + dev_dbg(dev, "SDAM base=0x%x size=0x%x PMIC_USID=%d\n", + priv->base, priv->size, priv->pmic_usid); + + return 0; +} + +static const struct udevice_id qcom_sdam_ids[] = { + { .compatible = "qcom,spmi-sdam" }, + { } +}; + +U_BOOT_DRIVER(qcom_spmi_sdam) = { + .name = "qcom-spmi-sdam", + .id = UCLASS_MISC, + .of_match = qcom_sdam_ids, + .probe = qcom_sdam_probe, + .ops = &qcom_sdam_ops, + .priv_auto = sizeof(struct qcom_sdam_priv), +}; diff --git a/drivers/mmc/msm_sdhci.c b/drivers/mmc/msm_sdhci.c index aaa87923604b..3b36eb897f97 100644 --- a/drivers/mmc/msm_sdhci.c +++ b/drivers/mmc/msm_sdhci.c @@ -188,6 +188,7 @@ static int msm_sdc_probe(struct udevice *dev) struct sdhci_host *host = &prv->host; u32 core_version, core_minor, core_major; struct reset_ctl bcr_rst; + struct blk_desc *bdesc; u32 caps; int ret; @@ -251,6 +252,12 @@ static int msm_sdc_probe(struct udevice *dev) if (ret) return ret; + if (plat->cfg.host_caps & MMC_CAP_NONREMOVABLE) { + bdesc = mmc_get_blk_desc(&plat->mmc); + if (bdesc) + bdesc->removable = 0; + } + host->mmc = &plat->mmc; host->mmc->dev = dev; host->ops = &msm_sdhci_ops; diff --git a/drivers/reboot-mode/Kconfig b/drivers/reboot-mode/Kconfig index 72b33d712235..6e8825f1e67d 100644 --- a/drivers/reboot-mode/Kconfig +++ b/drivers/reboot-mode/Kconfig @@ -39,4 +39,12 @@ config REBOOT_MODE_NVMEM Use any kind of non-volatile memory (EEPROM, RTC, etc) to control the reboot mode. +config REBOOT_MODE_ENV_UPDATE + bool "Automatically update reboot-mode env variable on boot" + depends on DM_REBOOT_MODE + help + When enabled, the reboot-mode uclass will automatically call + dm_reboot_mode_update() on the first reboot-mode device at last + stage init. + endmenu diff --git a/drivers/reboot-mode/reboot-mode-uclass.c b/drivers/reboot-mode/reboot-mode-uclass.c index 7cbe02eb4ed4..976544d28107 100644 --- a/drivers/reboot-mode/reboot-mode-uclass.c +++ b/drivers/reboot-mode/reboot-mode-uclass.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -116,9 +117,33 @@ int dm_reboot_mode_pre_probe(struct udevice *dev) return 0; } +/* + * reboot_mode_last_stage_init() - Update reboot-mode env variable at last + * stage init. + * + * Called via EVT_LAST_STAGE_INIT, which fires after the environment is fully + * initialized. + * + */ +static int reboot_mode_last_stage_init(void) +{ + struct udevice *dev; + int ret; + + if (!CONFIG_IS_ENABLED(REBOOT_MODE_ENV_UPDATE)) + return 0; + + ret = uclass_first_device_err(UCLASS_REBOOT_MODE, &dev); + if (ret) + return 0; + + return dm_reboot_mode_update(dev); +} +EVENT_SPY_SIMPLE(EVT_LAST_STAGE_INIT, reboot_mode_last_stage_init); + UCLASS_DRIVER(reboot_mode) = { - .name = "reboot-mode", - .id = UCLASS_REBOOT_MODE, + .name = "reboot-mode", + .id = UCLASS_REBOOT_MODE, .pre_probe = dm_reboot_mode_pre_probe, .per_device_plat_auto = sizeof(struct reboot_mode_uclass_platdata), diff --git a/drivers/scsi/scsi-uclass.c b/drivers/scsi/scsi-uclass.c index 39b4c7476d45..031dc01b9de6 100644 --- a/drivers/scsi/scsi-uclass.c +++ b/drivers/scsi/scsi-uclass.c @@ -30,9 +30,10 @@ int scsi_get_blk_by_uuid(const char *uuid, struct disk_partition *part_info_ptr) { struct blk_desc *blk; - int i, ret; + int i, ret, max; - for (i = 0; i < blk_find_max_devnum(UCLASS_SCSI) + 1; i++) { + max = blk_find_max_devnum(UCLASS_SCSI) + 1; + for (i = 0; i < max; i++) { ret = blk_get_desc(UCLASS_SCSI, i, &blk); if (ret) continue; @@ -47,6 +48,29 @@ int scsi_get_blk_by_uuid(const char *uuid, return -ENODEV; } +int scsi_get_blk_by_type_guid(const char *type_guid, + struct blk_desc **blk_desc_ptr, + struct disk_partition *part_info_ptr) +{ + struct blk_desc *blk; + int i, ret, max; + + max = blk_find_max_devnum(UCLASS_SCSI) + 1; + for (i = 0; i < max; i++) { + ret = blk_get_desc(UCLASS_SCSI, i, &blk); + if (ret) + continue; + + ret = part_get_info_by_type_guid(blk, type_guid, part_info_ptr); + if (ret > 0) { + *blk_desc_ptr = blk; + return 0; + } + } + + return -ENODEV; +} + int scsi_bus_reset(struct udevice *dev) { struct scsi_ops *ops = scsi_get_ops(dev); diff --git a/drivers/smem/Kconfig b/drivers/smem/Kconfig deleted file mode 100644 index e5d7dcc81b17..000000000000 --- a/drivers/smem/Kconfig +++ /dev/null @@ -1,25 +0,0 @@ -menuconfig SMEM - bool "SMEM (Shared Memory mamanger) support" - -if SMEM - -config SANDBOX_SMEM - bool "Sandbox Shared Memory Manager (SMEM)" - depends on SANDBOX && DM - help - enable SMEM support for sandbox. This is an emulation of a real SMEM - manager. - The sandbox driver allocates a shared memory from the heap and - initialzies it on start. - -config MSM_SMEM - bool "Qualcomm Shared Memory Manager (SMEM)" - depends on DM - depends on ARCH_SNAPDRAGON || ARCH_IPQ40XX - select DEVRES - help - Enable support for the Qualcomm Shared Memory Manager. - The driver provides an interface to items in a heap shared among all - processors in a Qualcomm platform. - -endif # menu "SMEM Support" diff --git a/drivers/smem/Makefile b/drivers/smem/Makefile deleted file mode 100644 index af3e9b50883c..000000000000 --- a/drivers/smem/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0+ -# -# Makefile for the U-Boot SMEM interface drivers - -obj-$(CONFIG_SANDBOX_SMEM) += sandbox_smem.o -obj-$(CONFIG_SMEM) += smem-uclass.o -obj-$(CONFIG_MSM_SMEM) += msm_smem.o diff --git a/drivers/smem/sandbox_smem.c b/drivers/smem/sandbox_smem.c deleted file mode 100644 index fec98e5611d6..000000000000 --- a/drivers/smem/sandbox_smem.c +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (c) 2018 Ramon Fried - */ - -#include -#include -#include -#include - -static int sandbox_smem_alloc(unsigned int host, - unsigned int item, size_t size) -{ - return 0; -} - -static void *sandbox_smem_get(unsigned int host, - unsigned int item, size_t *size) -{ - return NULL; -} - -static int sandbox_smem_get_free_space(unsigned int host) -{ - return 0; -} - -static const struct smem_ops sandbox_smem_ops = { - .alloc = sandbox_smem_alloc, - .get = sandbox_smem_get, - .get_free_space = sandbox_smem_get_free_space, -}; - -static const struct udevice_id sandbox_smem_ids[] = { - { .compatible = "sandbox,smem" }, - { } -}; - -U_BOOT_DRIVER(smem_sandbox) = { - .name = "smem_sandbox", - .id = UCLASS_SMEM, - .of_match = sandbox_smem_ids, - .ops = &sandbox_smem_ops, -}; diff --git a/drivers/smem/smem-uclass.c b/drivers/smem/smem-uclass.c deleted file mode 100644 index 4dea5cc4bf1c..000000000000 --- a/drivers/smem/smem-uclass.c +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (c) 2018 Ramon Fried - */ - -#define LOG_CATEGORY UCLASS_SMEM - -#include -#include - -int smem_alloc(struct udevice *dev, unsigned int host, - unsigned int item, size_t size) -{ - struct smem_ops *ops = smem_get_ops(dev); - - if (!ops->alloc) - return -ENOSYS; - - return ops->alloc(host, item, size); -} - -void *smem_get(struct udevice *dev, unsigned int host, - unsigned int item, size_t *size) -{ - struct smem_ops *ops = smem_get_ops(dev); - - if (!ops->get) - return NULL; - - return ops->get(host, item, size); -} - -int smem_get_free_space(struct udevice *dev, unsigned int host) -{ - struct smem_ops *ops = smem_get_ops(dev); - - if (!ops->get_free_space) - return -ENOSYS; - - return ops->get_free_space(host); -} - -UCLASS_DRIVER(smem) = { - .id = UCLASS_SMEM, - .name = "smem", -}; diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 8243805e46a3..cdd9e30f43e5 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -25,4 +25,12 @@ config QCOM_RPMH Say y here to support the Qualcomm RPMh (resource peripheral manager) if you need to control regulators on Qualcomm platforms, say y here. +config QCOM_SMEM + bool "Qualcomm SMEM support" + help + Say y here to support the Qualcomm SMEM (shared memory) client driver. + SMEM is a shared memory region that is used to pass information about + the hardware (e.g. DRAM layout, PMIC configuration, etc) between bootloader + staged and the OS. If in doubt, say y. + endif # SOC_QCOM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 78fae8bbfa16..f4102f9155a8 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o obj-$(CONFIG_QCOM_RPMH) += rpmh-rsc.o rpmh.o +obj-$(CONFIG_QCOM_SMEM) += smem.o diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c index d0a6047b8a65..61e157831231 100644 --- a/drivers/soc/qcom/cmd-db.c +++ b/drivers/soc/qcom/cmd-db.c @@ -248,7 +248,7 @@ static int cmd_db_bind(struct udevice *dev) } /* On SM8550/SM8650 and newer SoCs cmd-db might not be mapped */ - mmu_map_region((phys_addr_t)base, (phys_size_t)size, false); + mmu_map_region((phys_addr_t)base, (phys_size_t)size, false, false); cmd_db_header = base; if (!cmd_db_magic_matches(cmd_db_header)) { diff --git a/drivers/smem/msm_smem.c b/drivers/soc/qcom/smem.c similarity index 56% rename from drivers/smem/msm_smem.c rename to drivers/soc/qcom/smem.c index 7a50d5a5792c..7acf5b300eb1 100644 --- a/drivers/smem/msm_smem.c +++ b/drivers/soc/qcom/smem.c @@ -1,26 +1,25 @@ -// SPDX-License-Identifier: GPL-2.0+ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2015, Sony Mobile Communications AB. * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. - * Copyright (c) 2018, Ramon Fried */ -#include -#include +#define pr_fmt(fmt) "smem: " fmt + +#include +#include #include -#include -#include -#include -#include +#include #include -#include -#include #include +#include #include -#include +#include +#include +#include /* - * The Qualcomm shared memory system is an allocate-only heap structure that + * The Qualcomm shared memory system is a allocate only heap structure that * consists of one of more memory areas that can be accessed by the processors * in the SoC. * @@ -33,7 +32,7 @@ * parts of the shared memory space. * * - * In addition to this global heap, a set of "private" heaps can be set up at + * In addition to this global heap a set of "private" heaps can be set up at * boot time with access restrictions so that only certain processor pairs can * access the data. * @@ -58,6 +57,10 @@ * region with partition type (SMEM_GLOBAL_HOST) and the max smem item count is * set by the bootloader. * + * To synchronize allocations in the shared memory heaps a remote spinlock must + * be held - currently lock number 3 of the sfpb or tcsr is used for this on all + * platforms. + * */ /* @@ -85,14 +88,14 @@ #define SMEM_GLOBAL_HOST 0xfffe /* Max number of processors/hosts in a system */ -#define SMEM_HOST_COUNT 25 +#define SMEM_HOST_COUNT 20 /** - * struct smem_proc_comm - proc_comm communication struct (legacy) - * @command: current command to be executed - * @status: status of the currently requested command - * @params: parameters to the command - */ + * struct smem_proc_comm - proc_comm communication struct (legacy) + * @command: current command to be executed + * @status: status of the currently requested command + * @params: parameters to the command + */ struct smem_proc_comm { __le32 command; __le32 status; @@ -123,7 +126,7 @@ struct smem_global_entry { * @free_offset: index of the first unallocated byte in smem * @available: number of bytes available for allocation * @reserved: reserved field, must be 0 - * toc: array of references to items + * @toc: array of references to items */ struct smem_header { struct smem_proc_comm proc_comm[4]; @@ -195,6 +198,20 @@ struct smem_partition_header { __le32 reserved[3]; }; +/** + * struct smem_partition - describes smem partition + * @virt_base: starting virtual address of partition + * @phys_base: starting physical address of partition + * @cacheline: alignment for "cached" entries + * @size: size of partition + */ +struct smem_partition { + void __iomem *virt_base; + phys_addr_t phys_base; + size_t cacheline; + size_t size; +}; + static const u8 SMEM_PART_MAGIC[] = { 0x24, 0x50, 0x52, 0x54 }; /** @@ -234,6 +251,8 @@ struct smem_info { static const u8 SMEM_INFO_MAGIC[] = { 0x53, 0x49, 0x49, 0x49 }; /* SIII */ +#define SMEM_MAX_REGIONS 2 + /** * struct smem_region - representation of a chunk of memory used for smem * @aux_base: identifier of aux_mem base @@ -241,37 +260,37 @@ static const u8 SMEM_INFO_MAGIC[] = { 0x53, 0x49, 0x49, 0x49 }; /* SIII */ * @size: size of the memory region */ struct smem_region { - u32 aux_base; + phys_addr_t aux_base; void __iomem *virt_base; size_t size; }; /** * struct qcom_smem - device data for the smem device - * @dev: device pointer - * @global_partition: pointer to global partition when in use - * @global_cacheline: cacheline size for global partition - * @partitions: list of pointers to partitions affecting the current - * processor/host - * @cacheline: list of cacheline sizes for each host + * @ptable: virtual base of partition table + * @global_partition: describes for global partition when in use + * @partitions: list of partitions of current processor/host * @item_count: max accepted item number * @num_regions: number of @regions * @regions: list of the memory regions defining the shared memory */ struct qcom_smem { - struct udevice *dev; - - struct smem_partition_header *global_partition; - size_t global_cacheline; - struct smem_partition_header *partitions[SMEM_HOST_COUNT]; - size_t cacheline[SMEM_HOST_COUNT]; u32 item_count; + struct smem_ptable *ptable; + struct smem_partition global_partition; + struct smem_partition partitions[SMEM_HOST_COUNT]; - unsigned int num_regions; - struct smem_region regions[0]; + unsigned num_regions; + struct smem_region regions[SMEM_MAX_REGIONS]; }; -static struct smem_private_entry * +/* Pre-allocated storage for SMEM */ +static struct qcom_smem __smem __section(".data") = { 0 }; + +/* Pointer to the one and only smem handle */ +static struct qcom_smem *smem __section(".data") = NULL; + +static void * phdr_to_last_uncached_entry(struct smem_partition_header *phdr) { void *p = phdr; @@ -279,15 +298,18 @@ phdr_to_last_uncached_entry(struct smem_partition_header *phdr) return p + le32_to_cpu(phdr->offset_free_uncached); } -static void *phdr_to_first_cached_entry(struct smem_partition_header *phdr, +static struct smem_private_entry * +phdr_to_first_cached_entry(struct smem_partition_header *phdr, size_t cacheline) { void *p = phdr; + struct smem_private_entry *e; - return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*phdr), cacheline); + return p + le32_to_cpu(phdr->size) - ALIGN(sizeof(*e), cacheline); } -static void *phdr_to_last_cached_entry(struct smem_partition_header *phdr) +static void * +phdr_to_last_cached_entry(struct smem_partition_header *phdr) { void *p = phdr; @@ -333,40 +355,54 @@ static void *cached_entry_to_item(struct smem_private_entry *e) return p - le32_to_cpu(e->size); } -/* Pointer to the one and only smem handle */ -static struct qcom_smem *__smem; +/** + * qcom_smem_is_available() - Check if SMEM is available + * + * Return: true if SMEM is available, false otherwise. + */ +bool qcom_smem_is_available(void) +{ + return !!smem; +} +EXPORT_SYMBOL_GPL(qcom_smem_is_available); static int qcom_smem_alloc_private(struct qcom_smem *smem, - struct smem_partition_header *phdr, - unsigned int item, + struct smem_partition *part, + unsigned item, size_t size) { struct smem_private_entry *hdr, *end; + struct smem_partition_header *phdr; size_t alloc_size; void *cached; + void *p_end; + + phdr = (struct smem_partition_header __force *)part->virt_base; + p_end = (void *)phdr + part->size; hdr = phdr_to_first_uncached_entry(phdr); end = phdr_to_last_uncached_entry(phdr); cached = phdr_to_last_cached_entry(phdr); - while (hdr < end) { - if (hdr->canary != SMEM_PRIVATE_CANARY) { - dev_err(smem->dev, - "Found invalid canary in hosts %d:%d partition\n", - phdr->host0, phdr->host1); - return -EINVAL; - } + if (WARN_ON((void *)end > p_end || cached > p_end)) + return -EINVAL; + while (hdr < end) { + if (hdr->canary != SMEM_PRIVATE_CANARY) + goto bad_canary; if (le16_to_cpu(hdr->item) == item) return -EEXIST; hdr = uncached_entry_next(hdr); } + if (WARN_ON((void *)hdr > p_end)) + return -EINVAL; + /* Check that we don't grow into the cached region */ alloc_size = sizeof(*hdr) + ALIGN(size, 8); - if ((void *)hdr + alloc_size >= cached) { - dev_err(smem->dev, "Out of memory\n"); + if ((void *)hdr + alloc_size > cached) { + log_err("Out of memory\n"); return -ENOSPC; } @@ -381,14 +417,19 @@ static int qcom_smem_alloc_private(struct qcom_smem *smem, * that remote processors that does not take the remote spinlock still * gets a consistent view of the linked list. */ - dmb(); + wmb(); le32_add_cpu(&phdr->offset_free_uncached, alloc_size); return 0; +bad_canary: + log_err("Found invalid canary in hosts %hu:%hu partition\n", + le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); + + return -EINVAL; } static int qcom_smem_alloc_global(struct qcom_smem *smem, - unsigned int item, + unsigned item, size_t size) { struct smem_global_entry *entry; @@ -411,7 +452,7 @@ static int qcom_smem_alloc_global(struct qcom_smem *smem, * so that remote processors will get a consistent view of the item * even though they do not take the spinlock on read. */ - dmb(); + wmb(); entry->allocated = cpu_to_le32(1); le32_add_cpu(&header->free_offset, size); @@ -429,45 +470,47 @@ static int qcom_smem_alloc_global(struct qcom_smem *smem, * Allocate space for a given smem item of size @size, given that the item is * not yet allocated. */ -static int qcom_smem_alloc(unsigned int host, unsigned int item, size_t size) +int qcom_smem_alloc(unsigned host, unsigned item, size_t size) { - struct smem_partition_header *phdr; + struct smem_partition *part; int ret; - if (!__smem) - return -ENOMEM; + if (!smem) + return -EPROBE_DEFER; if (item < SMEM_ITEM_LAST_FIXED) { - dev_err(__smem->dev, - "Rejecting allocation of static entry %d\n", item); + log_err("Rejecting allocation of static entry %d\n", item); return -EINVAL; } - if (WARN_ON(item >= __smem->item_count)) + if (WARN_ON(item >= smem->item_count)) return -EINVAL; - if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { - phdr = __smem->partitions[host]; - ret = qcom_smem_alloc_private(__smem, phdr, item, size); - } else if (__smem->global_partition) { - phdr = __smem->global_partition; - ret = qcom_smem_alloc_private(__smem, phdr, item, size); + if (host < SMEM_HOST_COUNT && smem->partitions[host].virt_base) { + part = &smem->partitions[host]; + ret = qcom_smem_alloc_private(smem, part, item, size); + } else if (smem->global_partition.virt_base) { + part = &smem->global_partition; + ret = qcom_smem_alloc_private(smem, part, item, size); } else { - ret = qcom_smem_alloc_global(__smem, item, size); + ret = qcom_smem_alloc_global(smem, item, size); } return ret; } +EXPORT_SYMBOL_GPL(qcom_smem_alloc); static void *qcom_smem_get_global(struct qcom_smem *smem, - unsigned int item, + unsigned item, size_t *size) { struct smem_header *header; - struct smem_region *area; + struct smem_region *region; struct smem_global_entry *entry; + u64 entry_offset; + u32 e_size; u32 aux_base; - unsigned int i; + unsigned i; header = smem->regions[0].virt_base; entry = &header->toc[item]; @@ -477,12 +520,19 @@ static void *qcom_smem_get_global(struct qcom_smem *smem, aux_base = le32_to_cpu(entry->aux_base) & AUX_BASE_MASK; for (i = 0; i < smem->num_regions; i++) { - area = &smem->regions[i]; + region = &smem->regions[i]; + + if ((u32)region->aux_base == aux_base || !aux_base) { + e_size = le32_to_cpu(entry->size); + entry_offset = le32_to_cpu(entry->offset); + + if (WARN_ON(e_size + entry_offset > region->size)) + return ERR_PTR(-EINVAL); - if (area->aux_base == aux_base || !aux_base) { if (size != NULL) - *size = le32_to_cpu(entry->size); - return area->virt_base + le32_to_cpu(entry->offset); + *size = e_size; + + return region->virt_base + entry_offset; } } @@ -490,12 +540,18 @@ static void *qcom_smem_get_global(struct qcom_smem *smem, } static void *qcom_smem_get_private(struct qcom_smem *smem, - struct smem_partition_header *phdr, - size_t cacheline, - unsigned int item, + struct smem_partition *part, + unsigned item, size_t *size) { struct smem_private_entry *e, *end; + struct smem_partition_header *phdr; + void *item_ptr, *p_end; + u32 padding_data; + u32 e_size; + + phdr = (struct smem_partition_header __force *)part->virt_base; + p_end = (void *)phdr + part->size; e = phdr_to_first_uncached_entry(phdr); end = phdr_to_last_uncached_entry(phdr); @@ -505,41 +561,70 @@ static void *qcom_smem_get_private(struct qcom_smem *smem, goto invalid_canary; if (le16_to_cpu(e->item) == item) { - if (size != NULL) - *size = le32_to_cpu(e->size) - - le16_to_cpu(e->padding_data); + if (size != NULL) { + e_size = le32_to_cpu(e->size); + padding_data = le16_to_cpu(e->padding_data); + + if (WARN_ON(e_size > part->size || padding_data > e_size)) + return ERR_PTR(-EINVAL); - return uncached_entry_to_item(e); + *size = e_size - padding_data; + } + + item_ptr = uncached_entry_to_item(e); + if (WARN_ON(item_ptr > p_end)) + return ERR_PTR(-EINVAL); + + return item_ptr; } e = uncached_entry_next(e); } + if (WARN_ON((void *)e > p_end)) + return ERR_PTR(-EINVAL); + /* Item was not found in the uncached list, search the cached list */ - e = phdr_to_first_cached_entry(phdr, cacheline); + e = phdr_to_first_cached_entry(phdr, part->cacheline); end = phdr_to_last_cached_entry(phdr); + if (WARN_ON((void *)e < (void *)phdr || (void *)end > p_end)) + return ERR_PTR(-EINVAL); + while (e > end) { if (e->canary != SMEM_PRIVATE_CANARY) goto invalid_canary; if (le16_to_cpu(e->item) == item) { - if (size != NULL) - *size = le32_to_cpu(e->size) - - le16_to_cpu(e->padding_data); + if (size != NULL) { + e_size = le32_to_cpu(e->size); + padding_data = le16_to_cpu(e->padding_data); + + if (WARN_ON(e_size > part->size || padding_data > e_size)) + return ERR_PTR(-EINVAL); + + *size = e_size - padding_data; + } + + item_ptr = cached_entry_to_item(e); + if (WARN_ON(item_ptr < (void *)phdr)) + return ERR_PTR(-EINVAL); - return cached_entry_to_item(e); + return item_ptr; } - e = cached_entry_next(e, cacheline); + e = cached_entry_next(e, part->cacheline); } + if (WARN_ON((void *)e < (void *)phdr)) + return ERR_PTR(-EINVAL); + return ERR_PTR(-ENOENT); invalid_canary: - dev_err(smem->dev, "Found invalid canary in hosts %d:%d partition\n", - phdr->host0, phdr->host1); + log_err("Found invalid canary in hosts %hu:%hu partition\n", + le16_to_cpu(phdr->host0), le16_to_cpu(phdr->host1)); return ERR_PTR(-EINVAL); } @@ -553,33 +638,30 @@ static void *qcom_smem_get_private(struct qcom_smem *smem, * Looks up smem item and returns pointer to it. Size of smem * item is returned in @size. */ -static void *qcom_smem_get(unsigned int host, unsigned int item, size_t *size) +void *qcom_smem_get(unsigned host, unsigned item, size_t *size) { - struct smem_partition_header *phdr; - size_t cacheln; - void *ptr = ERR_PTR(-ENOMEM); + struct smem_partition *part; + void *ptr = ERR_PTR(-EPROBE_DEFER); - if (!__smem) + if (!smem) return ptr; - if (WARN_ON(item >= __smem->item_count)) + if (WARN_ON(item >= smem->item_count)) return ERR_PTR(-EINVAL); - if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { - phdr = __smem->partitions[host]; - cacheln = __smem->cacheline[host]; - ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size); - } else if (__smem->global_partition) { - phdr = __smem->global_partition; - cacheln = __smem->global_cacheline; - ptr = qcom_smem_get_private(__smem, phdr, cacheln, item, size); + if (host < SMEM_HOST_COUNT && smem->partitions[host].virt_base) { + part = &smem->partitions[host]; + ptr = qcom_smem_get_private(smem, part, item, size); + } else if (smem->global_partition.virt_base) { + part = &smem->global_partition; + ptr = qcom_smem_get_private(smem, part, item, size); } else { - ptr = qcom_smem_get_global(__smem, item, size); + ptr = qcom_smem_get_global(smem, item, size); } return ptr; - } +EXPORT_SYMBOL_GPL(qcom_smem_get); /** * qcom_smem_get_free_space() - retrieve amount of free space in a partition @@ -588,30 +670,94 @@ static void *qcom_smem_get(unsigned int host, unsigned int item, size_t *size) * To be used by smem clients as a quick way to determine if any new * allocations has been made. */ -static int qcom_smem_get_free_space(unsigned int host) +int qcom_smem_get_free_space(unsigned host) { + struct smem_partition *part; struct smem_partition_header *phdr; struct smem_header *header; - unsigned int ret; + unsigned ret; - if (!__smem) - return -ENOMEM; + if (!smem) + return -EPROBE_DEFER; - if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { - phdr = __smem->partitions[host]; + if (host < SMEM_HOST_COUNT && smem->partitions[host].virt_base) { + part = &smem->partitions[host]; + phdr = part->virt_base; ret = le32_to_cpu(phdr->offset_free_cached) - le32_to_cpu(phdr->offset_free_uncached); - } else if (__smem->global_partition) { - phdr = __smem->global_partition; + + if (ret > le32_to_cpu(part->size)) + return -EINVAL; + } else if (smem->global_partition.virt_base) { + part = &smem->global_partition; + phdr = part->virt_base; ret = le32_to_cpu(phdr->offset_free_cached) - le32_to_cpu(phdr->offset_free_uncached); + + if (ret > le32_to_cpu(part->size)) + return -EINVAL; } else { - header = __smem->regions[0].virt_base; + header = smem->regions[0].virt_base; ret = le32_to_cpu(header->available); + + if (ret > smem->regions[0].size) + return -EINVAL; } return ret; } +EXPORT_SYMBOL_GPL(qcom_smem_get_free_space); + +static bool addr_in_range(void __iomem *base, size_t size, void *addr) +{ + return base && ((void __iomem *)addr >= base && (void __iomem *)addr < base + size); +} + +/** + * qcom_smem_virt_to_phys() - return the physical address associated + * with an smem item pointer (previously returned by qcom_smem_get() + * @p: the virtual address to convert + * + * Returns 0 if the pointer provided is not within any smem region. + */ +phys_addr_t qcom_smem_virt_to_phys(void *p) +{ + struct smem_partition *part; + struct smem_region *area; + u64 offset; + u32 i; + + for (i = 0; i < SMEM_HOST_COUNT; i++) { + part = &smem->partitions[i]; + + if (addr_in_range(part->virt_base, part->size, p)) { + offset = p - part->virt_base; + + return (phys_addr_t)part->phys_base + offset; + } + } + + part = &smem->global_partition; + + if (addr_in_range(part->virt_base, part->size, p)) { + offset = p - part->virt_base; + + return (phys_addr_t)part->phys_base + offset; + } + + for (i = 0; i < smem->num_regions; i++) { + area = &smem->regions[i]; + + if (addr_in_range(area->virt_base, area->size, p)) { + offset = p - area->virt_base; + + return (phys_addr_t)area->aux_base + offset; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(qcom_smem_virt_to_phys); static int qcom_smem_get_sbl_version(struct qcom_smem *smem) { @@ -629,14 +775,13 @@ static struct smem_ptable *qcom_smem_get_ptable(struct qcom_smem *smem) struct smem_ptable *ptable; u32 version; - ptable = smem->regions[0].virt_base + smem->regions[0].size - SZ_4K; + ptable = smem->ptable; if (memcmp(ptable->magic, SMEM_PTABLE_MAGIC, sizeof(ptable->magic))) return ERR_PTR(-ENOENT); version = le32_to_cpu(ptable->version); if (version != 1) { - dev_err(smem->dev, - "Unsupported partition header version %d\n", version); + log_err("Unsupported partition header version %d\n", version); return ERR_PTR(-EINVAL); } return ptable; @@ -658,82 +803,117 @@ static u32 qcom_smem_get_item_count(struct qcom_smem *smem) return le16_to_cpu(info->num_items); } -static int qcom_smem_set_global_partition(struct qcom_smem *smem) +/* + * Validate the partition header for a partition whose partition + * table entry is supplied. Returns a pointer to its header if + * valid, or a null pointer otherwise. + */ +static struct smem_partition_header * +qcom_smem_partition_header(struct qcom_smem *smem, + struct smem_ptable_entry *entry, u16 host0, u16 host1) { struct smem_partition_header *header; - struct smem_ptable_entry *entry = NULL; - struct smem_ptable *ptable; - u32 host0, host1, size; - int i; + u64 phys_addr; + u32 size; - ptable = qcom_smem_get_ptable(smem); - if (IS_ERR(ptable)) - return PTR_ERR(ptable); + phys_addr = smem->regions[0].aux_base + le32_to_cpu(entry->offset); + header = (void *)phys_addr; // devm_ioremap_wc() - for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { - entry = &ptable->entry[i]; - host0 = le16_to_cpu(entry->host0); - host1 = le16_to_cpu(entry->host1); + if (!header) + return NULL; - if (host0 == SMEM_GLOBAL_HOST && host0 == host1) - break; + if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) { + log_err("bad partition magic %4ph\n", header->magic); + return NULL; } - if (!entry) { - dev_err(smem->dev, "Missing entry for global partition\n"); - return -EINVAL; + if (host0 != le16_to_cpu(header->host0)) { + log_err("bad host0 (%hu != %hu)\n", + host0, le16_to_cpu(header->host0)); + return NULL; + } + if (host1 != le16_to_cpu(header->host1)) { + log_err("bad host1 (%hu != %hu)\n", + host1, le16_to_cpu(header->host1)); + return NULL; } - if (!le32_to_cpu(entry->offset) || !le32_to_cpu(entry->size)) { - dev_err(smem->dev, "Invalid entry for global partition\n"); - return -EINVAL; + size = le32_to_cpu(header->size); + if (size != le32_to_cpu(entry->size)) { + log_err("bad partition size (%u != %u)\n", + size, le32_to_cpu(entry->size)); + return NULL; } - if (smem->global_partition) { - dev_err(smem->dev, "Already found the global partition\n"); - return -EINVAL; + if (le32_to_cpu(header->offset_free_uncached) > size) { + log_err("bad partition free uncached (%u > %u)\n", + le32_to_cpu(header->offset_free_uncached), size); + return NULL; } - header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); - host0 = le16_to_cpu(header->host0); - host1 = le16_to_cpu(header->host1); + return header; +} - if (memcmp(header->magic, SMEM_PART_MAGIC, sizeof(header->magic))) { - dev_err(smem->dev, "Global partition has invalid magic\n"); +static int qcom_smem_set_global_partition(struct qcom_smem *smem) +{ + struct smem_partition_header *header; + struct smem_ptable_entry *entry; + struct smem_ptable *ptable; + bool found = false; + int i; + + if (smem->global_partition.virt_base) { + log_err("Already found the global partition\n"); return -EINVAL; } - if (host0 != SMEM_GLOBAL_HOST && host1 != SMEM_GLOBAL_HOST) { - dev_err(smem->dev, "Global partition hosts are invalid\n"); - return -EINVAL; + ptable = qcom_smem_get_ptable(smem); + if (IS_ERR(ptable)) + return PTR_ERR(ptable); + + for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { + entry = &ptable->entry[i]; + if (!le32_to_cpu(entry->offset)) + continue; + if (!le32_to_cpu(entry->size)) + continue; + + if (le16_to_cpu(entry->host0) != SMEM_GLOBAL_HOST) + continue; + + if (le16_to_cpu(entry->host1) == SMEM_GLOBAL_HOST) { + found = true; + break; + } } - if (le32_to_cpu(header->size) != le32_to_cpu(entry->size)) { - dev_err(smem->dev, "Global partition has invalid size\n"); + if (!found) { + log_err("Missing entry for global partition\n"); return -EINVAL; } - size = le32_to_cpu(header->offset_free_uncached); - if (size > le32_to_cpu(header->size)) { - dev_err(smem->dev, - "Global partition has invalid free pointer\n"); + header = qcom_smem_partition_header(smem, entry, + SMEM_GLOBAL_HOST, SMEM_GLOBAL_HOST); + if (!header) return -EINVAL; - } - smem->global_partition = header; - smem->global_cacheline = le32_to_cpu(entry->cacheline); + smem->global_partition.virt_base = (void __iomem *)header; + smem->global_partition.phys_base = smem->regions[0].aux_base + + le32_to_cpu(entry->offset); + smem->global_partition.size = le32_to_cpu(entry->size); + smem->global_partition.cacheline = le32_to_cpu(entry->cacheline); return 0; } -static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, - unsigned int local_host) +static int +qcom_smem_enumerate_partitions(struct qcom_smem *smem, u16 local_host) { struct smem_partition_header *header; struct smem_ptable_entry *entry; struct smem_ptable *ptable; - unsigned int remote_host; - u32 host0, host1; + u16 remote_host; + u16 host0, host1; int i; ptable = qcom_smem_get_ptable(smem); @@ -742,169 +922,164 @@ static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, for (i = 0; i < le32_to_cpu(ptable->num_entries); i++) { entry = &ptable->entry[i]; - host0 = le16_to_cpu(entry->host0); - host1 = le16_to_cpu(entry->host1); - - if (host0 != local_host && host1 != local_host) - continue; - if (!le32_to_cpu(entry->offset)) continue; - if (!le32_to_cpu(entry->size)) continue; + host0 = le16_to_cpu(entry->host0); + host1 = le16_to_cpu(entry->host1); if (host0 == local_host) remote_host = host1; - else + else if (host1 == local_host) remote_host = host0; + else + continue; if (remote_host >= SMEM_HOST_COUNT) { - dev_err(smem->dev, - "Invalid remote host %d\n", - remote_host); + log_err("bad host %u\n", remote_host); return -EINVAL; } - if (smem->partitions[remote_host]) { - dev_err(smem->dev, - "Already found a partition for host %d\n", - remote_host); + if (smem->partitions[remote_host].virt_base) { + log_err("duplicate host %u\n", remote_host); return -EINVAL; } - header = smem->regions[0].virt_base + le32_to_cpu(entry->offset); - host0 = le16_to_cpu(header->host0); - host1 = le16_to_cpu(header->host1); - - if (memcmp(header->magic, SMEM_PART_MAGIC, - sizeof(header->magic))) { - dev_err(smem->dev, - "Partition %d has invalid magic\n", i); + header = qcom_smem_partition_header(smem, entry, host0, host1); + if (!header) return -EINVAL; - } - if (host0 != local_host && host1 != local_host) { - dev_err(smem->dev, - "Partition %d hosts are invalid\n", i); - return -EINVAL; - } + smem->partitions[remote_host].virt_base = (void __iomem *)header; + smem->partitions[remote_host].phys_base = smem->regions[0].aux_base + + le32_to_cpu(entry->offset); + smem->partitions[remote_host].size = le32_to_cpu(entry->size); + smem->partitions[remote_host].cacheline = le32_to_cpu(entry->cacheline); + } - if (host0 != remote_host && host1 != remote_host) { - dev_err(smem->dev, - "Partition %d hosts are invalid\n", i); - return -EINVAL; - } + return 0; +} - if (le32_to_cpu(header->size) != le32_to_cpu(entry->size)) { - dev_err(smem->dev, - "Partition %d has invalid size\n", i); - return -EINVAL; - } +static int qcom_smem_map_toc(struct qcom_smem *smem, struct smem_region *region) +{ + u32 ptable_start; - if (le32_to_cpu(header->offset_free_uncached) > le32_to_cpu(header->size)) { - dev_err(smem->dev, - "Partition %d has invalid free pointer\n", i); - return -EINVAL; - } + /* map starting 4K for smem header */ + region->virt_base = (void *)region->aux_base; + ptable_start = region->aux_base + region->size - SZ_4K; + /* map last 4k for toc */ + smem->ptable = (struct smem_ptable *)(u64)ptable_start; - smem->partitions[remote_host] = header; - smem->cacheline[remote_host] = le32_to_cpu(entry->cacheline); - } + if (!region->virt_base || !smem->ptable) + return -ENOMEM; + + if (mmu_status()) + mmu_map_region(region->aux_base, region->size, false, false); return 0; } -static int qcom_smem_map_memory(struct qcom_smem *smem, struct udevice *dev, - const char *name, int i) +static int qcom_smem_map_global(struct qcom_smem *smem, u32 size) { - int ret; - struct ofnode_phandle_args args; - struct resource r; + u64 phys_addr; - if (!dev_read_prop(dev, name, NULL)) { - dev_err(dev, "%s prop not found\n", name); - return -EINVAL; - } + phys_addr = smem->regions[0].aux_base; - ret = dev_read_phandle_with_args(dev, name, NULL, 0, 0, &args); - if (ret) { - dev_err(dev, "%s phandle read failed\n", name); - return -EINVAL; - } + smem->regions[0].size = size; + smem->regions[0].virt_base = (void *)phys_addr; - if (!ofnode_valid(args.node)) { - dev_err(dev, "Invalid node from phandle args\n"); - return -EINVAL; + if (!smem->regions[0].virt_base) + return -ENOMEM; + + return 0; +} + +int qcom_socinfo_init(void) +{ + struct socinfo *info; + size_t item_size; + char buf[32] = { 0 }; + + info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HW_SW_BUILD_ID, + &item_size); + if (IS_ERR(info)) { + log_err("Couldn't find socinfo: %ld\n", PTR_ERR(info)); + return PTR_ERR(info); } - ret = ofnode_read_resource(args.node, 0, &r); - if (ret) { - dev_err(dev, "Can't get mmap base address(%d)\n", ret); - return ret; + if (offsetof(struct socinfo, serial_num) + sizeof(info->serial_num) <= item_size) { + snprintf(buf, sizeof(buf), "%u", le32_to_cpu(info->serial_num)); + env_set("serial#", buf); + } else { + return -ENOENT; } - smem->regions[i].aux_base = (u32)r.start; - smem->regions[i].size = resource_size(&r); - smem->regions[i].virt_base = devm_ioremap(dev, r.start, resource_size(&r)); - if (!smem->regions[i].virt_base) - return -ENOMEM; return 0; } -static int qcom_smem_probe(struct udevice *dev) +int qcom_smem_init(void) { struct smem_header *header; - struct qcom_smem *smem; - size_t array_size; int num_regions; + fdt_size_t reg_size = 0; + u32 phandle; + ofnode node, mem_node; u32 version; + u32 size; int ret; - fdt_addr_t addr; - fdt_size_t size; + int i; + + if (mmu_status() && smem) + mmu_map_region(smem->regions[0].aux_base, smem->regions[0].size, + false, false); - if (__smem) + if (smem) return 0; + smem = &__smem; + num_regions = 1; - if (dev_read_prop(dev, "qcom,rpm-msg-ram", NULL)) - num_regions++; - array_size = num_regions * sizeof(struct smem_region); - smem = devm_kzalloc(dev, sizeof(*smem) + array_size, GFP_KERNEL); - if (!smem) - return -ENOMEM; + node = ofnode_by_compatible(ofnode_root(), "qcom,smem"); + if (!ofnode_valid(node)) + return -ENODEV; + + if (ofnode_has_property(node, "memory-region")) { + ofnode_read_u32(node, "memory-region", &phandle); + mem_node = ofnode_get_by_phandle(phandle); + } else { + mem_node = node; + } - smem->dev = dev; smem->num_regions = num_regions; - addr = dev_read_addr_size(dev, &size); - if (addr == FDT_ADDR_T_NONE) { - ret = qcom_smem_map_memory(smem, dev, "memory-region", 0); - if (ret) - return ret; - } else { - smem->regions[0].aux_base = (u32)addr; - smem->regions[0].size = size; - smem->regions[0].virt_base = devm_ioremap(dev, addr, size); - if (!smem->regions[0].virt_base) - return -ENOMEM; + smem->regions[0].aux_base = ofnode_get_addr(mem_node); + reg_size = ofnode_get_size(mem_node); + if (smem->regions[0].aux_base == FDT_ADDR_T_NONE) { + log_err("Failed to get base address\n"); + return -EINVAL; } + smem->regions[0].size = reg_size; - if (num_regions > 1) { - ret = qcom_smem_map_memory(smem, dev, - "qcom,rpm-msg-ram", 1); - if (ret) - return ret; + ret = qcom_smem_map_toc(smem, &smem->regions[0]); + if (ret) { + log_err("Failed to map toc\n"); + return ret; + } + + for (i = 1; i < num_regions; i++) { + smem->regions[i].virt_base = (void *)smem->regions[i].aux_base; } header = smem->regions[0].virt_base; if (le32_to_cpu(header->initialized) != 1 || le32_to_cpu(header->reserved)) { - dev_err(dev, "SMEM is not initialized by SBL\n"); + log_err("SMEM is not initialized by SBL\n"); return -EINVAL; } + size = readl_relaxed(&header->available) + readl_relaxed(&header->free_offset); + version = qcom_smem_get_sbl_version(smem); switch (version >> 16) { case SMEM_GLOBAL_PART_VERSION: @@ -914,45 +1089,20 @@ static int qcom_smem_probe(struct udevice *dev) smem->item_count = qcom_smem_get_item_count(smem); break; case SMEM_GLOBAL_HEAP_VERSION: + qcom_smem_map_global(smem, size); smem->item_count = SMEM_ITEM_COUNT; break; default: - dev_err(dev, "Unsupported SMEM version 0x%x\n", version); + log_err("Unsupported SMEM version 0x%x\n", version); return -EINVAL; } + BUILD_BUG_ON(SMEM_HOST_APPS >= SMEM_HOST_COUNT); ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); - if (ret < 0 && ret != -ENOENT) + if (ret < 0 && ret != -ENOENT) { + log_err("Failed to enumerate partitions\n"); return ret; - - __smem = smem; - - return 0; -} - -static int qcom_smem_remove(struct udevice *dev) -{ - __smem = NULL; + } return 0; } - -const struct udevice_id qcom_smem_of_match[] = { - { .compatible = "qcom,smem" }, - { } -}; - -static const struct smem_ops msm_smem_ops = { - .alloc = qcom_smem_alloc, - .get = qcom_smem_get, - .get_free_space = qcom_smem_get_free_space, -}; - -U_BOOT_DRIVER(qcom_smem) = { - .name = "qcom_smem", - .id = UCLASS_SMEM, - .of_match = qcom_smem_of_match, - .ops = &msm_smem_ops, - .probe = qcom_smem_probe, - .remove = qcom_smem_remove, -}; diff --git a/drivers/spmi/spmi-msm.c b/drivers/spmi/spmi-msm.c index b89dd0b406ba..da399685f7b2 100644 --- a/drivers/spmi/spmi-msm.c +++ b/drivers/spmi/spmi-msm.c @@ -59,6 +59,8 @@ #define SPMI_MAX_SLAVES 16 #define SPMI_MAX_PERIPH 256 +#define SDAM02_REBOOT_MODE_PID 0x71 + #define SPMI_CHANNEL_READ_ONLY BIT(31) #define SPMI_CHANNEL_VALID BIT(30) #define SPMI_CHANNEL_MASK 0xffff @@ -117,8 +119,11 @@ static int msm_spmi_write(struct udevice *dev, int usid, int pid, int off, return -EIO; if (!(priv->channel_map[usid][pid] & SPMI_CHANNEL_VALID)) return -EINVAL; - if (priv->channel_map[usid][pid] & SPMI_CHANNEL_READ_ONLY) - return -EPERM; + /* Skip readonly check for SDAM02 reboot reason pid */ + if (priv->channel_map[usid][pid] & SPMI_CHANNEL_READ_ONLY) { + if (!(usid == 0 && pid == SDAM02_REBOOT_MODE_PID)) + return -EPERM; + } channel = priv->channel_map[usid][pid] & SPMI_CHANNEL_MASK; diff --git a/drivers/ufs/ufs-qcom.c b/drivers/ufs/ufs-qcom.c index dc40ee62daf8..f5f5a6eb110c 100644 --- a/drivers/ufs/ufs-qcom.c +++ b/drivers/ufs/ufs-qcom.c @@ -30,6 +30,7 @@ #define UFS_CPU_MAX_BANDWIDTH 819200 static void ufs_qcom_dev_ref_clk_ctrl(struct ufs_hba *hba, bool enable); +static u32 ufs_qcom_get_core_clk_unipro_max_freq(struct ufs_hba *hba); static int ufs_qcom_enable_clks(struct ufs_qcom_priv *priv) { @@ -47,17 +48,6 @@ static int ufs_qcom_enable_clks(struct ufs_qcom_priv *priv) return 0; } -static int ufs_qcom_init_clks(struct ufs_qcom_priv *priv) -{ - int err; - struct udevice *dev = priv->hba->dev; - - err = clk_get_bulk(dev, &priv->clks); - if (err) - return err; - - return 0; -} static int ufs_qcom_check_hibern8(struct ufs_hba *hba) { @@ -557,10 +547,45 @@ static void ufs_qcom_dev_ref_clk_ctrl(struct ufs_hba *hba, bool enable) static int ufs_qcom_init(struct ufs_hba *hba) { struct ufs_qcom_priv *priv = dev_get_priv(hba->dev); + struct udevice *dev = hba->dev; + struct clk clk; + u32 max_freq; + long rate; int err; priv->hba = hba; + /* Get maximum frequency for core_clk_unipro from device tree */ + max_freq = ufs_qcom_get_core_clk_unipro_max_freq(hba); + + /* Get and configure core_clk_unipro */ + err = clk_get_by_name(dev, "core_clk_unipro", &clk); + if (err) { + dev_err(dev, "Failed to get core_clk_unipro: %d\n", err); + return err; + } + + rate = clk_set_rate(&clk, max_freq); + if (rate < 0) { + dev_err(dev, "Failed to set core_clk_unipro rate to %u Hz: %ld\n", + max_freq, rate); + } + + /* Get all clocks */ + err = clk_get_bulk(dev, &priv->clks); + if (err) { + dev_err(dev, "clk_get_bulk failed: %d\n", err); + return err; + } + + /* Enable clocks */ + err = ufs_qcom_enable_clks(priv); + if (err) { + dev_err(dev, "failed to enable clocks: %d\n", err); + clk_release_bulk(&priv->clks); + return err; + } + /* setup clocks */ ufs_qcom_setup_clocks(hba, true, PRE_CHANGE); @@ -579,14 +604,7 @@ static int ufs_qcom_init(struct ufs_hba *hba) priv->hw_ver.minor, priv->hw_ver.step); - err = ufs_qcom_init_clks(priv); - if (err) { - dev_err(hba->dev, "failed to initialize clocks, err:%d\n", err); - return err; - } - ufs_qcom_advertise_quirks(hba); - ufs_qcom_setup_clocks(hba, true, POST_CHANGE); return 0; } diff --git a/dts/upstream/src/arm64/qcom/qcs615-ride.dts b/dts/upstream/src/arm64/qcom/qcs615-ride.dts index 5a24c19c415e..c2c4db527f04 100644 --- a/dts/upstream/src/arm64/qcom/qcs615-ride.dts +++ b/dts/upstream/src/arm64/qcom/qcs615-ride.dts @@ -655,14 +655,12 @@ }; &usb_1 { - status = "okay"; -}; - -&usb_1_dwc3 { dr_mode = "peripheral"; + + status = "okay"; }; -&usb_hsphy_2 { +&usb_2_hsphy { vdd-supply = <&vreg_l5a>; vdda-pll-supply = <&vreg_l12a>; vdda-phy-dpdm-supply = <&vreg_l13a>; @@ -671,11 +669,9 @@ }; &usb_2 { - status = "okay"; -}; - -&usb_2_dwc3 { dr_mode = "host"; + + status = "okay"; }; &ufs_mem_hc { diff --git a/dts/upstream/src/arm64/qcom/talos-evk-lvds-auo,g133han01.dtso b/dts/upstream/src/arm64/qcom/talos-evk-lvds-auo,g133han01.dtso new file mode 100644 index 000000000000..8d16ce4a61d2 --- /dev/null +++ b/dts/upstream/src/arm64/qcom/talos-evk-lvds-auo,g133han01.dtso @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +/dts-v1/; +/plugin/; + +#include + +&{/} { + backlight: backlight { + compatible = "gpio-backlight"; + gpios = <&tlmm 115 GPIO_ACTIVE_HIGH>; + default-on; + }; + + panel-lvds { + compatible = "auo,g133han01"; + power-supply = <&vreg_v3p3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* LVDS A (Odd pixels) */ + port@0 { + reg = <0>; + dual-lvds-odd-pixels; + + lvds_panel_out_a: endpoint { + remote-endpoint = <&sn65dsi84_out_a>; + }; + }; + + /* LVDS B (Even pixels) */ + port@1 { + reg = <1>; + dual-lvds-even-pixels; + + lvds_panel_out_b: endpoint { + remote-endpoint = <&sn65dsi84_out_b>; + }; + }; + }; + }; + + vreg_v3p3: regulator-v3p3 { + compatible = "regulator-fixed"; + regulator-name = "vdd-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; +}; + +&hdmi_connector { + status = "disabled"; +}; + +&i2c1 { + clock-frequency = <400000>; + status = "okay"; + + #address-cells = <1>; + #size-cells = <0>; + + hdmi_bridge: bridge@3d { + reg = <0x3d>; + status = "disabled"; + }; + + lvds_bridge: bridge@2c { + compatible = "ti,sn65dsi84"; + reg = <0x2c>; + enable-gpios = <&tlmm 42 GPIO_ACTIVE_HIGH>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + sn65dsi84_in: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&mdss_dsi0_out>; + }; + }; + + port@2 { + reg = <2>; + + sn65dsi84_out_a: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&lvds_panel_out_a>; + }; + }; + + port@3 { + reg = <3>; + + sn65dsi84_out_b: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&lvds_panel_out_b>; + }; + }; + }; + }; +}; + +&mdss_dsi0 { + vdda-supply = <&vreg_l11a>; + + status = "okay"; +}; + +&mdss_dsi0_out { + remote-endpoint = <&sn65dsi84_in>; + data-lanes = <0 1 2 3>; +}; + +&tlmm { + lcd_bklt_en: lcd-bklt-en-state { + pins = "gpio115"; + function = "gpio"; + bias-disable; + }; +}; diff --git a/dts/upstream/src/arm64/qcom/talos-evk-som.dtsi b/dts/upstream/src/arm64/qcom/talos-evk-som.dtsi new file mode 100644 index 000000000000..1720f175719a --- /dev/null +++ b/dts/upstream/src/arm64/qcom/talos-evk-som.dtsi @@ -0,0 +1,610 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +/dts-v1/; + +#include +#include +#include "talos.dtsi" +#include "pm8150.dtsi" +/ { + aliases { + i2c1 = &i2c1; + i2c5 = &i2c5; + mmc0 = &sdhc_1; + serial0 = &uart0; + serial1 = &uart7; + spi6 = &spi6; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + clocks { + can_osc: can-oscillator { + compatible = "fixed-clock"; + clock-frequency = <20000000>; + #clock-cells = <0>; + }; + + sleep_clk: sleep-clk { + compatible = "fixed-clock"; + clock-frequency = <32764>; + #clock-cells = <0>; + }; + + xo_board_clk: xo-board-clk { + compatible = "fixed-clock"; + clock-frequency = <38400000>; + #clock-cells = <0>; + }; + }; + + vreg_conn_1p8: regulator-conn-1p8 { + compatible = "regulator-fixed"; + regulator-name = "vreg_conn_1p8"; + startup-delay-us = <4000>; + enable-active-high; + gpio = <&pm8150_gpios 1 GPIO_ACTIVE_HIGH>; + }; + + vreg_conn_pa: regulator-conn-pa { + compatible = "regulator-fixed"; + regulator-name = "vreg_conn_pa"; + startup-delay-us = <4000>; + enable-active-high; + gpio = <&pm8150_gpios 6 GPIO_ACTIVE_HIGH>; + }; + + regulator-usb2-vbus { + compatible = "regulator-fixed"; + regulator-name = "USB2_VBUS"; + gpio = <&pm8150_gpios 10 GPIO_ACTIVE_HIGH>; + pinctrl-0 = <&usb2_en>; + pinctrl-names = "default"; + enable-active-high; + regulator-always-on; + }; + + vreg_v3p3_can: regulator-v3p3-can { + compatible = "regulator-fixed"; + regulator-name = "vreg-v3p3-can"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + vreg_v5p0_can: regulator-v5p0-can { + compatible = "regulator-fixed"; + regulator-name = "vreg-v5p0-can"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + regulator-always-on; + }; + + wcn6855-pmu { + compatible = "qcom,wcn6855-pmu"; + + pinctrl-0 = <&bt_en_state>, <&wlan_en_state>; + pinctrl-names = "default"; + + bt-enable-gpios = <&tlmm 85 GPIO_ACTIVE_HIGH>; + wlan-enable-gpios = <&tlmm 84 GPIO_ACTIVE_HIGH>; + + vddio-supply = <&vreg_conn_pa>; + vddaon-supply = <&vreg_s5a>; + vddpmu-supply = <&vreg_conn_1p8>; + vddpmumx-supply = <&vreg_conn_1p8>; + vddpmucx-supply = <&vreg_conn_pa>; + vddrfa0p95-supply = <&vreg_s5a>; + vddrfa1p3-supply = <&vreg_s6a>; + vddrfa1p9-supply = <&vreg_l15a>; + vddpcie1p3-supply = <&vreg_s6a>; + vddpcie1p9-supply = <&vreg_l15a>; + + regulators { + vreg_pmu_rfa_cmn: ldo0 { + regulator-name = "vreg_pmu_rfa_cmn"; + }; + + vreg_pmu_aon_0p59: ldo1 { + regulator-name = "vreg_pmu_aon_0p59"; + }; + + vreg_pmu_wlcx_0p8: ldo2 { + regulator-name = "vreg_pmu_wlcx_0p8"; + }; + + vreg_pmu_wlmx_0p85: ldo3 { + regulator-name = "vreg_pmu_wlmx_0p85"; + }; + + vreg_pmu_btcmx_0p85: ldo4 { + regulator-name = "vreg_pmu_btcmx_0p85"; + }; + + vreg_pmu_rfa_0p8: ldo5 { + regulator-name = "vreg_pmu_rfa_0p8"; + }; + + vreg_pmu_rfa_1p2: ldo6 { + regulator-name = "vreg_pmu_rfa_1p2"; + }; + + vreg_pmu_rfa_1p7: ldo7 { + regulator-name = "vreg_pmu_rfa_1p7"; + }; + + vreg_pmu_pcie_0p9: ldo8 { + regulator-name = "vreg_pmu_pcie_0p9"; + }; + + vreg_pmu_pcie_1p8: ldo9 { + regulator-name = "vreg_pmu_pcie_1p8"; + }; + }; + }; +}; + +&apps_rsc { + regulators-0 { + compatible = "qcom,pm8150-rpmh-regulators"; + qcom,pmic-id = "a"; + + vreg_s3a: smps3 { + regulator-name = "vreg_s3a"; + regulator-min-microvolt = <600000>; + regulator-max-microvolt = <650000>; + regulator-initial-mode = ; + }; + + vreg_s4a: smps4 { + regulator-name = "vreg_s4a"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1829000>; + regulator-initial-mode = ; + }; + + vreg_s5a: smps5 { + regulator-name = "vreg_s5a"; + regulator-min-microvolt = <1896000>; + regulator-max-microvolt = <2040000>; + regulator-initial-mode = ; + }; + + vreg_s6a: smps6 { + regulator-name = "vreg_s6a"; + regulator-min-microvolt = <1304000>; + regulator-max-microvolt = <1404000>; + regulator-initial-mode = ; + }; + + vreg_l1a: ldo1 { + regulator-name = "vreg_l1a"; + regulator-min-microvolt = <488000>; + regulator-max-microvolt = <852000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l2a: ldo2 { + regulator-name = "vreg_l2a"; + regulator-min-microvolt = <1650000>; + regulator-max-microvolt = <3100000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l3a: ldo3 { + regulator-name = "vreg_l3a"; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1248000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l5a: ldo5 { + regulator-name = "vreg_l5a"; + regulator-min-microvolt = <875000>; + regulator-max-microvolt = <975000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l7a: ldo7 { + regulator-name = "vreg_l7a"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1900000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l8a: ldo8 { + regulator-name = "vreg_l8a"; + regulator-min-microvolt = <1150000>; + regulator-max-microvolt = <1350000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l10a: ldo10 { + regulator-name = "vreg_l10a"; + regulator-min-microvolt = <2950000>; + regulator-max-microvolt = <3312000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l11a: ldo11 { + regulator-name = "vreg_l11a"; + regulator-min-microvolt = <1232000>; + regulator-max-microvolt = <1260000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l12a: ldo12 { + regulator-name = "vreg_l12a"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1890000>; + regulator-initial-mode = ; + }; + + vreg_l13a: ldo13 { + regulator-name = "vreg_l13a"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3230000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l15a: ldo15 { + regulator-name = "vreg_l15a"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1904000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l16a: ldo16 { + regulator-name = "vreg_l16a"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3312000>; + regulator-initial-mode = ; + regulator-allow-set-load; + regulator-allowed-modes = ; + }; + + vreg_l17a: ldo17 { + regulator-name = "vreg_l17a"; + regulator-min-microvolt = <2950000>; + regulator-max-microvolt = <3312000>; + regulator-initial-mode = ; + }; + }; +}; + +&gpi_dma0 { + status = "okay"; +}; + +&gpi_dma1 { + status = "okay"; +}; + +&i2c5 { + clock-frequency = <400000>; + status = "okay"; + + eeprom@57 { + compatible = "atmel,24c02"; + reg = <0x57>; + pagesize = <16>; + }; + + eeprom@5f { + compatible = "atmel,24mac602"; + reg = <0x5f>; + pagesize = <16>; + }; +}; + +&mdss { + status = "okay"; +}; + +&mdss_dp0 { + status = "okay"; +}; + +&mdss_dp0_out { + link-frequencies = /bits/ 64 <1620000000 2700000000 5400000000>; + remote-endpoint = <&dp0_connector_in>; +}; + +&mdss_dsi0 { + vdda-supply = <&vreg_l11a>; + status = "okay"; +}; + +&mdss_dsi0_phy { + vcca-supply = <&vreg_l5a>; + status = "okay"; +}; + +&pcie { + perst-gpios = <&tlmm 89 GPIO_ACTIVE_LOW>; + wake-gpios = <&tlmm 100 GPIO_ACTIVE_HIGH>; + + pinctrl-0 = <&pcie_default_state>; + pinctrl-names = "default"; + + status = "okay"; +}; + +&pcie_phy { + vdda-phy-supply = <&vreg_l5a>; + vdda-pll-supply = <&vreg_l12a>; + + status = "okay"; +}; + +&pcie_port0 { + wifi@0 { + compatible = "pci17cb,1103"; + reg = <0x10000 0x0 0x0 0x0 0x0>; + + qcom,calibration-variant = "QC_QCS615_Ride"; + + vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; + vddaon-supply = <&vreg_pmu_aon_0p59>; + vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; + vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; + vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>; + vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>; + vddrfa1p8-supply = <&vreg_pmu_rfa_1p7>; + vddpcie0p9-supply = <&vreg_pmu_pcie_0p9>; + vddpcie1p8-supply = <&vreg_pmu_pcie_1p8>; + }; +}; + +&pm8150_gpios { + usb2_en: usb2-en-state { + pins = "gpio10"; + function = "normal"; + output-enable; + power-source = <0>; + }; +}; + +&qupv3_id_0 { + status = "okay"; +}; + +&qupv3_id_1 { + status = "okay"; +}; + +&remoteproc_adsp { + firmware-name = "qcom/qcs615/adsp.mbn"; + + status = "okay"; +}; + +&remoteproc_cdsp { + firmware-name = "qcom/qcs615/cdsp.mbn"; + + status = "okay"; +}; + +&sdhc_1 { + pinctrl-0 = <&sdc1_state_on>; + pinctrl-1 = <&sdc1_state_off>; + pinctrl-names = "default", "sleep"; + + bus-width = <8>; + mmc-ddr-1_8v; + mmc-hs200-1_8v; + mmc-hs400-1_8v; + mmc-hs400-enhanced-strobe; + vmmc-supply = <&vreg_l17a>; + vqmmc-supply = <&vreg_s4a>; + + non-removable; + no-sd; + no-sdio; + + status = "okay"; +}; + +&spi6 { + status = "okay"; + + can@0 { + compatible = "microchip,mcp2515"; + reg = <0>; + clocks = <&can_osc>; + interrupts-extended = <&tlmm 87 IRQ_TYPE_LEVEL_LOW>; + spi-max-frequency = <10000000>; + vdd-supply = <&vreg_v3p3_can>; + xceiver-supply = <&vreg_v5p0_can>; + }; +}; + +&tlmm { + bt_en_state: bt-en-state { + pins = "gpio85"; + function = "gpio"; + bias-pull-down; + }; + + pcie_default_state: pcie-default-state { + clkreq-pins { + pins = "gpio90"; + function = "pcie_clk_req"; + drive-strength = <2>; + bias-pull-up; + }; + + perst-pins { + pins = "gpio89"; + function = "gpio"; + drive-strength = <2>; + bias-pull-down; + }; + + wake-pins { + pins = "gpio100"; + function = "gpio"; + drive-strength = <2>; + bias-pull-up; + }; + }; + + wifi_reg_en_pins_state: wifi-reg-en-pins-state { + pins = "gpio91"; + function = "gpio"; + drive-strength = <8>; + output-high; + bias-pull-up; + }; + + wlan_en_state: wlan-en-state { + pins = "gpio84"; + function = "gpio"; + drive-strength = <16>; + bias-pull-up; + }; +}; + +&uart0 { + status = "okay"; +}; + +&uart7 { + status = "okay"; + + bluetooth { + compatible = "qcom,wcn6855-bt"; + firmware-name = "QCA6698/hpnv21", "QCA6698/hpbtfw21.tlv"; + + vddrfacmn-supply = <&vreg_pmu_rfa_cmn>; + vddaon-supply = <&vreg_pmu_aon_0p59>; + vddwlcx-supply = <&vreg_pmu_wlcx_0p8>; + vddwlmx-supply = <&vreg_pmu_wlmx_0p85>; + vddbtcmx-supply = <&vreg_pmu_btcmx_0p85>; + vddrfa0p8-supply = <&vreg_pmu_rfa_0p8>; + vddrfa1p2-supply = <&vreg_pmu_rfa_1p2>; + vddrfa1p8-supply = <&vreg_pmu_rfa_1p7>; + }; +}; + +/* + * USB0 routing and EDL mode: + * + * The USB0 controller’s HS differential pair is switched (manually) + * between the Micro-USB port for EDL/ADB and the on-board USB 3.0 hub. + * + * During EDL (Emergency Download) mode, the HS lines are explicitly + * routed to the Micro-USB port to allow the SoC to enter device mode + * for flashing. + * + * After EDL the switch is normally toggled so the HS lines stay + * connected to the hub’s Type-A downstream ports, leaving no electrical + * path to the Micro-USB connector — therefore USB0 runs host-only in + * normal runtime and device mode must not be advertised. + * + * USB0 is configured host-only in the base device tree; a separate + * device-tree overlay enables the Micro-USB peripheral configuration for + * ADB. For ADB to work during normal runtime the DIP switch SW1 must be + * manually toggled to the off position (reconnecting the HS pair to the + * Micro-USB port). + */ + +&usb_1 { + dr_mode = "host"; + + status = "okay"; +}; + +&usb_1_hsphy { + vdd-supply = <&vreg_l5a>; + vdda-pll-supply = <&vreg_l12a>; + vdda-phy-dpdm-supply = <&vreg_l13a>; + + status = "okay"; +}; + +&usb_2 { + dr_mode = "host"; + + status = "okay"; +}; + +&usb_2_hsphy { + vdd-supply = <&vreg_l5a>; + vdda-pll-supply = <&vreg_l12a>; + vdda-phy-dpdm-supply = <&vreg_l13a>; + + status = "okay"; +}; + +&usb_qmpphy { + vdda-phy-supply = <&vreg_l5a>; + vdda-pll-supply = <&vreg_l12a>; + + status = "okay"; +}; + +&usb_qmpphy_2 { + vdda-phy-supply = <&vreg_l11a>; + vdda-pll-supply = <&vreg_l5a>; + + status = "okay"; +}; + +&ufs_mem_hc { + reset-gpios = <&tlmm 123 GPIO_ACTIVE_LOW>; + vcc-supply = <&vreg_l17a>; + vcc-max-microamp = <600000>; + vccq2-supply = <&vreg_s4a>; + vccq2-max-microamp = <600000>; + + status = "okay"; +}; + +&ufs_mem_phy { + vdda-phy-supply = <&vreg_l5a>; + vdda-pll-supply = <&vreg_l12a>; + + status = "okay"; +}; + +&venus { + status = "okay"; +}; diff --git a/dts/upstream/src/arm64/qcom/talos-evk-usb1-peripheral.dtso b/dts/upstream/src/arm64/qcom/talos-evk-usb1-peripheral.dtso new file mode 100644 index 000000000000..7552ecf9d7ee --- /dev/null +++ b/dts/upstream/src/arm64/qcom/talos-evk-usb1-peripheral.dtso @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +/dts-v1/; +/plugin/; + +/* + * USB0 Peripheral Mode Overlay + * + * This overlay switches USB0 from host mode to peripheral mode + * by configuring the USB controller node. + * + * Hardware requirement: + * The DIP switch SW1 must be toggled to reconnect the USB0 HS + * differential pair to the Micro-USB connector instead of the + * on-board USB 3.0 hub. + * + * Without toggling SW1, there is no electrical path to the + * Micro-USB connector and device mode will not function. + */ + +&usb_1 { + dr_mode = "peripheral"; + + status = "okay"; +}; diff --git a/dts/upstream/src/arm64/qcom/talos-evk.dts b/dts/upstream/src/arm64/qcom/talos-evk.dts new file mode 100644 index 000000000000..af100e22beee --- /dev/null +++ b/dts/upstream/src/arm64/qcom/talos-evk.dts @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ +/dts-v1/; + +#include "talos-evk-som.dtsi" + +/ { + model = "Qualcomm QCS615 IQ 615 EVK"; + compatible = "qcom,talos-evk", "qcom,qcs615", "qcom,sm6150"; + chassis-type = "embedded"; + + aliases { + mmc1 = &sdhc_2; + }; + + dp0-connector { + compatible = "dp-connector"; + label = "DP0"; + type = "full-size"; + + hpd-gpios = <&tlmm 104 GPIO_ACTIVE_HIGH>; + + port { + dp0_connector_in: endpoint { + remote-endpoint = <&mdss_dp0_out>; + }; + }; + }; + + hdmi_connector: hdmi-out { + compatible = "hdmi-connector"; + type = "d"; + + port { + hdmi_con_out: endpoint { + remote-endpoint = <&adv7535_out>; + }; + }; + }; + + vreg_v1p8_out: regulator-v1p8-out { + compatible = "regulator-fixed"; + regulator-name = "vreg-v1p8-out"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + vin-supply = <&vreg_v5p0_out>; + regulator-boot-on; + regulator-always-on; + }; + + vreg_v3p3_out: regulator-v3p3-out { + compatible = "regulator-fixed"; + regulator-name = "vreg-v3p3-out"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + vin-supply = <&vreg_v5p0_out>; + regulator-boot-on; + regulator-always-on; + }; + + vreg_v5p0_out: regulator-v5p0-out { + compatible = "regulator-fixed"; + regulator-name = "vreg-v5p0-out"; + regulator-min-microvolt = <5000000>; + regulator-max-microvolt = <5000000>; + regulator-boot-on; + regulator-always-on; + /* Powered by system 20V rail (USBC_VBUS_IN) */ + }; +}; + +&i2c1 { + clock-frequency = <400000>; + status = "okay"; + + hdmi_bridge: bridge@3d { + compatible = "adi,adv7535"; + reg = <0x3d>; + avdd-supply = <&vreg_v1p8_out>; + dvdd-supply = <&vreg_v1p8_out>; + pvdd-supply = <&vreg_v1p8_out>; + a2vdd-supply = <&vreg_v1p8_out>; + v3p3-supply = <&vreg_v3p3_out>; + interrupts-extended = <&tlmm 26 IRQ_TYPE_LEVEL_LOW>; + adi,dsi-lanes = <4>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + adv7535_in: endpoint { + remote-endpoint = <&mdss_dsi0_out>; + }; + }; + + port@1 { + reg = <1>; + + adv7535_out: endpoint { + remote-endpoint = <&hdmi_con_out>; + }; + }; + }; + }; +}; + +&mdss_dsi0_out { + remote-endpoint = <&adv7535_in>; + data-lanes = <0 1 2 3>; +}; + +&pon_pwrkey { + status = "okay"; +}; + +&pon_resin { + linux,code = ; + + status = "okay"; +}; + +&sdhc_2 { + pinctrl-0 = <&sdc2_state_on>; + pinctrl-1 = <&sdc2_state_off>; + pinctrl-names = "default", "sleep"; + + bus-width = <4>; + cd-gpios = <&tlmm 99 GPIO_ACTIVE_LOW>; + + vmmc-supply = <&vreg_l10a>; + vqmmc-supply = <&vreg_s4a>; + + status = "okay"; +}; diff --git a/dts/upstream/src/arm64/qcom/talos.dtsi b/dts/upstream/src/arm64/qcom/talos.dtsi index 75716b4a58d6..79265436c718 100644 --- a/dts/upstream/src/arm64/qcom/talos.dtsi +++ b/dts/upstream/src/arm64/qcom/talos.dtsi @@ -4417,7 +4417,7 @@ status = "disabled"; }; - usb_hsphy_2: phy@88e3000 { + usb_2_hsphy: phy@88e3000 { compatible = "qcom,qcs615-qusb2-phy"; reg = <0x0 0x088e3000 0x0 0x180>; @@ -4486,9 +4486,9 @@ status = "disabled"; }; - usb_1: usb@a6f8800 { - compatible = "qcom,qcs615-dwc3", "qcom,dwc3"; - reg = <0x0 0x0a6f8800 0x0 0x400>; + usb_1: usb@a600000 { + compatible = "qcom,qcs615-dwc3", "qcom,snps-dwc3"; + reg = <0x0 0x0a600000 0x0 0xfc100>; clocks = <&gcc GCC_CFG_NOC_USB3_PRIM_AXI_CLK>, <&gcc GCC_USB30_PRIM_MASTER_CLK>, @@ -4507,52 +4507,44 @@ <&gcc GCC_USB30_PRIM_MASTER_CLK>; assigned-clock-rates = <19200000>, <200000000>; - interrupts-extended = <&intc GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH 0>, + interrupts-extended = <&intc GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH 0>, + <&intc GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH 0>, <&intc GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH 0>, <&pdc 9 IRQ_TYPE_EDGE_BOTH>, <&pdc 8 IRQ_TYPE_EDGE_BOTH>, <&pdc 6 IRQ_TYPE_LEVEL_HIGH>; - interrupt-names = "pwr_event", + interrupt-names = "dwc_usb3", + "pwr_event", "hs_phy_irq", "dp_hs_phy_irq", "dm_hs_phy_irq", "ss_phy_irq"; + iommus = <&apps_smmu 0x140 0x0>; + + phys = <&usb_1_hsphy>, <&usb_qmpphy>; + phy-names = "usb2-phy", "usb3-phy"; + power-domains = <&gcc USB30_PRIM_GDSC>; required-opps = <&rpmhpd_opp_nom>; resets = <&gcc GCC_USB30_PRIM_BCR>; - #address-cells = <2>; - #size-cells = <2>; - ranges; + snps,dis-u1-entry-quirk; + snps,dis-u2-entry-quirk; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + snps,dis_enblslpm_quirk; + snps,has-lpm-erratum; + snps,hird-threshold = /bits/ 8 <0x10>; + snps,usb3_lpm_capable; status = "disabled"; - - usb_1_dwc3: usb@a600000 { - compatible = "snps,dwc3"; - reg = <0x0 0x0a600000 0x0 0xcd00>; - - iommus = <&apps_smmu 0x140 0x0>; - interrupts = ; - - phys = <&usb_1_hsphy>, <&usb_qmpphy>; - phy-names = "usb2-phy", "usb3-phy"; - - snps,dis-u1-entry-quirk; - snps,dis-u2-entry-quirk; - snps,dis_u2_susphy_quirk; - snps,dis_u3_susphy_quirk; - snps,dis_enblslpm_quirk; - snps,has-lpm-erratum; - snps,hird-threshold = /bits/ 8 <0x10>; - snps,usb3_lpm_capable; - }; }; - usb_2: usb@a8f8800 { - compatible = "qcom,qcs615-dwc3", "qcom,dwc3"; - reg = <0x0 0x0a8f8800 0x0 0x400>; + usb_2: usb@a800000 { + compatible = "qcom,qcs615-dwc3", "qcom,snps-dwc3"; + reg = <0x0 0x0a800000 0x0 0xfc100>; clocks = <&gcc GCC_CFG_NOC_USB2_SEC_AXI_CLK>, <&gcc GCC_USB20_SEC_MASTER_CLK>, @@ -4571,15 +4563,22 @@ <&gcc GCC_USB20_SEC_MASTER_CLK>; assigned-clock-rates = <19200000>, <200000000>; - interrupts-extended = <&intc GIC_SPI 663 IRQ_TYPE_LEVEL_HIGH 0>, + interrupts-extended = <&intc GIC_SPI 664 IRQ_TYPE_LEVEL_HIGH 0>, + <&intc GIC_SPI 663 IRQ_TYPE_LEVEL_HIGH 0>, <&intc GIC_SPI 662 IRQ_TYPE_LEVEL_HIGH 0>, <&pdc 11 IRQ_TYPE_EDGE_BOTH>, <&pdc 10 IRQ_TYPE_EDGE_BOTH>; - interrupt-names = "pwr_event", + interrupt-names = "dwc_usb3", + "pwr_event", "hs_phy_irq", "dp_hs_phy_irq", "dm_hs_phy_irq"; + iommus = <&apps_smmu 0xe0 0x0>; + + phys = <&usb_2_hsphy>; + phy-names = "usb2-phy"; + power-domains = <&gcc USB20_SEC_GDSC>; required-opps = <&rpmhpd_opp_nom>; @@ -4587,30 +4586,15 @@ qcom,select-utmi-as-pipe-clk; - #address-cells = <2>; - #size-cells = <2>; - ranges; - - status = "disabled"; + snps,dis_u2_susphy_quirk; + snps,dis_u3_susphy_quirk; + snps,dis_enblslpm_quirk; + snps,has-lpm-erratum; + snps,hird-threshold = /bits/ 8 <0x10>; - usb_2_dwc3: usb@a800000 { - compatible = "snps,dwc3"; - reg = <0x0 0x0a800000 0x0 0xcd00>; + maximum-speed = "high-speed"; - iommus = <&apps_smmu 0xe0 0x0>; - interrupts = ; - - phys = <&usb_hsphy_2>; - phy-names = "usb2-phy"; - - snps,dis_u2_susphy_quirk; - snps,dis_u3_susphy_quirk; - snps,dis_enblslpm_quirk; - snps,has-lpm-erratum; - snps,hird-threshold = /bits/ 8 <0x10>; - - maximum-speed = "high-speed"; - }; + status = "disabled"; }; tsens0: thermal-sensor@c263000 { diff --git a/env/Kconfig b/env/Kconfig index 7abd82ab6f38..f59b2acb7e6c 100644 --- a/env/Kconfig +++ b/env/Kconfig @@ -299,16 +299,20 @@ config ENV_IS_IN_SCSI The size of the partition where the environment is stored in bytes. Must be a multiple of the partition block size. - - CONFIG_ENV_SCSI_HW_PARTITION: + The partition selection method is configured via a choice statement: - Specifies which SCSI partition the environment is stored in. If not - set, defaults to partition 0, the user area. Common values might be - 1 (first SCSI boot partition), 2 (second SCSI boot partition). Ignored - if CONFIG_ENV_SCSI_PART_UUID is set to non-empty string. + - ENV_SCSI_PART_USE_UUID: Use the partition's unique UUID to identify + the SCSI partition for environment storage. - - CONFIG_ENV_SCSI_PART_UUID: + - ENV_SCSI_PART_USE_TYPE_GUID: Use the partition type GUID to identify + the SCSI partition for environment storage. The first partition + matching the specified type GUID will be used. - UUID of the SCSI partition where the environment is stored. + - ENV_SCSI_PART_USE_HW: Use the hardware device number to identify + the SCSI device for environment storage. Specifies which SCSI + partition the environment is stored in. If not set, defaults to + partition 0, the user area. Common values might be 1 (first SCSI + boot partition), 2 (second SCSI boot partition). config ENV_RANGE @@ -780,10 +784,52 @@ config ENV_MMC_USE_DT The 2 defines CONFIG_ENV_OFFSET, CONFIG_ENV_OFFSET_REDUND are not used as fallback. +choice + prompt "SCSI partition selection method" + depends on ENV_IS_IN_SCSI + default ENV_SCSI_PART_USE_UUID + help + Select the method to identify the SCSI partition for environment storage. + +config ENV_SCSI_PART_USE_UUID + bool "Use partition UUID" + help + Use the partition's unique UUID to identify the SCSI partition + for environment storage. + +config ENV_SCSI_PART_USE_TYPE_GUID + bool "Use partition type GUID" + select PARTITION_TYPE_GUID + help + Use the partition type GUID to identify the SCSI partition + for environment storage. The first partition matching the + specified type GUID will be used. + +config ENV_SCSI_PART_USE_HW + bool "Use hardware partition number" + help + Use the hardware device number to identify the SCSI device + for environment storage. + +endchoice + +config ENV_SCSI_PART_UUID + string "SCSI partition UUID for saving environment" + depends on ENV_SCSI_PART_USE_UUID + help + UUID of the SCSI partition that you want to store the environment in. + +config ENV_SCSI_PART_TYPE_GUID + string "SCSI partition type GUID for saving environment" + depends on ENV_SCSI_PART_USE_TYPE_GUID + help + Type GUID of the SCSI partition to store the environment in. + Uses the first partition matching this type GUID. + config ENV_SCSI_HW_PARTITION string "SCSI hardware partition number" - depends on ENV_IS_IN_SCSI - default 0 + depends on ENV_SCSI_PART_USE_HW + default "0" help SCSI hardware partition device number on the platform where the environment is stored. Note that this is not related to any software @@ -791,12 +837,6 @@ config ENV_SCSI_HW_PARTITION partition 0 or the first boot partition, which is 1 or some other defined partition. -config ENV_SCSI_PART_UUID - string "SCSI partition UUID for saving environment" - depends on ENV_IS_IN_SCSI - help - UUID of the SCSI partition that you want to store the environment in. - config ENV_USE_DEFAULT_ENV_TEXT_FILE bool "Create default environment from file" depends on !COMPILE_TEST diff --git a/env/scsi.c b/env/scsi.c index 91a6c430302c..b170f4ee0c7f 100644 --- a/env/scsi.c +++ b/env/scsi.c @@ -41,14 +41,17 @@ static inline struct env_scsi_info *env_scsi_get_part(void) is_scsi_scanned = true; } - if (CONFIG_ENV_SCSI_PART_UUID[0] == '\0') { - if (blk_get_device_part_str("scsi", CONFIG_ENV_SCSI_HW_PARTITION, - &ep->blk, &ep->part, true)) - return NULL; - } else { - if (scsi_get_blk_by_uuid(CONFIG_ENV_SCSI_PART_UUID, &ep->blk, &ep->part)) - return NULL; - } +#if defined(CONFIG_ENV_SCSI_PART_USE_TYPE_GUID) + if (scsi_get_blk_by_type_guid(CONFIG_ENV_SCSI_PART_TYPE_GUID, &ep->blk, &ep->part)) + return NULL; +#elif defined(CONFIG_ENV_SCSI_PART_USE_UUID) + if (scsi_get_blk_by_uuid(CONFIG_ENV_SCSI_PART_UUID, &ep->blk, &ep->part)) + return NULL; +#elif defined(CONFIG_ENV_SCSI_PART_USE_HW) + if (blk_get_device_part_str("scsi", CONFIG_ENV_SCSI_HW_PARTITION, + &ep->blk, &ep->part, true)) + return NULL; +#endif ep->count = CONFIG_ENV_SIZE / ep->part.blksz; @@ -95,20 +98,24 @@ static int env_scsi_load(void) int ret; if (!ep) { - if (CONFIG_ENV_SCSI_PART_UUID[0] == '\0') - env_set_default("SCSI partition " CONFIG_ENV_SCSI_HW_PARTITION " not found", 0); - else - env_set_default(CONFIG_ENV_SCSI_PART_UUID " partition not found", 0); - +#if defined(CONFIG_ENV_SCSI_PART_USE_TYPE_GUID) + env_set_default("partition type " CONFIG_ENV_SCSI_PART_TYPE_GUID " not found", 0); +#elif defined(CONFIG_ENV_SCSI_PART_USE_UUID) + env_set_default(CONFIG_ENV_SCSI_PART_UUID " partition not found", 0); +#elif defined(CONFIG_ENV_SCSI_PART_USE_HW) + env_set_default("SCSI partition " CONFIG_ENV_SCSI_HW_PARTITION " not found", 0); +#endif return -ENOENT; } if (blk_dread(ep->blk, ep->part.start, ep->count, &envbuf) != ep->count) { - if (CONFIG_ENV_SCSI_PART_UUID[0] == '\0') - env_set_default("SCSI partition " CONFIG_ENV_SCSI_HW_PARTITION " read failed", 0); - else - env_set_default(CONFIG_ENV_SCSI_PART_UUID " partition read failed", 0); - +#if defined(CONFIG_ENV_SCSI_PART_USE_TYPE_GUID) + env_set_default("partition type " CONFIG_ENV_SCSI_PART_TYPE_GUID " read failed", 0); +#elif defined(CONFIG_ENV_SCSI_PART_USE_UUID) + env_set_default(CONFIG_ENV_SCSI_PART_UUID " partition read failed", 0); +#elif defined(CONFIG_ENV_SCSI_PART_USE_HW) + env_set_default("SCSI partition " CONFIG_ENV_SCSI_HW_PARTITION " read failed", 0); +#endif return -EIO; } diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 36b5d87c304f..fe0aae2720ca 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -134,7 +134,6 @@ enum uclass_id { UCLASS_SCSI, /* SCSI device */ UCLASS_SERIAL, /* Serial UART */ UCLASS_SIMPLE_BUS, /* Bus with child devices */ - UCLASS_SMEM, /* Shared memory interface */ UCLASS_SOC, /* SOC Device */ UCLASS_SOUND, /* Playing simple sounds */ UCLASS_SPI, /* SPI bus */ diff --git a/include/fastboot-internal.h b/include/fastboot-internal.h index 610d4f914140..35683cdb642d 100644 --- a/include/fastboot-internal.h +++ b/include/fastboot-internal.h @@ -40,4 +40,34 @@ void fastboot_getvar_all(char *response); */ void fastboot_getvar(char *cmd_parameter, char *response); +#if CONFIG_IS_ENABLED(EFI_PARTITION) +/** + * fastboot_flash_gpt_partition_table() - Flash GPT partition table + * + * @interface: Block interface name (e.g., "mmc", "scsi") + * @device: Device number + * @download_buffer: Buffer containing GPT data + * @response: Pointer to fastboot response buffer + */ +void fastboot_flash_gpt_partition_table(const char *interface, + int device, + void *download_buffer, + char *response); +#endif + +#if CONFIG_IS_ENABLED(DOS_PARTITION) +/** + * fastboot_flash_mbr_partition_table() - Flash MBR partition table + * + * @interface: Block interface name (e.g., "mmc", "scsi") + * @device: Device number + * @download_buffer: Buffer containing MBR data + * @response: Pointer to fastboot response buffer + */ +void fastboot_flash_mbr_partition_table(const char *interface, + int device, + void *download_buffer, + char *response); +#endif + #endif diff --git a/include/nvmem.h b/include/nvmem.h index e6a8a98828b3..c3d845c3a7eb 100644 --- a/include/nvmem.h +++ b/include/nvmem.h @@ -26,11 +26,15 @@ * @nvmem: The backing storage device * @offset: The offset of the cell from the start of @nvmem * @size: The size of the cell, in bytes + * @bit_offset: Bit offset within the cell (0 for byte-level access) + * @nbits: Number of bits to use (0 for byte-level access) */ struct nvmem_cell { struct udevice *nvmem; unsigned int offset; size_t size; + unsigned int bit_offset; + unsigned int nbits; }; struct udevice; @@ -39,13 +43,27 @@ struct udevice; /** * nvmem_cell_read() - Read the value of an nvmem cell - * @cell: The nvmem cell to read + * @cell: The nvmem cell to read, containing: + * - @cell->offset: Byte offset within the NVMEM device + * - @cell->size: Size of the cell in bytes + * - @cell->nbits: Number of bits to extract (0 = read entire cell) + * - @cell->bit_offset: Starting bit position for extraction * @buf: The buffer to read into * @size: The size of @buf * + * For cells with bit fields (@cell->nbits > 0), this function: + * - Reads the raw bytes from @cell->offset in hardware + * - Extracts the bit field using @cell->bit_offset and @cell->nbits + * - Returns the extracted value in @buf + * - Requires @size == sizeof(u32) and @cell->size <= sizeof(u32) + * + * For cells without bit fields (@cell->nbits == 0): + * - Reads raw bytes directly from @cell->offset + * - Requires @size == @cell->size + * * Return: * * 0 on success - * * -EINVAL if @buf is not the same size as @cell. + * * -EINVAL if @size doesn't match requirements * * -ENOSYS if CONFIG_NVMEM is disabled * * A negative error if there was a problem reading the underlying storage */ @@ -53,13 +71,27 @@ int nvmem_cell_read(struct nvmem_cell *cell, void *buf, size_t size); /** * nvmem_cell_write() - Write a value to an nvmem cell - * @cell: The nvmem cell to write + * @cell: The nvmem cell to write, containing: + * - @cell->offset: Byte offset within the NVMEM device + * - @cell->size: Size of the cell in bytes + * - @cell->nbits: Number of bits to write (0 = write entire cell) + * - @cell->bit_offset: Starting bit position for insertion * @buf: The buffer to write from * @size: The size of @buf * + * For cells with bit fields (@cell->nbits > 0), this function: + * - Performs Read-Modify-Write to preserve other bits at @cell->offset + * - Masks and shifts the value to @cell->bit_offset position + * - Merges with existing bits outside the @cell->nbits field + * - Requires @size == sizeof(u32) and @cell->size <= sizeof(u32) + * + * For cells without bit fields (@cell->nbits == 0): + * - Writes raw bytes directly to @cell->offset + * - Requires @size == @cell->size + * * Return: * * 0 on success - * * -EINVAL if @buf is not the same size as @cell + * * -EINVAL if @size doesn't match requirements * * -ENOSYS if @cell is read-only, or if CONFIG_NVMEM is disabled * * A negative error if there was a problem writing the underlying storage */ diff --git a/include/part.h b/include/part.h index 15daacd7faaa..32614bd085bc 100644 --- a/include/part.h +++ b/include/part.h @@ -327,6 +327,20 @@ int part_get_info_by_name(struct blk_desc *desc, const char *name, int part_get_info_by_uuid(struct blk_desc *desc, const char *uuid, struct disk_partition *info); +/** + * part_get_info_by_type_guid() - Search for a partition by type GUID + * among all available registered partitions + * + * @desc: block device descriptor + * @type_guid: the specified partition type GUID + * @info: the disk partition info + * + * Return: the partition number on match (starting on 1), -ENOENT on no match, + * otherwise error + */ +int part_get_info_by_type_guid(struct blk_desc *desc, const char *type_guid, + struct disk_partition *info); + /** * part_get_info_by_dev_and_name_or_num() - Get partition info from dev number * and part name, or dev number and @@ -404,6 +418,13 @@ static inline int part_get_info_by_uuid(struct blk_desc *desc, const char *uuid, return -ENOENT; } +static inline int part_get_info_by_type_guid(struct blk_desc *desc, + const char *type_guid, + struct disk_partition *info) +{ + return -ENOENT; +} + static inline int part_get_info_by_dev_and_name_or_num(const char *dev_iface, const char *dev_part_str, diff --git a/include/scsi.h b/include/scsi.h index 2520a8b8fe63..83aaf0a70f63 100644 --- a/include/scsi.h +++ b/include/scsi.h @@ -366,6 +366,17 @@ int scsi_scan_dev(struct udevice *dev, bool verbose); int scsi_get_blk_by_uuid(const char *uuid, struct blk_desc **blk_desc_ptr, struct disk_partition *part_info_ptr); +/** + * scsi_get_blk_by_type_guid() - Provides SCSI partition information by type GUID. + * + * @type_guid: Type GUID of the partition for fetching its info + * @blk_desc_ptr: Provides the blk descriptor + * @part_info_ptr: Provides partition info + * Return: 0 if OK, -ve on error + */ +int scsi_get_blk_by_type_guid(const char *type_guid, struct blk_desc **blk_desc_ptr, + struct disk_partition *part_info_ptr); + #define SCSI_IDENTIFY 0xC0 /* not used */ /* Hardware errors */ diff --git a/include/smem.h b/include/smem.h deleted file mode 100644 index b19c534ebc43..000000000000 --- a/include/smem.h +++ /dev/null @@ -1,90 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ */ -/* - * The shared memory system is an allocate-only heap structure that - * consists of one of more memory areas that can be accessed by the processors - * in the SoC. - * - * Allocation can be done globally for all processors or to an individual processor. - * This is controlled by the @host parameter. - * - * Allocation and management of heap can be implemented in various ways, - * The @item parameter should be used as an index/hash to the memory region. - * - * Copyright (c) 2018 Ramon Fried - */ - -#ifndef _smemh_ -#define _smemh_ - -/* struct smem_ops: Operations for the SMEM uclass */ -struct smem_ops { - /** - * alloc() - allocate space for a smem item - * - * @host: remote processor id, or -1 for all processors. - * @item: smem item handle - * @size: number of bytes to be allocated - * @return 0 if OK, -ve on error - */ - int (*alloc)(unsigned int host, - unsigned int item, size_t size); - - /** - * get() - Resolve ptr of size of a smem item - * - * @host: the remote processor, of -1 for all processors. - * @item: smem item handle - * @size: pointer to be filled out with the size of the item - * @return pointer on success, NULL on error - */ - void *(*get)(unsigned int host, - unsigned int item, size_t *size); - - /** - * get_free_space() - Get free space in smem in bytes - * - * @host: the remote processor identifying a partition, or -1 - * for all processors. - * @return free space, -ve on error - */ - int (*get_free_space)(unsigned int host); -}; - -#define smem_get_ops(dev) ((struct smem_ops *)(dev)->driver->ops) - -/** - * smem_alloc() - allocate space for a smem item - * @host: remote processor id, or -1 - * @item: smem item handle - * @size: number of bytes to be allocated - * Return: 0 if OK, -ve on error - * - * Allocate space for a given smem item of size @size, given that the item is - * not yet allocated. - */ -int smem_alloc(struct udevice *dev, unsigned int host, unsigned int item, size_t size); - -/** - * smem_get() - resolve ptr of size of a smem item - * @host: the remote processor, or -1 for all processors. - * @item: smem item handle - * @size: pointer to be filled out with size of the item - * Return: pointer on success, NULL on error - * - * Looks up smem item and returns pointer to it. Size of smem - * item is returned in @size. - */ -void *smem_get(struct udevice *dev, unsigned int host, unsigned int item, size_t *size); - -/** - * smem_get_free_space() - retrieve amount of free space in a partition - * @host: the remote processor identifying a partition, or -1 - * for all processors. - * Return: size in bytes, -ve on error - * - * To be used by smem clients as a quick way to determine if any new - * allocations has been made. - */ -int smem_get_free_space(struct udevice *dev, unsigned int host); - -#endif /* _smem_h_ */ diff --git a/include/soc/qcom/smem.h b/include/soc/qcom/smem.h new file mode 100644 index 000000000000..586432412eb8 --- /dev/null +++ b/include/soc/qcom/smem.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __QCOM_SMEM_H__ +#define __QCOM_SMEM_H__ + +#include +#include + +#define QCOM_SMEM_HOST_ANY -1 + +#if defined(CONFIG_QCOM_SMEM) +int qcom_smem_init(void); +int qcom_socinfo_init(void); + +bool qcom_smem_is_available(void); +int qcom_smem_alloc(unsigned host, unsigned item, size_t size); +void *qcom_smem_get(unsigned host, unsigned item, size_t *size); + +int qcom_smem_get_free_space(unsigned host); +#else +static int qcom_smem_init(void) { return -ENOSYS; } + +static bool qcom_smem_is_available(void) { return false; } +int qcom_smem_alloc(unsigned host, unsigned item, size_t size) +{ + return -ENOSYS; +} + +void *qcom_smem_get(unsigned host, unsigned item, size_t *size) +{ + return ERR_PTR(-ENOSYS); +} + +int qcom_smem_get_free_space(unsigned host); +#endif + +#endif diff --git a/include/soc/qcom/socinfo.h b/include/soc/qcom/socinfo.h new file mode 100644 index 000000000000..608950443eee --- /dev/null +++ b/include/soc/qcom/socinfo.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __QCOM_SOCINFO_H__ +#define __QCOM_SOCINFO_H__ + +#include + +/* + * SMEM item id, used to acquire handles to respective + * SMEM region. + */ +#define SMEM_HW_SW_BUILD_ID 137 + +#define SMEM_SOCINFO_BUILD_ID_LENGTH 32 +#define SMEM_SOCINFO_CHIP_ID_LENGTH 32 + +/* + * SoC version type with major number in the upper 16 bits and minor + * number in the lower 16 bits. + */ +#define SOCINFO_MAJOR(ver) (((ver) >> 16) & 0xffff) +#define SOCINFO_MINOR(ver) ((ver) & 0xffff) +#define SOCINFO_VERSION(maj, min) ((((maj) & 0xffff) << 16)|((min) & 0xffff)) + +/* Socinfo SMEM item structure */ +struct socinfo { + __le32 fmt; + __le32 id; + __le32 ver; + char build_id[SMEM_SOCINFO_BUILD_ID_LENGTH]; + /* Version 2 */ + __le32 raw_id; + __le32 raw_ver; + /* Version 3 */ + __le32 hw_plat; + /* Version 4 */ + __le32 plat_ver; + /* Version 5 */ + __le32 accessory_chip; + /* Version 6 */ + __le32 hw_plat_subtype; + /* Version 7 */ + __le32 pmic_model; + __le32 pmic_die_rev; + /* Version 8 */ + __le32 pmic_model_1; + __le32 pmic_die_rev_1; + __le32 pmic_model_2; + __le32 pmic_die_rev_2; + /* Version 9 */ + __le32 foundry_id; + /* Version 10 */ + __le32 serial_num; + /* Version 11 */ + __le32 num_pmics; + __le32 pmic_array_offset; + /* Version 12 */ + __le32 chip_family; + __le32 raw_device_family; + __le32 raw_device_num; + /* Version 13 */ + __le32 nproduct_id; + char chip_id[SMEM_SOCINFO_CHIP_ID_LENGTH]; + /* Version 14 */ + __le32 num_clusters; + __le32 ncluster_array_offset; + __le32 num_subset_parts; + __le32 nsubset_parts_array_offset; + /* Version 15 */ + __le32 nmodem_supported; + /* Version 16 */ + __le32 feature_code; + __le32 pcode; + __le32 npartnamemap_offset; + __le32 nnum_partname_mapping; + /* Version 17 */ + __le32 oem_variant; + /* Version 18 */ + __le32 num_kvps; + __le32 kvps_offset; + /* Version 19 */ + __le32 num_func_clusters; + __le32 boot_cluster; + __le32 boot_core; +}; + +/* Internal feature codes */ +enum qcom_socinfo_feature_code { + /* External feature codes */ + SOCINFO_FC_UNKNOWN = 0x0, + SOCINFO_FC_AA, + SOCINFO_FC_AB, + SOCINFO_FC_AC, + SOCINFO_FC_AD, + SOCINFO_FC_AE, + SOCINFO_FC_AF, + SOCINFO_FC_AG, + SOCINFO_FC_AH, +}; + +/* Internal feature codes */ +/* Valid values: 0 <= n <= 0xf */ +#define SOCINFO_FC_Yn(n) (0xf1 + (n)) +#define SOCINFO_FC_INT_MAX SOCINFO_FC_Yn(0xf) + +/* Product codes */ +#define SOCINFO_PC_UNKNOWN 0 +#define SOCINFO_PCn(n) ((n) + 1) +#define SOCINFO_PC_RESERVE (BIT(31) - 1) + +#endif diff --git a/lib/efi_loader/efi_firmware.c b/lib/efi_loader/efi_firmware.c index b41969c70fde..ce6ca04ac691 100644 --- a/lib/efi_loader/efi_firmware.c +++ b/lib/efi_loader/efi_firmware.c @@ -52,9 +52,9 @@ struct fmp_payload_header { */ struct fmp_state { u32 fw_version; - u32 lowest_supported_version; /* not used */ - u32 last_attempt_version; /* not used */ - u32 last_attempt_status; /* not used */ + u32 lowest_supported_version; /* not used - read from DTB for security */ + u32 last_attempt_version; /* used for esrt tracking */ + u32 last_attempt_status; /* used for esrt tracking */ }; /** @@ -190,6 +190,67 @@ static void efi_firmware_get_lsv_from_dtb(u8 image_index, } } +/** + * efi_firmware_set_last_attempt - set last attempt information + * @state: Pointer to fmp state + * @attempt_version: Version that was attempted + * @attempt_status: Status of the attempt + * + * Set the last attempt version and status in the fmp_state structure. + */ +static void efi_firmware_set_last_attempt(struct fmp_state *state, + u32 attempt_version, + u32 attempt_status) +{ + state->last_attempt_version = attempt_version; + state->last_attempt_status = attempt_status; +} + +/** + * efi_firmware_get_last_attempt - get last attempt information + * @state: Pointer to fmp state + * @attempt_version: Pointer to store last attempt version + * @attempt_status: Pointer to store last attempt status + * + * Retrieve the last attempt version and status from fmp_state structure. + */ +static void efi_firmware_get_last_attempt(const struct fmp_state *state, + u32 *attempt_version, + u32 *attempt_status) +{ + if (attempt_version) + *attempt_version = state->last_attempt_version; + if (attempt_status) + *attempt_status = state->last_attempt_status; +} + +/** + * efi_firmware_map_error_to_status - map internal errors to UEFI status codes + * @error: Internal error code + * + * Map U-Boot internal error codes to UEFI-compliant last attempt status codes. + * + * Return: UEFI last attempt status code + */ +static u32 efi_firmware_map_error_to_status(efi_status_t error) +{ + switch (error) { + case EFI_SUCCESS: + return LAST_ATTEMPT_STATUS_SUCCESS; + case EFI_OUT_OF_RESOURCES: + return LAST_ATTEMPT_STATUS_ERROR_INSUFFICIENT_RESOURCES; + case EFI_INVALID_PARAMETER: + return LAST_ATTEMPT_STATUS_ERROR_INCORRECT_VERSION; + case EFI_SECURITY_VIOLATION: + return LAST_ATTEMPT_STATUS_ERROR_AUTH_ERROR; + case EFI_UNSUPPORTED: + return LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT; + case EFI_DEVICE_ERROR: + default: + return LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL; + } +} + /** * efi_firmware_fill_version_info - fill the version information * @image_info: Image information @@ -237,8 +298,15 @@ void efi_firmware_fill_version_info(struct efi_firmware_image_descriptor *image_ ret = efi_get_variable_int(varname, &fw_array->image_type_id, NULL, &size, var_state, NULL); - if (ret == EFI_SUCCESS && expected_size == size) + if (ret == EFI_SUCCESS && expected_size == size) { image_info->version = var_state[active_index].fw_version; + image_info->last_attempt_version = var_state[active_index].last_attempt_version; + image_info->last_attempt_status = var_state[active_index].last_attempt_status; + } else { + /* Default values if no previous state exists */ + image_info->last_attempt_version = 0; + image_info->last_attempt_status = LAST_ATTEMPT_STATUS_SUCCESS; + } free(var_state); } @@ -403,6 +471,15 @@ efi_status_t efi_firmware_capsule_authenticate(const void **p_image, if (status == EFI_SECURITY_VIOLATION) { printf("Capsule authentication check failed. Aborting update\n"); + /* + * Even though authentication failed, update the pointers + * to skip past the auth wrapper so the caller can read + * the FMP payload header for version information. + */ + image = capsule_payload; + image_size = capsule_payload_size; + *p_image = image; + *p_image_size = image_size; return status; } else if (status != EFI_SUCCESS) { return status; @@ -427,6 +504,10 @@ efi_status_t efi_firmware_capsule_authenticate(const void **p_image, * @image_index: image index * * Update the FmpStateXXXX variable with the firmware update state. + * On successful update (last_attempt_status == LAST_ATTEMPT_STATUS_SUCCESS), + * updates fw_version to the new version. + * On failed update, preserves the old fw_version. + * Always updates last_attempt_version and last_attempt_status. * * Return: status code */ @@ -471,11 +552,19 @@ efi_status_t efi_firmware_set_fmp_state_var(struct fmp_state *state, u8 image_in memset(var_state, 0, num_banks * sizeof(*var_state)); /* - * Only the fw_version is set here. + * Set fw_version and last attempt information. * lowest_supported_version in FmpState variable is ignored since * it can be tampered if the file based EFI variable storage is used. + * + * Only update fw_version if the update succeeded. + * On failure, preserve the old fw_version to maintain accurate ESRT state. */ - var_state[update_bank].fw_version = state->fw_version; + if (state->last_attempt_status == LAST_ATTEMPT_STATUS_SUCCESS) + var_state[update_bank].fw_version = state->fw_version; + /* else: keep existing fw_version (don't update on failure) */ + + var_state[update_bank].last_attempt_version = state->last_attempt_version; + var_state[update_bank].last_attempt_status = state->last_attempt_status; size = num_banks * sizeof(*var_state); ret = efi_set_variable_int(varname, image_type_id, @@ -540,11 +629,10 @@ efi_status_t efi_firmware_verify_image(const void **p_image, efi_guid_t *image_type_id; ret = efi_firmware_capsule_authenticate(p_image, p_image_size); + efi_firmware_get_fw_version(p_image, p_image_size, state); if (ret != EFI_SUCCESS) return ret; - efi_firmware_get_fw_version(p_image, p_image_size, state); - image_type_id = efi_firmware_get_image_type_id(image_index); if (!image_type_id) return EFI_INVALID_PARAMETER; @@ -661,21 +749,40 @@ efi_status_t EFIAPI efi_firmware_fit_set_image( status = efi_firmware_verify_image(&image, &image_size, image_index, &state); - if (status != EFI_SUCCESS) + if (status != EFI_SUCCESS) { + /* Set last attempt information for failed verification */ + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); return EFI_EXIT(status); + } + + /* Set last attempt version before starting the update */ + efi_firmware_set_last_attempt(&state, state.fw_version, + LAST_ATTEMPT_STATUS_SUCCESS); orig_dfu_env = env_get("dfu_alt_info"); + if (orig_dfu_env) { orig_dfu_env = strdup(orig_dfu_env); if (!orig_dfu_env) { log_err("strdup() failed!\n"); - return EFI_EXIT(EFI_OUT_OF_RESOURCES); + status = EFI_OUT_OF_RESOURCES; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); } } + if (env_set("dfu_alt_info", update_info.dfu_string)) { log_err("Unable to set env variable \"dfu_alt_info\"!\n"); free(orig_dfu_env); - return EFI_EXIT(EFI_DEVICE_ERROR); + status = EFI_DEVICE_ERROR; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); } /* Make sure the update fitImage is properly aligned to 8-bytes */ @@ -698,9 +805,17 @@ efi_status_t EFIAPI efi_firmware_fit_set_image( free(orig_dfu_env); - if (ret) - return EFI_EXIT(EFI_DEVICE_ERROR); + if (ret) { + status = EFI_DEVICE_ERROR; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); + } + /* Update successful - set success status */ + efi_firmware_set_last_attempt(&state, state.fw_version, + LAST_ATTEMPT_STATUS_SUCCESS); efi_firmware_set_fmp_state_var(&state, image_index); return EFI_EXIT(EFI_SUCCESS); @@ -762,8 +877,17 @@ efi_status_t EFIAPI efi_firmware_raw_set_image( status = efi_firmware_verify_image(&image, &image_size, image_index, &state); - if (status != EFI_SUCCESS) + if (status != EFI_SUCCESS) { + /* Set last attempt information for failed verification */ + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); return EFI_EXIT(status); + } + + /* Set last attempt version before starting the update */ + efi_firmware_set_last_attempt(&state, state.fw_version, + LAST_ATTEMPT_STATUS_SUCCESS); /* * dfu_alt_num is assigned from 0 while image_index starts from 1. @@ -788,13 +912,21 @@ efi_status_t EFIAPI efi_firmware_raw_set_image( orig_dfu_env = strdup(orig_dfu_env); if (!orig_dfu_env) { log_err("strdup() failed!\n"); - return EFI_EXIT(EFI_OUT_OF_RESOURCES); + status = EFI_OUT_OF_RESOURCES; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); } } if (env_set("dfu_alt_info", update_info.dfu_string)) { log_err("Unable to set env variable \"dfu_alt_info\"!\n"); free(orig_dfu_env); - return EFI_EXIT(EFI_DEVICE_ERROR); + status = EFI_DEVICE_ERROR; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); } ret = dfu_write_by_alt(dfu_alt_num, (void *)image, image_size, @@ -805,9 +937,17 @@ efi_status_t EFIAPI efi_firmware_raw_set_image( free(orig_dfu_env); - if (ret) - return EFI_EXIT(EFI_DEVICE_ERROR); + if (ret) { + status = EFI_DEVICE_ERROR; + efi_firmware_set_last_attempt(&state, state.fw_version, + efi_firmware_map_error_to_status(status)); + efi_firmware_set_fmp_state_var(&state, image_index); + return EFI_EXIT(status); + } + /* Update successful - set success status */ + efi_firmware_set_last_attempt(&state, state.fw_version, + LAST_ATTEMPT_STATUS_SUCCESS); efi_firmware_set_fmp_state_var(&state, image_index); return EFI_EXIT(EFI_SUCCESS); diff --git a/test/dm/Makefile b/test/dm/Makefile index d69b0e08d66a..5995e06f5094 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -75,6 +75,7 @@ obj-$(CONFIG_CMD_MUX) += mux-cmd.o obj-$(CONFIG_MULTIPLEXER) += mux-emul.o obj-$(CONFIG_MUX_MMIO) += mux-mmio.o obj-y += fdtdec.o +obj-$(CONFIG_NVMEM) += nvmem.o obj-$(CONFIG_MTD_RAW_NAND) += nand.o obj-$(CONFIG_UT_DM) += nop.o obj-y += ofnode.o @@ -113,7 +114,6 @@ obj-$(CONFIG_DM_SERIAL) += serial.o obj-$(CONFIG_DM_SPI_FLASH) += sf.o obj-$(CONFIG_SIMPLE_BUS) += simple-bus.o obj-$(CONFIG_SIMPLE_PM_BUS) += simple-pm-bus.o -obj-$(CONFIG_SMEM) += smem.o obj-$(CONFIG_SOC_DEVICE) += soc.o obj-$(CONFIG_SOUND) += sound.o obj-$(CONFIG_DM_SPI) += spi.o diff --git a/test/dm/nvmem.c b/test/dm/nvmem.c new file mode 100644 index 000000000000..dd8d0151d2b9 --- /dev/null +++ b/test/dm/nvmem.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for NVMEM bit field support + */ + +#include +#include +#include +#include +#include +#include + +static int nvmem_test_write_raw(struct udevice *dev, uint offset, + const void *buf, uint size) +{ + return i2c_eeprom_write(dev, offset, buf, size); +} + +static int nvmem_test_read_raw(struct udevice *dev, uint offset, + void *buf, uint size) +{ + return i2c_eeprom_read(dev, offset, buf, size); +} + +/* Test NVMEM bit field operations */ +static int dm_test_nvmem_bitfield(struct unit_test_state *uts) +{ + struct udevice *nvmem_dev; + struct nvmem_cell cell; + u32 value; + u8 hw_value_u8; + u32 hw_value_u32; + + ut_assertok(uclass_get_device_by_name(UCLASS_I2C_EEPROM, + "nvmem-test@50", &nvmem_dev)); + + cell.nvmem = nvmem_dev; + + /* Test reg = <0x0 0x1>; bits = <1 7>: */ + cell.offset = 0x0; + cell.size = 1; + cell.bit_offset = 1; + cell.nbits = 7; + hw_value_u8 = 0x01; + ut_assertok(nvmem_test_write_raw(nvmem_dev, cell.offset, &hw_value_u8, 1)); + value = 0x7f; + ut_assertok(nvmem_cell_write(&cell, &value, sizeof(value))); + value = 0; + ut_assertok(nvmem_cell_read(&cell, &value, sizeof(value))); + ut_asserteq(0x7f, value); + ut_assertok(nvmem_test_read_raw(nvmem_dev, cell.offset, &hw_value_u8, 1)); + ut_asserteq(0xff, hw_value_u8); + + /* Test reg = <0x18 0x4>; bits = <4 12>: Spanning byte boundary */ + cell.offset = 0x18; + cell.size = 4; + cell.bit_offset = 4; + cell.nbits = 12; + hw_value_u32 = 0x0000000f; + ut_assertok(nvmem_test_write_raw(nvmem_dev, cell.offset, (u8 *)&hw_value_u32, 4)); + value = 0xfff; + ut_assertok(nvmem_cell_write(&cell, &value, sizeof(value))); + value = 0; + ut_assertok(nvmem_cell_read(&cell, &value, sizeof(value))); + ut_asserteq(0xfff, value); + ut_assertok(nvmem_test_read_raw(nvmem_dev, cell.offset, (u8 *)&hw_value_u32, 4)); + ut_asserteq(0x0000ffff, hw_value_u32); + + /* Test reg = <0x9 0x4>: Full 4-byte access without bit field */ + cell.offset = 0x9; + cell.bit_offset = 0; + cell.nbits = 0; + value = 0x12345678; + ut_assertok(nvmem_cell_write(&cell, &value, sizeof(value))); + value = 0; + ut_assertok(nvmem_cell_read(&cell, &value, sizeof(value))); + ut_asserteq(0x12345678, value); + + /* Test reg = <0xc 0x4>; bits = <16 16>: Upper 2 bytes */ + cell.offset = 0xc; + cell.bit_offset = 16; + cell.nbits = 16; + hw_value_u32 = 0x0000ffff; + ut_assertok(nvmem_test_write_raw(nvmem_dev, cell.offset, (u8 *)&hw_value_u32, 4)); + value = 0xffff; + ut_assertok(nvmem_cell_write(&cell, &value, sizeof(value))); + value = 0; + ut_assertok(nvmem_cell_read(&cell, &value, sizeof(value))); + ut_asserteq(0xffff, value); + ut_assertok(nvmem_test_read_raw(nvmem_dev, cell.offset, (u8 *)&hw_value_u32, 4)); + ut_asserteq(0xffffffff, hw_value_u32); + + return 0; +} +DM_TEST(dm_test_nvmem_bitfield, + UTF_PROBE_TEST | UTF_SCAN_FDT | UTF_FLAT_TREE); + +/* Test NVMEM error handling for invalid configurations */ +static int dm_test_nvmem_bitfield_errors(struct unit_test_state *uts) +{ + struct udevice *nvmem_dev; + struct nvmem_cell cell; + u32 value; + int ret; + + ut_assertok(uclass_get_device_by_name(UCLASS_I2C_EEPROM, + "nvmem-test@50", &nvmem_dev)); + + /* Test bit field exceeding cell size */ + cell.nvmem = nvmem_dev; + cell.offset = 0xd; + cell.size = 1; + cell.bit_offset = 0; + cell.nbits = 9; + + value = 0xff; + ret = nvmem_cell_write(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + ret = nvmem_cell_read(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + /* Test bit field exceeding 32 bits */ + cell.size = 4; + cell.bit_offset = 0; + cell.nbits = 33; + + ret = nvmem_cell_write(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + ret = nvmem_cell_read(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + /* Test invalid bit_offset + nbits */ + cell.size = 1; + cell.bit_offset = 7; + cell.nbits = 2; + + ret = nvmem_cell_write(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + ret = nvmem_cell_read(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + /* Test nbits=0 requires buffer size == cell size */ + cell.size = 1; + cell.bit_offset = 0; + cell.nbits = 0; + + value = 0xff; + ret = nvmem_cell_write(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + ret = nvmem_cell_read(&cell, &value, sizeof(value)); + ut_asserteq(-EINVAL, ret); + + return 0; +} +DM_TEST(dm_test_nvmem_bitfield_errors, + UTF_PROBE_TEST | UTF_SCAN_FDT | UTF_FLAT_TREE); diff --git a/test/dm/part.c b/test/dm/part.c index caae23bd4aad..7a427d3d41dd 100644 --- a/test/dm/part.c +++ b/test/dm/part.c @@ -8,9 +8,13 @@ #include #include #include +#include +#include #include #include +DECLARE_GLOBAL_DATA_PTR; + static int do_test(struct unit_test_state *uts, int expected, const char *part_str, bool whole) { @@ -195,3 +199,48 @@ static int dm_test_part_get_info_by_type(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_part_get_info_by_type, UTF_SCAN_PDATA | UTF_SCAN_FDT); + +static int dm_test_part_get_info_by_type_guid(struct unit_test_state *uts) +{ + struct udevice *dev, *blk_dev; + struct blk_desc *desc; + struct disk_partition info; + ofnode root, node; + int partnum; + + if (!IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) + return -EAGAIN; + + /* Bind the mmc5 node (ChromeOS image with type GUIDs) */ + root = oftree_root(oftree_default()); + node = ofnode_find_subnode(root, "mmc5"); + ut_assert(ofnode_valid(node)); + ut_assertok(lists_bind_fdt(gd->dm_root, node, &dev, NULL, false)); + + /* Get the MMC device (probes it), then walk MMC -> BLK parent link */ + ut_assertok(uclass_get_device_by_seq(UCLASS_MMC, 5, &dev)); + ut_assertok(blk_get_from_parent(dev, &blk_dev)); + desc = dev_get_uclass_plat(blk_dev); + ut_assert(desc); + + /* + * Test: look up the first ChromeOS kernel partition by type GUID. + * In the ChromeOS image KERN_A is the first partition carrying the + * ChromeOS kernel type GUID (fe3a2a5d-...). This is partition 2. + */ + partnum = part_get_info_by_type_guid(desc, + "FE3A2A5D-4F32-41A7-B725-ACCC3285A309", + &info); + ut_asserteq(2, partnum); + ut_asserteq_str("KERN_A", info.name); + + /* Test: non-existent GUID must return -ENOENT */ + ut_asserteq(-ENOENT, + part_get_info_by_type_guid(desc, + "00000000-0000-0000-0000-000000000000", + &info)); + + return 0; +} + +DM_TEST(dm_test_part_get_info_by_type_guid, UTF_SCAN_PDATA | UTF_SCAN_FDT); diff --git a/test/dm/smem.c b/test/dm/smem.c deleted file mode 100644 index 89e74cccc574..000000000000 --- a/test/dm/smem.c +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2018 Ramon Fried - */ - -#include -#include -#include -#include -#include - -/* Basic test of the smem uclass */ -static int dm_test_smem_base(struct unit_test_state *uts) -{ - struct udevice *dev; - size_t size; - - ut_assertok(uclass_get_device(UCLASS_SMEM, 0, &dev)); - ut_assertnonnull(dev); - ut_assertok(smem_alloc(dev, -1, 0, 16)); - ut_asserteq(0, smem_get_free_space(dev, -1)); - ut_assertnull(smem_get(dev, -1, 0, &size)); - - return 0; -} -DM_TEST(dm_test_smem_base, UTF_SCAN_PDATA | UTF_SCAN_FDT); diff --git a/tools/efivar.py b/tools/efivar.py index 67729fa8505c..d248cd868ba5 100755 --- a/tools/efivar.py +++ b/tools/efivar.py @@ -79,6 +79,50 @@ def __init__(self, size, attrs, time, guid, name, data): def calc_crc32(buf): return zlib.crc32(buf) & 0xffffffff +def strip_auth_descriptor(data, attrs): + """ + Strip the EFI auth descriptor from authenticated variable data. + + This is used during efivar.py-based pre-seeding of ubootefi.var to + match U-Boot SetVariable() behavior, where the authentication header + is consumed during validation and only the payload is stored. + + For variables with EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS: + - Input format: [EFI_VARIABLE_AUTHENTICATION_2 | payload] + - Stored format: [payload only] + + Auth header size calculation (aligned with U-Boot efi_variable_authenticate()): + auth_size = sizeof(EFI_TIME) + WIN_CERTIFICATE.dwLength + + Only the payload portion is retained after stripping the header. + """ + if not (attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS): + return data + if not data: + return data + + efi = EfiStruct() + if len(data) < efi.var_time_size: + return data + + offset = efi.var_time_size + if len(data) < offset + efi.var_win_cert_size: + return data + + try: + cert_hdr = struct.unpack_from(efi.var_win_cert_fmt, data, offset) + dwLength = cert_hdr[0] + except struct.error: + return data + + auth_size = efi.var_time_size + dwLength + if auth_size <= 0 or auth_size > len(data): + return data + + if len(data) <= auth_size: + return b'' + return data[auth_size:] + class EfiVariableStore: def __init__(self, infile): self.infile = infile @@ -172,8 +216,12 @@ def set_var(self, guid, name, data, size, attrs): break offs = loffs + if data and (attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS): + data = strip_auth_descriptor(data, attrs) + size = len(data) if data else 0 + tsec = int(time.time()) if attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS else 0 - nd = name.encode('utf_16_le') + b"\x00\x00" + data + nd = name.encode('utf_16_le') + b"\x00\x00" + (data if data else b'') # U-Boot variable format requires the name + data blob to be 8-byte aligned pad = ((len(nd) + 7) & ~7) - len(nd) nd += bytes([0] * pad) diff --git a/tools/fvupdate_to_fit.py b/tools/fvupdate_to_fit.py new file mode 100755 index 000000000000..bfd9cc631d79 --- /dev/null +++ b/tools/fvupdate_to_fit.py @@ -0,0 +1,605 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +""" +FvUpdate.xml to Capsule-Compatible FIT Image and Capsule Converter + +Complete end-to-end converter that: +1. Processes FvUpdate.xml files +2. Creates FIT images compatible with Qualcomm U-Boot capsule update system +3. Generates GUIDs using mkeficapsule guidgen +4. Creates final capsule files ready for deployment + +Usage: ./fvupdate_to_fit.py +""" + +import xml.etree.ElementTree as ET +import os +import sys +import subprocess +import shutil +import re +from pathlib import Path +import argparse + +class FvUpdateToFitConverter: + """ + Complete FvUpdate.xml to Capsule converter. + + This converter provides end-to-end functionality: + 1. Converts FvUpdate.xml to FIT images + 2. Generates GUIDs using mkeficapsule guidgen + 3. Creates final capsule files + 4. Auto-installs missing tools + 5. Ensures compatibility with Qualcomm U-Boot capsule update system + """ + + def __init__(self, xml_path, mkeficapsule_path=None): + self.xml_path = Path(xml_path) + self.mkeficapsule_path = mkeficapsule_path or "mkeficapsule" + self.base_dir = self.xml_path.parent + self.images_dir = self.base_dir / "Images" + self.fw_entries = [] + + def parse_xml(self): + """ + Parse FvUpdate.xml and extract UPDATE operations only. + + Only processes FwEntry elements with Operation="UPDATE" to ensure + compatibility with capsule update requirements. + """ + print(f"Parsing XML file: {self.xml_path}") + + try: + tree = ET.parse(self.xml_path) + root = tree.getroot() + except ET.ParseError as e: + raise ValueError(f"Invalid XML format: {e}") + except FileNotFoundError: + raise FileNotFoundError(f"XML file not found: {self.xml_path}") + + # Find all FwEntry elements + fw_entries = root.findall('.//FwEntry') + if not fw_entries: + raise ValueError("No FwEntry elements found in XML") + + print(f"Found {len(fw_entries)} FwEntry elements") + + for fw_entry in fw_entries: + # Check operation type + operation_elem = fw_entry.find('Operation') + if operation_elem is None: + print("Warning: FwEntry missing Operation element, skipping") + continue + + operation = operation_elem.text + if operation != "UPDATE": + print(f"Skipping FwEntry with operation: {operation}") + continue + + # Extract required elements + try: + dest = fw_entry.find('.//Dest') + if dest is None: + raise ValueError("FwEntry missing Dest element") + + partition_name_elem = dest.find('PartitionName') + if partition_name_elem is None: + raise ValueError("Dest missing PartitionName element") + partition_name = partition_name_elem.text + + binary_file_elem = fw_entry.find('InputBinary') + if binary_file_elem is None: + raise ValueError("FwEntry missing InputBinary element") + binary_file = binary_file_elem.text + + # Validate partition name + if not partition_name or not partition_name.strip(): + raise ValueError("Empty partition name") + + # Validate binary file name + if not binary_file or not binary_file.strip(): + raise ValueError("Empty binary file name") + + self.fw_entries.append({ + 'partition_name': partition_name.strip(), + 'binary_file': binary_file.strip(), + 'binary_path': self.images_dir / binary_file.strip() + }) + + print(f" Added: {partition_name} -> {binary_file}") + + except ValueError as e: + raise ValueError(f"Invalid FwEntry: {e}") + + if not self.fw_entries: + raise ValueError("No valid UPDATE FwEntry elements found") + + print(f"Successfully parsed {len(self.fw_entries)} UPDATE entries") + + def validate_files(self): + """ + Validate all binary files exist. + + Fails completely if any binary file is missing, as requested. + """ + print("Validating binary files...") + + if not self.images_dir.exists(): + raise FileNotFoundError(f"Images directory not found: {self.images_dir}") + + missing_files = [] + for entry in self.fw_entries: + if not entry['binary_path'].exists(): + missing_files.append(str(entry['binary_path'])) + else: + # Check file size + size = entry['binary_path'].stat().st_size + print(f" {entry['binary_file']}: {size} bytes") + + if missing_files: + raise FileNotFoundError(f"Missing binary files: {missing_files}") + + print("All binary files validated successfully") + + def generate_its(self, its_path): + """ + Generate capsule-compatible ITS file. + + Creates ITS content that works with the Qualcomm capsule update flow: + - Node names match partition names for DFU mapping + - All images marked as "firmware" type + - SHA256 hashes for integrity verification + - Single configuration referencing all firmware + """ + print(f"Generating ITS file: {its_path}") + + its_content = self._build_its_content() + + try: + with open(its_path, 'w') as f: + f.write(its_content) + except IOError as e: + raise IOError(f"Failed to write ITS file: {e}") + + print(f"ITS file generated successfully") + + def _build_its_content(self): + """ + Build ITS content compatible with capsule update flow. + + Format ensures compatibility with: + - qcom_configure_capsule_updates() partition discovery + - fit_update() processing requirements + - fit_image_verify() hash validation + - dfu_write_by_name() partition targeting + """ + images_section = "" + firmware_list = [] + + for entry in self.fw_entries: + # Node name format: partition_name (without @1 to avoid reg issues) + # This MUST match the partition name discovered by U-Boot + node_name = entry['partition_name'] + firmware_list.append(f'"{node_name}"') + + # Generate image node without reg properties to avoid DTC warnings + images_section += f''' + {node_name} {{ + description = "{entry['partition_name']} Firmware"; + data = /incbin/("Images/{entry['binary_file']}"); + type = "firmware"; /* Required for fit_update() */ + arch = "arm64"; /* Target architecture */ + compression = "none"; /* No compression */ + load = <0x00000000>; /* Required by fit_image_get_load() */ + + hash-1 {{ + algo = "sha256"; /* For fit_image_verify() */ + }}; + }};''' + + # Build firmware reference list for configuration + firmware_refs = ", ".join(firmware_list) + + # Complete ITS content + return f'''/dts-v1/; + +/ {{ + description = "Qualcomm Firmware Update Package"; + + images {{{images_section} + }}; + + configurations {{ + default = "config-1"; + config-1 {{ + description = "Qualcomm Multi-Partition Update"; + firmware = {firmware_refs}; + }}; + }}; +}}; +''' + + def compile_fit(self, its_path, fit_path): + """ + Compile ITS to FIT using mkimage. + + Uses U-Boot's mkimage tool to create the final FIT image. + """ + print(f"Compiling FIT image: {fit_path}") + + # Check if mkimage is available + try: + subprocess.run(["mkimage", "-V"], capture_output=True, check=True) + except (subprocess.CalledProcessError, FileNotFoundError): + raise RuntimeError("mkimage tool not found. Please install U-Boot tools.") + + # Compile ITS to FIT + cmd = ["mkimage", "-f", str(its_path), str(fit_path)] + print(f"Running: {' '.join(cmd)}") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, + cwd=self.base_dir, check=True) + + if result.stdout: + print("mkimage output:") + print(result.stdout) + + except subprocess.CalledProcessError as e: + error_msg = f"mkimage compilation failed (exit code {e.returncode})" + if e.stderr: + error_msg += f": {e.stderr}" + raise RuntimeError(error_msg) + + # Verify output file was created + if not fit_path.exists(): + raise RuntimeError("FIT image was not created") + + fit_size = fit_path.stat().st_size + print(f"FIT image compiled successfully: {fit_size} bytes") + + def check_required_tools(self): + """Check if required tools are available, auto-install if missing""" + required_tools = { + 'mkimage': 'u-boot-tools', + 'mkeficapsule': 'u-boot-tools' + } + + missing_tools = [] + for tool, package in required_tools.items(): + if not self.tool_available(tool): + missing_tools.append((tool, package)) + + if missing_tools: + print("Installing missing tools...") + packages_to_install = set() + for tool, package in missing_tools: + packages_to_install.add(package) + + for package in packages_to_install: + self.install_package(package) + print(f" Installed {package} ✓") + + def tool_available(self, tool_name): + """Check if a tool is available in PATH""" + return shutil.which(tool_name) is not None + + def install_package(self, package): + """Install package using system package manager""" + # Detect package manager and install + if shutil.which('apt-get'): + cmd = ['sudo', 'apt-get', 'install', '-y', package] + elif shutil.which('yum'): + cmd = ['sudo', 'yum', 'install', '-y', package] + elif shutil.which('dnf'): + cmd = ['sudo', 'dnf', 'install', '-y', package] + elif shutil.which('pacman'): + cmd = ['sudo', 'pacman', '-S', '--noconfirm', package] + else: + raise RuntimeError(f"Cannot auto-install {package}. Please install manually.") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + except subprocess.CalledProcessError as e: + raise RuntimeError(f"Failed to install {package}: {e.stderr}") + + def get_primary_partition_name(self): + """Get partition name from first FwEntry with Operation='UPDATE'""" + if not self.fw_entries: + raise ValueError("No UPDATE FwEntry found in XML") + + # Return first UPDATE partition found + primary_partition = self.fw_entries[0]['partition_name'] + print(f"Primary partition selected: {primary_partition} (first UPDATE entry)") + return primary_partition + + def is_valid_guid(self, guid): + """Validate GUID format""" + # GUID format: 8-4-4-4-12 hex digits + guid_pattern = r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' + return re.match(guid_pattern, guid) is not None + + def create_capsule(self, fit_path, guid, capsule_path, fw_version=None, monotonic_count=None, private_key=None, certificate=None): + """Create capsule using mkeficapsule with optional firmware version and signing""" + print(f"Creating capsule: {capsule_path}") + + cmd = [ + self.mkeficapsule_path, + '--guid', guid, + '--index', '1', + ] + + # Add firmware version if provided + if fw_version is not None: + version_parts = fw_version.split('.') + try: + major = int(version_parts[2]) + minor = int(version_parts[3]) + encoded_version = (major << 16) | minor + cmd.extend(['--fw-version', str(encoded_version)]) + print(f" Encoded Firmware version: {encoded_version} (from {fw_version})") + except (ValueError, IndexError): + raise ValueError(f"Invalid version string: {fw_version}") + + # Add signing parameters if provided + if private_key and certificate and monotonic_count is not None: + print("Signing capsule...") + cmd.extend([ + '--monotonic-count', str(monotonic_count), + '--private-key', str(private_key), + '--certificate', str(certificate), + ]) + elif any([private_key, certificate, monotonic_count is not None]): + raise ValueError("All signing parameters (--private-key, --certificate, --monotonic-count) must be provided together.") + + cmd.extend([str(fit_path), str(capsule_path)]) + + print(f" Command: {' '.join(cmd)}") + + try: + result = subprocess.run(cmd, capture_output=True, text=True, + check=True, timeout=60) + + if result.stdout: + print("mkeficapsule output:") + print(result.stdout) + + except subprocess.CalledProcessError as e: + error_msg = f"Capsule creation failed (exit code {e.returncode})" + if e.stderr: + error_msg += f": {e.stderr}" + raise RuntimeError(error_msg) + except subprocess.TimeoutExpired: + raise RuntimeError("Capsule creation timed out") + + # Verify output file was created + if not capsule_path.exists(): + raise RuntimeError("Capsule file was not created") + + capsule_size = capsule_path.stat().st_size + print(f"Capsule created successfully: {capsule_size} bytes ✓") + return capsule_size + + def convert_complete(self, fit_output_name="system.fit", capsule_output_name="firmware.capsule", fw_version=None, guid=None, monotonic_count=None, private_key=None, certificate=None): + """ + Complete conversion workflow: XML → FIT → Capsule + + Performs the full end-to-end conversion: + 1. Check and install required tools + 2. XML parsing and validation + 3. Binary file validation + 4. ITS generation + 5. FIT compilation + 6. Capsule creation with provided GUID and optional firmware version + 7. Keep all intermediate files + """ + print(f"Converting {self.xml_path} to capsule...") + print("=" * 60) + + # Step 1: Check and install required tools + self.check_required_tools() + + # Step 2-5: Generate FIT + self.parse_xml() + self.validate_files() + + # Generate file paths + its_path = self.base_dir / "system.its" + fit_path = self.base_dir / fit_output_name + capsule_path = self.base_dir / capsule_output_name + + self.generate_its(its_path) + self.compile_fit(its_path, fit_path) + + # Step 6: Create capsule with provided GUID and optional firmware version + if not guid: + raise ValueError("GUID is required for capsule creation. Use --guid option.") + + print(f"Using GUID: {guid}") + capsule_size = self.create_capsule(fit_path, guid, capsule_path, fw_version, monotonic_count, private_key, certificate) + + # Step 7: Summary (keep all files) + print("=" * 60) + print("SUCCESS: Complete capsule workflow completed!") + print() + print("Files created:") + print(f" ITS file: {its_path}") + print(f" FIT file: {fit_path}") + print(f" Capsule file: {capsule_path} ({capsule_size / (1024*1024):.1f} MB)") + print() + print(f"Capsule GUID: {guid}") + print() + print("Ready for deployment:") + print(" 1. Copy firmware.capsule to boot partition") + print(" 2. In U-Boot: fatload mmc 0:1 $loadaddr firmware.capsule") + print(" 3. In U-Boot: efidebug capsule update $loadaddr") + + return capsule_path + + def convert(self, output_name="system.fit"): + """ + Main conversion workflow. + + Converts FvUpdate.xml to capsule-compatible FIT image through: + 1. XML parsing and validation + 2. Binary file validation + 3. ITS generation + 4. FIT compilation + """ + print(f"Converting {self.xml_path} to FIT image...") + print("=" * 60) + + # Parse and validate XML + self.parse_xml() + + # Validate binary files + self.validate_files() + + # Generate ITS file + its_path = self.base_dir / "system.its" + self.generate_its(its_path) + + # Compile FIT image + fit_path = self.base_dir / output_name + self.compile_fit(its_path, fit_path) + + print("=" * 60) + print("CONVERSION SUMMARY:") + print(f" Input XML: {self.xml_path}") + print(f" Generated ITS: {its_path}") + print(f" Output FIT: {fit_path}") + print(f" Partitions: {len(self.fw_entries)}") + for entry in self.fw_entries: + print(f" - {entry['partition_name']} ({entry['binary_file']})") + + return fit_path + +def main(): + """Main entry point with command line argument parsing.""" + parser = argparse.ArgumentParser( + description="Convert FvUpdate.xml to capsule-compatible FIT image and capsule", + epilog=""" +Examples: + # Generate FIT image only: + %(prog)s FvUpdate.xml + + # Generate FIT image and create capsule: + %(prog)s FvUpdate.xml --mkeficapsule --guid --fw-version + +The script can operate in two modes: +1. FIT-only mode: Creates FIT image from FvUpdate.xml +2. Complete mode: Creates FIT image and capsule (requires --guid and --mkeficapsule) + +Workflow: +1. Auto-install missing tools (mkimage, mkeficapsule) +2. Parse FvUpdate.xml for UPDATE operations +3. Validate all binary files in Images/ directory +4. Generate system.its file +5. Compile system.fit image using mkimage +6. Create firmware.capsule using mkeficapsule (if --guid provided) + +The generated capsule is ready for deployment in Qualcomm U-Boot systems. + """, + formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument('xml_file', + help='Path to FvUpdate.xml file') + parser.add_argument('--guid', + help='Capsule GUID for capsule generation. Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. ' + 'Board-specific GUIDs: ' + 'qcs615: 9FD379D2-670E-4BB3-86A1-40497E6E17B0, ' + 'qcs6490-rb3gen2: 6f25bfd2-a165-468b-980f-ac51a0a45c52, ' + 'lemans-evk: 78462415-6133-431c-9fae-48f2bafd5c71') + parser.add_argument('--mkeficapsule', + help='Path to mkeficapsule binary (default: use system PATH)') + parser.add_argument('-o', '--output', + default='system.fit', + help='Output FIT image name (default: %(default)s)') + parser.add_argument('-c', '--capsule-output', + default='firmware.capsule', + help='Output capsule file name (default: %(default)s)') + parser.add_argument('--fw-version', + help='Firmware version for capsule in "0.0.A.B" format (e.g., "0.0.1.0"). This version will be stored in ESRT.') + parser.add_argument('--monotonic-count', + type=int, + help='Monotonic count for capsule signing.') + parser.add_argument('--private-key', + help='Path to the private key for signing.') + parser.add_argument('--certificate', + help='Path to the certificate for signing.') + parser.add_argument('-v', '--verbose', + action='store_true', + help='Enable verbose output') + + args = parser.parse_args() + + # Validate input file + xml_path = Path(args.xml_file) + if not xml_path.exists(): + print(f"ERROR: XML file not found: {xml_path}") + sys.exit(1) + + if not xml_path.is_file(): + print(f"ERROR: Path is not a file: {xml_path}") + sys.exit(1) + + # Validate GUID format if provided + if args.guid: + guid_pattern = r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$' + if not re.match(guid_pattern, args.guid): + print(f"ERROR: Invalid GUID format: {args.guid}") + print("Expected format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") + sys.exit(1) + + # Validate fw-version format if provided + if args.fw_version: + version_pattern = r'^\d+\.\d+\.\d+\.\d+$' + if not re.match(version_pattern, args.fw_version): + print(f"ERROR: Invalid firmware version format: {args.fw_version}") + print('Expected format: "0.0.A.B" (e.g., "0.0.1.0")') + sys.exit(1) + + # Create converter + converter = FvUpdateToFitConverter(xml_path, args.mkeficapsule) + + try: + if args.guid: + # Complete capsule workflow + capsule_path = converter.convert_complete( + args.output, + args.capsule_output, + args.fw_version, + args.guid, + args.monotonic_count, + args.private_key, + args.certificate + ) + + print("\n" + "=" * 60) + print("SUCCESS: Complete capsule workflow completed!") + print(f"Final capsule: {capsule_path}") + if args.fw_version: + print(f"Firmware version: {args.fw_version}") + + else: + # FIT-only mode + fit_path = converter.convert(args.output) + + print("\n" + "=" * 60) + print("SUCCESS: FIT image created successfully!") + print(f"Output: {fit_path}") + print("\nTo create capsule, run again with:") + print(f" {sys.argv[0]} {args.xml_file} --mkeficapsule --guid --fw-version ") + + except Exception as e: + print(f"\nERROR: {e}") + if args.verbose: + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main()