xref: /netbsd-src/sys/dev/efi.c (revision 320f2e6f88d556a8b4792d288ebcf676462225ac)
1*320f2e6fSriastradh /* $NetBSD: efi.c,v 1.9 2023/05/24 00:02:51 riastradh Exp $ */
2c3146444Sjmcneill 
3c3146444Sjmcneill /*-
4c3146444Sjmcneill  * Copyright (c) 2021 Jared McNeill <jmcneill@invisible.ca>
5c3146444Sjmcneill  * All rights reserved.
6c3146444Sjmcneill  *
7c3146444Sjmcneill  * Redistribution and use in source and binary forms, with or without
8c3146444Sjmcneill  * modification, are permitted provided that the following conditions
9c3146444Sjmcneill  * are met:
10c3146444Sjmcneill  * 1. Redistributions of source code must retain the above copyright
11c3146444Sjmcneill  *    notice, this list of conditions and the following disclaimer.
12c3146444Sjmcneill  * 2. Redistributions in binary form must reproduce the above copyright
13c3146444Sjmcneill  *    notice, this list of conditions and the following disclaimer in the
14c3146444Sjmcneill  *    documentation and/or other materials provided with the distribution.
15c3146444Sjmcneill  *
16c3146444Sjmcneill  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17c3146444Sjmcneill  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18c3146444Sjmcneill  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19c3146444Sjmcneill  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20c3146444Sjmcneill  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21c3146444Sjmcneill  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22c3146444Sjmcneill  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23c3146444Sjmcneill  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24c3146444Sjmcneill  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25c3146444Sjmcneill  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26c3146444Sjmcneill  * SUCH DAMAGE.
27c3146444Sjmcneill  */
28c3146444Sjmcneill 
29c3146444Sjmcneill /*
30c3146444Sjmcneill  * This pseudo-driver implements a /dev/efi character device that provides
31c3146444Sjmcneill  * ioctls for using UEFI runtime time and variable services.
32c3146444Sjmcneill  */
33c3146444Sjmcneill 
34c3146444Sjmcneill #include <sys/cdefs.h>
35*320f2e6fSriastradh __KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.9 2023/05/24 00:02:51 riastradh Exp $");
36c3146444Sjmcneill 
37c3146444Sjmcneill #include <sys/param.h>
38c3146444Sjmcneill #include <sys/conf.h>
39c3146444Sjmcneill #include <sys/kmem.h>
40c3146444Sjmcneill #include <sys/atomic.h>
41c3146444Sjmcneill #include <sys/efiio.h>
42c3146444Sjmcneill 
43007aafd7Sriastradh #include <uvm/uvm_extern.h>
44007aafd7Sriastradh 
45c3146444Sjmcneill #include <dev/efivar.h>
46007aafd7Sriastradh #include <dev/mm.h>
47c3146444Sjmcneill 
48c3146444Sjmcneill #include "ioconf.h"
49c3146444Sjmcneill 
50c3146444Sjmcneill /*
51c3146444Sjmcneill  * Maximum length of an EFI variable name. The UEFI spec doesn't specify a
52c3146444Sjmcneill  * constraint, but we want to limit the size to act as a guard rail against
53c3146444Sjmcneill  * allocating too much kernel memory.
54c3146444Sjmcneill  */
55c3146444Sjmcneill #define	EFI_VARNAME_MAXLENGTH		EFI_PAGE_SIZE
56c3146444Sjmcneill 
57c3146444Sjmcneill /*
58c3146444Sjmcneill  * Pointer to arch specific EFI backend.
59c3146444Sjmcneill  */
60c3146444Sjmcneill static const struct efi_ops *efi_ops = NULL;
61c3146444Sjmcneill 
62c3146444Sjmcneill /*
63c3146444Sjmcneill  * Only allow one user of /dev/efi at a time. Even though the MD EFI backends
64c3146444Sjmcneill  * should serialize individual UEFI RT calls, the UEFI specification says
65c3146444Sjmcneill  * that a SetVariable() call between calls to GetNextVariableName() may
66c3146444Sjmcneill  * produce unpredictable results, and we want to avoid this.
67c3146444Sjmcneill  */
68409bc7a5Sriastradh static volatile u_int efi_isopen = 0;
69c3146444Sjmcneill 
70c3146444Sjmcneill static dev_type_open(efi_open);
71c3146444Sjmcneill static dev_type_close(efi_close);
72c3146444Sjmcneill static dev_type_ioctl(efi_ioctl);
73c3146444Sjmcneill 
74c3146444Sjmcneill const struct cdevsw efi_cdevsw = {
75c3146444Sjmcneill 	.d_open =	efi_open,
76c3146444Sjmcneill 	.d_close =	efi_close,
77c3146444Sjmcneill 	.d_ioctl =	efi_ioctl,
78c3146444Sjmcneill 	.d_read =	noread,
79c3146444Sjmcneill 	.d_write =	nowrite,
80c3146444Sjmcneill 	.d_stop =	nostop,
81c3146444Sjmcneill 	.d_tty =	notty,
82c3146444Sjmcneill 	.d_poll =	nopoll,
83c3146444Sjmcneill 	.d_mmap =	nommap,
84c3146444Sjmcneill 	.d_kqfilter =	nokqfilter,
85c3146444Sjmcneill 	.d_discard =	nodiscard,
86c3146444Sjmcneill 	.d_flag =	D_OTHER | D_MPSAFE,
87c3146444Sjmcneill };
88c3146444Sjmcneill 
89c3146444Sjmcneill static int
efi_open(dev_t dev,int flags,int type,struct lwp * l)90c3146444Sjmcneill efi_open(dev_t dev, int flags, int type, struct lwp *l)
91c3146444Sjmcneill {
92409bc7a5Sriastradh 
93c3146444Sjmcneill 	if (efi_ops == NULL) {
94c3146444Sjmcneill 		return ENXIO;
95c3146444Sjmcneill 	}
96409bc7a5Sriastradh 	if (atomic_swap_uint(&efi_isopen, 1) == 1) {
97c3146444Sjmcneill 		return EBUSY;
98c3146444Sjmcneill 	}
99409bc7a5Sriastradh 	membar_acquire();
100c3146444Sjmcneill 	return 0;
101c3146444Sjmcneill }
102c3146444Sjmcneill 
103c3146444Sjmcneill static int
efi_close(dev_t dev,int flags,int type,struct lwp * l)104c3146444Sjmcneill efi_close(dev_t dev, int flags, int type, struct lwp *l)
105c3146444Sjmcneill {
106409bc7a5Sriastradh 
107c3146444Sjmcneill 	KASSERT(efi_isopen);
108409bc7a5Sriastradh 	atomic_store_release(&efi_isopen, 0);
109c3146444Sjmcneill 	return 0;
110c3146444Sjmcneill }
111c3146444Sjmcneill 
112c3146444Sjmcneill static int
efi_status_to_error(efi_status status)113c3146444Sjmcneill efi_status_to_error(efi_status status)
114c3146444Sjmcneill {
115c3146444Sjmcneill 	switch (status) {
116c3146444Sjmcneill 	case EFI_SUCCESS:
117c3146444Sjmcneill 		return 0;
118c3146444Sjmcneill 	case EFI_INVALID_PARAMETER:
119c3146444Sjmcneill 		return EINVAL;
120c3146444Sjmcneill 	case EFI_UNSUPPORTED:
121c3146444Sjmcneill 		return EOPNOTSUPP;
122c3146444Sjmcneill 	case EFI_BUFFER_TOO_SMALL:
123c3146444Sjmcneill 		return ERANGE;
124c3146444Sjmcneill 	case EFI_DEVICE_ERROR:
125c3146444Sjmcneill 		return EIO;
126c3146444Sjmcneill 	case EFI_WRITE_PROTECTED:
127c3146444Sjmcneill 		return EROFS;
128c3146444Sjmcneill 	case EFI_OUT_OF_RESOURCES:
129c3146444Sjmcneill 		return ENOMEM;
130c3146444Sjmcneill 	case EFI_NOT_FOUND:
131c3146444Sjmcneill 		return ENOENT;
132c3146444Sjmcneill 	case EFI_SECURITY_VIOLATION:
133c3146444Sjmcneill 		return EACCES;
134c3146444Sjmcneill 	default:
135c3146444Sjmcneill 		return EIO;
136c3146444Sjmcneill 	}
137c3146444Sjmcneill }
138c3146444Sjmcneill 
139007aafd7Sriastradh /* XXX move to efi.h */
140007aafd7Sriastradh #define	EFI_SYSTEM_RESOURCE_TABLE_GUID					      \
141007aafd7Sriastradh 	{0xb122a263,0x3661,0x4f68,0x99,0x29,{0x78,0xf8,0xb0,0xd6,0x21,0x80}}
142007aafd7Sriastradh #define	EFI_PROPERTIES_TABLE						      \
143007aafd7Sriastradh 	{0x880aaca3,0x4adc,0x4a04,0x90,0x79,{0xb7,0x47,0x34,0x08,0x25,0xe5}}
144007aafd7Sriastradh 
145007aafd7Sriastradh #define	EFI_SYSTEM_RESOURCE_TABLE_FIRMWARE_RESOURCE_VERSION	1
146007aafd7Sriastradh 
147007aafd7Sriastradh struct EFI_SYSTEM_RESOURCE_ENTRY {
148007aafd7Sriastradh 	struct uuid	FwClass;
149007aafd7Sriastradh 	uint32_t	FwType;
150007aafd7Sriastradh 	uint32_t	FwVersion;
151007aafd7Sriastradh 	uint32_t	LowestSupportedFwVersion;
152007aafd7Sriastradh 	uint32_t	CapsuleFlags;
153007aafd7Sriastradh 	uint32_t	LastAttemptVersion;
154007aafd7Sriastradh 	uint32_t	LastAttemptStatus;
155007aafd7Sriastradh };
156007aafd7Sriastradh 
157007aafd7Sriastradh struct EFI_SYSTEM_RESOURCE_TABLE {
158007aafd7Sriastradh 	uint32_t	FwResourceCount;
159007aafd7Sriastradh 	uint32_t	FwResourceCountMax;
160007aafd7Sriastradh 	uint64_t	FwResourceVersion;
161007aafd7Sriastradh 	struct EFI_SYSTEM_RESOURCE_ENTRY	Entries[];
162007aafd7Sriastradh };
163007aafd7Sriastradh 
164007aafd7Sriastradh static void *
efi_map_pa(uint64_t addr,bool * directp)165007aafd7Sriastradh efi_map_pa(uint64_t addr, bool *directp)
166007aafd7Sriastradh {
167007aafd7Sriastradh 	paddr_t pa = addr;
168007aafd7Sriastradh 	vaddr_t va;
169007aafd7Sriastradh 
170007aafd7Sriastradh 	/*
171007aafd7Sriastradh 	 * Verify the address is not truncated by conversion to
172007aafd7Sriastradh 	 * paddr_t.  This might happen with a 64-bit EFI booting a
173007aafd7Sriastradh 	 * 32-bit OS.
174007aafd7Sriastradh 	 */
175007aafd7Sriastradh 	if (pa != addr)
176007aafd7Sriastradh 		return NULL;
177007aafd7Sriastradh 
178007aafd7Sriastradh 	/*
179007aafd7Sriastradh 	 * Try direct-map if we have it.  If it works, note that it was
180007aafd7Sriastradh 	 * direct-mapped for efi_unmap.
181007aafd7Sriastradh 	 */
182007aafd7Sriastradh #ifdef __HAVE_MM_MD_DIRECT_MAPPED_PHYS
183007aafd7Sriastradh 	if (mm_md_direct_mapped_phys(pa, &va)) {
184007aafd7Sriastradh 		*directp = true;
185007aafd7Sriastradh 		return (void *)va;
186007aafd7Sriastradh 	}
187007aafd7Sriastradh #endif
188007aafd7Sriastradh 
189007aafd7Sriastradh 	/*
190007aafd7Sriastradh 	 * No direct map.  Reserve a page of kernel virtual address
191007aafd7Sriastradh 	 * space, with no backing, to map to the physical address.
192007aafd7Sriastradh 	 */
193007aafd7Sriastradh 	va = uvm_km_alloc(kernel_map, PAGE_SIZE, 0,
194007aafd7Sriastradh 	    UVM_KMF_VAONLY|UVM_KMF_WAITVA);
195007aafd7Sriastradh 	KASSERT(va != 0);
196007aafd7Sriastradh 
197007aafd7Sriastradh 	/*
198007aafd7Sriastradh 	 * Map the kva page to the physical address and update the
199007aafd7Sriastradh 	 * kernel pmap so we can use it.
200007aafd7Sriastradh 	 */
201007aafd7Sriastradh 	pmap_kenter_pa(va, pa, VM_PROT_READ, 0);
202007aafd7Sriastradh 	pmap_update(pmap_kernel());
203007aafd7Sriastradh 
204007aafd7Sriastradh 	/*
205007aafd7Sriastradh 	 * Success!  Return the VA and note that it was not
206007aafd7Sriastradh 	 * direct-mapped for efi_unmap.
207007aafd7Sriastradh 	 */
208007aafd7Sriastradh 	*directp = false;
209007aafd7Sriastradh 	return (void *)va;
210007aafd7Sriastradh }
211007aafd7Sriastradh 
212007aafd7Sriastradh static void
efi_unmap(void * ptr,bool direct)213007aafd7Sriastradh efi_unmap(void *ptr, bool direct)
214007aafd7Sriastradh {
215007aafd7Sriastradh 	vaddr_t va = (vaddr_t)ptr;
216007aafd7Sriastradh 
217007aafd7Sriastradh 	/*
218007aafd7Sriastradh 	 * If it was direct-mapped, nothing to do here.
219007aafd7Sriastradh 	 */
220007aafd7Sriastradh 	if (direct)
221007aafd7Sriastradh 		return;
222007aafd7Sriastradh 
223007aafd7Sriastradh 	/*
224007aafd7Sriastradh 	 * First remove the mapping from the kernel pmap so that it can
225007aafd7Sriastradh 	 * be reused, before we free the kva and let anyone else reuse
226007aafd7Sriastradh 	 * it.
227007aafd7Sriastradh 	 */
228007aafd7Sriastradh 	pmap_kremove(va, PAGE_SIZE);
229007aafd7Sriastradh 	pmap_update(pmap_kernel());
230007aafd7Sriastradh 
231007aafd7Sriastradh 	/*
232007aafd7Sriastradh 	 * Next free the kva so it can be reused by someone else.
233007aafd7Sriastradh 	 */
234007aafd7Sriastradh 	uvm_km_free(kernel_map, va, PAGE_SIZE, UVM_KMF_VAONLY);
235007aafd7Sriastradh }
236007aafd7Sriastradh 
237007aafd7Sriastradh static int
efi_ioctl_got_table(struct efi_get_table_ioc * ioc,void * ptr,size_t len)238007aafd7Sriastradh efi_ioctl_got_table(struct efi_get_table_ioc *ioc, void *ptr, size_t len)
239007aafd7Sriastradh {
240007aafd7Sriastradh 
241007aafd7Sriastradh 	/*
242007aafd7Sriastradh 	 * Return the actual table length.
243007aafd7Sriastradh 	 */
244007aafd7Sriastradh 	ioc->table_len = len;
245007aafd7Sriastradh 
246007aafd7Sriastradh 	/*
247007aafd7Sriastradh 	 * Copy out as much as we can into the user's allocated buffer.
248007aafd7Sriastradh 	 */
249007aafd7Sriastradh 	return copyout(ptr, ioc->buf, MIN(ioc->buf_len, len));
250007aafd7Sriastradh }
251007aafd7Sriastradh 
252007aafd7Sriastradh static int
efi_ioctl_get_esrt(struct efi_get_table_ioc * ioc,struct EFI_SYSTEM_RESOURCE_TABLE * tab)253007aafd7Sriastradh efi_ioctl_get_esrt(struct efi_get_table_ioc *ioc,
254007aafd7Sriastradh     struct EFI_SYSTEM_RESOURCE_TABLE *tab)
255007aafd7Sriastradh {
256007aafd7Sriastradh 
257007aafd7Sriastradh 	/*
258007aafd7Sriastradh 	 * Verify the firmware resource version is one we understand.
259007aafd7Sriastradh 	 */
260007aafd7Sriastradh 	if (tab->FwResourceVersion !=
261007aafd7Sriastradh 	    EFI_SYSTEM_RESOURCE_TABLE_FIRMWARE_RESOURCE_VERSION)
262007aafd7Sriastradh 		return ENOENT;
263007aafd7Sriastradh 
264007aafd7Sriastradh 	/*
265007aafd7Sriastradh 	 * Verify the resource count fits within the single page we
266007aafd7Sriastradh 	 * have mapped.
267007aafd7Sriastradh 	 *
268007aafd7Sriastradh 	 * XXX What happens if it doesn't?  Are we expected to map more
269007aafd7Sriastradh 	 * than one page, according to the table header?  The UEFI spec
270007aafd7Sriastradh 	 * is unclear on this.
271007aafd7Sriastradh 	 */
272007aafd7Sriastradh 	const size_t entry_space = PAGE_SIZE -
273007aafd7Sriastradh 	    offsetof(struct EFI_SYSTEM_RESOURCE_TABLE, Entries);
274007aafd7Sriastradh 	if (tab->FwResourceCount > entry_space/sizeof(tab->Entries[0]))
275007aafd7Sriastradh 		return ENOENT;
276007aafd7Sriastradh 
277007aafd7Sriastradh 	/*
278007aafd7Sriastradh 	 * Success!  Return everything through the last table entry.
279007aafd7Sriastradh 	 */
280007aafd7Sriastradh 	const size_t len = offsetof(struct EFI_SYSTEM_RESOURCE_TABLE,
281007aafd7Sriastradh 	    Entries[tab->FwResourceCount]);
282007aafd7Sriastradh 	return efi_ioctl_got_table(ioc, tab, len);
283007aafd7Sriastradh }
284007aafd7Sriastradh 
285007aafd7Sriastradh static int
efi_ioctl_get_table(struct efi_get_table_ioc * ioc)286007aafd7Sriastradh efi_ioctl_get_table(struct efi_get_table_ioc *ioc)
287007aafd7Sriastradh {
288007aafd7Sriastradh 	uint64_t addr;
289007aafd7Sriastradh 	bool direct;
290007aafd7Sriastradh 	efi_status status;
291007aafd7Sriastradh 	int error;
292007aafd7Sriastradh 
293007aafd7Sriastradh 	/*
294007aafd7Sriastradh 	 * If the platform doesn't support it yet, fail now.
295007aafd7Sriastradh 	 */
296007aafd7Sriastradh 	if (efi_ops->efi_gettab == NULL)
297007aafd7Sriastradh 		return ENODEV;
298007aafd7Sriastradh 
299007aafd7Sriastradh 	/*
300007aafd7Sriastradh 	 * Get the address of the requested table out of the EFI
301007aafd7Sriastradh 	 * configuration table.
302007aafd7Sriastradh 	 */
303007aafd7Sriastradh 	status = efi_ops->efi_gettab(&ioc->uuid, &addr);
304007aafd7Sriastradh 	if (status != EFI_SUCCESS)
305007aafd7Sriastradh 		return efi_status_to_error(status);
306007aafd7Sriastradh 
307007aafd7Sriastradh 	/*
308007aafd7Sriastradh 	 * UEFI provides no generic way to identify the size of the
309007aafd7Sriastradh 	 * table, so we have to bake knowledge of every vendor GUID
310007aafd7Sriastradh 	 * into this code to safely expose the right amount of data to
311007aafd7Sriastradh 	 * userland.
312007aafd7Sriastradh 	 *
313007aafd7Sriastradh 	 * We even have to bake knowledge of which ones are physically
314007aafd7Sriastradh 	 * addressed and which ones might be virtually addressed
315007aafd7Sriastradh 	 * according to the vendor GUID into this code, although for
316007aafd7Sriastradh 	 * the moment we never use RT->SetVirtualAddressMap so we only
317007aafd7Sriastradh 	 * ever have to deal with physical addressing.
318007aafd7Sriastradh 	 */
319007aafd7Sriastradh 	if (memcmp(&ioc->uuid, &(struct uuid)EFI_SYSTEM_RESOURCE_TABLE_GUID,
320007aafd7Sriastradh 		sizeof(ioc->uuid)) == 0) {
321007aafd7Sriastradh 		struct EFI_SYSTEM_RESOURCE_TABLE *tab;
322007aafd7Sriastradh 
323007aafd7Sriastradh 		if ((tab = efi_map_pa(addr, &direct)) == NULL)
324007aafd7Sriastradh 			return ENOENT;
325007aafd7Sriastradh 		error = efi_ioctl_get_esrt(ioc, tab);
326007aafd7Sriastradh 		efi_unmap(tab, direct);
327007aafd7Sriastradh 	} else {
328007aafd7Sriastradh 		error = ENOENT;
329007aafd7Sriastradh 	}
330007aafd7Sriastradh 
331007aafd7Sriastradh 	return error;
332007aafd7Sriastradh }
333007aafd7Sriastradh 
334c3146444Sjmcneill static int
efi_ioctl_var_get(struct efi_var_ioc * var)335c3146444Sjmcneill efi_ioctl_var_get(struct efi_var_ioc *var)
336c3146444Sjmcneill {
337c3146444Sjmcneill 	uint16_t *namebuf;
338c3146444Sjmcneill 	void *databuf = NULL;
339c0ec0a7dSriastradh 	size_t databufsize;
340c0ec0a7dSriastradh 	unsigned long datasize;
341c3146444Sjmcneill 	efi_status status;
342c3146444Sjmcneill 	int error;
343c3146444Sjmcneill 
344c3146444Sjmcneill 	if (var->name == NULL || var->namesize == 0 ||
345c3146444Sjmcneill 	    (var->data != NULL && var->datasize == 0)) {
346c3146444Sjmcneill 		return EINVAL;
347c3146444Sjmcneill 	}
348c3146444Sjmcneill 	if (var->namesize > EFI_VARNAME_MAXLENGTH) {
349c3146444Sjmcneill 		return ENOMEM;
350c3146444Sjmcneill 	}
351c0ec0a7dSriastradh 	if (var->datasize > ULONG_MAX) { /* XXX stricter limit */
352c0ec0a7dSriastradh 		return ENOMEM;
353c0ec0a7dSriastradh 	}
354c3146444Sjmcneill 
355c3146444Sjmcneill 	namebuf = kmem_alloc(var->namesize, KM_SLEEP);
356c3146444Sjmcneill 	error = copyin(var->name, namebuf, var->namesize);
357c3146444Sjmcneill 	if (error != 0) {
358c3146444Sjmcneill 		goto done;
359c3146444Sjmcneill 	}
360c3146444Sjmcneill 	if (namebuf[var->namesize / 2 - 1] != '\0') {
361c3146444Sjmcneill 		error = EINVAL;
362c3146444Sjmcneill 		goto done;
363c3146444Sjmcneill 	}
364c0ec0a7dSriastradh 	databufsize = var->datasize;
365c0ec0a7dSriastradh 	if (databufsize != 0) {
366c0ec0a7dSriastradh 		databuf = kmem_alloc(databufsize, KM_SLEEP);
367c0ec0a7dSriastradh 		error = copyin(var->data, databuf, databufsize);
368c3146444Sjmcneill 		if (error != 0) {
369c3146444Sjmcneill 			goto done;
370c3146444Sjmcneill 		}
371c3146444Sjmcneill 	}
372c3146444Sjmcneill 
373c0ec0a7dSriastradh 	datasize = databufsize;
374c3146444Sjmcneill 	status = efi_ops->efi_getvar(namebuf, &var->vendor, &var->attrib,
375c0ec0a7dSriastradh 	    &datasize, databuf);
376c3146444Sjmcneill 	if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
377c3146444Sjmcneill 		error = efi_status_to_error(status);
378c3146444Sjmcneill 		goto done;
379c3146444Sjmcneill 	}
380c0ec0a7dSriastradh 	var->datasize = datasize;
381*320f2e6fSriastradh 	if (status == EFI_SUCCESS && databufsize != 0) {
382*320f2e6fSriastradh 		error = copyout(databuf, var->data,
383*320f2e6fSriastradh 		    MIN(datasize, databufsize));
384c3146444Sjmcneill 	} else {
385c3146444Sjmcneill 		var->data = NULL;
386c3146444Sjmcneill 	}
387c3146444Sjmcneill 
388c3146444Sjmcneill done:
389c3146444Sjmcneill 	kmem_free(namebuf, var->namesize);
390c3146444Sjmcneill 	if (databuf != NULL) {
391c0ec0a7dSriastradh 		kmem_free(databuf, databufsize);
392c3146444Sjmcneill 	}
393c3146444Sjmcneill 	return error;
394c3146444Sjmcneill }
395c3146444Sjmcneill 
396c3146444Sjmcneill static int
efi_ioctl_var_next(struct efi_var_ioc * var)397c3146444Sjmcneill efi_ioctl_var_next(struct efi_var_ioc *var)
398c3146444Sjmcneill {
399c3146444Sjmcneill 	efi_status status;
400c3146444Sjmcneill 	uint16_t *namebuf;
401c0ec0a7dSriastradh 	size_t namebufsize;
402c0ec0a7dSriastradh 	unsigned long namesize;
403c3146444Sjmcneill 	int error;
404c3146444Sjmcneill 
405c3146444Sjmcneill 	if (var->name == NULL || var->namesize == 0) {
406c3146444Sjmcneill 		return EINVAL;
407c3146444Sjmcneill 	}
408c3146444Sjmcneill 	if (var->namesize > EFI_VARNAME_MAXLENGTH) {
409c3146444Sjmcneill 		return ENOMEM;
410c3146444Sjmcneill 	}
411c3146444Sjmcneill 
412c0ec0a7dSriastradh 	namebufsize = var->namesize;
413c0ec0a7dSriastradh 	namebuf = kmem_alloc(namebufsize, KM_SLEEP);
414c0ec0a7dSriastradh 	error = copyin(var->name, namebuf, namebufsize);
415c3146444Sjmcneill 	if (error != 0) {
416c3146444Sjmcneill 		goto done;
417c3146444Sjmcneill 	}
418c3146444Sjmcneill 
419c0ec0a7dSriastradh 	CTASSERT(EFI_VARNAME_MAXLENGTH <= ULONG_MAX);
420c0ec0a7dSriastradh 	namesize = namebufsize;
421c0ec0a7dSriastradh 	status = efi_ops->efi_nextvar(&namesize, namebuf, &var->vendor);
422c3146444Sjmcneill 	if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
423c3146444Sjmcneill 		error = efi_status_to_error(status);
424c3146444Sjmcneill 		goto done;
425c3146444Sjmcneill 	}
426c0ec0a7dSriastradh 	var->namesize = namesize;
427c3146444Sjmcneill 	if (status == EFI_SUCCESS) {
428*320f2e6fSriastradh 		error = copyout(namebuf, var->name,
429*320f2e6fSriastradh 		    MIN(namesize, namebufsize));
430c3146444Sjmcneill 	} else {
431c3146444Sjmcneill 		var->name = NULL;
432c3146444Sjmcneill 	}
433c3146444Sjmcneill 
434c3146444Sjmcneill done:
435c0ec0a7dSriastradh 	kmem_free(namebuf, namebufsize);
436c3146444Sjmcneill 	return error;
437c3146444Sjmcneill }
438c3146444Sjmcneill 
439c3146444Sjmcneill static int
efi_ioctl_var_set(struct efi_var_ioc * var)440c3146444Sjmcneill efi_ioctl_var_set(struct efi_var_ioc *var)
441c3146444Sjmcneill {
442c3146444Sjmcneill 	efi_status status;
443c3146444Sjmcneill 	uint16_t *namebuf;
444c3146444Sjmcneill 	uint16_t *databuf = NULL;
445c3146444Sjmcneill 	int error;
446c3146444Sjmcneill 
447c3146444Sjmcneill 	if (var->name == NULL || var->namesize == 0) {
448c3146444Sjmcneill 		return EINVAL;
449c3146444Sjmcneill 	}
450c3146444Sjmcneill 
451c3146444Sjmcneill 	namebuf = kmem_alloc(var->namesize, KM_SLEEP);
452c3146444Sjmcneill 	error = copyin(var->name, namebuf, var->namesize);
453c3146444Sjmcneill 	if (error != 0) {
454c3146444Sjmcneill 		goto done;
455c3146444Sjmcneill 	}
456c3146444Sjmcneill 	if (namebuf[var->namesize / 2 - 1] != '\0') {
457c3146444Sjmcneill 		error = EINVAL;
458c3146444Sjmcneill 		goto done;
459c3146444Sjmcneill 	}
460c3146444Sjmcneill 	if (var->datasize != 0) {
461c3146444Sjmcneill 		databuf = kmem_alloc(var->datasize, KM_SLEEP);
462c3146444Sjmcneill 		error = copyin(var->data, databuf, var->datasize);
463c3146444Sjmcneill 		if (error != 0) {
464c3146444Sjmcneill 			goto done;
465c3146444Sjmcneill 		}
466c3146444Sjmcneill 	}
467c3146444Sjmcneill 
468c3146444Sjmcneill 	status = efi_ops->efi_setvar(namebuf, &var->vendor, var->attrib,
469c3146444Sjmcneill 	    var->datasize, databuf);
470c3146444Sjmcneill 	error = efi_status_to_error(status);
471c3146444Sjmcneill 
472c3146444Sjmcneill done:
473c3146444Sjmcneill 	kmem_free(namebuf, var->namesize);
474c3146444Sjmcneill 	if (databuf != NULL) {
475c3146444Sjmcneill 		kmem_free(databuf, var->datasize);
476c3146444Sjmcneill 	}
477c3146444Sjmcneill 	return error;
478c3146444Sjmcneill }
479c3146444Sjmcneill 
480c3146444Sjmcneill static int
efi_ioctl(dev_t dev,u_long cmd,void * data,int flags,struct lwp * l)481c3146444Sjmcneill efi_ioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l)
482c3146444Sjmcneill {
483c3146444Sjmcneill 	KASSERT(efi_ops != NULL);
484c3146444Sjmcneill 
485c3146444Sjmcneill 	switch (cmd) {
486007aafd7Sriastradh 	case EFIIOC_GET_TABLE:
487007aafd7Sriastradh 		return efi_ioctl_get_table(data);
488c3146444Sjmcneill 	case EFIIOC_VAR_GET:
489c3146444Sjmcneill 		return efi_ioctl_var_get(data);
490c3146444Sjmcneill 	case EFIIOC_VAR_NEXT:
491c3146444Sjmcneill 		return efi_ioctl_var_next(data);
492c3146444Sjmcneill 	case EFIIOC_VAR_SET:
493c3146444Sjmcneill 		return efi_ioctl_var_set(data);
494c3146444Sjmcneill 	}
495c3146444Sjmcneill 
496c3146444Sjmcneill 	return ENOTTY;
497c3146444Sjmcneill }
498c3146444Sjmcneill 
499c3146444Sjmcneill void
efi_register_ops(const struct efi_ops * ops)500c3146444Sjmcneill efi_register_ops(const struct efi_ops *ops)
501c3146444Sjmcneill {
502c3146444Sjmcneill 	KASSERT(efi_ops == NULL);
503c3146444Sjmcneill 	efi_ops = ops;
504c3146444Sjmcneill }
505c3146444Sjmcneill 
506c3146444Sjmcneill void
efiattach(int count)507c3146444Sjmcneill efiattach(int count)
508c3146444Sjmcneill {
509c3146444Sjmcneill }
510