1 /*
2 * Copyright (c) 2020 Travis Geiselbrecht
3 *
4 * Use of this source code is governed by a MIT-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/MIT
7 */
8 #include <lib/fdtwalk.h>
9
10 #include <assert.h>
11 #include <libfdt.h>
12 #include <lk/cpp.h>
13 #include <lk/err.h>
14 #include <lk/trace.h>
15 #include <stdio.h>
16 #include <sys/types.h>
17
18 #define LOCAL_TRACE 0
19
20 namespace {
21
22 const int MAX_DEPTH = 16;
23
24 /* read the #address-cells and #size-cells properties at the current node to
25 * see if there are any overriding sizes at this level. It's okay to not
26 * find the properties.
27 */
read_address_size_cells(const void * fdt,int offset,int depth,uint32_t * address_cells,uint32_t * size_cells)28 void read_address_size_cells(const void *fdt, int offset, int depth,
29 uint32_t *address_cells, uint32_t *size_cells) {
30 LTRACEF_LEVEL(3, "fdt %p, offset %d depth %d\n", fdt, offset, depth);
31
32 DEBUG_ASSERT(depth >= 0 && depth < MAX_DEPTH);
33
34 int len;
35 const void *prop_ptr = fdt_getprop(fdt, offset, "#address-cells", &len);
36 LTRACEF_LEVEL(3, "%p, len %d\n", prop_ptr, len);
37 if (prop_ptr && len == 4) {
38 address_cells[depth] = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
39 }
40
41 prop_ptr = fdt_getprop(fdt, offset, "#size-cells", &len);
42 LTRACEF_LEVEL(3, "%p, len %d\n", prop_ptr, len);
43 if (prop_ptr && len == 4) {
44 size_cells[depth] = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
45 }
46
47 LTRACEF_LEVEL(3, "address-cells %u size-cells %u\n", address_cells[depth], size_cells[depth]);
48 }
49
read_base_len_pair(const uint8_t * prop_ptr,size_t prop_len,size_t address_cell_size,size_t size_cell_size,uint64_t * base,uint64_t * len)50 status_t read_base_len_pair(const uint8_t *prop_ptr, size_t prop_len,
51 size_t address_cell_size, size_t size_cell_size,
52 uint64_t *base, uint64_t *len) {
53 *base = 0;
54 *len = 0;
55
56 /* we're looking at a memory descriptor */
57 if (address_cell_size == 2 && prop_len >= 8) {
58 *base = fdt64_to_cpu(*(const uint64_t *)prop_ptr);
59 prop_ptr += 8;
60 prop_len -= 8;
61 } else if (address_cell_size == 1 && prop_len >= 4) {
62 *base = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
63 prop_ptr += 4;
64 prop_len -= 4;
65 } else {
66 return ERR_NOT_IMPLEMENTED;
67 }
68
69 if (size_cell_size == 2 && prop_len >= 8) {
70 *len = fdt64_to_cpu(*((const uint64_t *)prop_ptr));
71 prop_ptr += 8;
72 prop_len -= 8;
73 } else if (size_cell_size == 1 && prop_len >= 4) {
74 *len = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
75 prop_ptr += 4;
76 prop_len -= 4;
77 } else {
78 return ERR_NOT_IMPLEMENTED;
79 }
80
81 return NO_ERROR;
82 }
83
84 // returns true or false if a particular property is a particular value
check_prop_is_val_string(const void * fdt,int offset,const char * prop,const char * val)85 bool check_prop_is_val_string(const void *fdt, int offset, const char *prop, const char *val) {
86 int lenp;
87 const uint8_t *prop_ptr = static_cast<const uint8_t *>(fdt_getprop(fdt, offset, prop, &lenp));
88 if (!prop_ptr || lenp <= 0) {
89 return false;
90 }
91
92 if (strncmp(val, reinterpret_cast<const char *>(prop_ptr), strlen(val)) == 0) {
93 return true;
94 }
95
96 return false;
97 }
98
get_prop_string(const void * fdt,int offset,const char * prop)99 const char *get_prop_string(const void *fdt, int offset, const char *prop) {
100 int lenp;
101 const uint8_t *prop_ptr = static_cast<const uint8_t *>(fdt_getprop(fdt, offset, prop, &lenp));
102 if (!prop_ptr || lenp <= 0) {
103 return nullptr;
104 }
105
106 // check to see that it appears to be null terminated
107 auto str = reinterpret_cast<const char *>(prop_ptr);
108 if (str[lenp-1] != '\0') {
109 return nullptr;
110 }
111
112 // seems safe
113 return str;
114 }
115
116 struct fdt_walk_state {
117 const void *fdt;
118 int offset;
119 int depth;
120 uint32_t address_cells[MAX_DEPTH];
121 uint32_t size_cells[MAX_DEPTH];
122
curr_address_cell__anon0d0f43e60111::fdt_walk_state123 uint32_t curr_address_cell() const { return address_cells[depth]; }
curr_size_cell__anon0d0f43e60111::fdt_walk_state124 uint32_t curr_size_cell() const { return size_cells[depth]; }
125 };
126
127 // Inner page table walker routine. Takes a callback in the form of a function or lambda
128 // and calls on every node in the tree.
129 template <typename callback>
_fdt_walk(const void * fdt,callback cb)130 status_t _fdt_walk(const void *fdt, callback cb) {
131 int err = fdt_check_header(fdt);
132 if (err != 0) {
133 return ERR_NOT_FOUND;
134 }
135
136 /* walk the nodes */
137 fdt_walk_state state = {};
138 state.fdt = fdt;
139
140 /* read the address/size cells properties at the root, if present */
141 state.address_cells[0] = 2;
142 state.size_cells[0] = 1;
143 read_address_size_cells(fdt, state.offset, 0, state.address_cells, state.size_cells);
144
145 for (;;) {
146 state.offset = fdt_next_node(fdt, state.offset, &state.depth);
147 if (state.offset < 0 || state.depth < 0) {
148 break;
149 }
150
151 LTRACEF_LEVEL(3, "fdt_next node offset %d, depth %d\n", state.offset, state.depth);
152
153 if (state.depth >= MAX_DEPTH) {
154 printf("FDTWALK: exceeded max depth %d\n", MAX_DEPTH);
155 return ERR_NO_MEMORY;
156 }
157
158 // TODO: fix the way address and size cells are inherited, they're not exactly correct
159 // here.
160
161 /* copy the address/size cells from the parent depth and then see if we
162 * have local properties to override it. */
163 if (state.depth > 0) {
164 state.address_cells[state.depth] = state.address_cells[state.depth - 1];
165 state.size_cells[state.depth] = state.size_cells[state.depth - 1];
166 }
167 read_address_size_cells(fdt, state.offset, state.depth, state.address_cells, state.size_cells);
168
169 /* get the name */
170 const char *name = fdt_get_name(fdt, state.offset, NULL);
171 if (!name)
172 continue;
173
174 LTRACEF_LEVEL(2, "name '%s', depth %d, address cells %u, size cells %u\n",
175 name, state.depth, state.address_cells[state.depth], state.size_cells[state.depth]);
176
177 // Callback
178 cb(state, name);
179 }
180
181 return NO_ERROR;
182 }
183
184 } // anonymous namespace
185
fdt_walk_dump(const void * fdt)186 status_t fdt_walk_dump(const void *fdt) {
187 auto cb = [](const fdt_walk_state &state, const char *name) {
188 for (auto i = 0; i < state.depth; i++) {
189 printf(" ");
190 }
191 printf("offset %d depth %d acells %u scells %u name '%s'\n", state.offset, state.depth,
192 state.curr_address_cell(), state.curr_size_cell(), name);
193 };
194
195 printf("FDT dump: address %p total size %#x\n", fdt, fdt_totalsize(fdt));
196
197 return _fdt_walk(fdt, cb);
198 }
199
fdt_walk_find_cpus(const void * fdt,struct fdt_walk_cpu_info * cpu,size_t * cpu_count)200 status_t fdt_walk_find_cpus(const void *fdt, struct fdt_walk_cpu_info *cpu, size_t *cpu_count) {
201 const size_t max_cpu_count = *cpu_count;
202 *cpu_count = 0;
203
204 auto walker = [max_cpu_count, cpu, cpu_count](const fdt_walk_state &state, const char *name) {
205 /* look for a cpu leaf and count the number of cpus */
206 if (*cpu_count < max_cpu_count && strncmp(name, "cpu@", 4) == 0 && state.depth == 2) {
207 int lenp;
208 const uint8_t *prop_ptr = (const uint8_t *)fdt_getprop(state.fdt, state.offset, "reg", &lenp);
209 LTRACEF("%p, lenp %u\n", prop_ptr, lenp);
210 if (prop_ptr) {
211 LTRACEF_LEVEL(2, "found '%s' reg prop len %d, ac %u, sc %u\n", name, lenp,
212 state.curr_address_cell(), state.curr_size_cell());
213 uint32_t id = 0;
214 if (state.curr_address_cell() == 1 && lenp >= 4) {
215 id = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
216 prop_ptr += 4;
217 lenp -= 4;
218 } else {
219 PANIC_UNIMPLEMENTED;
220 }
221
222 // is it disabled?
223 if (check_prop_is_val_string(state.fdt, state.offset, "status", "disabled")) {
224 LTRACEF("cpu id %#x is disabled, skipping...\n", id);
225 return;
226 }
227
228 // clear the cpu state, we're about to write down some information about it
229 cpu[*cpu_count] = {};
230
231 #if ARCH_RISCV
232 // look for riscv,isa and riscv,isa-extensions
233 auto isa_string = get_prop_string(state.fdt, state.offset, "riscv,isa");
234 if (isa_string) {
235 cpu[*cpu_count].isa_string = isa_string;
236 }
237
238 auto isa_extensions_string = get_prop_string(state.fdt, state.offset, "riscv,isa-extensions");
239 if (isa_extensions_string) {
240 cpu[*cpu_count].isa_extensions_string = isa_extensions_string;
241 }
242 #endif
243
244 // cpu is found
245 LTRACEF("found cpu id %u\n", id);
246 cpu[*cpu_count].id = id;
247 (*cpu_count)++;
248 }
249 }
250 };
251
252 return _fdt_walk(fdt, walker);
253 }
254
fdt_walk_find_memory(const void * fdt,struct fdt_walk_memory_region * memory,size_t * mem_count,struct fdt_walk_memory_region * reserved_memory,size_t * reserved_mem_count)255 status_t fdt_walk_find_memory(const void *fdt, struct fdt_walk_memory_region *memory, size_t *mem_count,
256 struct fdt_walk_memory_region *reserved_memory, size_t *reserved_mem_count) {
257 /* if >= 0, we're inside /reserved-memory */
258 int reserved_memory_depth = -1;
259 const size_t max_memory_index = *mem_count;
260 const size_t max_reserved_index = *reserved_mem_count;
261 *mem_count = *reserved_mem_count = 0;
262
263 auto walker = [&](const fdt_walk_state &state, const char *name) {
264 int err;
265
266 /* look for the 'memory@*' property */
267 if (memory && *mem_count < max_memory_index) {
268 if (strncmp(name, "memory@", 7) == 0 && state.depth == 1) {
269 int lenp;
270 const uint8_t *prop_ptr = (const uint8_t *)fdt_getprop(state.fdt, state.offset, "reg", &lenp);
271 if (prop_ptr) {
272 LTRACEF_LEVEL(2, "found '%s' reg prop len %d, ac %u, sc %u\n", name, lenp,
273 state.curr_address_cell(), state.curr_size_cell());
274 /* we're looking at a memory descriptor */
275 uint64_t base;
276 uint64_t len;
277 err = read_base_len_pair(prop_ptr, lenp, state.curr_address_cell(), state.curr_size_cell(), &base, &len);
278 if (err != NO_ERROR) {
279 TRACEF("error reading base/length from memory@ node\n");
280 /* continue on */
281 } else {
282 LTRACEF("mem base %#llx len %#llx\n", base, len);
283 memory[*mem_count].base = base;
284 memory[*mem_count].len = len;
285 (*mem_count)++;
286 }
287 }
288 }
289 }
290
291 /* look for the 'reserved-memory' tree */
292 if (reserved_memory && *reserved_mem_count < max_reserved_index) {
293 /* once we see the reserved-memory first level node, track that we are inside
294 * it until we step out to a node at the same or higher depth.
295 */
296 if (strncmp(name, "reserved-memory", 15) == 0 && state.depth == 1) {
297 LTRACEF_LEVEL(2, "found reserved memory node\n");
298
299 reserved_memory_depth = state.depth;
300 } else if (reserved_memory_depth >= 0) {
301 if (state.depth <= reserved_memory_depth) {
302 /* we have exited the reserved memory tree, so clear our tracking depth */
303 LTRACEF_LEVEL(2, "exiting reserved memory node\n");
304 reserved_memory_depth = -1;
305 } else {
306 /* if we're inside the reserved meory tree, so this node must
307 * be a reserved memory region */
308 int lenp;
309 const uint8_t *prop_ptr = (const uint8_t *)fdt_getprop(state.fdt, state.offset, "reg", &lenp);
310 if (prop_ptr) {
311 LTRACEF_LEVEL(2, "found '%s' reg prop len %d, ac %u, sc %u\n", name, lenp,
312 state.curr_address_cell(), state.curr_size_cell());
313 /* we're looking at a memory descriptor */
314 uint64_t base;
315 uint64_t len;
316 err = read_base_len_pair(prop_ptr, lenp, state.curr_address_cell(), state.curr_size_cell(), &base, &len);
317 if (err != NO_ERROR) {
318 TRACEF("error reading base/length from reserved-memory node\n");
319 /* continue on */
320 } else {
321 LTRACEF("reserved memory base %#llx len %#llx\n", base, len);
322 reserved_memory[*reserved_mem_count].base = base;
323 reserved_memory[*reserved_mem_count].len = len;
324 (*reserved_mem_count)++;
325 }
326 }
327 }
328 }
329 }
330 };
331
332 return _fdt_walk(fdt, walker);
333 }
334
fdt_walk_find_pcie_info(const void * fdt,struct fdt_walk_pcie_info * info,size_t * count)335 status_t fdt_walk_find_pcie_info(const void *fdt, struct fdt_walk_pcie_info *info, size_t *count) {
336 size_t info_len = *count;
337 *count = 0;
338 auto walker = [info, info_len, &count](const fdt_walk_state &state, const char *name) {
339 /* look for a pcie leaf and pass the address of the ecam and other info to the callback */
340 if (*count < info_len && (strncmp(name, "pcie@", 5) == 0 || strncmp(name, "pci@", 4) == 0)) {
341 int lenp;
342
343 /* check the status, is it disabled? */
344 const uint8_t *prop_ptr = (const uint8_t *)fdt_getprop(state.fdt, state.offset, "status", &lenp);
345 if (prop_ptr) {
346 if (fdt_stringlist_contains((const char *)prop_ptr, lenp, "disabled")) {
347 LTRACEF("found disabled pci node\n");
348 return;
349 }
350 }
351
352
353 /* find the range of the ecam */
354 prop_ptr = (const uint8_t *)fdt_getprop(state.fdt, state.offset, "reg", &lenp);
355 LTRACEF("%p, lenp %u\n", prop_ptr, lenp);
356 if (prop_ptr) {
357 LTRACEF_LEVEL(2, "found '%s' prop 'reg' len %d, ac %u, sc %u\n", name, lenp,
358 state.curr_address_cell(), state.curr_size_cell());
359
360 /* seems to always be full address cells 2, size cells 2, despite it being 3/2 */
361 info[*count].ecam_base = fdt64_to_cpu(*(const uint64_t *)prop_ptr);
362 prop_ptr += 8;
363 info[*count].ecam_len = fdt64_to_cpu(*(const uint64_t *)prop_ptr);
364 }
365
366 /* find which bus range the ecam covers */
367 prop_ptr = (const uint8_t *)fdt_getprop(state.fdt, state.offset, "bus-range", &lenp);
368 LTRACEF("%p, lenp %u\n", prop_ptr, lenp);
369 if (prop_ptr) {
370 LTRACEF_LEVEL(2, "found '%s' prop 'bus-range' len %d, ac %u, sc %u\n", name, lenp,
371 state.curr_address_cell(), state.curr_size_cell());
372
373 if (lenp == 8) {
374 info[*count].bus_start = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
375 prop_ptr += 4;
376 info[*count].bus_end = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
377 }
378 }
379
380 prop_ptr = (const uint8_t *)fdt_getprop(state.fdt, state.offset, "ranges", &lenp);
381 LTRACEF("%p, lenp %u\n", prop_ptr, lenp);
382 if (prop_ptr) {
383 LTRACEF_LEVEL(2, "found '%s' prop 'ranges' len %d, ac %u, sc %u\n", name, lenp,
384 state.curr_address_cell(), state.curr_size_cell());
385
386 /* iterate this packed property */
387 const uint8_t *prop_end = prop_ptr + lenp;
388 while (prop_ptr < prop_end) {
389 uint32_t type = fdt32_to_cpu(*(const uint32_t *)(prop_ptr));
390 prop_ptr += 4;
391
392 /* read 3 64bit values */
393 uint64_t base1, base2, size;
394 base1 = fdt64_to_cpu(*(const uint64_t *)prop_ptr);
395 prop_ptr += 8;
396 base2 = fdt64_to_cpu(*(const uint64_t *)prop_ptr);
397 prop_ptr += 8;
398 size = fdt64_to_cpu(*(const uint64_t *)prop_ptr);
399 prop_ptr += 8;
400
401 switch (type) {
402 case 0x1000000: // io range
403 LTRACEF_LEVEL(2, "io range\n");
404 info[*count].io_base = base1;
405 info[*count].io_base_mmio = base2;
406 info[*count].io_len = size;
407 break;
408 case 0x2000000: // mmio range
409 LTRACEF_LEVEL(2, "mmio range\n");
410 info[*count].mmio_base = base1;
411 info[*count].mmio_len = size;
412 break;
413 case 0x3000000: // mmio range (64bit)
414 LTRACEF_LEVEL(2, "mmio range (64bit)\n");
415 info[*count].mmio64_base = base1;
416 info[*count].mmio64_len = size;
417 break;
418 default:
419 LTRACEF_LEVEL(2, "unhandled type %#x\n", type);
420 }
421
422 LTRACEF_LEVEL(2, "base %#llx base2 %#llx size %#llx\n", base1, base2, size);
423 }
424 }
425
426 (*count)++;
427 }
428 };
429
430 return _fdt_walk(fdt, walker);
431 }
432
433