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