1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2009-2014 The FreeBSD Foundation
5 *
6 * This software was developed by Andrew Turner under sponsorship from
7 * the FreeBSD Foundation.
8 * This software was developed by Semihalf under sponsorship from
9 * the FreeBSD Foundation.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33 #include <sys/param.h>
34 #include <sys/systm.h>
35 #include <sys/kernel.h>
36 #include <sys/module.h>
37 #include <sys/bus.h>
38 #include <sys/limits.h>
39 #include <sys/sysctl.h>
40
41 #include <machine/resource.h>
42
43 #include <dev/fdt/fdt_common.h>
44 #include <dev/ofw/ofw_bus.h>
45 #include <dev/ofw/ofw_bus_subr.h>
46 #include <dev/ofw/openfirm.h>
47
48 #include "ofw_bus_if.h"
49
50 #ifdef DEBUG
51 #define debugf(fmt, args...) do { printf("%s(): ", __func__); \
52 printf(fmt,##args); } while (0)
53 #else
54 #define debugf(fmt, args...)
55 #endif
56
57 #define FDT_COMPAT_LEN 255
58
59 #define FDT_REG_CELLS 4
60 #define FDT_RANGES_SIZE 48
61
62 SYSCTL_NODE(_hw, OID_AUTO, fdt, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
63 "Flattened Device Tree");
64
65 static int
fdt_get_range_by_busaddr(phandle_t node,u_long addr,u_long * base,u_long * size)66 fdt_get_range_by_busaddr(phandle_t node, u_long addr, u_long *base,
67 u_long *size)
68 {
69 pcell_t ranges[32], *rangesptr;
70 pcell_t addr_cells, size_cells, par_addr_cells;
71 u_long bus_addr, par_bus_addr, pbase, psize;
72 int err, i, len, tuple_size, tuples;
73
74 if (node == 0) {
75 *base = 0;
76 *size = ULONG_MAX;
77 return (0);
78 }
79
80 if ((fdt_addrsize_cells(node, &addr_cells, &size_cells)) != 0)
81 return (ENXIO);
82 /*
83 * Process 'ranges' property.
84 */
85 par_addr_cells = fdt_parent_addr_cells(node);
86 if (par_addr_cells > 2) {
87 return (ERANGE);
88 }
89
90 len = OF_getproplen(node, "ranges");
91 if (len < 0)
92 return (-1);
93 if (len > sizeof(ranges))
94 return (ENOMEM);
95 if (len == 0) {
96 return (fdt_get_range_by_busaddr(OF_parent(node), addr,
97 base, size));
98 }
99
100 if (OF_getprop(node, "ranges", ranges, sizeof(ranges)) <= 0)
101 return (EINVAL);
102
103 tuple_size = addr_cells + par_addr_cells + size_cells;
104 tuples = len / (tuple_size * sizeof(cell_t));
105
106 if (par_addr_cells > 2 || addr_cells > 2 || size_cells > 2)
107 return (ERANGE);
108
109 *base = 0;
110 *size = 0;
111
112 for (i = 0; i < tuples; i++) {
113 rangesptr = &ranges[i * tuple_size];
114
115 bus_addr = fdt_data_get((void *)rangesptr, addr_cells);
116 if (bus_addr != addr)
117 continue;
118 rangesptr += addr_cells;
119
120 par_bus_addr = fdt_data_get((void *)rangesptr, par_addr_cells);
121 rangesptr += par_addr_cells;
122
123 err = fdt_get_range_by_busaddr(OF_parent(node), par_bus_addr,
124 &pbase, &psize);
125 if (err > 0)
126 return (err);
127 if (err == 0)
128 *base = pbase;
129 else
130 *base = par_bus_addr;
131
132 *size = fdt_data_get((void *)rangesptr, size_cells);
133
134 return (0);
135 }
136
137 return (EINVAL);
138 }
139
140 int
fdt_get_range(phandle_t node,int range_id,u_long * base,u_long * size)141 fdt_get_range(phandle_t node, int range_id, u_long *base, u_long *size)
142 {
143 pcell_t ranges[FDT_RANGES_SIZE], *rangesptr;
144 pcell_t addr_cells, size_cells, par_addr_cells;
145 u_long par_bus_addr, pbase, psize;
146 int err, len;
147
148 if ((fdt_addrsize_cells(node, &addr_cells, &size_cells)) != 0)
149 return (ENXIO);
150 /*
151 * Process 'ranges' property.
152 */
153 par_addr_cells = fdt_parent_addr_cells(node);
154 if (par_addr_cells > 2)
155 return (ERANGE);
156
157 len = OF_getproplen(node, "ranges");
158 if (len > sizeof(ranges))
159 return (ENOMEM);
160 if (len == 0) {
161 *base = 0;
162 *size = ULONG_MAX;
163 return (0);
164 }
165
166 if (!(range_id < len))
167 return (ERANGE);
168
169 if (OF_getprop(node, "ranges", ranges, sizeof(ranges)) <= 0)
170 return (EINVAL);
171
172 if (par_addr_cells > 2 || addr_cells > 2 || size_cells > 2)
173 return (ERANGE);
174
175 *base = 0;
176 *size = 0;
177 rangesptr = &ranges[range_id];
178
179 *base = fdt_data_get((void *)rangesptr, addr_cells);
180 rangesptr += addr_cells;
181
182 par_bus_addr = fdt_data_get((void *)rangesptr, par_addr_cells);
183 rangesptr += par_addr_cells;
184
185 err = fdt_get_range_by_busaddr(OF_parent(node), par_bus_addr,
186 &pbase, &psize);
187 if (err == 0)
188 *base += pbase;
189 else
190 *base += par_bus_addr;
191
192 *size = fdt_data_get((void *)rangesptr, size_cells);
193 return (0);
194 }
195
196 int
fdt_is_compatible_strict(phandle_t node,const char * compatible)197 fdt_is_compatible_strict(phandle_t node, const char *compatible)
198 {
199 char compat[FDT_COMPAT_LEN];
200
201 if (OF_getproplen(node, "compatible") <= 0)
202 return (0);
203
204 if (OF_getprop(node, "compatible", compat, FDT_COMPAT_LEN) < 0)
205 return (0);
206
207 if (strncasecmp(compat, compatible, FDT_COMPAT_LEN) == 0)
208 /* This fits. */
209 return (1);
210
211 return (0);
212 }
213
214 phandle_t
fdt_find_compatible(phandle_t start,const char * compat,int strict)215 fdt_find_compatible(phandle_t start, const char *compat, int strict)
216 {
217 phandle_t child;
218
219 /*
220 * Traverse all children of 'start' node, and find first with
221 * matching 'compatible' property.
222 */
223 for (child = OF_child(start); child != 0; child = OF_peer(child))
224 if (ofw_bus_node_is_compatible(child, compat)) {
225 if (strict)
226 if (!fdt_is_compatible_strict(child, compat))
227 continue;
228 return (child);
229 }
230 return (0);
231 }
232
233 phandle_t
fdt_depth_search_compatible(phandle_t start,const char * compat,int strict)234 fdt_depth_search_compatible(phandle_t start, const char *compat, int strict)
235 {
236 phandle_t child, node;
237
238 /*
239 * Depth-search all descendants of 'start' node, and find first with
240 * matching 'compatible' property.
241 */
242 for (node = OF_child(start); node != 0; node = OF_peer(node)) {
243 if (ofw_bus_node_is_compatible(node, compat) &&
244 (strict == 0 || fdt_is_compatible_strict(node, compat))) {
245 return (node);
246 }
247 child = fdt_depth_search_compatible(node, compat, strict);
248 if (child != 0)
249 return (child);
250 }
251 return (0);
252 }
253
254 int
fdt_parent_addr_cells(phandle_t node)255 fdt_parent_addr_cells(phandle_t node)
256 {
257 pcell_t addr_cells;
258
259 /* Find out #address-cells of the superior bus. */
260 if (OF_searchprop(OF_parent(node), "#address-cells", &addr_cells,
261 sizeof(addr_cells)) <= 0)
262 return (2);
263
264 return ((int)fdt32_to_cpu(addr_cells));
265 }
266
267 u_long
fdt_data_get(const void * data,int cells)268 fdt_data_get(const void *data, int cells)
269 {
270
271 if (cells == 1)
272 return (fdt32_to_cpu(*((const uint32_t *)data)));
273
274 return (fdt64_to_cpu(*((const uint64_t *)data)));
275 }
276
277 int
fdt_addrsize_cells(phandle_t node,int * addr_cells,int * size_cells)278 fdt_addrsize_cells(phandle_t node, int *addr_cells, int *size_cells)
279 {
280 pcell_t cell;
281 int cell_size;
282
283 /*
284 * Retrieve #{address,size}-cells.
285 */
286 cell_size = sizeof(cell);
287 if (OF_getencprop(node, "#address-cells", &cell, cell_size) < cell_size)
288 cell = 2;
289 *addr_cells = (int)cell;
290
291 if (OF_getencprop(node, "#size-cells", &cell, cell_size) < cell_size)
292 cell = 1;
293 *size_cells = (int)cell;
294
295 if (*addr_cells > 3 || *size_cells > 2)
296 return (ERANGE);
297 return (0);
298 }
299
300 int
fdt_data_to_res(const pcell_t * data,int addr_cells,int size_cells,u_long * start,u_long * count)301 fdt_data_to_res(const pcell_t *data, int addr_cells, int size_cells,
302 u_long *start, u_long *count)
303 {
304
305 /* Address portion. */
306 if (addr_cells > 2)
307 return (ERANGE);
308
309 *start = fdt_data_get((const void *)data, addr_cells);
310 data += addr_cells;
311
312 /* Size portion. */
313 if (size_cells > 2)
314 return (ERANGE);
315
316 *count = fdt_data_get((const void *)data, size_cells);
317 return (0);
318 }
319
320 int
fdt_regsize(phandle_t node,u_long * base,u_long * size)321 fdt_regsize(phandle_t node, u_long *base, u_long *size)
322 {
323 pcell_t reg[4];
324 int addr_cells, len, size_cells;
325
326 if (fdt_addrsize_cells(OF_parent(node), &addr_cells, &size_cells))
327 return (ENXIO);
328
329 if ((sizeof(pcell_t) * (addr_cells + size_cells)) > sizeof(reg))
330 return (ENOMEM);
331
332 len = OF_getprop(node, "reg", ®, sizeof(reg));
333 if (len <= 0)
334 return (EINVAL);
335
336 *base = fdt_data_get(®[0], addr_cells);
337 *size = fdt_data_get(®[addr_cells], size_cells);
338 return (0);
339 }
340
341 int
fdt_get_phyaddr(phandle_t node,device_t dev,int * phy_addr,void ** phy_sc)342 fdt_get_phyaddr(phandle_t node, device_t dev, int *phy_addr, void **phy_sc)
343 {
344 phandle_t phy_node;
345 pcell_t phy_handle, phy_reg;
346 uint32_t i;
347 device_t parent, child;
348
349 if (OF_getencprop(node, "phy-handle", (void *)&phy_handle,
350 sizeof(phy_handle)) <= 0)
351 return (ENXIO);
352
353 phy_node = OF_node_from_xref(phy_handle);
354
355 if (OF_getencprop(phy_node, "reg", (void *)&phy_reg,
356 sizeof(phy_reg)) <= 0)
357 return (ENXIO);
358
359 *phy_addr = phy_reg;
360
361 if (phy_sc == NULL)
362 return (0);
363
364 /*
365 * Search for softc used to communicate with phy.
366 */
367
368 /*
369 * Step 1: Search for ancestor of the phy-node with a "phy-handle"
370 * property set.
371 */
372 phy_node = OF_parent(phy_node);
373 while (phy_node != 0) {
374 if (OF_getprop(phy_node, "phy-handle", (void *)&phy_handle,
375 sizeof(phy_handle)) > 0)
376 break;
377 phy_node = OF_parent(phy_node);
378 }
379 if (phy_node == 0)
380 return (ENXIO);
381
382 /*
383 * Step 2: For each device with the same parent and name as ours
384 * compare its node with the one found in step 1, ancestor of phy
385 * node (stored in phy_node).
386 */
387 parent = device_get_parent(dev);
388 i = 0;
389 child = device_find_child(parent, device_get_name(dev), i);
390 while (child != NULL) {
391 if (ofw_bus_get_node(child) == phy_node)
392 break;
393 i++;
394 child = device_find_child(parent, device_get_name(dev), i);
395 }
396 if (child == NULL)
397 return (ENXIO);
398
399 /*
400 * Use softc of the device found.
401 */
402 *phy_sc = (void *)device_get_softc(child);
403
404 return (0);
405 }
406
407 int
fdt_foreach_reserved_region(fdt_mem_region_cb cb,void * arg)408 fdt_foreach_reserved_region(fdt_mem_region_cb cb, void *arg)
409 {
410 struct mem_region mr;
411 pcell_t reserve[FDT_REG_CELLS * FDT_MEM_REGIONS];
412 pcell_t *reservep;
413 phandle_t memory, root;
414 int addr_cells, size_cells;
415 int i, res_len, rv, tuple_size, tuples;
416
417 root = OF_finddevice("/");
418 memory = OF_finddevice("/memory");
419 if (memory == -1)
420 return (ENXIO);
421
422 if ((rv = fdt_addrsize_cells(OF_parent(memory), &addr_cells,
423 &size_cells)) != 0)
424 return (rv);
425
426 if (addr_cells > 2)
427 return (ERANGE);
428
429 tuple_size = sizeof(pcell_t) * (addr_cells + size_cells);
430
431 res_len = OF_getproplen(root, "memreserve");
432 if (res_len <= 0 || res_len > sizeof(reserve))
433 return (ERANGE);
434
435 if (OF_getprop(root, "memreserve", reserve, res_len) <= 0)
436 return (ENXIO);
437
438 tuples = res_len / tuple_size;
439 reservep = (pcell_t *)&reserve;
440 for (i = 0; i < tuples; i++) {
441
442 memset(&mr, 0, sizeof(mr));
443 rv = fdt_data_to_res(reservep, addr_cells, size_cells,
444 (u_long *)&mr.mr_start, (u_long *)&mr.mr_size);
445
446 if (rv != 0)
447 return (rv);
448
449 cb(&mr, arg);
450
451 reservep += addr_cells + size_cells;
452 }
453
454 return (0);
455 }
456
457 int
fdt_foreach_reserved_mem(fdt_mem_region_cb cb,void * arg)458 fdt_foreach_reserved_mem(fdt_mem_region_cb cb, void *arg)
459 {
460 struct mem_region mr;
461 pcell_t reg[FDT_REG_CELLS];
462 phandle_t child, root;
463 int addr_cells, size_cells;
464 int rv;
465
466 root = OF_finddevice("/reserved-memory");
467 if (root == -1)
468 return (ENXIO);
469
470 if ((rv = fdt_addrsize_cells(root, &addr_cells, &size_cells)) != 0)
471 return (rv);
472
473 if (addr_cells + size_cells > FDT_REG_CELLS)
474 panic("Too many address and size cells %d %d", addr_cells,
475 size_cells);
476
477 for (child = OF_child(root); child != 0; child = OF_peer(child)) {
478 if (!OF_hasprop(child, "no-map"))
479 continue;
480
481 rv = OF_getprop(child, "reg", reg, sizeof(reg));
482 if (rv <= 0)
483 /* XXX: Does a no-map of a dynamic range make sense? */
484 continue;
485
486 memset(&mr, 0, sizeof(mr));
487 fdt_data_to_res(reg, addr_cells, size_cells,
488 (u_long *)&mr.mr_start, (u_long *)&mr.mr_size);
489
490 cb(&mr, arg);
491 }
492
493 return (0);
494 }
495
496 int
fdt_foreach_mem_region(fdt_mem_region_cb cb,void * arg)497 fdt_foreach_mem_region(fdt_mem_region_cb cb, void *arg)
498 {
499 struct mem_region mr;
500 pcell_t reg[FDT_REG_CELLS * FDT_MEM_REGIONS];
501 pcell_t *regp;
502 phandle_t memory;
503 int addr_cells, size_cells;
504 int i, reg_len, rv, tuple_size, tuples;
505
506 memory = OF_finddevice("/memory");
507 if (memory == -1)
508 return (ENXIO);
509
510 if ((rv = fdt_addrsize_cells(OF_parent(memory), &addr_cells,
511 &size_cells)) != 0)
512 return (rv);
513
514 if (addr_cells > 2)
515 return (ERANGE);
516
517 tuple_size = sizeof(pcell_t) * (addr_cells + size_cells);
518 reg_len = OF_getproplen(memory, "reg");
519 if (reg_len <= 0 || reg_len > sizeof(reg))
520 return (ERANGE);
521
522 if (OF_getprop(memory, "reg", reg, reg_len) <= 0)
523 return (ENXIO);
524
525 tuples = reg_len / tuple_size;
526 regp = (pcell_t *)®
527 for (i = 0; i < tuples; i++) {
528
529 memset(&mr, 0, sizeof(mr));
530 rv = fdt_data_to_res(regp, addr_cells, size_cells,
531 (u_long *)&mr.mr_start, (u_long *)&mr.mr_size);
532
533 if (rv != 0)
534 return (rv);
535
536 cb(&mr, arg);
537
538 regp += addr_cells + size_cells;
539 }
540
541 return (0);
542 }
543
544 int
fdt_get_chosen_bootargs(char * bootargs,size_t max_size)545 fdt_get_chosen_bootargs(char *bootargs, size_t max_size)
546 {
547 phandle_t chosen;
548
549 chosen = OF_finddevice("/chosen");
550 if (chosen == -1)
551 return (ENXIO);
552 if (OF_getprop(chosen, "bootargs", bootargs, max_size) == -1)
553 return (ENXIO);
554 return (0);
555 }
556