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