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 <stdio.h>
13 #include <lk/err.h>
14 #include <lk/trace.h>
15 #include <sys/types.h>
16 
17 #define LOCAL_TRACE 0
18 #define MAX_DEPTH 16
19 
20 /* read the #address-cells and #size-cells properties at the current node to
21  * see if there are any overriding sizes at this level. It's okay to not
22  * find the properties.
23  */
read_address_size_cells(const void * fdt,int offset,int depth,uint32_t * address_cells,uint32_t * size_cells)24 static void read_address_size_cells(const void *fdt, int offset, int depth,
25                                     uint32_t *address_cells, uint32_t *size_cells) {
26     LTRACEF_LEVEL(3, "fdt %p, offset %d depth %d\n", fdt, offset, depth);
27 
28     DEBUG_ASSERT(depth >= 0 && depth < MAX_DEPTH);
29 
30     int len;
31     const void *prop_ptr = fdt_getprop(fdt, offset, "#address-cells", &len);
32     LTRACEF_LEVEL(3, "%p, len %d\n", prop_ptr, len);
33     if (prop_ptr && len == 4) {
34         address_cells[depth] = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
35     }
36 
37     prop_ptr = fdt_getprop(fdt, offset, "#size-cells", &len);
38     LTRACEF_LEVEL(3, "%p, len %d\n", prop_ptr, len);
39     if (prop_ptr && len == 4) {
40         size_cells[depth] = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
41     }
42 
43     LTRACEF_LEVEL(3, "address-cells %u size-cells %u\n", address_cells[depth], size_cells[depth]);
44 }
45 
fdt_walk(const void * fdt,const struct fdt_walk_callbacks * cb)46 status_t fdt_walk(const void *fdt, const struct fdt_walk_callbacks *cb) {
47     int err = fdt_check_header(fdt);
48     if (err != 0) {
49         return ERR_NOT_FOUND;
50     }
51 
52     /* walk the nodes */
53     int depth = 0;
54     int offset = 0;
55     uint32_t address_cells[MAX_DEPTH];
56     uint32_t size_cells[MAX_DEPTH];
57 
58     /* read the address/size cells properties at the root, if present */
59     address_cells[0] = size_cells[0] = 1;
60     read_address_size_cells(fdt, offset, 0, address_cells, size_cells);
61 
62     for (;;) {
63         offset = fdt_next_node(fdt, offset, &depth);
64         if (offset < 0 || depth < 0) {
65             break;
66         }
67 
68         LTRACEF_LEVEL(3, "fdt_next node offset %d, depth %d\n", offset, depth);
69 
70         if (depth >= MAX_DEPTH) {
71             printf("FDTWALK: exceeded max depth %d\n", MAX_DEPTH);
72             return ERR_NO_MEMORY;
73         }
74 
75         /* copy the address/size cells from the parent depth and then see if we
76          * have local properties to override it. */
77         if (depth > 0) {
78             address_cells[depth] = address_cells[depth - 1];
79             size_cells[depth] = size_cells[depth - 1];
80         }
81         read_address_size_cells(fdt, offset, depth, address_cells, size_cells);
82 
83         /* get the name */
84         const char *name = fdt_get_name(fdt, offset, NULL);
85         if (!name)
86             continue;
87 
88         LTRACEF_LEVEL(2, "name '%s', depth %d, address cells %u, size cells %u\n",
89                       name, depth, address_cells[depth], size_cells[depth]);
90 
91         /* look for the 'memory@*' property */
92         if (strncmp(name, "memory@", 7) == 0 && depth == 1) {
93             int lenp;
94             const uint8_t *prop_ptr = fdt_getprop(fdt, offset, "reg", &lenp);
95             if (prop_ptr) {
96                 LTRACEF_LEVEL(2, "found '%s' reg prop len %d, ac %u, sc %u\n", name, lenp,
97                               address_cells[depth], size_cells[depth]);
98                 /* we're looking at a memory descriptor */
99                 uint64_t base = 0;
100                 uint64_t len = 0;
101                 if (address_cells[depth] == 2 && lenp >= 8) {
102                     base = fdt64_to_cpu(*(const uint64_t *)prop_ptr);
103                     prop_ptr += 8;
104                     lenp -= 8;
105                 } else {
106                     PANIC_UNIMPLEMENTED;
107                 }
108                 if (size_cells[depth] == 2 && lenp >= 8) {
109                     len = fdt64_to_cpu(*((const uint64_t *)prop_ptr));
110                     prop_ptr += 8;
111                     lenp -= 8;
112                 } else {
113                     PANIC_UNIMPLEMENTED;
114                 }
115 
116                 if (cb->mem) {
117                     LTRACEF("calling mem callback with base %#llx len %#llx\n", base, len);
118                     cb->mem(base, len, cb->memcookie);
119                 }
120             }
121         }
122 
123         /* look for a cpu leaf and count the number of cpus */
124         if (strncmp(name, "cpu@", 4) == 0 && depth == 2) {
125             int lenp;
126             const uint8_t *prop_ptr = fdt_getprop(fdt, offset, "reg", &lenp);
127             LTRACEF("%p, lenp %u\n", prop_ptr, lenp);
128             if (prop_ptr) {
129                 LTRACEF_LEVEL(2, "found '%s' reg prop len %d, ac %u, sc %u\n", name, lenp,
130                               address_cells[depth], size_cells[depth]);
131                 uint32_t id = 0;
132                 if (address_cells[depth] == 1 && lenp >= 4) {
133                     id = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
134                     prop_ptr += 4;
135                     lenp -= 4;
136                 } else {
137                     PANIC_UNIMPLEMENTED;
138                 }
139 
140                 if (cb->cpu) {
141                     LTRACEF("calling cpu callback with id %#x\n", id);
142                     cb->cpu(id, cb->cpucookie);
143                 }
144             }
145         }
146 
147         /* look for a pcie leaf and pass the address of the ecam to the callback */
148         if (strncmp(name, "pcie@", 5) == 0 || strncmp(name, "pci@", 4) == 0) {
149             uint64_t ecam_base, ecam_size;
150             uint8_t bus_start, bus_end;
151             ecam_base = ecam_size = bus_start = bus_end = 0;
152 
153             int lenp;
154             const uint8_t *prop_ptr = fdt_getprop(fdt, offset, "reg", &lenp);
155             LTRACEF("%p, lenp %u\n", prop_ptr, lenp);
156             if (prop_ptr) {
157                 LTRACEF_LEVEL(2, "found '%s' reg prop len %d, ac %u, sc %u\n", name, lenp,
158                               address_cells[depth], size_cells[depth]);
159 
160                 /* seems to always be full address cells 2, size cells 2, despite it being 3/2 */
161                 ecam_base = fdt64_to_cpu(*(const uint64_t *)prop_ptr);
162                 prop_ptr += 8;
163                 ecam_size = fdt64_to_cpu(*(const uint64_t *)prop_ptr);
164             }
165 
166             prop_ptr = fdt_getprop(fdt, offset, "bus-range", &lenp);
167             LTRACEF("%p, lenp %u\n", prop_ptr, lenp);
168             if (prop_ptr) {
169                 LTRACEF_LEVEL(2, "found '%s' bus-range prop len %d, ac %u, sc %u\n", name, lenp,
170                               address_cells[depth], size_cells[depth]);
171 
172                 if (lenp == 8) {
173                     bus_start = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
174                     prop_ptr += 4;
175                     bus_end = fdt32_to_cpu(*(const uint32_t *)prop_ptr);
176                 }
177             }
178 
179             if (cb->cpu && ecam_size > 0) {
180                 LTRACEF("calling cpu callback with base %#llx size %#llx\n", ecam_base, ecam_size);
181                 cb->pcie(ecam_base, ecam_size, bus_start, bus_end, cb->pciecookie);
182             }
183         }
184 
185     }
186 
187     return NO_ERROR;
188 }
189 
190