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