xref: /netbsd-src/sys/dev/efi.c (revision f4748aaa01faf324805f9747191535eb6600f82c)
1 /* $NetBSD: efi.c,v 1.4 2022/09/24 11:06:03 riastradh Exp $ */
2 
3 /*-
4  * Copyright (c) 2021 Jared McNeill <jmcneill@invisible.ca>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 /*
30  * This pseudo-driver implements a /dev/efi character device that provides
31  * ioctls for using UEFI runtime time and variable services.
32  */
33 
34 #include <sys/cdefs.h>
35 __KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.4 2022/09/24 11:06:03 riastradh Exp $");
36 
37 #include <sys/param.h>
38 #include <sys/conf.h>
39 #include <sys/kmem.h>
40 #include <sys/atomic.h>
41 #include <sys/efiio.h>
42 
43 #include <dev/efivar.h>
44 
45 #ifdef _LP64
46 #define	EFIERR(x)		(0x8000000000000000 | x)
47 #else
48 #define	EFIERR(x)		(0x80000000 | x)
49 #endif
50 
51 #define	EFI_SUCCESS		0
52 #define	EFI_INVALID_PARAMETER	EFIERR(2)
53 #define	EFI_UNSUPPORTED		EFIERR(3)
54 #define	EFI_BUFFER_TOO_SMALL	EFIERR(5)
55 #define	EFI_DEVICE_ERROR	EFIERR(7)
56 #define	EFI_WRITE_PROTECTED	EFIERR(8)
57 #define	EFI_OUT_OF_RESOURCES	EFIERR(9)
58 #define	EFI_NOT_FOUND		EFIERR(14)
59 #define	EFI_SECURITY_VIOLATION	EFIERR(26)
60 
61 #include "ioconf.h"
62 
63 /*
64  * Maximum length of an EFI variable name. The UEFI spec doesn't specify a
65  * constraint, but we want to limit the size to act as a guard rail against
66  * allocating too much kernel memory.
67  */
68 #define	EFI_VARNAME_MAXLENGTH		EFI_PAGE_SIZE
69 
70 /*
71  * Pointer to arch specific EFI backend.
72  */
73 static const struct efi_ops *efi_ops = NULL;
74 
75 /*
76  * Only allow one user of /dev/efi at a time. Even though the MD EFI backends
77  * should serialize individual UEFI RT calls, the UEFI specification says
78  * that a SetVariable() call between calls to GetNextVariableName() may
79  * produce unpredictable results, and we want to avoid this.
80  */
81 static volatile u_int efi_isopen = 0;
82 
83 static dev_type_open(efi_open);
84 static dev_type_close(efi_close);
85 static dev_type_ioctl(efi_ioctl);
86 
87 const struct cdevsw efi_cdevsw = {
88 	.d_open =	efi_open,
89 	.d_close =	efi_close,
90 	.d_ioctl =	efi_ioctl,
91 	.d_read =	noread,
92 	.d_write =	nowrite,
93 	.d_stop =	nostop,
94 	.d_tty =	notty,
95 	.d_poll =	nopoll,
96 	.d_mmap =	nommap,
97 	.d_kqfilter =	nokqfilter,
98 	.d_discard =	nodiscard,
99 	.d_flag =	D_OTHER | D_MPSAFE,
100 };
101 
102 static int
103 efi_open(dev_t dev, int flags, int type, struct lwp *l)
104 {
105 
106 	if (efi_ops == NULL) {
107 		return ENXIO;
108 	}
109 	if (atomic_swap_uint(&efi_isopen, 1) == 1) {
110 		return EBUSY;
111 	}
112 	membar_acquire();
113 	return 0;
114 }
115 
116 static int
117 efi_close(dev_t dev, int flags, int type, struct lwp *l)
118 {
119 
120 	KASSERT(efi_isopen);
121 	atomic_store_release(&efi_isopen, 0);
122 	return 0;
123 }
124 
125 static int
126 efi_status_to_error(efi_status status)
127 {
128 	switch (status) {
129 	case EFI_SUCCESS:
130 		return 0;
131 	case EFI_INVALID_PARAMETER:
132 		return EINVAL;
133 	case EFI_UNSUPPORTED:
134 		return EOPNOTSUPP;
135 	case EFI_BUFFER_TOO_SMALL:
136 		return ERANGE;
137 	case EFI_DEVICE_ERROR:
138 		return EIO;
139 	case EFI_WRITE_PROTECTED:
140 		return EROFS;
141 	case EFI_OUT_OF_RESOURCES:
142 		return ENOMEM;
143 	case EFI_NOT_FOUND:
144 		return ENOENT;
145 	case EFI_SECURITY_VIOLATION:
146 		return EACCES;
147 	default:
148 		return EIO;
149 	}
150 }
151 
152 static int
153 efi_ioctl_var_get(struct efi_var_ioc *var)
154 {
155 	uint16_t *namebuf;
156 	void *databuf = NULL;
157 	size_t datasize;
158 	efi_status status;
159 	int error;
160 
161 	if (var->name == NULL || var->namesize == 0 ||
162 	    (var->data != NULL && var->datasize == 0)) {
163 		return EINVAL;
164 	}
165 	if (var->namesize > EFI_VARNAME_MAXLENGTH) {
166 		return ENOMEM;
167 	}
168 
169 	namebuf = kmem_alloc(var->namesize, KM_SLEEP);
170 	error = copyin(var->name, namebuf, var->namesize);
171 	if (error != 0) {
172 		goto done;
173 	}
174 	if (namebuf[var->namesize / 2 - 1] != '\0') {
175 		error = EINVAL;
176 		goto done;
177 	}
178 	datasize = var->datasize;
179 	if (datasize != 0) {
180 		databuf = kmem_alloc(datasize, KM_SLEEP);
181 		error = copyin(var->data, databuf, datasize);
182 		if (error != 0) {
183 			goto done;
184 		}
185 	}
186 
187 	status = efi_ops->efi_getvar(namebuf, &var->vendor, &var->attrib,
188 	    &var->datasize, databuf);
189 	if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
190 		error = efi_status_to_error(status);
191 		goto done;
192 	}
193 	if (status == EFI_SUCCESS && databuf != NULL) {
194 		error = copyout(databuf, var->data, var->datasize);
195 	} else {
196 		var->data = NULL;
197 	}
198 
199 done:
200 	kmem_free(namebuf, var->namesize);
201 	if (databuf != NULL) {
202 		kmem_free(databuf, datasize);
203 	}
204 	return error;
205 }
206 
207 static int
208 efi_ioctl_var_next(struct efi_var_ioc *var)
209 {
210 	efi_status status;
211 	uint16_t *namebuf;
212 	size_t namesize;
213 	int error;
214 
215 	if (var->name == NULL || var->namesize == 0) {
216 		return EINVAL;
217 	}
218 	if (var->namesize > EFI_VARNAME_MAXLENGTH) {
219 		return ENOMEM;
220 	}
221 
222 	namesize = var->namesize;
223 	namebuf = kmem_alloc(namesize, KM_SLEEP);
224 	error = copyin(var->name, namebuf, namesize);
225 	if (error != 0) {
226 		goto done;
227 	}
228 
229 	status = efi_ops->efi_nextvar(&var->namesize, namebuf, &var->vendor);
230 	if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
231 		error = efi_status_to_error(status);
232 		goto done;
233 	}
234 	if (status == EFI_SUCCESS) {
235 		error = copyout(namebuf, var->name, var->namesize);
236 	} else {
237 		var->name = NULL;
238 	}
239 
240 done:
241 	kmem_free(namebuf, namesize);
242 	return error;
243 }
244 
245 static int
246 efi_ioctl_var_set(struct efi_var_ioc *var)
247 {
248 	efi_status status;
249 	uint16_t *namebuf;
250 	uint16_t *databuf = NULL;
251 	int error;
252 
253 	if (var->name == NULL || var->namesize == 0) {
254 		return EINVAL;
255 	}
256 
257 	namebuf = kmem_alloc(var->namesize, KM_SLEEP);
258 	error = copyin(var->name, namebuf, var->namesize);
259 	if (error != 0) {
260 		goto done;
261 	}
262 	if (namebuf[var->namesize / 2 - 1] != '\0') {
263 		error = EINVAL;
264 		goto done;
265 	}
266 	if (var->datasize != 0) {
267 		databuf = kmem_alloc(var->datasize, KM_SLEEP);
268 		error = copyin(var->data, databuf, var->datasize);
269 		if (error != 0) {
270 			goto done;
271 		}
272 	}
273 
274 	status = efi_ops->efi_setvar(namebuf, &var->vendor, var->attrib,
275 	    var->datasize, databuf);
276 	error = efi_status_to_error(status);
277 
278 done:
279 	kmem_free(namebuf, var->namesize);
280 	if (databuf != NULL) {
281 		kmem_free(databuf, var->datasize);
282 	}
283 	return error;
284 }
285 
286 static int
287 efi_ioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l)
288 {
289 	KASSERT(efi_ops != NULL);
290 
291 	switch (cmd) {
292 	case EFIIOC_VAR_GET:
293 		return efi_ioctl_var_get(data);
294 	case EFIIOC_VAR_NEXT:
295 		return efi_ioctl_var_next(data);
296 	case EFIIOC_VAR_SET:
297 		return efi_ioctl_var_set(data);
298 	}
299 
300 	return ENOTTY;
301 }
302 
303 void
304 efi_register_ops(const struct efi_ops *ops)
305 {
306 	KASSERT(efi_ops == NULL);
307 	efi_ops = ops;
308 }
309 
310 void
311 efiattach(int count)
312 {
313 }
314