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 | D_GIANTOK,
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	case OFIOCGET:
155	case OFIOCGETPROPLEN:
156		if (node == 0)
157			return (EINVAL);
158		error = openfirm_getstr(of->of_namelen, of->of_name, &name);
159		if (error)
160			break;
161		len = OF_getproplen(node, name);
162		if (cmd == OFIOCGETPROPLEN) {
163			of->of_buflen = len;
164			break;
165		}
166		if (len > of->of_buflen) {
167			error = ENOMEM;
168			break;
169		}
170		of->of_buflen = len;
171		/* -1 means no entry; 0 means no value. */
172		if (len <= 0)
173			break;
174		value = malloc(len, M_TEMP, M_WAITOK);
175		len = OF_getprop(node, name, (void *)value, len);
176		error = copyout(value, of->of_buf, len);
177		break;
178
179	case OFIOCSET:
180		/*
181		 * Note: Text string values for at least the /options node
182		 * have to be null-terminated and the length parameter must
183		 * include this terminating null.  However, like OF_getprop(),
184		 * OF_setprop() will return the actual length of the text
185		 * string, i.e. omitting the terminating null.
186		 */
187		if ((flags & FWRITE) == 0)
188			return (EBADF);
189		if (node == 0)
190			return (EINVAL);
191		if ((u_int)of->of_buflen > OFIOCMAXVALUE)
192			return (ENAMETOOLONG);
193		error = openfirm_getstr(of->of_namelen, of->of_name, &name);
194		if (error)
195			break;
196		value = malloc(of->of_buflen, M_TEMP, M_WAITOK);
197		error = copyin(of->of_buf, value, of->of_buflen);
198		if (error)
199			break;
200		len = OF_setprop(node, name, value, of->of_buflen);
201		if (len < 0)
202			error = EINVAL;
203		of->of_buflen = len;
204		break;
205
206	case OFIOCNEXTPROP:
207		if (node == 0 || of->of_buflen < 0)
208			return (EINVAL);
209		if (of->of_namelen != 0) {
210			error = openfirm_getstr(of->of_namelen, of->of_name,
211			    &name);
212			if (error)
213				break;
214		}
215		ok = OF_nextprop(node, name, newname, sizeof(newname));
216		if (ok == 0) {
217			error = ENOENT;
218			break;
219		}
220		if (ok == -1) {
221			error = EINVAL;
222			break;
223		}
224		len = strlen(newname) + 1;
225		if (len > of->of_buflen) {
226			/*
227			 * Passed buffer was insufficient.
228			 *
229			 * Instead of returning an error here, truncate the
230			 * property name to fit the buffer.
231			 *
232			 * This allows us to retain compatibility with old
233			 * tools which always pass a 32 character buffer.
234			 */
235			len = of->of_buflen;
236			newname[len - 1] = '\0';
237		}
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 == -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