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/cdefs.h>
34 #include <sys/param.h>
35 #include <sys/systm.h>
36 #include <sys/kernel.h>
37 #include <sys/module.h>
38 #include <sys/bus.h>
39 #include <sys/limits.h>
40 #include <sys/sysctl.h>
41
42 #include <machine/resource.h>
43
44 #include <dev/fdt/fdt_common.h>
45 #include <dev/ofw/ofw_bus.h>
46 #include <dev/ofw/ofw_bus_subr.h>
47 #include <dev/ofw/openfirm.h>
48
49 #include "ofw_bus_if.h"
50
51 #ifdef DEBUG
52 #define debugf(fmt, args...) do { printf("%s(): ", __func__); \
53 printf(fmt,##args); } while (0)
54 #else
55 #define debugf(fmt, args...)
56 #endif
57
58 #define FDT_COMPAT_LEN 255
59
60 #define FDT_REG_CELLS 4
61 #define FDT_RANGES_SIZE 48
62
63 SYSCTL_NODE(_hw, OID_AUTO, fdt, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
64 "Flattened Device Tree");
65
66 vm_paddr_t fdt_immr_pa;
67 vm_offset_t fdt_immr_va;
68 vm_offset_t fdt_immr_size;
69
70 struct fdt_ic_list fdt_ic_list_head = SLIST_HEAD_INITIALIZER(fdt_ic_list_head);
71
72 static int
fdt_get_range_by_busaddr(phandle_t node,u_long addr,u_long * base,u_long * size)73 fdt_get_range_by_busaddr(phandle_t node, u_long addr, u_long *base,
74 u_long *size)
75 {
76 pcell_t ranges[32], *rangesptr;
77 pcell_t addr_cells, size_cells, par_addr_cells;
78 u_long bus_addr, par_bus_addr, pbase, psize;
79 int err, i, len, tuple_size, tuples;
80
81 if (node == 0) {
82 *base = 0;
83 *size = ULONG_MAX;
84 return (0);
85 }
86
87 if ((fdt_addrsize_cells(node, &addr_cells, &size_cells)) != 0)
88 return (ENXIO);
89 /*
90 * Process 'ranges' property.
91 */
92 par_addr_cells = fdt_parent_addr_cells(node);
93 if (par_addr_cells > 2) {
94 return (ERANGE);
95 }
96
97 len = OF_getproplen(node, "ranges");
98 if (len < 0)
99 return (-1);
100 if (len > sizeof(ranges))
101 return (ENOMEM);
102 if (len == 0) {
103 return (fdt_get_range_by_busaddr(OF_parent(node), addr,
104 base, size));
105 }
106
107 if (OF_getprop(node, "ranges", ranges, sizeof(ranges)) <= 0)
108 return (EINVAL);
109
110 tuple_size = addr_cells + par_addr_cells + size_cells;
111 tuples = len / (tuple_size * sizeof(cell_t));
112
113 if (par_addr_cells > 2 || addr_cells > 2 || size_cells > 2)
114 return (ERANGE);
115
116 *base = 0;
117 *size = 0;
118
119 for (i = 0; i < tuples; i++) {
120 rangesptr = &ranges[i * tuple_size];
121
122 bus_addr = fdt_data_get((void *)rangesptr, addr_cells);
123 if (bus_addr != addr)
124 continue;
125 rangesptr += addr_cells;
126
127 par_bus_addr = fdt_data_get((void *)rangesptr, par_addr_cells);
128 rangesptr += par_addr_cells;
129
130 err = fdt_get_range_by_busaddr(OF_parent(node), par_bus_addr,
131 &pbase, &psize);
132 if (err > 0)
133 return (err);
134 if (err == 0)
135 *base = pbase;
136 else
137 *base = par_bus_addr;
138
139 *size = fdt_data_get((void *)rangesptr, size_cells);
140
141 return (0);
142 }
143
144 return (EINVAL);
145 }
146
147 int
fdt_get_range(phandle_t node,int range_id,u_long * base,u_long * size)148 fdt_get_range(phandle_t node, int range_id, u_long *base, u_long *size)
149 {
150 pcell_t ranges[FDT_RANGES_SIZE], *rangesptr;
151 pcell_t addr_cells, size_cells, par_addr_cells;
152 u_long par_bus_addr, pbase, psize;
153 int err, len;
154
155 if ((fdt_addrsize_cells(node, &addr_cells, &size_cells)) != 0)
156 return (ENXIO);
157 /*
158 * Process 'ranges' property.
159 */
160 par_addr_cells = fdt_parent_addr_cells(node);
161 if (par_addr_cells > 2)
162 return (ERANGE);
163
164 len = OF_getproplen(node, "ranges");
165 if (len > sizeof(ranges))
166 return (ENOMEM);
167 if (len == 0) {
168 *base = 0;
169 *size = ULONG_MAX;
170 return (0);
171 }
172
173 if (!(range_id < len))
174 return (ERANGE);
175
176 if (OF_getprop(node, "ranges", ranges, sizeof(ranges)) <= 0)
177 return (EINVAL);
178
179 if (par_addr_cells > 2 || addr_cells > 2 || size_cells > 2)
180 return (ERANGE);
181
182 *base = 0;
183 *size = 0;
184 rangesptr = &ranges[range_id];
185
186 *base = fdt_data_get((void *)rangesptr, addr_cells);
187 rangesptr += addr_cells;
188
189 par_bus_addr = fdt_data_get((void *)rangesptr, par_addr_cells);
190 rangesptr += par_addr_cells;
191
192 err = fdt_get_range_by_busaddr(OF_parent(node), par_bus_addr,
193 &pbase, &psize);
194 if (err == 0)
195 *base += pbase;
196 else
197 *base += par_bus_addr;
198
199 *size = fdt_data_get((void *)rangesptr, size_cells);
200 return (0);
201 }
202
203 int
fdt_immr_addr(vm_offset_t immr_va)204 fdt_immr_addr(vm_offset_t immr_va)
205 {
206 phandle_t node;
207 u_long base, size;
208 int r;
209
210 /*
211 * Try to access the SOC node directly i.e. through /aliases/.
212 */
213 if ((node = OF_finddevice("soc")) != -1)
214 if (ofw_bus_node_is_compatible(node, "simple-bus"))
215 goto moveon;
216 /*
217 * Find the node the long way.
218 */
219 if ((node = OF_finddevice("/")) == -1)
220 return (ENXIO);
221
222 if ((node = fdt_find_compatible(node, "simple-bus", 0)) == 0)
223 return (ENXIO);
224
225 moveon:
226 if ((r = fdt_get_range(node, 0, &base, &size)) == 0) {
227 fdt_immr_pa = base;
228 fdt_immr_va = immr_va;
229 fdt_immr_size = size;
230 }
231
232 return (r);
233 }
234
235 int
fdt_is_compatible_strict(phandle_t node,const char * compatible)236 fdt_is_compatible_strict(phandle_t node, const char *compatible)
237 {
238 char compat[FDT_COMPAT_LEN];
239
240 if (OF_getproplen(node, "compatible") <= 0)
241 return (0);
242
243 if (OF_getprop(node, "compatible", compat, FDT_COMPAT_LEN) < 0)
244 return (0);
245
246 if (strncasecmp(compat, compatible, FDT_COMPAT_LEN) == 0)
247 /* This fits. */
248 return (1);
249
250 return (0);
251 }
252
253 phandle_t
fdt_find_compatible(phandle_t start,const char * compat,int strict)254 fdt_find_compatible(phandle_t start, const char *compat, int strict)
255 {
256 phandle_t child;
257
258 /*
259 * Traverse all children of 'start' node, and find first with
260 * matching 'compatible' property.
261 */
262 for (child = OF_child(start); child != 0; child = OF_peer(child))
263 if (ofw_bus_node_is_compatible(child, compat)) {
264 if (strict)
265 if (!fdt_is_compatible_strict(child, compat))
266 continue;
267 return (child);
268 }
269 return (0);
270 }
271
272 phandle_t
fdt_depth_search_compatible(phandle_t start,const char * compat,int strict)273 fdt_depth_search_compatible(phandle_t start, const char *compat, int strict)
274 {
275 phandle_t child, node;
276
277 /*
278 * Depth-search all descendants of 'start' node, and find first with
279 * matching 'compatible' property.
280 */
281 for (node = OF_child(start); node != 0; node = OF_peer(node)) {
282 if (ofw_bus_node_is_compatible(node, compat) &&
283 (strict == 0 || fdt_is_compatible_strict(node, compat))) {
284 return (node);
285 }
286 child = fdt_depth_search_compatible(node, compat, strict);
287 if (child != 0)
288 return (child);
289 }
290 return (0);
291 }
292
293 int
fdt_parent_addr_cells(phandle_t node)294 fdt_parent_addr_cells(phandle_t node)
295 {
296 pcell_t addr_cells;
297
298 /* Find out #address-cells of the superior bus. */
299 if (OF_searchprop(OF_parent(node), "#address-cells", &addr_cells,
300 sizeof(addr_cells)) <= 0)
301 return (2);
302
303 return ((int)fdt32_to_cpu(addr_cells));
304 }
305
306 u_long
fdt_data_get(void * data,int cells)307 fdt_data_get(void *data, int cells)
308 {
309
310 if (cells == 1)
311 return (fdt32_to_cpu(*((uint32_t *)data)));
312
313 return (fdt64_to_cpu(*((uint64_t *)data)));
314 }
315
316 int
fdt_addrsize_cells(phandle_t node,int * addr_cells,int * size_cells)317 fdt_addrsize_cells(phandle_t node, int *addr_cells, int *size_cells)
318 {
319 pcell_t cell;
320 int cell_size;
321
322 /*
323 * Retrieve #{address,size}-cells.
324 */
325 cell_size = sizeof(cell);
326 if (OF_getencprop(node, "#address-cells", &cell, cell_size) < cell_size)
327 cell = 2;
328 *addr_cells = (int)cell;
329
330 if (OF_getencprop(node, "#size-cells", &cell, cell_size) < cell_size)
331 cell = 1;
332 *size_cells = (int)cell;
333
334 if (*addr_cells > 3 || *size_cells > 2)
335 return (ERANGE);
336 return (0);
337 }
338
339 int
fdt_data_to_res(pcell_t * data,int addr_cells,int size_cells,u_long * start,u_long * count)340 fdt_data_to_res(pcell_t *data, int addr_cells, int size_cells, u_long *start,
341 u_long *count)
342 {
343
344 /* Address portion. */
345 if (addr_cells > 2)
346 return (ERANGE);
347
348 *start = fdt_data_get((void *)data, addr_cells);
349 data += addr_cells;
350
351 /* Size portion. */
352 if (size_cells > 2)
353 return (ERANGE);
354
355 *count = fdt_data_get((void *)data, size_cells);
356 return (0);
357 }
358
359 int
fdt_regsize(phandle_t node,u_long * base,u_long * size)360 fdt_regsize(phandle_t node, u_long *base, u_long *size)
361 {
362 pcell_t reg[4];
363 int addr_cells, len, size_cells;
364
365 if (fdt_addrsize_cells(OF_parent(node), &addr_cells, &size_cells))
366 return (ENXIO);
367
368 if ((sizeof(pcell_t) * (addr_cells + size_cells)) > sizeof(reg))
369 return (ENOMEM);
370
371 len = OF_getprop(node, "reg", ®, sizeof(reg));
372 if (len <= 0)
373 return (EINVAL);
374
375 *base = fdt_data_get(®[0], addr_cells);
376 *size = fdt_data_get(®[addr_cells], size_cells);
377 return (0);
378 }
379
380 int
fdt_get_phyaddr(phandle_t node,device_t dev,int * phy_addr,void ** phy_sc)381 fdt_get_phyaddr(phandle_t node, device_t dev, int *phy_addr, void **phy_sc)
382 {
383 phandle_t phy_node;
384 pcell_t phy_handle, phy_reg;
385 uint32_t i;
386 device_t parent, child;
387
388 if (OF_getencprop(node, "phy-handle", (void *)&phy_handle,
389 sizeof(phy_handle)) <= 0)
390 return (ENXIO);
391
392 phy_node = OF_node_from_xref(phy_handle);
393
394 if (OF_getencprop(phy_node, "reg", (void *)&phy_reg,
395 sizeof(phy_reg)) <= 0)
396 return (ENXIO);
397
398 *phy_addr = phy_reg;
399
400 if (phy_sc == NULL)
401 return (0);
402
403 /*
404 * Search for softc used to communicate with phy.
405 */
406
407 /*
408 * Step 1: Search for ancestor of the phy-node with a "phy-handle"
409 * property set.
410 */
411 phy_node = OF_parent(phy_node);
412 while (phy_node != 0) {
413 if (OF_getprop(phy_node, "phy-handle", (void *)&phy_handle,
414 sizeof(phy_handle)) > 0)
415 break;
416 phy_node = OF_parent(phy_node);
417 }
418 if (phy_node == 0)
419 return (ENXIO);
420
421 /*
422 * Step 2: For each device with the same parent and name as ours
423 * compare its node with the one found in step 1, ancestor of phy
424 * node (stored in phy_node).
425 */
426 parent = device_get_parent(dev);
427 i = 0;
428 child = device_find_child(parent, device_get_name(dev), i);
429 while (child != NULL) {
430 if (ofw_bus_get_node(child) == phy_node)
431 break;
432 i++;
433 child = device_find_child(parent, device_get_name(dev), i);
434 }
435 if (child == NULL)
436 return (ENXIO);
437
438 /*
439 * Use softc of the device found.
440 */
441 *phy_sc = (void *)device_get_softc(child);
442
443 return (0);
444 }
445
446 int
fdt_get_reserved_regions(struct mem_region * mr,int * mrcnt)447 fdt_get_reserved_regions(struct mem_region *mr, int *mrcnt)
448 {
449 pcell_t reserve[FDT_REG_CELLS * FDT_MEM_REGIONS];
450 pcell_t *reservep;
451 phandle_t memory, root;
452 int addr_cells, size_cells;
453 int i, res_len, rv, tuple_size, tuples;
454
455 root = OF_finddevice("/");
456 memory = OF_finddevice("/memory");
457 if (memory == -1) {
458 rv = ENXIO;
459 goto out;
460 }
461
462 if ((rv = fdt_addrsize_cells(OF_parent(memory), &addr_cells,
463 &size_cells)) != 0)
464 goto out;
465
466 if (addr_cells > 2) {
467 rv = ERANGE;
468 goto out;
469 }
470
471 tuple_size = sizeof(pcell_t) * (addr_cells + size_cells);
472
473 res_len = OF_getproplen(root, "memreserve");
474 if (res_len <= 0 || res_len > sizeof(reserve)) {
475 rv = ERANGE;
476 goto out;
477 }
478
479 if (OF_getprop(root, "memreserve", reserve, res_len) <= 0) {
480 rv = ENXIO;
481 goto out;
482 }
483
484 tuples = res_len / tuple_size;
485 reservep = (pcell_t *)&reserve;
486 for (i = 0; i < tuples; i++) {
487
488 rv = fdt_data_to_res(reservep, addr_cells, size_cells,
489 (u_long *)&mr[i].mr_start, (u_long *)&mr[i].mr_size);
490
491 if (rv != 0)
492 goto out;
493
494 reservep += addr_cells + size_cells;
495 }
496
497 *mrcnt = i;
498 rv = 0;
499 out:
500 return (rv);
501 }
502
503 int
fdt_get_reserved_mem(struct mem_region * reserved,int * mreserved)504 fdt_get_reserved_mem(struct mem_region *reserved, int *mreserved)
505 {
506 pcell_t reg[FDT_REG_CELLS];
507 phandle_t child, root;
508 int addr_cells, size_cells;
509 int i, rv;
510
511 root = OF_finddevice("/reserved-memory");
512 if (root == -1) {
513 return (ENXIO);
514 }
515
516 if ((rv = fdt_addrsize_cells(root, &addr_cells, &size_cells)) != 0)
517 return (rv);
518
519 if (addr_cells + size_cells > FDT_REG_CELLS)
520 panic("Too many address and size cells %d %d", addr_cells,
521 size_cells);
522
523 i = 0;
524 for (child = OF_child(root); child != 0; child = OF_peer(child)) {
525 if (!OF_hasprop(child, "no-map"))
526 continue;
527
528 rv = OF_getprop(child, "reg", reg, sizeof(reg));
529 if (rv <= 0)
530 /* XXX: Does a no-map of a dynamic range make sense? */
531 continue;
532
533 fdt_data_to_res(reg, addr_cells, size_cells,
534 (u_long *)&reserved[i].mr_start,
535 (u_long *)&reserved[i].mr_size);
536 i++;
537 }
538
539 *mreserved = i;
540
541 return (0);
542 }
543
544 int
fdt_get_mem_regions(struct mem_region * mr,int * mrcnt,uint64_t * memsize)545 fdt_get_mem_regions(struct mem_region *mr, int *mrcnt, uint64_t *memsize)
546 {
547 pcell_t reg[FDT_REG_CELLS * FDT_MEM_REGIONS];
548 pcell_t *regp;
549 phandle_t memory;
550 uint64_t memory_size;
551 int addr_cells, size_cells;
552 int i, reg_len, rv, tuple_size, tuples;
553
554 memory = OF_finddevice("/memory");
555 if (memory == -1) {
556 rv = ENXIO;
557 goto out;
558 }
559
560 if ((rv = fdt_addrsize_cells(OF_parent(memory), &addr_cells,
561 &size_cells)) != 0)
562 goto out;
563
564 if (addr_cells > 2) {
565 rv = ERANGE;
566 goto out;
567 }
568
569 tuple_size = sizeof(pcell_t) * (addr_cells + size_cells);
570 reg_len = OF_getproplen(memory, "reg");
571 if (reg_len <= 0 || reg_len > sizeof(reg)) {
572 rv = ERANGE;
573 goto out;
574 }
575
576 if (OF_getprop(memory, "reg", reg, reg_len) <= 0) {
577 rv = ENXIO;
578 goto out;
579 }
580
581 memory_size = 0;
582 tuples = reg_len / tuple_size;
583 regp = (pcell_t *)®
584 for (i = 0; i < tuples; i++) {
585
586 rv = fdt_data_to_res(regp, addr_cells, size_cells,
587 (u_long *)&mr[i].mr_start, (u_long *)&mr[i].mr_size);
588
589 if (rv != 0)
590 goto out;
591
592 regp += addr_cells + size_cells;
593 memory_size += mr[i].mr_size;
594 }
595
596 if (memory_size == 0) {
597 rv = ERANGE;
598 goto out;
599 }
600
601 *mrcnt = i;
602 if (memsize != NULL)
603 *memsize = memory_size;
604 rv = 0;
605 out:
606 return (rv);
607 }
608
609 int
fdt_get_chosen_bootargs(char * bootargs,size_t max_size)610 fdt_get_chosen_bootargs(char *bootargs, size_t max_size)
611 {
612 phandle_t chosen;
613
614 chosen = OF_finddevice("/chosen");
615 if (chosen == -1)
616 return (ENXIO);
617 if (OF_getprop(chosen, "bootargs", bootargs, max_size) == -1)
618 return (ENXIO);
619 return (0);
620 }
621