1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2009-2010 The FreeBSD Foundation
5 *
6 * This software was developed by Semihalf under sponsorship from
7 * the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/param.h>
32#include <sys/ctype.h>
33#include <sys/kernel.h>
34#include <sys/malloc.h>
35#include <sys/systm.h>
36
37#include <contrib/libfdt/libfdt.h>
38
39#include <machine/stdarg.h>
40
41#include <dev/fdt/fdt_common.h>
42#include <dev/ofw/ofwvar.h>
43#include <dev/ofw/openfirm.h>
44#include <dev/ofw/ofw_bus_subr.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
55#if defined(__arm__)
56#if defined(SOC_MV_ARMADAXP) || defined(SOC_MV_ARMADA38X) || \
57    defined(SOC_MV_DISCOVERY) || defined(SOC_MV_DOVE) || \
58    defined(SOC_MV_FREY) || defined(SOC_MV_KIRKWOOD) || \
59    defined(SOC_MV_LOKIPLUS) || defined(SOC_MV_ORION)
60#define FDT_MARVELL
61#endif
62#endif
63
64static int ofw_fdt_init(ofw_t, void *);
65static phandle_t ofw_fdt_peer(ofw_t, phandle_t);
66static phandle_t ofw_fdt_child(ofw_t, phandle_t);
67static phandle_t ofw_fdt_parent(ofw_t, phandle_t);
68static phandle_t ofw_fdt_instance_to_package(ofw_t, ihandle_t);
69static ssize_t ofw_fdt_getproplen(ofw_t, phandle_t, const char *);
70static ssize_t ofw_fdt_getprop(ofw_t, phandle_t, const char *, void *, size_t);
71static int ofw_fdt_nextprop(ofw_t, phandle_t, const char *, char *, size_t);
72static int ofw_fdt_setprop(ofw_t, phandle_t, const char *, const void *,
73    size_t);
74static ssize_t ofw_fdt_canon(ofw_t, const char *, char *, size_t);
75static phandle_t ofw_fdt_finddevice(ofw_t, const char *);
76static ssize_t ofw_fdt_instance_to_path(ofw_t, ihandle_t, char *, size_t);
77static ssize_t ofw_fdt_package_to_path(ofw_t, phandle_t, char *, size_t);
78static int ofw_fdt_interpret(ofw_t, const char *, int, cell_t *);
79
80static ofw_method_t ofw_fdt_methods[] = {
81	OFWMETHOD(ofw_init,			ofw_fdt_init),
82	OFWMETHOD(ofw_peer,			ofw_fdt_peer),
83	OFWMETHOD(ofw_child,			ofw_fdt_child),
84	OFWMETHOD(ofw_parent,			ofw_fdt_parent),
85	OFWMETHOD(ofw_instance_to_package,	ofw_fdt_instance_to_package),
86	OFWMETHOD(ofw_getproplen,		ofw_fdt_getproplen),
87	OFWMETHOD(ofw_getprop,			ofw_fdt_getprop),
88	OFWMETHOD(ofw_nextprop,			ofw_fdt_nextprop),
89	OFWMETHOD(ofw_setprop,			ofw_fdt_setprop),
90	OFWMETHOD(ofw_canon,			ofw_fdt_canon),
91	OFWMETHOD(ofw_finddevice,		ofw_fdt_finddevice),
92	OFWMETHOD(ofw_instance_to_path,		ofw_fdt_instance_to_path),
93	OFWMETHOD(ofw_package_to_path,		ofw_fdt_package_to_path),
94	OFWMETHOD(ofw_interpret,		ofw_fdt_interpret),
95	{ 0, 0 }
96};
97
98static ofw_def_t ofw_fdt = {
99	OFW_FDT,
100	ofw_fdt_methods,
101	0
102};
103OFW_DEF(ofw_fdt);
104
105#define	FDT_FBSDVER_LEN	16
106#define	FDT_MODEL_LEN	80
107#define	FDT_COMPAT_LEN	255
108#define	FDT_SERIAL_LEN	32
109
110static void *fdtp = NULL;
111static char fdt_model[FDT_MODEL_LEN];
112static char fdt_compatible[FDT_COMPAT_LEN];
113static char fdt_fbsd_version[FDT_FBSDVER_LEN];
114static char fdt_serial[FDT_SERIAL_LEN];
115
116static int
117sysctl_handle_dtb(SYSCTL_HANDLER_ARGS)
118{
119
120        return (sysctl_handle_opaque(oidp, fdtp, fdt_totalsize(fdtp), req));
121}
122
123static void
124sysctl_register_fdt_oid(void *arg)
125{
126
127	/* If there is no FDT registered, skip adding the sysctl */
128	if (fdtp == NULL)
129		return;
130
131	SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw_fdt), OID_AUTO, "dtb",
132	    CTLTYPE_OPAQUE | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, 0,
133	    sysctl_handle_dtb, "", "Device Tree Blob");
134	if (fdt_model[0] != '\0')
135		SYSCTL_ADD_STRING(NULL, SYSCTL_STATIC_CHILDREN(_hw_fdt),
136		    OID_AUTO, "model", CTLFLAG_RD, fdt_model,
137		    FDT_MODEL_LEN, "System board model");
138	if (fdt_compatible[0] != '\0')
139		SYSCTL_ADD_STRING(NULL, SYSCTL_STATIC_CHILDREN(_hw_fdt),
140		    OID_AUTO, "compatible", CTLFLAG_RD, fdt_compatible,
141		    FDT_COMPAT_LEN, "Compatible platforms");
142	if (fdt_fbsd_version[0] != '\0')
143		SYSCTL_ADD_STRING(NULL, SYSCTL_STATIC_CHILDREN(_hw_fdt),
144		    OID_AUTO, "freebsd-version", CTLFLAG_RD, fdt_fbsd_version,
145		    FDT_FBSDVER_LEN, "FreeBSD DTS branding version");
146	if (fdt_serial[0] != '\0')
147		SYSCTL_ADD_STRING(NULL, SYSCTL_STATIC_CHILDREN(_hw_fdt),
148		    OID_AUTO, "serial-number", CTLFLAG_RD, fdt_serial,
149		    FDT_SERIAL_LEN, "Serial number");
150}
151SYSINIT(dtb_oid, SI_SUB_KMEM, SI_ORDER_ANY, sysctl_register_fdt_oid, NULL);
152
153static int
154ofw_fdt_init(ofw_t ofw, void *data)
155{
156	phandle_t root;
157	ssize_t len;
158	int err;
159	int i;
160
161	/* Check FDT blob integrity */
162	if ((err = fdt_check_header(data)) != 0)
163		return (err);
164
165	fdtp = data;
166	root = ofw_fdt_finddevice(NULL, "/");
167	len = ofw_fdt_getproplen(NULL, root, "model");
168	if (len > 0 && len <= FDT_MODEL_LEN) {
169		bzero(fdt_model, FDT_MODEL_LEN);
170		ofw_fdt_getprop(NULL, root, "model", fdt_model, FDT_MODEL_LEN);
171	}
172	len = ofw_fdt_getproplen(NULL, root, "compatible");
173	if (len > 0 && len <= FDT_COMPAT_LEN) {
174		bzero(fdt_compatible, FDT_COMPAT_LEN);
175		ofw_fdt_getprop(NULL, root, "compatible", fdt_compatible,
176		    FDT_COMPAT_LEN);
177		/* Replace the middle '\0' with ' ' */
178		for (i = 0; i < len - 1; i++)
179			if (fdt_compatible[i] == '\0')
180				fdt_compatible[i] = ' ';
181	}
182	len = ofw_fdt_getproplen(NULL, root, "freebsd,dts-version");
183	if (len > 0 && len <= FDT_FBSDVER_LEN) {
184		bzero(fdt_fbsd_version, FDT_FBSDVER_LEN);
185		ofw_fdt_getprop(NULL, root, "freebsd,dts-version",
186		  fdt_fbsd_version, FDT_FBSDVER_LEN);
187	}
188	len = ofw_fdt_getproplen(NULL, root, "serial-number");
189	if (len > 0 && len <= FDT_SERIAL_LEN) {
190		bzero(fdt_serial, FDT_SERIAL_LEN);
191		ofw_fdt_getprop(NULL, root, "serial-number",
192		    fdt_serial, FDT_SERIAL_LEN);
193		/*
194		 * Non-standard property; check for NUL-terminated
195		 * printable string.
196		 */
197		for (i = 0; i < len - 1; i++) {
198			if (!isprint(fdt_serial[i])) {
199				fdt_serial[0] = '\0';
200				break;
201			}
202		}
203		if (fdt_serial[len - 1] != '\0')
204			fdt_serial[0] = '\0';
205	}
206	return (0);
207}
208
209/*
210 * Device tree functions.
211 *
212 * We use the offset from fdtp to the node as the 'phandle' in OF interface.
213 *
214 * phandle is a u32 value, therefore we cannot use the pointer to node as
215 * phandle in 64 bit. We also do not use the usual fdt offset as phandle,
216 * as it can be 0, and the OF interface has special meaning for phandle 0.
217 */
218
219static phandle_t
220fdt_offset_phandle(int offset)
221{
222	if (offset < 0)
223		return (0);
224	return ((phandle_t)offset + fdt_off_dt_struct(fdtp));
225}
226
227static int
228fdt_phandle_offset(phandle_t p)
229{
230	int pint = (int)p;
231	int dtoff = fdt_off_dt_struct(fdtp);
232
233	if (pint < dtoff)
234		return (-1);
235	return (pint - dtoff);
236}
237
238/* Return the next sibling of this node or 0. */
239static phandle_t
240ofw_fdt_peer(ofw_t ofw, phandle_t node)
241{
242	int offset;
243
244	if (node == 0) {
245		/* Find root node */
246		offset = fdt_path_offset(fdtp, "/");
247
248		return (fdt_offset_phandle(offset));
249	}
250
251	offset = fdt_phandle_offset(node);
252	if (offset < 0)
253		return (0);
254	offset = fdt_next_subnode(fdtp, offset);
255	return (fdt_offset_phandle(offset));
256}
257
258/* Return the first child of this node or 0. */
259static phandle_t
260ofw_fdt_child(ofw_t ofw, phandle_t node)
261{
262	int offset;
263
264	offset = fdt_phandle_offset(node);
265	if (offset < 0)
266		return (0);
267	offset = fdt_first_subnode(fdtp, offset);
268	return (fdt_offset_phandle(offset));
269}
270
271/* Return the parent of this node or 0. */
272static phandle_t
273ofw_fdt_parent(ofw_t ofw, phandle_t node)
274{
275	int offset, paroffset;
276
277	offset = fdt_phandle_offset(node);
278	if (offset < 0)
279		return (0);
280
281	paroffset = fdt_parent_offset(fdtp, offset);
282	return (fdt_offset_phandle(paroffset));
283}
284
285/* Return the package handle that corresponds to an instance handle. */
286static phandle_t
287ofw_fdt_instance_to_package(ofw_t ofw, ihandle_t instance)
288{
289
290	/* Where real OF uses ihandles in the tree, FDT uses xref phandles */
291	return (OF_node_from_xref(instance));
292}
293
294/* Get the length of a property of a package. */
295static ssize_t
296ofw_fdt_getproplen(ofw_t ofw, phandle_t package, const char *propname)
297{
298	const void *prop;
299	int offset, len;
300
301	offset = fdt_phandle_offset(package);
302	if (offset < 0)
303		return (-1);
304
305	len = -1;
306	prop = fdt_getprop(fdtp, offset, propname, &len);
307
308	if (prop == NULL && strcmp(propname, "name") == 0) {
309		/* Emulate the 'name' property */
310		fdt_get_name(fdtp, offset, &len);
311		return (len + 1);
312	}
313
314	if (prop == NULL && offset == fdt_path_offset(fdtp, "/chosen")) {
315		if (strcmp(propname, "fdtbootcpu") == 0)
316			return (sizeof(cell_t));
317		if (strcmp(propname, "fdtmemreserv") == 0)
318			return (sizeof(uint64_t)*2*fdt_num_mem_rsv(fdtp));
319	}
320
321	if (prop == NULL)
322		return (-1);
323
324	return (len);
325}
326
327/* Get the value of a property of a package. */
328static ssize_t
329ofw_fdt_getprop(ofw_t ofw, phandle_t package, const char *propname, void *buf,
330    size_t buflen)
331{
332	const void *prop;
333	const char *name;
334	int len, offset;
335	uint32_t cpuid;
336
337	offset = fdt_phandle_offset(package);
338	if (offset < 0)
339		return (-1);
340
341	prop = fdt_getprop(fdtp, offset, propname, &len);
342
343	if (prop == NULL && strcmp(propname, "name") == 0) {
344		/* Emulate the 'name' property */
345		name = fdt_get_name(fdtp, offset, &len);
346		strncpy(buf, name, buflen);
347		return (len + 1);
348	}
349
350	if (prop == NULL && offset == fdt_path_offset(fdtp, "/chosen")) {
351		if (strcmp(propname, "fdtbootcpu") == 0) {
352			cpuid = cpu_to_fdt32(fdt_boot_cpuid_phys(fdtp));
353			len = sizeof(cpuid);
354			prop = &cpuid;
355		}
356		if (strcmp(propname, "fdtmemreserv") == 0) {
357			prop = (char *)fdtp + fdt_off_mem_rsvmap(fdtp);
358			len = sizeof(uint64_t)*2*fdt_num_mem_rsv(fdtp);
359		}
360	}
361
362	if (prop == NULL)
363		return (-1);
364
365	bcopy(prop, buf, min(len, buflen));
366
367	return (len);
368}
369
370/*
371 * Get the next property of a package. Return values:
372 *  -1: package or previous property does not exist
373 *   0: no more properties
374 *   1: success
375 */
376static int
377ofw_fdt_nextprop(ofw_t ofw, phandle_t package, const char *previous, char *buf,
378    size_t size)
379{
380	const void *prop;
381	const char *name;
382	int offset;
383
384	offset = fdt_phandle_offset(package);
385	if (offset < 0)
386		return (-1);
387
388	if (previous == NULL)
389		/* Find the first prop in the node */
390		offset = fdt_first_property_offset(fdtp, offset);
391	else {
392		fdt_for_each_property_offset(offset, fdtp, offset) {
393			prop = fdt_getprop_by_offset(fdtp, offset, &name, NULL);
394			if (prop == NULL)
395				return (-1); /* Internal error */
396			/* Skip until we find 'previous', then bail out */
397			if (strcmp(name, previous) != 0)
398				continue;
399			offset = fdt_next_property_offset(fdtp, offset);
400			break;
401		}
402	}
403
404	if (offset < 0)
405		return (0); /* No properties */
406
407	prop = fdt_getprop_by_offset(fdtp, offset, &name, &offset);
408	if (prop == NULL)
409		return (-1); /* Internal error */
410
411	strncpy(buf, name, size);
412
413	return (1);
414}
415
416/* Set the value of a property of a package. */
417static int
418ofw_fdt_setprop(ofw_t ofw, phandle_t package, const char *propname,
419    const void *buf, size_t len)
420{
421	int offset;
422
423	offset = fdt_phandle_offset(package);
424	if (offset < 0)
425		return (-1);
426
427	if (fdt_setprop_inplace(fdtp, offset, propname, buf, len) != 0)
428		/* Try to add property, when setting value inplace failed */
429		return (fdt_setprop(fdtp, offset, propname, buf, len));
430
431	return (0);
432}
433
434/* Convert a device specifier to a fully qualified pathname. */
435static ssize_t
436ofw_fdt_canon(ofw_t ofw, const char *device, char *buf, size_t len)
437{
438
439	return (-1);
440}
441
442/* Return a package handle for the specified device. */
443static phandle_t
444ofw_fdt_finddevice(ofw_t ofw, const char *device)
445{
446	int offset;
447
448	offset = fdt_path_offset(fdtp, device);
449	if (offset < 0)
450		return (-1);
451	return (fdt_offset_phandle(offset));
452}
453
454/* Return the fully qualified pathname corresponding to an instance. */
455static ssize_t
456ofw_fdt_instance_to_path(ofw_t ofw, ihandle_t instance, char *buf, size_t len)
457{
458	phandle_t phandle;
459
460	phandle = OF_instance_to_package(instance);
461	if (phandle == -1)
462		return (-1);
463
464	return (OF_package_to_path(phandle, buf, len));
465}
466
467/* Return the fully qualified pathname corresponding to a package. */
468static ssize_t
469ofw_fdt_package_to_path(ofw_t ofw, phandle_t package, char *buf, size_t len)
470{
471
472	return (-1);
473}
474
475#if defined(FDT_MARVELL)
476static int
477ofw_fdt_fixup(ofw_t ofw)
478{
479	char model[FDT_MODEL_LEN];
480	phandle_t root;
481	ssize_t len;
482	int i;
483
484	if ((root = ofw_fdt_finddevice(ofw, "/")) == -1)
485		return (ENODEV);
486
487	if ((len = ofw_fdt_getproplen(ofw, root, "model")) <= 0)
488		return (0);
489
490	bzero(model, FDT_MODEL_LEN);
491	if (ofw_fdt_getprop(ofw, root, "model", model, FDT_MODEL_LEN) <= 0)
492		return (0);
493
494	/*
495	 * Search fixup table and call handler if appropriate.
496	 */
497	for (i = 0; fdt_fixup_table[i].model != NULL; i++) {
498		if (strncmp(model, fdt_fixup_table[i].model,
499		    FDT_MODEL_LEN) != 0)
500			/*
501			 * Sometimes it's convenient to provide one
502			 * fixup entry that refers to many boards.
503			 * To handle this case, simply check if model
504			 * is compatible parameter
505			 */
506			if(!ofw_bus_node_is_compatible(root,
507			    fdt_fixup_table[i].model))
508				continue;
509
510		if (fdt_fixup_table[i].handler != NULL)
511			(*fdt_fixup_table[i].handler)(root);
512	}
513
514	return (0);
515}
516#endif
517
518static int
519ofw_fdt_interpret(ofw_t ofw, const char *cmd, int nret, cell_t *retvals)
520{
521#if defined(FDT_MARVELL)
522	int rv;
523
524	/*
525	 * Note: FDT does not have the possibility to 'interpret' commands,
526	 * but we abuse the interface a bit to use it for doing non-standard
527	 * operations on the device tree blob.
528	 *
529	 * Currently the only supported 'command' is to trigger performing
530	 * fixups.
531	 */
532	if (strncmp("perform-fixup", cmd, 13) != 0)
533		return (0);
534
535	rv = ofw_fdt_fixup(ofw);
536	if (nret > 0)
537		retvals[0] = rv;
538
539	return (rv);
540#else
541	return (0);
542#endif
543}
544