xref: /openbmc/linux/arch/arm/boot/compressed/fdt_check_mem_start.c (revision 2612e3bbc0386368a850140a6c9b990cd496a5ec)
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