xref: /linux/arch/arm/boot/compressed/fdt_check_mem_start.c (revision fd7d598270724cc787982ea48bbe17ad383a8b7f)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #include <linux/kernel.h>
4 #include <linux/libfdt.h>
5 #include <linux/sizes.h>
6 #include "misc.h"
7 
8 static const void *get_prop(const void *fdt, const char *node_path,
9 			    const char *property, int minlen)
10 {
11 	const void *prop;
12 	int offset, len;
13 
14 	offset = fdt_path_offset(fdt, node_path);
15 	if (offset < 0)
16 		return NULL;
17 
18 	prop = fdt_getprop(fdt, offset, property, &len);
19 	if (!prop || len < minlen)
20 		return NULL;
21 
22 	return prop;
23 }
24 
25 static uint32_t get_cells(const void *fdt, const char *name)
26 {
27 	const fdt32_t *prop = get_prop(fdt, "/", name, sizeof(fdt32_t));
28 
29 	if (!prop) {
30 		/* default */
31 		return 1;
32 	}
33 
34 	return fdt32_ld(prop);
35 }
36 
37 static uint64_t get_val(const fdt32_t *cells, uint32_t ncells)
38 {
39 	uint64_t r;
40 
41 	r = fdt32_ld(cells);
42 	if (ncells > 1)
43 		r = (r << 32) | fdt32_ld(cells + 1);
44 
45 	return r;
46 }
47 
48 /*
49  * Check the start of physical memory
50  *
51  * Traditionally, the start address of physical memory is obtained by masking
52  * the program counter.  However, this does require that this address is a
53  * multiple of 128 MiB, precluding booting Linux on platforms where this
54  * requirement is not fulfilled.
55  * Hence validate the calculated address against the memory information in the
56  * DTB, and, if out-of-range, replace it by the real start address.
57  * To preserve backwards compatibility (systems reserving a block of memory
58  * at the start of physical memory, kdump, ...), the traditional method is
59  * used if it yields a valid address, unless the "linux,usable-memory-range"
60  * property is present.
61  *
62  * Return value: start address of physical memory to use
63  */
64 uint32_t fdt_check_mem_start(uint32_t mem_start, const void *fdt)
65 {
66 	uint32_t addr_cells, size_cells, usable_base, base;
67 	uint32_t fdt_mem_start = 0xffffffff;
68 	const fdt32_t *usable, *reg, *endp;
69 	uint64_t size, usable_end, end;
70 	const char *type;
71 	int offset, len;
72 
73 	if (!fdt)
74 		return mem_start;
75 
76 	if (fdt_magic(fdt) != FDT_MAGIC)
77 		return mem_start;
78 
79 	/* There may be multiple cells on LPAE platforms */
80 	addr_cells = get_cells(fdt, "#address-cells");
81 	size_cells = get_cells(fdt, "#size-cells");
82 	if (addr_cells > 2 || size_cells > 2)
83 		return mem_start;
84 
85 	/*
86 	 * Usable memory in case of a crash dump kernel
87 	 * This property describes a limitation: memory within this range is
88 	 * only valid when also described through another mechanism
89 	 */
90 	usable = get_prop(fdt, "/chosen", "linux,usable-memory-range",
91 			  (addr_cells + size_cells) * sizeof(fdt32_t));
92 	if (usable) {
93 		size = get_val(usable + addr_cells, size_cells);
94 		if (!size)
95 			return mem_start;
96 
97 		if (addr_cells > 1 && fdt32_ld(usable)) {
98 			/* Outside 32-bit address space */
99 			return mem_start;
100 		}
101 
102 		usable_base = fdt32_ld(usable + addr_cells - 1);
103 		usable_end = usable_base + size;
104 	}
105 
106 	/* Walk all memory nodes and regions */
107 	for (offset = fdt_next_node(fdt, -1, NULL); offset >= 0;
108 	     offset = fdt_next_node(fdt, offset, NULL)) {
109 		type = fdt_getprop(fdt, offset, "device_type", NULL);
110 		if (!type || strcmp(type, "memory"))
111 			continue;
112 
113 		reg = fdt_getprop(fdt, offset, "linux,usable-memory", &len);
114 		if (!reg)
115 			reg = fdt_getprop(fdt, offset, "reg", &len);
116 		if (!reg)
117 			continue;
118 
119 		for (endp = reg + (len / sizeof(fdt32_t));
120 		     endp - reg >= addr_cells + size_cells;
121 		     reg += addr_cells + size_cells) {
122 			size = get_val(reg + addr_cells, size_cells);
123 			if (!size)
124 				continue;
125 
126 			if (addr_cells > 1 && fdt32_ld(reg)) {
127 				/* Outside 32-bit address space, skipping */
128 				continue;
129 			}
130 
131 			base = fdt32_ld(reg + addr_cells - 1);
132 			end = base + size;
133 			if (usable) {
134 				/*
135 				 * Clip to usable range, which takes precedence
136 				 * over mem_start
137 				 */
138 				if (base < usable_base)
139 					base = usable_base;
140 
141 				if (end > usable_end)
142 					end = usable_end;
143 
144 				if (end <= base)
145 					continue;
146 			} else if (mem_start >= base && mem_start < end) {
147 				/* Calculated address is valid, use it */
148 				return mem_start;
149 			}
150 
151 			if (base < fdt_mem_start)
152 				fdt_mem_start = base;
153 		}
154 	}
155 
156 	if (fdt_mem_start == 0xffffffff) {
157 		/* No usable memory found, falling back to default */
158 		return mem_start;
159 	}
160 
161 	/*
162 	 * The calculated address is not usable, or was overridden by the
163 	 * "linux,usable-memory-range" property.
164 	 * Use the lowest usable physical memory address from the DTB instead,
165 	 * and make sure this is a multiple of 2 MiB for phys/virt patching.
166 	 */
167 	return round_up(fdt_mem_start, SZ_2M);
168 }
169