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