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
62SYSCTL_NODE(_hw, OID_AUTO, fdt, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
63    "Flattened Device Tree");
64
65vm_paddr_t fdt_immr_pa;
66vm_offset_t fdt_immr_va;
67vm_offset_t fdt_immr_size;
68
69struct fdt_ic_list fdt_ic_list_head = SLIST_HEAD_INITIALIZER(fdt_ic_list_head);
70
71static int
72fdt_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
146int
147fdt_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
202int
203fdt_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
224moveon:
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
234int
235fdt_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
252phandle_t
253fdt_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
271phandle_t
272fdt_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
292int
293fdt_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
305u_long
306fdt_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
315int
316fdt_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
338int
339fdt_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
358int
359fdt_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", &reg, sizeof(reg));
371	if (len <= 0)
372		return (EINVAL);
373
374	*base = fdt_data_get(&reg[0], addr_cells);
375	*size = fdt_data_get(&reg[addr_cells], size_cells);
376	return (0);
377}
378
379int
380fdt_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
445int
446fdt_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;
498out:
499	return (rv);
500}
501
502int
503fdt_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
543int
544fdt_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 *)&reg;
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;
604out:
605	return (rv);
606}
607
608int
609fdt_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