openfirmio.c revision 330897
1/*	$NetBSD: openfirmio.c,v 1.4 2002/09/06 13:23:19 gehenna Exp $ */
2
3#include <sys/cdefs.h>
4__FBSDID("$FreeBSD: stable/11/sys/dev/ofw/openfirmio.c 330897 2018-03-14 03:19:51Z eadler $");
5
6/*-
7 * SPDX-License-Identifier: BSD-3-Clause
8 *
9 * Copyright (c) 1992, 1993
10 *	The Regents of the University of California.  All rights reserved.
11 *
12 * This software was developed by the Computer Systems Engineering group
13 * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
14 * contributed to Berkeley.
15 *
16 * All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 *	This product includes software developed by the University of
19 *	California, Lawrence Berkeley Laboratory.
20 *
21 * Redistribution and use in source and binary forms, with or without
22 * modification, are permitted provided that the following conditions
23 * are met:
24 * 1. Redistributions of source code must retain the above copyright
25 *    notice, this list of conditions and the following disclaimer.
26 * 2. Redistributions in binary form must reproduce the above copyright
27 *    notice, this list of conditions and the following disclaimer in the
28 *    documentation and/or other materials provided with the distribution.
29 * 4. Neither the name of the University nor the names of its contributors
30 *    may be used to endorse or promote products derived from this software
31 *    without specific prior written permission.
32 *
33 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
34 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
35 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
37 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
38 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
39 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
41 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
42 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 * SUCH DAMAGE.
44 *
45 *	@(#)openfirm.c	8.1 (Berkeley) 6/11/93
46 *
47 */
48
49#include <sys/param.h>
50#include <sys/systm.h>
51#include <sys/conf.h>
52#include <sys/errno.h>
53#include <sys/fcntl.h>
54#include <sys/ioccom.h>
55#include <sys/kernel.h>
56#include <sys/malloc.h>
57#include <sys/module.h>
58
59#include <dev/ofw/openfirmio.h>
60
61static struct cdev *openfirm_dev;
62
63static d_ioctl_t openfirm_ioctl;
64
65#define	OPENFIRM_MINOR	0
66
67static struct cdevsw openfirm_cdevsw = {
68	.d_version =	D_VERSION,
69	.d_flags =	D_NEEDGIANT,
70	.d_ioctl =	openfirm_ioctl,
71	.d_name =	"openfirm",
72};
73
74static phandle_t lastnode;	/* speed hack */
75
76static int openfirm_checkid(phandle_t, phandle_t);
77static int openfirm_getstr(int, const char *, char **);
78
79/*
80 * Verify target ID is valid (exists in the OPENPROM tree), as
81 * listed from node ID sid forward.
82 */
83static int
84openfirm_checkid(phandle_t sid, phandle_t tid)
85{
86
87	for (; sid != 0; sid = OF_peer(sid))
88		if (sid == tid || openfirm_checkid(OF_child(sid), tid))
89			return (1);
90
91	return (0);
92}
93
94static int
95openfirm_getstr(int len, const char *user, char **cpp)
96{
97	int error;
98	char *cp;
99
100	/* Reject obvious bogus requests. */
101	if ((u_int)len > OFIOCMAXNAME)
102		return (ENAMETOOLONG);
103
104	*cpp = cp = malloc(len + 1, M_TEMP, M_WAITOK);
105	if (cp == NULL)
106		return (ENOMEM);
107	error = copyin(user, cp, len);
108	cp[len] = '\0';
109	return (error);
110}
111
112int
113openfirm_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags,
114    struct thread *td)
115{
116	struct ofiocdesc *of;
117	phandle_t node;
118	int len, ok, error;
119	char *name, *value;
120	char newname[32];
121
122	if ((flags & FREAD) == 0)
123		return (EBADF);
124
125	of = (struct ofiocdesc *)data;
126	switch (cmd) {
127	case OFIOCGETOPTNODE:
128		*(phandle_t *) data = OF_finddevice("/options");
129		return (0);
130	case OFIOCGET:
131	case OFIOCSET:
132	case OFIOCNEXTPROP:
133	case OFIOCFINDDEVICE:
134	case OFIOCGETPROPLEN:
135		node = of->of_nodeid;
136		break;
137	case OFIOCGETNEXT:
138	case OFIOCGETCHILD:
139		node = *(phandle_t *)data;
140		break;
141	default:
142		return (ENOIOCTL);
143	}
144
145	if (node != 0 && node != lastnode) {
146		/* Not an easy one, we must search for it. */
147		ok = openfirm_checkid(OF_peer(0), node);
148		if (!ok)
149			return (EINVAL);
150		lastnode = node;
151	}
152
153	name = value = NULL;
154	error = 0;
155	switch (cmd) {
156
157	case OFIOCGET:
158	case OFIOCGETPROPLEN:
159		if (node == 0)
160			return (EINVAL);
161		error = openfirm_getstr(of->of_namelen, of->of_name, &name);
162		if (error)
163			break;
164		len = OF_getproplen(node, name);
165		if (cmd == OFIOCGETPROPLEN) {
166			of->of_buflen = len;
167			break;
168		}
169		if (len > of->of_buflen) {
170			error = ENOMEM;
171			break;
172		}
173		of->of_buflen = len;
174		/* -1 means no entry; 0 means no value. */
175		if (len <= 0)
176			break;
177		value = malloc(len, M_TEMP, M_WAITOK);
178		if (value == NULL) {
179			error = ENOMEM;
180			break;
181		}
182		len = OF_getprop(node, name, (void *)value, len);
183		error = copyout(value, of->of_buf, len);
184		break;
185
186	case OFIOCSET:
187		/*
188		 * Note: Text string values for at least the /options node
189		 * have to be null-terminated and the length parameter must
190		 * include this terminating null.  However, like OF_getprop(),
191		 * OF_setprop() will return the actual length of the text
192		 * string, i.e. omitting the terminating null.
193		 */
194		if ((flags & FWRITE) == 0)
195			return (EBADF);
196		if (node == 0)
197			return (EINVAL);
198		if ((u_int)of->of_buflen > OFIOCMAXVALUE)
199			return (ENAMETOOLONG);
200		error = openfirm_getstr(of->of_namelen, of->of_name, &name);
201		if (error)
202			break;
203		value = malloc(of->of_buflen, M_TEMP, M_WAITOK);
204		if (value == NULL) {
205			error = ENOMEM;
206			break;
207		}
208		error = copyin(of->of_buf, value, of->of_buflen);
209		if (error)
210			break;
211		len = OF_setprop(node, name, value, of->of_buflen);
212		if (len < 0)
213			error = EINVAL;
214		of->of_buflen = len;
215		break;
216
217	case OFIOCNEXTPROP:
218		if (node == 0 || of->of_buflen < 0)
219			return (EINVAL);
220		if (of->of_namelen != 0) {
221			error = openfirm_getstr(of->of_namelen, of->of_name,
222			    &name);
223			if (error)
224				break;
225		}
226		ok = OF_nextprop(node, name, newname, sizeof(newname));
227		if (ok == 0) {
228			error = ENOENT;
229			break;
230		}
231		if (ok == -1) {
232			error = EINVAL;
233			break;
234		}
235		len = strlen(newname) + 1;
236		if (len > of->of_buflen)
237			len = of->of_buflen;
238		else
239			of->of_buflen = len;
240		error = copyout(newname, of->of_buf, len);
241		break;
242
243	case OFIOCGETNEXT:
244		node = OF_peer(node);
245		*(phandle_t *)data = lastnode = node;
246		break;
247
248	case OFIOCGETCHILD:
249		if (node == 0)
250			return (EINVAL);
251		node = OF_child(node);
252		*(phandle_t *)data = lastnode = node;
253		break;
254
255	case OFIOCFINDDEVICE:
256		error = openfirm_getstr(of->of_namelen, of->of_name, &name);
257		if (error)
258			break;
259		node = OF_finddevice(name);
260		if (node == 0 || node == -1) {
261			error = ENOENT;
262			break;
263		}
264		of->of_nodeid = lastnode = node;
265		break;
266	}
267
268	if (name != NULL)
269		free(name, M_TEMP);
270	if (value != NULL)
271		free(value, M_TEMP);
272
273	return (error);
274}
275
276static int
277openfirm_modevent(module_t mod, int type, void *data)
278{
279
280	switch(type) {
281	case MOD_LOAD:
282		if (bootverbose)
283			printf("openfirm: <Open Firmware control device>\n");
284		/*
285		 * Allow only root access by default; this device may allow
286		 * users to peek into firmware passwords, and likely to crash
287		 * the machine on some boxen due to firmware quirks.
288		 */
289		openfirm_dev = make_dev(&openfirm_cdevsw, OPENFIRM_MINOR,
290		    UID_ROOT, GID_WHEEL, 0600, "openfirm");
291		return 0;
292
293	case MOD_UNLOAD:
294		destroy_dev(openfirm_dev);
295		return 0;
296
297	case MOD_SHUTDOWN:
298		return 0;
299
300	default:
301		return EOPNOTSUPP;
302	}
303}
304
305DEV_MODULE(openfirm, openfirm_modevent, NULL);
306