xref: /freebsd/sys/dev/fdt/fdt_common.c (revision 17c67ba24df03c5c04f5149265aa514786dc220b)
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", &reg, sizeof(reg));
335 	if (len <= 0)
336 		return (EINVAL);
337 
338 	*base = fdt_data_get(&reg[0], addr_cells);
339 	*size = fdt_data_get(&reg[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 *)&reg;
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