Loading...
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
8static 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
25static 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
37static 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 */
64uint32_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}
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
7static 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
24static 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
36static 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 * always used if it yields a valid address.
59 *
60 * Return value: start address of physical memory to use
61 */
62uint32_t fdt_check_mem_start(uint32_t mem_start, const void *fdt)
63{
64 uint32_t addr_cells, size_cells, base;
65 uint32_t fdt_mem_start = 0xffffffff;
66 const fdt32_t *reg, *endp;
67 uint64_t size, end;
68 const char *type;
69 int offset, len;
70
71 if (!fdt)
72 return mem_start;
73
74 if (fdt_magic(fdt) != FDT_MAGIC)
75 return mem_start;
76
77 /* There may be multiple cells on LPAE platforms */
78 addr_cells = get_cells(fdt, "#address-cells");
79 size_cells = get_cells(fdt, "#size-cells");
80 if (addr_cells > 2 || size_cells > 2)
81 return mem_start;
82
83 /* Walk all memory nodes and regions */
84 for (offset = fdt_next_node(fdt, -1, NULL); offset >= 0;
85 offset = fdt_next_node(fdt, offset, NULL)) {
86 type = fdt_getprop(fdt, offset, "device_type", NULL);
87 if (!type || strcmp(type, "memory"))
88 continue;
89
90 reg = fdt_getprop(fdt, offset, "linux,usable-memory", &len);
91 if (!reg)
92 reg = fdt_getprop(fdt, offset, "reg", &len);
93 if (!reg)
94 continue;
95
96 for (endp = reg + (len / sizeof(fdt32_t));
97 endp - reg >= addr_cells + size_cells;
98 reg += addr_cells + size_cells) {
99 size = get_val(reg + addr_cells, size_cells);
100 if (!size)
101 continue;
102
103 if (addr_cells > 1 && fdt32_ld(reg)) {
104 /* Outside 32-bit address space, skipping */
105 continue;
106 }
107
108 base = fdt32_ld(reg + addr_cells - 1);
109 end = base + size;
110 if (mem_start >= base && mem_start < end) {
111 /* Calculated address is valid, use it */
112 return mem_start;
113 }
114
115 if (base < fdt_mem_start)
116 fdt_mem_start = base;
117 }
118 }
119
120 if (fdt_mem_start == 0xffffffff) {
121 /* No usable memory found, falling back to default */
122 return mem_start;
123 }
124
125 /*
126 * The calculated address is not usable.
127 * Use the lowest usable physical memory address from the DTB instead,
128 * and make sure this is a multiple of 2 MiB for phys/virt patching.
129 */
130 return round_up(fdt_mem_start, SZ_2M);
131}