[PATCH v7 09/11] mach-snapdragon: add board_spl.c and split out common code
Casey Connolly
casey.connolly at linaro.org
Mon May 11 16:00:46 CEST 2026
On 11/05/2026 15:55, Michael Srba wrote:
>
>
> On 5/11/26 15:33, Casey Connolly wrote:
>> Hi Michael,
>>
>> On 08/05/2026 23:45, michael.srba at seznam.cz wrote:
>>> From: Michael Srba <Michael.Srba at seznam.cz>
>>>
>>> Code in board.c will now only be compiled into U-Boot proper,
>>> and the new board_spl.c will only be built into SPL.
>>> Code in board_common.c is common to both phases.
>>>
>>> Also split out mem_map.c, which is currently common to both
>>> phases since it seems to not cause issues in SPL. In the future
>>> it should probably behave differenly in SPL, especially if dram
>>> initialization is supported.
>> Sorry to be a pest with this one, could you rebase this on the SMEM
>> series since it also pulls out all the memory mapping stuff.
>>
>> Since the only thing you left in board_common.c is the board_usb_init(),
>> it seems like it's only actually used in db410c at least according to a
>> quick grep. Could you just move it over to board/qualcomm/
>> dragonboard410c?
>>
>> Along with the other changes maybe most of the contents of board_spl.c
>> should go into some board/qualcomm/sdm845-spl/board.c file or something?
> but there's nothing sdm845-specific about it? SPL won't ever be passed
> dt by a previous stage, empty board-init is a sensible default even
> if someone wants to override it in specific cases, reset-cpu is a stub and
> if qcom wants to implement it there's no good reason to use PSCI for that
> so it can be universal (though an ifdef for the non-typical case
> of late-in-the-chain SPL would probably still be cleaner than multiple
> separate board.c files), and fully implemented spl_boot_device should check
> what medium the BROM booted from (or at least if it was EDL, which is
> in a hw register, not sure if emmc vs ufs vs sdcard vs spi needs awareness
> of data passed from PBL), so again can be perfectly shared.
> If DFU is not enabled in config then the USB boot branch can even
> be ifdef'd away at compile time.
Alright, I'm a bit unsure how Qualcomm want to base their stuff on top
so I'll defer to you and we can keep this as-is. fwiw the "sdm845-spl"
naming is just arbitrary, if you can pick a better name please do.
Sumit: any thoughts on how to align these approaches and keep it all
maintainable?
>> I'd expect with qcom also doing SPL stuff there will be some conflicting
>> implementations we'll need to handle at build time and this might be the
>> easiest way to handle it.
>>
>>> Signed-off-by: Michael Srba <Michael.Srba at seznam.cz>
>>> Reviewed-by: Simon Glass <sjg at chromium.org>
>>> ---
>>> arch/arm/mach-snapdragon/Makefile | 8 +
>>> arch/arm/mach-snapdragon/board.c | 358
>>> +-------------------------------
>>> arch/arm/mach-snapdragon/board_common.c | 56 +++++
>>> arch/arm/mach-snapdragon/board_spl.c | 35 ++++
>>> arch/arm/mach-snapdragon/mem_map.c | 318 +++++++++++++++++++++
>>> +++++++
>>> arch/arm/mach-snapdragon/qcom-priv.h | 4 +-
>>> 6 files changed, 429 insertions(+), 350 deletions(-)
>>>
>>> diff --git a/arch/arm/mach-snapdragon/Makefile b/arch/arm/mach-
>>> snapdragon/Makefile
>>> index 343e825c6fd..4b265b746ce 100644
>>> --- a/arch/arm/mach-snapdragon/Makefile
>>> +++ b/arch/arm/mach-snapdragon/Makefile
>>> @@ -2,6 +2,14 @@
>>> #
>>> # (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski at gmail.com>
>>> +obj-y += board_common.o
>>> +obj-y += mem_map.o
>>> +
>>> +ifeq ($(CONFIG_SPL_BUILD),y)
>>> +obj-y += board_spl.o
>>> +else
>>> obj-y += board.o
>>> obj-$(CONFIG_EFI_HAVE_CAPSULE_SUPPORT) += capsule_update.o
>>> +endif
>>> +
>>> obj-$(CONFIG_OF_LIVE) += of_fixup.o
>>> diff --git a/arch/arm/mach-snapdragon/board.c b/arch/arm/mach-
>>> snapdragon/board.c
>>> index 829a0109ac7..a20c9853789 100644
>>> --- a/arch/arm/mach-snapdragon/board.c
>>> +++ b/arch/arm/mach-snapdragon/board.c
>>> @@ -1,6 +1,7 @@
>>> // SPDX-License-Identifier: GPL-2.0+
>>> /*
>>> * Common initialisation for Qualcomm Snapdragon boards.
>>> + * U-Boot proper only, see board_common.c for parts shared with SPL
>>> *
>>> * Copyright (c) 2024 Linaro Ltd.
>>> * Author: Casey Connolly <casey.connolly at linaro.org>
>>> @@ -9,155 +10,21 @@
>>> #define LOG_CATEGORY LOGC_BOARD
>>> #define pr_fmt(fmt) "QCOM: " fmt
>>> -#include <asm/armv8/mmu.h>
>>> -#include <asm/gpio.h>
>>> -#include <asm/io.h>
>>> -#include <asm/psci.h>
>>> -#include <asm/system.h>
>>> -#include <dm/device.h>
>>> -#include <dm/pinctrl.h>
>>> -#include <dm/uclass-internal.h>
>>> -#include <dm/read.h>
>>> -#include <power/regulator.h>
>>> +#include <dm/ofnode.h>
>>> #include <env.h>
>>> #include <fdt_support.h>
>>> #include <init.h>
>>> #include <linux/arm-smccc.h>
>>> -#include <linux/bug.h>
>>> +#include <linux/errno.h>
>>> #include <linux/psci.h>
>>> #include <linux/sizes.h>
>>> #include <lmb.h>
>>> -#include <malloc.h>
>>> -#include <fdt_support.h>
>>> -#include <usb.h>
>>> -#include <sort.h>
>>> -#include <time.h>
>>> #include "qcom-priv.h"
>>> -DECLARE_GLOBAL_DATA_PTR;
>>> -
>>> enum qcom_boot_source qcom_boot_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;
>>> -}
>>> -
>>> +#if CONFIG_IS_ENABLED(SYSRESET_PSCI)
>>> static void show_psci_version(void)
>>> {
>>> struct arm_smccc_res res;
>>> @@ -199,6 +66,7 @@ static void qcom_psci_fixup(void *fdt)
>>> if (ret)
>>> log_err("Failed to delete /psci node: %d\n", ret);
>>> }
>>> +#endif
>>> /* We support booting U-Boot with an internal DT when running as
>>> a first-stage bootloader
>>> * or for supporting quirky devices where it's easier to leave the
>>> downstream DT in place
>>> @@ -258,49 +126,13 @@ int board_fdt_blob_setup(void **fdtp)
>>> ret = 0;
>>> }
>>> +#if CONFIG_IS_ENABLED(SYSRESET_PSCI)
>>> qcom_psci_fixup(*fdtp);
>>> +#endif
>>> return ret;
>>> }
>>> -/*
>>> - * Some Qualcomm boards require GPIO configuration when switching
>>> USB modes.
>>> - * Support setting this configuration via pinctrl state.
>>> - */
>>> -int board_usb_init(int index, enum usb_init_type init)
>>> -{
>>> - struct udevice *usb;
>>> - int ret = 0;
>>> -
>>> - /* USB device */
>>> - ret = uclass_find_device_by_seq(UCLASS_USB, index, &usb);
>>> - if (ret) {
>>> - printf("Cannot find USB device\n");
>>> - return ret;
>>> - }
>>> -
>>> - ret = dev_read_stringlist_search(usb, "pinctrl-names",
>>> - "device");
>>> - /* No "device" pinctrl state, so just bail */
>>> - if (ret < 0)
>>> - return 0;
>>> -
>>> - /* Select "default" or "device" pinctrl */
>>> - switch (init) {
>>> - case USB_INIT_HOST:
>>> - pinctrl_select_state(usb, "default");
>>> - break;
>>> - case USB_INIT_DEVICE:
>>> - pinctrl_select_state(usb, "device");
>>> - break;
>>> - default:
>>> - debug("Unknown usb_init_type %d\n", init);
>>> - break;
>>> - }
>>> -
>>> - return 0;
>>> -}
>>> -
>>> /*
>>> * Some boards still need board specific init code, they can
>>> implement that by
>>> * overriding this function.
>>> @@ -313,7 +145,9 @@ void __weak qcom_board_init(void)
>>> int board_init(void)
>>> {
>>> +#if CONFIG_IS_ENABLED(SYSRESET_PSCI) && !defined(CONFIG_SPL_BUILD)
>>> show_psci_version();
>>> +#endif
>>> qcom_board_init();
>>> return 0;
>>> }
>>> @@ -580,177 +414,3 @@ int board_late_init(void)
>>> return 0;
>>> }
>>> -
>>> -static void build_mem_map(void)
>>> -{
>>> - int i, j;
>>> -
>>> - /*
>>> - * Ensure the peripheral block is sized to correctly cover the
>>> address range
>>> - * up to the first memory bank.
>>> - * Don't map the first page to ensure that we actually trigger
>>> an abort on a
>>> - * null pointer access rather than just hanging.
>>> - * FIXME: we should probably split this into more precise regions
>>> - */
>>> - mem_map[0].phys = 0x1000;
>>> - mem_map[0].virt = mem_map[0].phys;
>>> - mem_map[0].size = gd->bd->bi_dram[0].start - mem_map[0].phys;
>>> - mem_map[0].attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
>>> - PTE_BLOCK_NON_SHARE |
>>> - PTE_BLOCK_PXN | PTE_BLOCK_UXN;
>>> -
>>> - for (i = 1, j = 0; i < ARRAY_SIZE(rbx_mem_map) - 1 && gd->bd-
>>> >bi_dram[j].size; i++, j++) {
>>> - mem_map[i].phys = gd->bd->bi_dram[j].start;
>>> - mem_map[i].virt = mem_map[i].phys;
>>> - mem_map[i].size = gd->bd->bi_dram[j].size;
>>> - mem_map[i].attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) | \
>>> - PTE_BLOCK_INNER_SHARE;
>>> - }
>>> -
>>> - mem_map[i].phys = UINT64_MAX;
>>> - mem_map[i].size = 0;
>>> -
>>> -#ifdef DEBUG
>>> - debug("Configured memory map:\n");
>>> - for (i = 0; mem_map[i].size; i++)
>>> - debug(" 0x%016llx - 0x%016llx: entry %d\n",
>>> - mem_map[i].phys, mem_map[i].phys + mem_map[i].size, i);
>>> -#endif
>>> -}
>>> -
>>> -u64 get_page_table_size(void)
>>> -{
>>> - return SZ_1M;
>>> -}
>>> -
>>> -static int fdt_cmp_res(const void *v1, const void *v2)
>>> -{
>>> - const struct fdt_resource *res1 = v1, *res2 = v2;
>>> -
>>> - return res1->start - res2->start;
>>> -}
>>> -
>>> -#define N_RESERVED_REGIONS 32
>>> -
>>> -/* 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.
>>> - */
>>> -static void carve_out_reserved_memory(void)
>>> -{
>>> - static struct fdt_resource res[N_RESERVED_REGIONS] = { 0 };
>>> - int parent, rmem, count, i = 0;
>>> - phys_addr_t start;
>>> - size_t size;
>>> -
>>> - /* Some reserved nodes must be carved out, as the cache-
>>> prefetcher may otherwise
>>> - * attempt to access them, causing a security exception.
>>> - */
>>> - parent = fdt_path_offset(gd->fdt_blob, "/reserved-memory");
>>> - if (parent <= 0) {
>>> - log_err("No reserved memory regions found\n");
>>> - return;
>>> - }
>>> -
>>> - /* Collect the reserved memory regions */
>>> - fdt_for_each_subnode(rmem, gd->fdt_blob, parent) {
>>> - const fdt32_t *ptr;
>>> - int len;
>>> - if (!fdt_getprop(gd->fdt_blob, rmem, "no-map", NULL))
>>> - continue;
>>> -
>>> - if (i == N_RESERVED_REGIONS) {
>>> - log_err("Too many reserved regions!\n");
>>> - break;
>>> - }
>>> -
>>> - /* Read the address and size out from the reg property.
>>> Doing this "properly" with
>>> - * 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);
>>> - 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]);
>>> - i++;
>>> - }
>>> - }
>>> -
>>> - /* Sort the reserved memory regions by address */
>>> - count = i;
>>> - qsort(res, count, sizeof(struct fdt_resource), fdt_cmp_res);
>>> -
>>> - /* 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
>>> - * regions.
>>> - */
>>> - start = ALIGN_DOWN(res[0].start, SZ_2M);
>>> - size = ALIGN(res[0].end - start, SZ_2M);
>>> - 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)
>>> - break;
>>> - start = ALIGN_DOWN(res[i].start, SZ_2M);
>>> - size = ALIGN(res[i].end - start, SZ_2M);
>>> - } else {
>>> - /* Bump size if this region is immediately after the
>>> previous one */
>>> - size = ALIGN(res[i].end - start, SZ_2M);
>>> - }
>>> - }
>>> -}
>>> -
>>> -/* This function open-codes setup_all_pgtables() so that we can
>>> - * insert additional mappings *before* turning on the MMU.
>>> - */
>>> -void enable_caches(void)
>>> -{
>>> - u64 tlb_addr = gd->arch.tlb_addr;
>>> - u64 tlb_size = gd->arch.tlb_size;
>>> - u64 pt_size;
>>> - ulong carveout_start;
>>> -
>>> - gd->arch.tlb_fillptr = tlb_addr;
>>> -
>>> - build_mem_map();
>>> -
>>> - icache_enable();
>>> -
>>> - /* Create normal system page tables */
>>> - setup_pgtables();
>>> -
>>> - pt_size = (uintptr_t)gd->arch.tlb_fillptr -
>>> - (uintptr_t)gd->arch.tlb_addr;
>>> - debug("Primary pagetable size: %lluKiB\n", pt_size / 1024);
>>> -
>>> - /* Create emergency page tables */
>>> - gd->arch.tlb_size -= pt_size;
>>> - gd->arch.tlb_addr = gd->arch.tlb_fillptr;
>>> - setup_pgtables();
>>> - gd->arch.tlb_emerg = gd->arch.tlb_addr;
>>> - 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) {
>>> - carveout_start = get_timer(0);
>>> - /* Takes ~20-50ms on SDM845 */
>>> - carve_out_reserved_memory();
>>> - debug("carveout time: %lums\n", get_timer(carveout_start));
>>> - }
>>> - dcache_enable();
>>> -}
>>> diff --git a/arch/arm/mach-snapdragon/board_common.c b/arch/arm/mach-
>>> snapdragon/board_common.c
>>> new file mode 100644
>>> index 00000000000..f6daeb6edf4
>>> --- /dev/null
>>> +++ b/arch/arm/mach-snapdragon/board_common.c
>>> @@ -0,0 +1,56 @@
>>> +// SPDX-License-Identifier: GPL-2.0+
>>> +/*
>>> + * Common initialisation for Qualcomm Snapdragon boards.
>>> + *
>>> + * Copyright (c) 2024 Linaro Ltd.
>>> + * Author: Casey Connolly <casey.connolly at linaro.org>
>>> + */
>>> +
>>> +#define LOG_CATEGORY LOGC_BOARD
>>> +#define pr_fmt(fmt) "QCOM: " fmt
>>> +
>>> +#include <dm/device.h>
>>> +#include <dm/pinctrl.h>
>>> +#include <dm/uclass-internal.h>
>>> +#include <dm/read.h>
>>> +#include <usb.h>
>>> +
>>> +#include "qcom-priv.h"
>>> +
>>> +/*
>>> + * Some Qualcomm boards require GPIO configuration when switching
>>> USB modes.
>>> + * Support setting this configuration via pinctrl state.
>>> + */
>>> +int board_usb_init(int index, enum usb_init_type init)
>>> +{
>>> + struct udevice *usb;
>>> + int ret = 0;
>>> +
>>> + /* USB device */
>>> + ret = uclass_find_device_by_seq(UCLASS_USB, index, &usb);
>>> + if (ret) {
>>> + printf("Cannot find USB device\n");
>>> + return ret;
>>> + }
>>> +
>>> + ret = dev_read_stringlist_search(usb, "pinctrl-names",
>>> + "device");
>>> + /* No "device" pinctrl state, so just bail */
>>> + if (ret < 0)
>>> + return 0;
>>> +
>>> + /* Select "default" or "device" pinctrl */
>>> + switch (init) {
>>> + case USB_INIT_HOST:
>>> + pinctrl_select_state(usb, "default");
>>> + break;
>>> + case USB_INIT_DEVICE:
>>> + pinctrl_select_state(usb, "device");
>>> + break;
>>> + default:
>>> + debug("Unknown usb_init_type %d\n", init);
>>> + break;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> diff --git a/arch/arm/mach-snapdragon/board_spl.c b/arch/arm/mach-
>>> snapdragon/board_spl.c
>>> new file mode 100644
>>> index 00000000000..19260975063
>>> --- /dev/null
>>> +++ b/arch/arm/mach-snapdragon/board_spl.c
>>> @@ -0,0 +1,35 @@
>>> +// SPDX-License-Identifier: GPL-2.0+
>>> +/*
>>> + * Common SPL code for Qualcomm Snapdragon boards.
>>> + *
>>> + * Copyright (c) 2026 Michael Srba <Michael.Srba at seznam.cz>
>>> + */
>>> +
>>> +#include <hang.h>
>>> +#include <spl.h>
>>> +
>>> +/* in SPL, we always use internal DT */
>>> +int board_fdt_blob_setup(void **fdtp)
>>> +{
>>> + return -EEXIST;
>>> +}
>>> +
>>> +int board_init(void)
>>> +{
>>> + return 0;
>>> +}
>>> +
>>> +__weak void reset_cpu(void)
>>> +{
>>> + /* This should currently not get called in non-error paths, so
>>> just hang */
>>> + printf("reset_cpu called, going to hang()\n");
>>> + hang();
>>> +}
>>> +
>>> +u32 spl_boot_device(void)
>>> +{
>>> + /* TODO: check boot reason to support UFS and sdcard */
>>> + u32 boot_device = BOOT_DEVICE_DFU;
>>> +
>>> + return boot_device;
>>> +}
>>> diff --git a/arch/arm/mach-snapdragon/mem_map.c b/arch/arm/mach-
>>> snapdragon/mem_map.c
>>> new file mode 100644
>>> index 00000000000..70e3c1d1fcc
>>> --- /dev/null
>>> +++ b/arch/arm/mach-snapdragon/mem_map.c
>>> @@ -0,0 +1,318 @@
>>> +// SPDX-License-Identifier: GPL-2.0+
>>> +/*
>>> + * Common initialisation for Qualcomm Snapdragon boards.
>>> + *
>>> + * Copyright (c) 2024 Linaro Ltd.
>>> + * Author: Casey Connolly <casey.connolly at linaro.org>
>>> + */
>>> +
>>> +#define LOG_CATEGORY LOGC_BOARD
>>> +#define pr_fmt(fmt) "QCOM: " fmt
>>> +
>>> +#include <asm/armv8/mmu.h>
>>> +#include <asm/global_data.h>
>>> +#include <asm/system.h>
>>> +#include <asm-generic/unaligned.h>
>>> +#include <cpu_func.h>
>>> +#include <fdt_support.h>
>>> +#include <linux/errno.h>
>>> +#include <linux/sizes.h>
>>> +#include <sort.h>
>>> +#include <time.h>
>>> +
>>> +#include "qcom-priv.h"
>>> +
>>> +DECLARE_GLOBAL_DATA_PTR;
>>> +
>>> +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
>>> + */
>>> +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 build_mem_map(void)
>>> +{
>>> + int i, j;
>>> +
>>> + /*
>>> + * Ensure the peripheral block is sized to correctly cover the
>>> address range
>>> + * up to the first memory bank.
>>> + * Don't map the first page to ensure that we actually trigger
>>> an abort on a
>>> + * null pointer access rather than just hanging.
>>> + * FIXME: we should probably split this into more precise regions
>>> + */
>>> + mem_map[0].phys = 0x1000;
>>> + mem_map[0].virt = mem_map[0].phys;
>>> + mem_map[0].size = gd->bd->bi_dram[0].start - mem_map[0].phys;
>>> + mem_map[0].attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
>>> + PTE_BLOCK_NON_SHARE |
>>> + PTE_BLOCK_PXN | PTE_BLOCK_UXN;
>>> +
>>> + for (i = 1, j = 0; i < ARRAY_SIZE(rbx_mem_map) - 1 && gd->bd-
>>> >bi_dram[j].size; i++, j++) {
>>> + mem_map[i].phys = gd->bd->bi_dram[j].start;
>>> + mem_map[i].virt = mem_map[i].phys;
>>> + mem_map[i].size = gd->bd->bi_dram[j].size;
>>> + mem_map[i].attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) | \
>>> + PTE_BLOCK_INNER_SHARE;
>>> + }
>>> +
>>> + mem_map[i].phys = UINT64_MAX;
>>> + mem_map[i].size = 0;
>>> +
>>> +#ifdef DEBUG
>>> + debug("Configured memory map:\n");
>>> + for (i = 0; mem_map[i].size; i++)
>>> + debug(" 0x%016llx - 0x%016llx: entry %d\n",
>>> + mem_map[i].phys, mem_map[i].phys + mem_map[i].size, i);
>>> +#endif
>>> +}
>>> +
>>> +u64 get_page_table_size(void)
>>> +{
>>> + return SZ_1M;
>>> +}
>>> +
>>> +static int fdt_cmp_res(const void *v1, const void *v2)
>>> +{
>>> + const struct fdt_resource *res1 = v1, *res2 = v2;
>>> +
>>> + return res1->start - res2->start;
>>> +}
>>> +
>>> +#define N_RESERVED_REGIONS 32
>>> +
>>> +/* 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.
>>> + */
>>> +static void carve_out_reserved_memory(void)
>>> +{
>>> + static struct fdt_resource res[N_RESERVED_REGIONS] = { 0 };
>>> + int parent, rmem, count, i = 0;
>>> + phys_addr_t start;
>>> + size_t size;
>>> +
>>> + /* Some reserved nodes must be carved out, as the cache-
>>> prefetcher may otherwise
>>> + * attempt to access them, causing a security exception.
>>> + */
>>> + parent = fdt_path_offset(gd->fdt_blob, "/reserved-memory");
>>> + if (parent <= 0) {
>>> + log_err("No reserved memory regions found\n");
>>> + return;
>>> + }
>>> +
>>> + /* Collect the reserved memory regions */
>>> + fdt_for_each_subnode(rmem, gd->fdt_blob, parent) {
>>> + const fdt32_t *ptr;
>>> + int len;
>>> + if (!fdt_getprop(gd->fdt_blob, rmem, "no-map", NULL))
>>> + continue;
>>> +
>>> + if (i == N_RESERVED_REGIONS) {
>>> + log_err("Too many reserved regions!\n");
>>> + break;
>>> + }
>>> +
>>> + /* Read the address and size out from the reg property.
>>> Doing this "properly" with
>>> + * 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);
>>> + 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]);
>>> + i++;
>>> + }
>>> + }
>>> +
>>> + /* Sort the reserved memory regions by address */
>>> + count = i;
>>> + qsort(res, count, sizeof(struct fdt_resource), fdt_cmp_res);
>>> +
>>> + /* 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
>>> + * regions.
>>> + */
>>> + start = ALIGN_DOWN(res[0].start, SZ_2M);
>>> + size = ALIGN(res[0].end - start, SZ_2M);
>>> + 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)
>>> + break;
>>> + start = ALIGN_DOWN(res[i].start, SZ_2M);
>>> + size = ALIGN(res[i].end - start, SZ_2M);
>>> + } else {
>>> + /* Bump size if this region is immediately after the
>>> previous one */
>>> + size = ALIGN(res[i].end - start, SZ_2M);
>>> + }
>>> + }
>>> +}
>>> +
>>> +/* This function open-codes setup_all_pgtables() so that we can
>>> + * insert additional mappings *before* turning on the MMU.
>>> + */
>>> +void enable_caches(void)
>>> +{
>>> + u64 tlb_addr = gd->arch.tlb_addr;
>>> + u64 tlb_size = gd->arch.tlb_size;
>>> + u64 pt_size;
>>> + ulong carveout_start;
>>> +
>>> + gd->arch.tlb_fillptr = tlb_addr;
>>> +
>>> + build_mem_map();
>>> +
>>> + icache_enable();
>>> +
>>> + /* Create normal system page tables */
>>> + setup_pgtables();
>>> +
>>> + pt_size = (uintptr_t)gd->arch.tlb_fillptr -
>>> + (uintptr_t)gd->arch.tlb_addr;
>>> + debug("Primary pagetable size: %lluKiB\n", pt_size / 1024);
>>> +
>>> + /* Create emergency page tables */
>>> + gd->arch.tlb_size -= pt_size;
>>> + gd->arch.tlb_addr = gd->arch.tlb_fillptr;
>>> + setup_pgtables();
>>> + gd->arch.tlb_emerg = gd->arch.tlb_addr;
>>> + 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) {
>>> + carveout_start = get_timer(0);
>>> + /* Takes ~20-50ms on SDM845 */
>>> + carve_out_reserved_memory();
>>> + debug("carveout time: %lums\n", get_timer(carveout_start));
>>> + }
>>> + dcache_enable();
>>> +}
>>> diff --git a/arch/arm/mach-snapdragon/qcom-priv.h b/arch/arm/mach-
>>> snapdragon/qcom-priv.h
>>> index b8bf574e8bb..b372465d125 100644
>>> --- a/arch/arm/mach-snapdragon/qcom-priv.h
>>> +++ b/arch/arm/mach-snapdragon/qcom-priv.h
>>> @@ -20,7 +20,9 @@ extern enum qcom_boot_source qcom_boot_source;
>>> #if IS_ENABLED(CONFIG_EFI_HAVE_CAPSULE_SUPPORT)
>>> void qcom_configure_capsule_updates(void);
>>> #else
>>> -void qcom_configure_capsule_updates(void) {}
>>> +static inline void qcom_configure_capsule_updates(void) {}
>>> #endif /* EFI_HAVE_CAPSULE_SUPPORT */
>>> +int qcom_parse_memory(const void *fdt);
>>> +
>>> #endif /* __QCOM_PRIV_H__ */
>>>
>
--
// Casey (she/her)
More information about the U-Boot
mailing list