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 * 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 * 3. 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	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[OFIOCSUGGPROPNAMELEN];
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		len = OF_getprop(node, name, (void *)value, len);
177		error = copyout(value, of->of_buf, len);
178		break;
179
180	case OFIOCSET:
181		/*
182		 * Note: Text string values for at least the /options node
183		 * have to be null-terminated and the length parameter must
184		 * include this terminating null.  However, like OF_getprop(),
185		 * OF_setprop() will return the actual length of the text
186		 * string, i.e. omitting the terminating null.
187		 */
188		if ((flags & FWRITE) == 0)
189			return (EBADF);
190		if (node == 0)
191			return (EINVAL);
192		if ((u_int)of->of_buflen > OFIOCMAXVALUE)
193			return (ENAMETOOLONG);
194		error = openfirm_getstr(of->of_namelen, of->of_name, &name);
195		if (error)
196			break;
197		value = malloc(of->of_buflen, M_TEMP, M_WAITOK);
198		error = copyin(of->of_buf, value, of->of_buflen);
199		if (error)
200			break;
201		len = OF_setprop(node, name, value, of->of_buflen);
202		if (len < 0)
203			error = EINVAL;
204		of->of_buflen = len;
205		break;
206
207	case OFIOCNEXTPROP:
208		if (node == 0 || of->of_buflen < 0)
209			return (EINVAL);
210		if (of->of_namelen != 0) {
211			error = openfirm_getstr(of->of_namelen, of->of_name,
212			    &name);
213			if (error)
214				break;
215		}
216		ok = OF_nextprop(node, name, newname, sizeof(newname));
217		if (ok == 0) {
218			error = ENOENT;
219			break;
220		}
221		if (ok == -1) {
222			error = EINVAL;
223			break;
224		}
225		len = strlen(newname) + 1;
226		if (len > of->of_buflen) {
227			/*
228			 * Passed buffer was insufficient.
229			 *
230			 * Instead of returning an error here, truncate the
231			 * property name to fit the buffer.
232			 *
233			 * This allows us to retain compatibility with old
234			 * tools which always pass a 32 character buffer.
235			 */
236			len = of->of_buflen;
237			newname[len - 1] = '\0';
238		}
239		else
240			of->of_buflen = len;
241		error = copyout(newname, of->of_buf, len);
242		break;
243
244	case OFIOCGETNEXT:
245		node = OF_peer(node);
246		*(phandle_t *)data = lastnode = node;
247		break;
248
249	case OFIOCGETCHILD:
250		if (node == 0)
251			return (EINVAL);
252		node = OF_child(node);
253		*(phandle_t *)data = lastnode = node;
254		break;
255
256	case OFIOCFINDDEVICE:
257		error = openfirm_getstr(of->of_namelen, of->of_name, &name);
258		if (error)
259			break;
260		node = OF_finddevice(name);
261		if (node == -1) {
262			error = ENOENT;
263			break;
264		}
265		of->of_nodeid = lastnode = node;
266		break;
267	}
268
269	if (name != NULL)
270		free(name, M_TEMP);
271	if (value != NULL)
272		free(value, M_TEMP);
273
274	return (error);
275}
276
277static int
278openfirm_modevent(module_t mod, int type, void *data)
279{
280
281	switch(type) {
282	case MOD_LOAD:
283		if (bootverbose)
284			printf("openfirm: <Open Firmware control device>\n");
285		/*
286		 * Allow only root access by default; this device may allow
287		 * users to peek into firmware passwords, and likely to crash
288		 * the machine on some boxen due to firmware quirks.
289		 */
290		openfirm_dev = make_dev(&openfirm_cdevsw, OPENFIRM_MINOR,
291		    UID_ROOT, GID_WHEEL, 0600, "openfirm");
292		return 0;
293
294	case MOD_UNLOAD:
295		destroy_dev(openfirm_dev);
296		return 0;
297
298	case MOD_SHUTDOWN:
299		return 0;
300
301	default:
302		return EOPNOTSUPP;
303	}
304}
305
306DEV_MODULE(openfirm, openfirm_modevent, NULL);
307