ofw_fdt.c revision 212477
1/*-
2 * Copyright (c) 2009-2010 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Semihalf under sponsorship from
6 * the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include <sys/cdefs.h>
31__FBSDID("$FreeBSD: head/sys/dev/ofw/ofw_fdt.c 212477 2010-09-11 18:55:00Z marius $");
32
33#include <sys/param.h>
34#include <sys/kernel.h>
35#include <sys/malloc.h>
36#include <sys/systm.h>
37
38#include <contrib/libfdt/libfdt.h>
39
40#include <machine/stdarg.h>
41
42#include <dev/fdt/fdt_common.h>
43#include <dev/ofw/ofwvar.h>
44#include <dev/ofw/openfirm.h>
45
46#include "ofw_if.h"
47
48#ifdef DEBUG
49#define debugf(fmt, args...) do { printf("%s(): ", __func__);	\
50    printf(fmt,##args); } while (0)
51#else
52#define debugf(fmt, args...)
53#endif
54
55static int ofw_fdt_init(ofw_t, void *);
56static phandle_t ofw_fdt_peer(ofw_t, phandle_t);
57static phandle_t ofw_fdt_child(ofw_t, phandle_t);
58static phandle_t ofw_fdt_parent(ofw_t, phandle_t);
59static phandle_t ofw_fdt_instance_to_package(ofw_t, ihandle_t);
60static ssize_t ofw_fdt_getproplen(ofw_t, phandle_t, const char *);
61static ssize_t ofw_fdt_getprop(ofw_t, phandle_t, const char *, void *, size_t);
62static int ofw_fdt_nextprop(ofw_t, phandle_t, const char *, char *, size_t);
63static int ofw_fdt_setprop(ofw_t, phandle_t, const char *, const void *,
64    size_t);
65static ssize_t ofw_fdt_canon(ofw_t, const char *, char *, size_t);
66static phandle_t ofw_fdt_finddevice(ofw_t, const char *);
67static ssize_t ofw_fdt_instance_to_path(ofw_t, ihandle_t, char *, size_t);
68static ssize_t ofw_fdt_package_to_path(ofw_t, phandle_t, char *, size_t);
69static int ofw_fdt_interpret(ofw_t, const char *, int, cell_t *);
70
71static ofw_method_t ofw_fdt_methods[] = {
72	OFWMETHOD(ofw_init,			ofw_fdt_init),
73	OFWMETHOD(ofw_peer,			ofw_fdt_peer),
74	OFWMETHOD(ofw_child,			ofw_fdt_child),
75	OFWMETHOD(ofw_parent,			ofw_fdt_parent),
76	OFWMETHOD(ofw_instance_to_package,	ofw_fdt_instance_to_package),
77	OFWMETHOD(ofw_getproplen,		ofw_fdt_getproplen),
78	OFWMETHOD(ofw_getprop,			ofw_fdt_getprop),
79	OFWMETHOD(ofw_nextprop,			ofw_fdt_nextprop),
80	OFWMETHOD(ofw_setprop,			ofw_fdt_setprop),
81	OFWMETHOD(ofw_canon,			ofw_fdt_canon),
82	OFWMETHOD(ofw_finddevice,		ofw_fdt_finddevice),
83	OFWMETHOD(ofw_instance_to_path,		ofw_fdt_instance_to_path),
84	OFWMETHOD(ofw_package_to_path,		ofw_fdt_package_to_path),
85	OFWMETHOD(ofw_interpret,		ofw_fdt_interpret),
86	{ 0, 0 }
87};
88
89static ofw_def_t ofw_fdt = {
90	OFW_FDT,
91	ofw_fdt_methods,
92	0
93};
94OFW_DEF(ofw_fdt);
95
96static void *fdtp = NULL;
97
98static int
99ofw_fdt_init(ofw_t ofw, void *data)
100{
101	int err;
102
103	/* Check FDT blob integrity */
104	if ((err = fdt_check_header(data)) != 0)
105		return (err);
106
107	fdtp = data;
108	return (0);
109}
110
111/*
112 * Device tree functions
113 */
114
115static int
116fdt_phandle_offset(phandle_t p)
117{
118	const char *dt_struct;
119	int offset;
120
121	dt_struct = (const char *)fdtp + fdt_off_dt_struct(fdtp);
122
123	if (((const char *)p < dt_struct) ||
124	    (const char *)p > (dt_struct + fdt_size_dt_struct(fdtp)))
125		return (-1);
126
127	offset = (const char *)p - dt_struct;
128	if (offset < 0)
129		return (-1);
130
131	return (offset);
132}
133
134/* Return the next sibling of this node or 0. */
135static phandle_t
136ofw_fdt_peer(ofw_t ofw, phandle_t node)
137{
138	phandle_t p;
139	int depth, offset;
140
141	if (node == 0) {
142		/* Find root node */
143		offset = fdt_path_offset(fdtp, "/");
144		p = (phandle_t)fdt_offset_ptr(fdtp, offset, sizeof(p));
145
146		return (p);
147	}
148
149	offset = fdt_phandle_offset(node);
150	if (offset < 0)
151		return (0);
152
153	for (depth = 1, offset = fdt_next_node(fdtp, offset, &depth);
154	    offset >= 0;
155	    offset = fdt_next_node(fdtp, offset, &depth)) {
156		if (depth < 0)
157			return (0);
158		if (depth == 1) {
159			p = (phandle_t)fdt_offset_ptr(fdtp, offset, sizeof(p));
160			return (p);
161		}
162	}
163
164	return (0);
165}
166
167/* Return the first child of this node or 0. */
168static phandle_t
169ofw_fdt_child(ofw_t ofw, phandle_t node)
170{
171	phandle_t p;
172	int depth, offset;
173
174	offset = fdt_phandle_offset(node);
175	if (offset < 0)
176		return (0);
177
178	for (depth = 0, offset = fdt_next_node(fdtp, offset, &depth);
179	    (offset >= 0) && (depth > 0);
180	    offset = fdt_next_node(fdtp, offset, &depth)) {
181		if (depth < 0)
182			return (0);
183		if (depth == 1) {
184			p = (phandle_t)fdt_offset_ptr(fdtp, offset, sizeof(p));
185			return (p);
186		}
187	}
188
189	return (0);
190}
191
192/* Return the parent of this node or 0. */
193static phandle_t
194ofw_fdt_parent(ofw_t ofw, phandle_t node)
195{
196	phandle_t p;
197	int offset, paroffset;
198
199	offset = fdt_phandle_offset(node);
200	if (offset < 0)
201		return (0);
202
203	paroffset = fdt_parent_offset(fdtp, offset);
204	p = (phandle_t)fdt_offset_ptr(fdtp, paroffset, sizeof(phandle_t));
205	return (p);
206}
207
208/* Return the package handle that corresponds to an instance handle. */
209static phandle_t
210ofw_fdt_instance_to_package(ofw_t ofw, ihandle_t instance)
211{
212	phandle_t p;
213	int offset;
214
215	/*
216	 * Note: FDT does not have the notion of instances, but we somewhat
217	 * abuse the semantics and let treat as 'instance' the internal
218	 * 'phandle' prop, so that ofw I/F consumers have a uniform way of
219	 * translation between internal representation (which appear in some
220	 * contexts as property values) and effective phandles.
221	 */
222	offset = fdt_node_offset_by_phandle(fdtp, instance);
223	if (offset < 0)
224		return (0);
225
226	p = (phandle_t)fdt_offset_ptr(fdtp, offset, sizeof(phandle_t));
227	return (p);
228}
229
230/* Get the length of a property of a package. */
231static ssize_t
232ofw_fdt_getproplen(ofw_t ofw, phandle_t package, const char *propname)
233{
234	const struct fdt_property *prop;
235	int offset, len;
236
237	offset = fdt_phandle_offset(package);
238	if (offset < 0)
239		return (0);
240
241	if (strcmp(propname, "name") == 0) {
242		/* Emulate the 'name' property */
243		fdt_get_name(fdtp, offset, &len);
244		return (len + 1);
245	}
246
247	len = 0;
248	prop = fdt_get_property(fdtp, offset, propname, &len);
249
250	return (len);
251}
252
253/* Get the value of a property of a package. */
254static ssize_t
255ofw_fdt_getprop(ofw_t ofw, phandle_t package, const char *propname, void *buf,
256    size_t buflen)
257{
258	const void *prop;
259	const char *name;
260	int len, offset;
261
262	offset = fdt_phandle_offset(package);
263	if (offset < 0)
264		return (0);
265
266	if (strcmp(propname, "name") == 0) {
267		/* Emulate the 'name' property */
268		name = fdt_get_name(fdtp, offset, &len);
269		strncpy(buf, name, buflen);
270		if (len + 1 > buflen)
271			len = buflen;
272		return (len + 1);
273	}
274
275	prop = fdt_getprop(fdtp, offset, propname, &len);
276	if (prop == NULL)
277		return (0);
278
279	if (len > buflen)
280		len = buflen;
281	bcopy(prop, buf, len);
282	return (len);
283}
284
285static int
286fdt_nextprop(int offset, char *buf, size_t size)
287{
288	const struct fdt_property *prop;
289	const char *name;
290	uint32_t tag;
291	int nextoffset, depth;
292
293	depth = 0;
294	tag = fdt_next_tag(fdtp, offset, &nextoffset);
295
296	/* Find the next prop */
297	do {
298		offset = nextoffset;
299		tag = fdt_next_tag(fdtp, offset, &nextoffset);
300
301		if (tag == FDT_BEGIN_NODE)
302			depth++;
303		else if (tag == FDT_END_NODE)
304			depth--;
305		else if ((tag == FDT_PROP) && (depth == 0)) {
306			prop =
307			    (const struct fdt_property *)fdt_offset_ptr(fdtp,
308			    offset, sizeof(*prop));
309			name = fdt_string(fdtp,
310			    fdt32_to_cpu(prop->nameoff));
311			strncpy(buf, name, size);
312			return (strlen(name));
313		} else
314			depth = -1;
315	} while (depth >= 0);
316
317	return (0);
318}
319
320/*
321 * Get the next property of a package. Return the actual len of retrieved
322 * prop name.
323 */
324static int
325ofw_fdt_nextprop(ofw_t ofw, phandle_t package, const char *previous, char *buf,
326    size_t size)
327{
328	const struct fdt_property *prop;
329	int offset, rv;
330
331	offset = fdt_phandle_offset(package);
332	if (offset < 0)
333		return (0);
334
335	if (previous == NULL)
336		/* Find the first prop in the node */
337		return (fdt_nextprop(offset, buf, size));
338
339	/*
340	 * Advance to the previous prop
341	 */
342	prop = fdt_get_property(fdtp, offset, previous, NULL);
343	if (prop == NULL)
344		return (0);
345
346	offset = fdt_phandle_offset((phandle_t)prop);
347	rv = fdt_nextprop(offset, buf, size);
348	return (rv);
349}
350
351/* Set the value of a property of a package. */
352static int
353ofw_fdt_setprop(ofw_t ofw, phandle_t package, const char *propname,
354    const void *buf, size_t len)
355{
356	int offset;
357
358	offset = fdt_phandle_offset(package);
359	if (offset < 0)
360		return (-1);
361
362	return (fdt_setprop_inplace(fdtp, offset, propname, buf, len));
363}
364
365/* Convert a device specifier to a fully qualified pathname. */
366static ssize_t
367ofw_fdt_canon(ofw_t ofw, const char *device, char *buf, size_t len)
368{
369
370	return (-1);
371}
372
373/* Return a package handle for the specified device. */
374static phandle_t
375ofw_fdt_finddevice(ofw_t ofw, const char *device)
376{
377	phandle_t p;
378	int offset;
379
380	offset = fdt_path_offset(fdtp, device);
381
382	p = (phandle_t)fdt_offset_ptr(fdtp, offset, sizeof(p));
383
384	return (p);
385}
386
387/* Return the fully qualified pathname corresponding to an instance. */
388static ssize_t
389ofw_fdt_instance_to_path(ofw_t ofw, ihandle_t instance, char *buf, size_t len)
390{
391
392	return (-1);
393}
394
395/* Return the fully qualified pathname corresponding to a package. */
396static ssize_t
397ofw_fdt_package_to_path(ofw_t ofw, phandle_t package, char *buf, size_t len)
398{
399
400	return (-1);
401}
402
403static int
404ofw_fdt_fixup(ofw_t ofw)
405{
406#define FDT_MODEL_LEN	80
407	char model[FDT_MODEL_LEN];
408	phandle_t root;
409	ssize_t len;
410	int i;
411
412	if ((root = ofw_fdt_finddevice(ofw, "/")) == 0)
413		return (ENODEV);
414
415	if ((len = ofw_fdt_getproplen(ofw, root, "model")) <= 0)
416		return (0);
417
418	bzero(model, FDT_MODEL_LEN);
419	if (ofw_fdt_getprop(ofw, root, "model", model, FDT_MODEL_LEN) <= 0)
420		return (0);
421
422	/*
423	 * Search fixup table and call handler if appropriate.
424	 */
425	for (i = 0; fdt_fixup_table[i].model != NULL; i++) {
426		if (strncmp(model, fdt_fixup_table[i].model,
427		    FDT_MODEL_LEN) != 0)
428			continue;
429
430		if (fdt_fixup_table[i].handler != NULL)
431			(*fdt_fixup_table[i].handler)(root);
432	}
433
434	return (0);
435}
436
437static int
438ofw_fdt_interpret(ofw_t ofw, const char *cmd, int nret, cell_t *retvals)
439{
440	int rv;
441
442	/*
443	 * Note: FDT does not have the possibility to 'interpret' commands,
444	 * but we abuse the interface a bit to use it for doing non-standard
445	 * operations on the device tree blob.
446	 *
447	 * Currently the only supported 'command' is to trigger performing
448	 * fixups.
449	 */
450	if (strncmp("perform-fixup", cmd, 13) != 0)
451		return (0);
452
453	rv = ofw_fdt_fixup(ofw);
454	if (nret > 0)
455		retvals[0] = rv;
456
457	return (rv);
458}
459