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