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