10673cb38SGeert Uytterhoeven // SPDX-License-Identifier: GPL-2.0-only
20673cb38SGeert Uytterhoeven
30673cb38SGeert Uytterhoeven #include <linux/kernel.h>
40673cb38SGeert Uytterhoeven #include <linux/libfdt.h>
50673cb38SGeert Uytterhoeven #include <linux/sizes.h>
6*9d1f3aa6SArnd Bergmann #include "misc.h"
70673cb38SGeert Uytterhoeven
get_prop(const void * fdt,const char * node_path,const char * property,int minlen)80673cb38SGeert Uytterhoeven static const void *get_prop(const void *fdt, const char *node_path,
90673cb38SGeert Uytterhoeven const char *property, int minlen)
100673cb38SGeert Uytterhoeven {
110673cb38SGeert Uytterhoeven const void *prop;
120673cb38SGeert Uytterhoeven int offset, len;
130673cb38SGeert Uytterhoeven
140673cb38SGeert Uytterhoeven offset = fdt_path_offset(fdt, node_path);
150673cb38SGeert Uytterhoeven if (offset < 0)
160673cb38SGeert Uytterhoeven return NULL;
170673cb38SGeert Uytterhoeven
180673cb38SGeert Uytterhoeven prop = fdt_getprop(fdt, offset, property, &len);
190673cb38SGeert Uytterhoeven if (!prop || len < minlen)
200673cb38SGeert Uytterhoeven return NULL;
210673cb38SGeert Uytterhoeven
220673cb38SGeert Uytterhoeven return prop;
230673cb38SGeert Uytterhoeven }
240673cb38SGeert Uytterhoeven
get_cells(const void * fdt,const char * name)250673cb38SGeert Uytterhoeven static uint32_t get_cells(const void *fdt, const char *name)
260673cb38SGeert Uytterhoeven {
270673cb38SGeert Uytterhoeven const fdt32_t *prop = get_prop(fdt, "/", name, sizeof(fdt32_t));
280673cb38SGeert Uytterhoeven
290673cb38SGeert Uytterhoeven if (!prop) {
300673cb38SGeert Uytterhoeven /* default */
310673cb38SGeert Uytterhoeven return 1;
320673cb38SGeert Uytterhoeven }
330673cb38SGeert Uytterhoeven
340673cb38SGeert Uytterhoeven return fdt32_ld(prop);
350673cb38SGeert Uytterhoeven }
360673cb38SGeert Uytterhoeven
get_val(const fdt32_t * cells,uint32_t ncells)370673cb38SGeert Uytterhoeven static uint64_t get_val(const fdt32_t *cells, uint32_t ncells)
380673cb38SGeert Uytterhoeven {
390673cb38SGeert Uytterhoeven uint64_t r;
400673cb38SGeert Uytterhoeven
410673cb38SGeert Uytterhoeven r = fdt32_ld(cells);
420673cb38SGeert Uytterhoeven if (ncells > 1)
430673cb38SGeert Uytterhoeven r = (r << 32) | fdt32_ld(cells + 1);
440673cb38SGeert Uytterhoeven
450673cb38SGeert Uytterhoeven return r;
460673cb38SGeert Uytterhoeven }
470673cb38SGeert Uytterhoeven
480673cb38SGeert Uytterhoeven /*
490673cb38SGeert Uytterhoeven * Check the start of physical memory
500673cb38SGeert Uytterhoeven *
510673cb38SGeert Uytterhoeven * Traditionally, the start address of physical memory is obtained by masking
520673cb38SGeert Uytterhoeven * the program counter. However, this does require that this address is a
530673cb38SGeert Uytterhoeven * multiple of 128 MiB, precluding booting Linux on platforms where this
540673cb38SGeert Uytterhoeven * requirement is not fulfilled.
550673cb38SGeert Uytterhoeven * Hence validate the calculated address against the memory information in the
560673cb38SGeert Uytterhoeven * DTB, and, if out-of-range, replace it by the real start address.
570673cb38SGeert Uytterhoeven * To preserve backwards compatibility (systems reserving a block of memory
580673cb38SGeert Uytterhoeven * at the start of physical memory, kdump, ...), the traditional method is
5948342ae7SGeert Uytterhoeven * used if it yields a valid address, unless the "linux,usable-memory-range"
6048342ae7SGeert Uytterhoeven * property is present.
610673cb38SGeert Uytterhoeven *
620673cb38SGeert Uytterhoeven * Return value: start address of physical memory to use
630673cb38SGeert Uytterhoeven */
fdt_check_mem_start(uint32_t mem_start,const void * fdt)640673cb38SGeert Uytterhoeven uint32_t fdt_check_mem_start(uint32_t mem_start, const void *fdt)
650673cb38SGeert Uytterhoeven {
6648342ae7SGeert Uytterhoeven uint32_t addr_cells, size_cells, usable_base, base;
670673cb38SGeert Uytterhoeven uint32_t fdt_mem_start = 0xffffffff;
6848342ae7SGeert Uytterhoeven const fdt32_t *usable, *reg, *endp;
6948342ae7SGeert Uytterhoeven uint64_t size, usable_end, end;
700673cb38SGeert Uytterhoeven const char *type;
710673cb38SGeert Uytterhoeven int offset, len;
720673cb38SGeert Uytterhoeven
730673cb38SGeert Uytterhoeven if (!fdt)
740673cb38SGeert Uytterhoeven return mem_start;
750673cb38SGeert Uytterhoeven
760673cb38SGeert Uytterhoeven if (fdt_magic(fdt) != FDT_MAGIC)
770673cb38SGeert Uytterhoeven return mem_start;
780673cb38SGeert Uytterhoeven
790673cb38SGeert Uytterhoeven /* There may be multiple cells on LPAE platforms */
800673cb38SGeert Uytterhoeven addr_cells = get_cells(fdt, "#address-cells");
810673cb38SGeert Uytterhoeven size_cells = get_cells(fdt, "#size-cells");
820673cb38SGeert Uytterhoeven if (addr_cells > 2 || size_cells > 2)
830673cb38SGeert Uytterhoeven return mem_start;
840673cb38SGeert Uytterhoeven
8548342ae7SGeert Uytterhoeven /*
8648342ae7SGeert Uytterhoeven * Usable memory in case of a crash dump kernel
8748342ae7SGeert Uytterhoeven * This property describes a limitation: memory within this range is
8848342ae7SGeert Uytterhoeven * only valid when also described through another mechanism
8948342ae7SGeert Uytterhoeven */
9048342ae7SGeert Uytterhoeven usable = get_prop(fdt, "/chosen", "linux,usable-memory-range",
9148342ae7SGeert Uytterhoeven (addr_cells + size_cells) * sizeof(fdt32_t));
9248342ae7SGeert Uytterhoeven if (usable) {
9348342ae7SGeert Uytterhoeven size = get_val(usable + addr_cells, size_cells);
9448342ae7SGeert Uytterhoeven if (!size)
9548342ae7SGeert Uytterhoeven return mem_start;
9648342ae7SGeert Uytterhoeven
9748342ae7SGeert Uytterhoeven if (addr_cells > 1 && fdt32_ld(usable)) {
9848342ae7SGeert Uytterhoeven /* Outside 32-bit address space */
9948342ae7SGeert Uytterhoeven return mem_start;
10048342ae7SGeert Uytterhoeven }
10148342ae7SGeert Uytterhoeven
10248342ae7SGeert Uytterhoeven usable_base = fdt32_ld(usable + addr_cells - 1);
10348342ae7SGeert Uytterhoeven usable_end = usable_base + size;
10448342ae7SGeert Uytterhoeven }
10548342ae7SGeert Uytterhoeven
1060673cb38SGeert Uytterhoeven /* Walk all memory nodes and regions */
1070673cb38SGeert Uytterhoeven for (offset = fdt_next_node(fdt, -1, NULL); offset >= 0;
1080673cb38SGeert Uytterhoeven offset = fdt_next_node(fdt, offset, NULL)) {
1090673cb38SGeert Uytterhoeven type = fdt_getprop(fdt, offset, "device_type", NULL);
1100673cb38SGeert Uytterhoeven if (!type || strcmp(type, "memory"))
1110673cb38SGeert Uytterhoeven continue;
1120673cb38SGeert Uytterhoeven
1130673cb38SGeert Uytterhoeven reg = fdt_getprop(fdt, offset, "linux,usable-memory", &len);
1140673cb38SGeert Uytterhoeven if (!reg)
1150673cb38SGeert Uytterhoeven reg = fdt_getprop(fdt, offset, "reg", &len);
1160673cb38SGeert Uytterhoeven if (!reg)
1170673cb38SGeert Uytterhoeven continue;
1180673cb38SGeert Uytterhoeven
1190673cb38SGeert Uytterhoeven for (endp = reg + (len / sizeof(fdt32_t));
1200673cb38SGeert Uytterhoeven endp - reg >= addr_cells + size_cells;
1210673cb38SGeert Uytterhoeven reg += addr_cells + size_cells) {
1220673cb38SGeert Uytterhoeven size = get_val(reg + addr_cells, size_cells);
1230673cb38SGeert Uytterhoeven if (!size)
1240673cb38SGeert Uytterhoeven continue;
1250673cb38SGeert Uytterhoeven
1260673cb38SGeert Uytterhoeven if (addr_cells > 1 && fdt32_ld(reg)) {
1270673cb38SGeert Uytterhoeven /* Outside 32-bit address space, skipping */
1280673cb38SGeert Uytterhoeven continue;
1290673cb38SGeert Uytterhoeven }
1300673cb38SGeert Uytterhoeven
1310673cb38SGeert Uytterhoeven base = fdt32_ld(reg + addr_cells - 1);
1320673cb38SGeert Uytterhoeven end = base + size;
13348342ae7SGeert Uytterhoeven if (usable) {
13448342ae7SGeert Uytterhoeven /*
13548342ae7SGeert Uytterhoeven * Clip to usable range, which takes precedence
13648342ae7SGeert Uytterhoeven * over mem_start
13748342ae7SGeert Uytterhoeven */
13848342ae7SGeert Uytterhoeven if (base < usable_base)
13948342ae7SGeert Uytterhoeven base = usable_base;
14048342ae7SGeert Uytterhoeven
14148342ae7SGeert Uytterhoeven if (end > usable_end)
14248342ae7SGeert Uytterhoeven end = usable_end;
14348342ae7SGeert Uytterhoeven
14448342ae7SGeert Uytterhoeven if (end <= base)
14548342ae7SGeert Uytterhoeven continue;
14648342ae7SGeert Uytterhoeven } else if (mem_start >= base && mem_start < end) {
1470673cb38SGeert Uytterhoeven /* Calculated address is valid, use it */
1480673cb38SGeert Uytterhoeven return mem_start;
1490673cb38SGeert Uytterhoeven }
1500673cb38SGeert Uytterhoeven
1510673cb38SGeert Uytterhoeven if (base < fdt_mem_start)
1520673cb38SGeert Uytterhoeven fdt_mem_start = base;
1530673cb38SGeert Uytterhoeven }
1540673cb38SGeert Uytterhoeven }
1550673cb38SGeert Uytterhoeven
1560673cb38SGeert Uytterhoeven if (fdt_mem_start == 0xffffffff) {
1570673cb38SGeert Uytterhoeven /* No usable memory found, falling back to default */
1580673cb38SGeert Uytterhoeven return mem_start;
1590673cb38SGeert Uytterhoeven }
1600673cb38SGeert Uytterhoeven
1610673cb38SGeert Uytterhoeven /*
16248342ae7SGeert Uytterhoeven * The calculated address is not usable, or was overridden by the
16348342ae7SGeert Uytterhoeven * "linux,usable-memory-range" property.
1640673cb38SGeert Uytterhoeven * Use the lowest usable physical memory address from the DTB instead,
1650673cb38SGeert Uytterhoeven * and make sure this is a multiple of 2 MiB for phys/virt patching.
1660673cb38SGeert Uytterhoeven */
1670673cb38SGeert Uytterhoeven return round_up(fdt_mem_start, SZ_2M);
1680673cb38SGeert Uytterhoeven }
169