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