1/*-
2 * Copyright (c) 2016 Netflix, Inc.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer
9 *    in this position and unchanged.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include <sys/param.h>
27#include <sys/systm.h>
28#include <sys/kernel.h>
29#include <sys/bus.h>
30#include <sys/conf.h>
31#include <sys/lock.h>
32#include <sys/malloc.h>
33#include <sys/module.h>
34
35#include <machine/efi.h>
36#include <sys/efiio.h>
37
38static d_ioctl_t efidev_ioctl;
39
40static struct cdevsw efi_cdevsw = {
41	.d_name = "efi",
42	.d_version = D_VERSION,
43	.d_ioctl = efidev_ioctl,
44};
45
46static int
47efidev_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
48    int flags __unused, struct thread *td __unused)
49{
50	int error;
51
52	switch (cmd) {
53	case EFIIOC_GET_TABLE:
54	{
55		struct efi_get_table_ioc *egtioc =
56		    (struct efi_get_table_ioc *)addr;
57		void *buf = NULL;
58
59		error = efi_copy_table(&egtioc->uuid, egtioc->buf ? &buf : NULL,
60		    egtioc->buf_len, &egtioc->table_len);
61
62		if (error != 0 || egtioc->buf == NULL)
63			break;
64
65		if (egtioc->buf_len < egtioc->table_len) {
66			error = EINVAL;
67			free(buf, M_TEMP);
68			break;
69		}
70
71		error = copyout(buf, egtioc->buf, egtioc->buf_len);
72		free(buf, M_TEMP);
73
74		break;
75	}
76	case EFIIOC_GET_TIME:
77	{
78		struct efi_tm *tm = (struct efi_tm *)addr;
79
80		error = efi_get_time(tm);
81		break;
82	}
83	case EFIIOC_SET_TIME:
84	{
85		struct efi_tm *tm = (struct efi_tm *)addr;
86
87		error = efi_set_time(tm);
88		break;
89	}
90	case EFIIOC_GET_WAKETIME:
91	{
92		struct efi_waketime_ioc *wt = (struct efi_waketime_ioc *)addr;
93
94		error = efi_get_waketime(&wt->enabled, &wt->pending,
95		    &wt->waketime);
96		break;
97	}
98	case EFIIOC_SET_WAKETIME:
99	{
100		struct efi_waketime_ioc *wt = (struct efi_waketime_ioc *)addr;
101
102		error = efi_set_waketime(wt->enabled, &wt->waketime);
103		break;
104	}
105	case EFIIOC_VAR_GET:
106	{
107		struct efi_var_ioc *ev = (struct efi_var_ioc *)addr;
108		void *data;
109		efi_char *name;
110
111		data = malloc(ev->datasize, M_TEMP, M_WAITOK);
112		name = malloc(ev->namesize, M_TEMP, M_WAITOK);
113		error = copyin(ev->name, name, ev->namesize);
114		if (error)
115			goto vg_out;
116		if (name[ev->namesize / sizeof(efi_char) - 1] != 0) {
117			error = EINVAL;
118			goto vg_out;
119		}
120
121		error = efi_var_get(name, &ev->vendor, &ev->attrib,
122		    &ev->datasize, data);
123
124		if (error == 0) {
125			error = copyout(data, ev->data, ev->datasize);
126		} else if (error == EOVERFLOW) {
127			/*
128			 * Pass back the size we really need, but
129			 * convert the error to 0 so the copyout
130			 * happens. datasize was updated in the
131			 * efi_var_get call.
132			 */
133			ev->data = NULL;
134			error = 0;
135		}
136vg_out:
137		free(data, M_TEMP);
138		free(name, M_TEMP);
139		break;
140	}
141	case EFIIOC_VAR_NEXT:
142	{
143		struct efi_var_ioc *ev = (struct efi_var_ioc *)addr;
144		efi_char *name;
145
146		name = malloc(ev->namesize, M_TEMP, M_WAITOK);
147		error = copyin(ev->name, name, ev->namesize);
148		if (error)
149			goto vn_out;
150		/* Note: namesize is the buffer size, not the string lenght */
151
152		error = efi_var_nextname(&ev->namesize, name, &ev->vendor);
153		if (error == 0) {
154			error = copyout(name, ev->name, ev->namesize);
155		} else if (error == EOVERFLOW) {
156			ev->name = NULL;
157			error = 0;
158		}
159	vn_out:
160		free(name, M_TEMP);
161		break;
162	}
163	case EFIIOC_VAR_SET:
164	{
165		struct efi_var_ioc *ev = (struct efi_var_ioc *)addr;
166		void *data = NULL;
167		efi_char *name;
168
169		/* datasize == 0 -> delete (more or less) */
170		if (ev->datasize > 0)
171			data = malloc(ev->datasize, M_TEMP, M_WAITOK);
172		name = malloc(ev->namesize, M_TEMP, M_WAITOK);
173		if (ev->datasize) {
174			error = copyin(ev->data, data, ev->datasize);
175			if (error)
176				goto vs_out;
177		}
178		error = copyin(ev->name, name, ev->namesize);
179		if (error)
180			goto vs_out;
181		if (name[ev->namesize / sizeof(efi_char) - 1] != 0) {
182			error = EINVAL;
183			goto vs_out;
184		}
185
186		error = efi_var_set(name, &ev->vendor, ev->attrib, ev->datasize,
187		    data);
188vs_out:
189		free(data, M_TEMP);
190		free(name, M_TEMP);
191		break;
192	}
193	default:
194		error = ENOTTY;
195		break;
196	}
197
198	return (error);
199}
200
201static struct cdev *efidev;
202
203static int
204efidev_modevents(module_t m, int event, void *arg __unused)
205{
206	struct make_dev_args mda;
207	int error;
208
209	switch (event) {
210	case MOD_LOAD:
211		/*
212		 * If we have no efi environment, then don't create the device.
213		 */
214		if (efi_rt_ok() != 0)
215			return (0);
216		make_dev_args_init(&mda);
217		mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
218		mda.mda_devsw = &efi_cdevsw;
219		mda.mda_uid = UID_ROOT;
220		mda.mda_gid = GID_WHEEL;
221		mda.mda_mode = 0700;
222		error = make_dev_s(&mda, &efidev, "efi");
223		return (error);
224
225	case MOD_UNLOAD:
226		if (efidev != NULL)
227			destroy_dev(efidev);
228		efidev = NULL;
229		return (0);
230
231	case MOD_SHUTDOWN:
232		return (0);
233
234	default:
235		return (EOPNOTSUPP);
236	}
237}
238
239static moduledata_t efidev_moddata = {
240	.name = "efidev",
241	.evhand = efidev_modevents,
242	.priv = NULL,
243};
244
245DECLARE_MODULE(efidev, efidev_moddata, SI_SUB_DRIVERS, SI_ORDER_ANY);
246MODULE_VERSION(efidev, 1);
247MODULE_DEPEND(efidev, efirt, 1, 1, 1);
248