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 struct fdt_ic_list fdt_ic_list_head = SLIST_HEAD_INITIALIZER(fdt_ic_list_head);
66
67 static int
fdt_get_range_by_busaddr(phandle_t node,u_long addr,u_long * base,u_long * size)68 fdt_get_range_by_busaddr(phandle_t node, u_long addr, u_long *base,
69 u_long *size)
70 {
71 pcell_t ranges[32], *rangesptr;
72 pcell_t addr_cells, size_cells, par_addr_cells;
73 u_long bus_addr, par_bus_addr, pbase, psize;
74 int err, i, len, tuple_size, tuples;
75
76 if (node == 0) {
77 *base = 0;
78 *size = ULONG_MAX;
79 return (0);
80 }
81
82 if ((fdt_addrsize_cells(node, &addr_cells, &size_cells)) != 0)
83 return (ENXIO);
84 /*
85 * Process 'ranges' property.
86 */
87 par_addr_cells = fdt_parent_addr_cells(node);
88 if (par_addr_cells > 2) {
89 return (ERANGE);
90 }
91
92 len = OF_getproplen(node, "ranges");
93 if (len < 0)
94 return (-1);
95 if (len > sizeof(ranges))
96 return (ENOMEM);
97 if (len == 0) {
98 return (fdt_get_range_by_busaddr(OF_parent(node), addr,
99 base, size));
100 }
101
102 if (OF_getprop(node, "ranges", ranges, sizeof(ranges)) <= 0)
103 return (EINVAL);
104
105 tuple_size = addr_cells + par_addr_cells + size_cells;
106 tuples = len / (tuple_size * sizeof(cell_t));
107
108 if (par_addr_cells > 2 || addr_cells > 2 || size_cells > 2)
109 return (ERANGE);
110
111 *base = 0;
112 *size = 0;
113
114 for (i = 0; i < tuples; i++) {
115 rangesptr = &ranges[i * tuple_size];
116
117 bus_addr = fdt_data_get((void *)rangesptr, addr_cells);
118 if (bus_addr != addr)
119 continue;
120 rangesptr += addr_cells;
121
122 par_bus_addr = fdt_data_get((void *)rangesptr, par_addr_cells);
123 rangesptr += par_addr_cells;
124
125 err = fdt_get_range_by_busaddr(OF_parent(node), par_bus_addr,
126 &pbase, &psize);
127 if (err > 0)
128 return (err);
129 if (err == 0)
130 *base = pbase;
131 else
132 *base = par_bus_addr;
133
134 *size = fdt_data_get((void *)rangesptr, size_cells);
135
136 return (0);
137 }
138
139 return (EINVAL);
140 }
141
142 int
fdt_get_range(phandle_t node,int range_id,u_long * base,u_long * size)143 fdt_get_range(phandle_t node, int range_id, u_long *base, u_long *size)
144 {
145 pcell_t ranges[FDT_RANGES_SIZE], *rangesptr;
146 pcell_t addr_cells, size_cells, par_addr_cells;
147 u_long par_bus_addr, pbase, psize;
148 int err, len;
149
150 if ((fdt_addrsize_cells(node, &addr_cells, &size_cells)) != 0)
151 return (ENXIO);
152 /*
153 * Process 'ranges' property.
154 */
155 par_addr_cells = fdt_parent_addr_cells(node);
156 if (par_addr_cells > 2)
157 return (ERANGE);
158
159 len = OF_getproplen(node, "ranges");
160 if (len > sizeof(ranges))
161 return (ENOMEM);
162 if (len == 0) {
163 *base = 0;
164 *size = ULONG_MAX;
165 return (0);
166 }
167
168 if (!(range_id < len))
169 return (ERANGE);
170
171 if (OF_getprop(node, "ranges", ranges, sizeof(ranges)) <= 0)
172 return (EINVAL);
173
174 if (par_addr_cells > 2 || addr_cells > 2 || size_cells > 2)
175 return (ERANGE);
176
177 *base = 0;
178 *size = 0;
179 rangesptr = &ranges[range_id];
180
181 *base = fdt_data_get((void *)rangesptr, addr_cells);
182 rangesptr += addr_cells;
183
184 par_bus_addr = fdt_data_get((void *)rangesptr, par_addr_cells);
185 rangesptr += par_addr_cells;
186
187 err = fdt_get_range_by_busaddr(OF_parent(node), par_bus_addr,
188 &pbase, &psize);
189 if (err == 0)
190 *base += pbase;
191 else
192 *base += par_bus_addr;
193
194 *size = fdt_data_get((void *)rangesptr, size_cells);
195 return (0);
196 }
197
198 int
fdt_is_compatible_strict(phandle_t node,const char * compatible)199 fdt_is_compatible_strict(phandle_t node, const char *compatible)
200 {
201 char compat[FDT_COMPAT_LEN];
202
203 if (OF_getproplen(node, "compatible") <= 0)
204 return (0);
205
206 if (OF_getprop(node, "compatible", compat, FDT_COMPAT_LEN) < 0)
207 return (0);
208
209 if (strncasecmp(compat, compatible, FDT_COMPAT_LEN) == 0)
210 /* This fits. */
211 return (1);
212
213 return (0);
214 }
215
216 phandle_t
fdt_find_compatible(phandle_t start,const char * compat,int strict)217 fdt_find_compatible(phandle_t start, const char *compat, int strict)
218 {
219 phandle_t child;
220
221 /*
222 * Traverse all children of 'start' node, and find first with
223 * matching 'compatible' property.
224 */
225 for (child = OF_child(start); child != 0; child = OF_peer(child))
226 if (ofw_bus_node_is_compatible(child, compat)) {
227 if (strict)
228 if (!fdt_is_compatible_strict(child, compat))
229 continue;
230 return (child);
231 }
232 return (0);
233 }
234
235 phandle_t
fdt_depth_search_compatible(phandle_t start,const char * compat,int strict)236 fdt_depth_search_compatible(phandle_t start, const char *compat, int strict)
237 {
238 phandle_t child, node;
239
240 /*
241 * Depth-search all descendants of 'start' node, and find first with
242 * matching 'compatible' property.
243 */
244 for (node = OF_child(start); node != 0; node = OF_peer(node)) {
245 if (ofw_bus_node_is_compatible(node, compat) &&
246 (strict == 0 || fdt_is_compatible_strict(node, compat))) {
247 return (node);
248 }
249 child = fdt_depth_search_compatible(node, compat, strict);
250 if (child != 0)
251 return (child);
252 }
253 return (0);
254 }
255
256 int
fdt_parent_addr_cells(phandle_t node)257 fdt_parent_addr_cells(phandle_t node)
258 {
259 pcell_t addr_cells;
260
261 /* Find out #address-cells of the superior bus. */
262 if (OF_searchprop(OF_parent(node), "#address-cells", &addr_cells,
263 sizeof(addr_cells)) <= 0)
264 return (2);
265
266 return ((int)fdt32_to_cpu(addr_cells));
267 }
268
269 u_long
fdt_data_get(const void * data,int cells)270 fdt_data_get(const void *data, int cells)
271 {
272
273 if (cells == 1)
274 return (fdt32_to_cpu(*((const uint32_t *)data)));
275
276 return (fdt64_to_cpu(*((const uint64_t *)data)));
277 }
278
279 int
fdt_addrsize_cells(phandle_t node,int * addr_cells,int * size_cells)280 fdt_addrsize_cells(phandle_t node, int *addr_cells, int *size_cells)
281 {
282 pcell_t cell;
283 int cell_size;
284
285 /*
286 * Retrieve #{address,size}-cells.
287 */
288 cell_size = sizeof(cell);
289 if (OF_getencprop(node, "#address-cells", &cell, cell_size) < cell_size)
290 cell = 2;
291 *addr_cells = (int)cell;
292
293 if (OF_getencprop(node, "#size-cells", &cell, cell_size) < cell_size)
294 cell = 1;
295 *size_cells = (int)cell;
296
297 if (*addr_cells > 3 || *size_cells > 2)
298 return (ERANGE);
299 return (0);
300 }
301
302 int
fdt_data_to_res(const pcell_t * data,int addr_cells,int size_cells,u_long * start,u_long * count)303 fdt_data_to_res(const pcell_t *data, int addr_cells, int size_cells,
304 u_long *start, u_long *count)
305 {
306
307 /* Address portion. */
308 if (addr_cells > 2)
309 return (ERANGE);
310
311 *start = fdt_data_get((const void *)data, addr_cells);
312 data += addr_cells;
313
314 /* Size portion. */
315 if (size_cells > 2)
316 return (ERANGE);
317
318 *count = fdt_data_get((const void *)data, size_cells);
319 return (0);
320 }
321
322 int
fdt_regsize(phandle_t node,u_long * base,u_long * size)323 fdt_regsize(phandle_t node, u_long *base, u_long *size)
324 {
325 pcell_t reg[4];
326 int addr_cells, len, size_cells;
327
328 if (fdt_addrsize_cells(OF_parent(node), &addr_cells, &size_cells))
329 return (ENXIO);
330
331 if ((sizeof(pcell_t) * (addr_cells + size_cells)) > sizeof(reg))
332 return (ENOMEM);
333
334 len = OF_getprop(node, "reg", ®, sizeof(reg));
335 if (len <= 0)
336 return (EINVAL);
337
338 *base = fdt_data_get(®[0], addr_cells);
339 *size = fdt_data_get(®[addr_cells], size_cells);
340 return (0);
341 }
342
343 int
fdt_get_phyaddr(phandle_t node,device_t dev,int * phy_addr,void ** phy_sc)344 fdt_get_phyaddr(phandle_t node, device_t dev, int *phy_addr, void **phy_sc)
345 {
346 phandle_t phy_node;
347 pcell_t phy_handle, phy_reg;
348 uint32_t i;
349 device_t parent, child;
350
351 if (OF_getencprop(node, "phy-handle", (void *)&phy_handle,
352 sizeof(phy_handle)) <= 0)
353 return (ENXIO);
354
355 phy_node = OF_node_from_xref(phy_handle);
356
357 if (OF_getencprop(phy_node, "reg", (void *)&phy_reg,
358 sizeof(phy_reg)) <= 0)
359 return (ENXIO);
360
361 *phy_addr = phy_reg;
362
363 if (phy_sc == NULL)
364 return (0);
365
366 /*
367 * Search for softc used to communicate with phy.
368 */
369
370 /*
371 * Step 1: Search for ancestor of the phy-node with a "phy-handle"
372 * property set.
373 */
374 phy_node = OF_parent(phy_node);
375 while (phy_node != 0) {
376 if (OF_getprop(phy_node, "phy-handle", (void *)&phy_handle,
377 sizeof(phy_handle)) > 0)
378 break;
379 phy_node = OF_parent(phy_node);
380 }
381 if (phy_node == 0)
382 return (ENXIO);
383
384 /*
385 * Step 2: For each device with the same parent and name as ours
386 * compare its node with the one found in step 1, ancestor of phy
387 * node (stored in phy_node).
388 */
389 parent = device_get_parent(dev);
390 i = 0;
391 child = device_find_child(parent, device_get_name(dev), i);
392 while (child != NULL) {
393 if (ofw_bus_get_node(child) == phy_node)
394 break;
395 i++;
396 child = device_find_child(parent, device_get_name(dev), i);
397 }
398 if (child == NULL)
399 return (ENXIO);
400
401 /*
402 * Use softc of the device found.
403 */
404 *phy_sc = (void *)device_get_softc(child);
405
406 return (0);
407 }
408
409 int
fdt_foreach_reserved_region(fdt_mem_region_cb cb,void * arg)410 fdt_foreach_reserved_region(fdt_mem_region_cb cb, void *arg)
411 {
412 struct mem_region mr;
413 pcell_t reserve[FDT_REG_CELLS * FDT_MEM_REGIONS];
414 pcell_t *reservep;
415 phandle_t memory, root;
416 int addr_cells, size_cells;
417 int i, res_len, rv, tuple_size, tuples;
418
419 root = OF_finddevice("/");
420 memory = OF_finddevice("/memory");
421 if (memory == -1)
422 return (ENXIO);
423
424 if ((rv = fdt_addrsize_cells(OF_parent(memory), &addr_cells,
425 &size_cells)) != 0)
426 return (rv);
427
428 if (addr_cells > 2)
429 return (ERANGE);
430
431 tuple_size = sizeof(pcell_t) * (addr_cells + size_cells);
432
433 res_len = OF_getproplen(root, "memreserve");
434 if (res_len <= 0 || res_len > sizeof(reserve))
435 return (ERANGE);
436
437 if (OF_getprop(root, "memreserve", reserve, res_len) <= 0)
438 return (ENXIO);
439
440 tuples = res_len / tuple_size;
441 reservep = (pcell_t *)&reserve;
442 for (i = 0; i < tuples; i++) {
443
444 rv = fdt_data_to_res(reservep, addr_cells, size_cells,
445 (u_long *)&mr.mr_start, (u_long *)&mr.mr_size);
446
447 if (rv != 0)
448 return (rv);
449
450 cb(&mr, arg);
451
452 reservep += addr_cells + size_cells;
453 }
454
455 return (0);
456 }
457
458 int
fdt_foreach_reserved_mem(fdt_mem_region_cb cb,void * arg)459 fdt_foreach_reserved_mem(fdt_mem_region_cb cb, void *arg)
460 {
461 struct mem_region mr;
462 pcell_t reg[FDT_REG_CELLS];
463 phandle_t child, root;
464 int addr_cells, size_cells;
465 int rv;
466
467 root = OF_finddevice("/reserved-memory");
468 if (root == -1)
469 return (ENXIO);
470
471 if ((rv = fdt_addrsize_cells(root, &addr_cells, &size_cells)) != 0)
472 return (rv);
473
474 if (addr_cells + size_cells > FDT_REG_CELLS)
475 panic("Too many address and size cells %d %d", addr_cells,
476 size_cells);
477
478 for (child = OF_child(root); child != 0; child = OF_peer(child)) {
479 if (!OF_hasprop(child, "no-map"))
480 continue;
481
482 rv = OF_getprop(child, "reg", reg, sizeof(reg));
483 if (rv <= 0)
484 /* XXX: Does a no-map of a dynamic range make sense? */
485 continue;
486
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 rv = fdt_data_to_res(regp, addr_cells, size_cells,
530 (u_long *)&mr.mr_start, (u_long *)&mr.mr_size);
531
532 if (rv != 0)
533 return (rv);
534
535 cb(&mr, arg);
536
537 regp += addr_cells + size_cells;
538 }
539
540 return (0);
541 }
542
543 int
fdt_get_chosen_bootargs(char * bootargs,size_t max_size)544 fdt_get_chosen_bootargs(char *bootargs, size_t max_size)
545 {
546 phandle_t chosen;
547
548 chosen = OF_finddevice("/chosen");
549 if (chosen == -1)
550 return (ENXIO);
551 if (OF_getprop(chosen, "bootargs", bootargs, max_size) == -1)
552 return (ENXIO);
553 return (0);
554 }
555