// 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); }