root/arch/arm/boot/compressed/fdt_check_mem_start.c
// SPDX-License-Identifier: GPL-2.0-only

#include <linux/kernel.h>
#include <linux/libfdt.h>
#include <linux/sizes.h>
#include "misc.h"

static const void *get_prop(const void *fdt, const char *node_path,
                            const char *property, int minlen)
{
        const void *prop;
        int offset, len;

        offset = fdt_path_offset(fdt, node_path);
        if (offset < 0)
                return NULL;

        prop = fdt_getprop(fdt, offset, property, &len);
        if (!prop || len < minlen)
                return NULL;

        return prop;
}

static uint32_t get_cells(const void *fdt, const char *name)
{
        const fdt32_t *prop = get_prop(fdt, "/", name, sizeof(fdt32_t));

        if (!prop) {
                /* default */
                return 1;
        }

        return fdt32_ld(prop);
}

static uint64_t get_val(const fdt32_t *cells, uint32_t ncells)
{
        uint64_t r;

        r = fdt32_ld(cells);
        if (ncells > 1)
                r = (r << 32) | fdt32_ld(cells + 1);

        return r;
}

/*
 * Check the start of physical memory
 *
 * Traditionally, the start address of physical memory is obtained by masking
 * the program counter.  However, this does require that this address is a
 * multiple of 128 MiB, precluding booting Linux on platforms where this
 * requirement is not fulfilled.
 * Hence validate the calculated address against the memory information in the
 * DTB, and, if out-of-range, replace it by the real start address.
 * To preserve backwards compatibility (systems reserving a block of memory
 * at the start of physical memory, kdump, ...), the traditional method is
 * used if it yields a valid address, unless the "linux,usable-memory-range"
 * property is present.
 *
 * Return value: start address of physical memory to use
 */
uint32_t fdt_check_mem_start(uint32_t mem_start, const void *fdt)
{
        uint32_t addr_cells, size_cells, usable_base, base;
        uint32_t fdt_mem_start = 0xffffffff;
        const fdt32_t *usable, *reg, *endp;
        uint64_t size, usable_end, end;
        const char *type;
        int offset, len;

        if (!fdt)
                return mem_start;

        if (fdt_magic(fdt) != FDT_MAGIC)
                return mem_start;

        /* There may be multiple cells on LPAE platforms */
        addr_cells = get_cells(fdt, "#address-cells");
        size_cells = get_cells(fdt, "#size-cells");
        if (addr_cells > 2 || size_cells > 2)
                return mem_start;

        /*
         * Usable memory in case of a crash dump kernel
         * This property describes a limitation: memory within this range is
         * only valid when also described through another mechanism
         */
        usable = get_prop(fdt, "/chosen", "linux,usable-memory-range",
                          (addr_cells + size_cells) * sizeof(fdt32_t));
        if (usable) {
                size = get_val(usable + addr_cells, size_cells);
                if (!size)
                        return mem_start;

                if (addr_cells > 1 && fdt32_ld(usable)) {
                        /* Outside 32-bit address space */
                        return mem_start;
                }

                usable_base = fdt32_ld(usable + addr_cells - 1);
                usable_end = usable_base + size;
        }

        /* Walk all memory nodes and regions */
        for (offset = fdt_next_node(fdt, -1, NULL); offset >= 0;
             offset = fdt_next_node(fdt, offset, NULL)) {
                type = fdt_getprop(fdt, offset, "device_type", NULL);
                if (!type || strcmp(type, "memory"))
                        continue;

                reg = fdt_getprop(fdt, offset, "linux,usable-memory", &len);
                if (!reg)
                        reg = fdt_getprop(fdt, offset, "reg", &len);
                if (!reg)
                        continue;

                for (endp = reg + (len / sizeof(fdt32_t));
                     endp - reg >= addr_cells + size_cells;
                     reg += addr_cells + size_cells) {
                        size = get_val(reg + addr_cells, size_cells);
                        if (!size)
                                continue;

                        if (addr_cells > 1 && fdt32_ld(reg)) {
                                /* Outside 32-bit address space, skipping */
                                continue;
                        }

                        base = fdt32_ld(reg + addr_cells - 1);
                        end = base + size;
                        if (usable) {
                                /*
                                 * Clip to usable range, which takes precedence
                                 * over mem_start
                                 */
                                if (base < usable_base)
                                        base = usable_base;

                                if (end > usable_end)
                                        end = usable_end;

                                if (end <= base)
                                        continue;
                        } else if (mem_start >= base && mem_start < end) {
                                /* Calculated address is valid, use it */
                                return mem_start;
                        }

                        if (base < fdt_mem_start)
                                fdt_mem_start = base;
                }
        }

        if (fdt_mem_start == 0xffffffff) {
                /* No usable memory found, falling back to default */
                return mem_start;
        }

        /*
         * The calculated address is not usable, or was overridden by the
         * "linux,usable-memory-range" property.
         * Use the lowest usable physical memory address from the DTB instead,
         * and make sure this is a multiple of 2 MiB for phys/virt patching.
         */
        return round_up(fdt_mem_start, SZ_2M);
}