1 /* $NetBSD: efi.c,v 1.10 2025/03/30 14:36:48 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.10 2025/03/30 14:36:48 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 <uvm/uvm_extern.h>
44 
45 #include <dev/efivar.h>
46 #include <dev/mm.h>
47 
48 #include "ioconf.h"
49 
50 /*
51  * Maximum length of an EFI variable name in bytes. The UEFI spec
52  * doesn't specify a constraint, but we want to limit the size to act
53  * as a guard rail against allocating too much kernel memory.
54  */
55 #define   EFI_VARNAME_MAXBYTES                    EFI_PAGE_SIZE
56 
57 /*
58  * Pointer to arch specific EFI backend.
59  */
60 static const struct efi_ops *efi_ops = NULL;
61 
62 /*
63  * Only allow one user of /dev/efi at a time. Even though the MD EFI backends
64  * should serialize individual UEFI RT calls, the UEFI specification says
65  * that a SetVariable() call between calls to GetNextVariableName() may
66  * produce unpredictable results, and we want to avoid this.
67  */
68 static volatile u_int efi_isopen = 0;
69 
70 static dev_type_open(efi_open);
71 static dev_type_close(efi_close);
72 static dev_type_ioctl(efi_ioctl);
73 
74 const struct cdevsw efi_cdevsw = {
75           .d_open = efi_open,
76           .d_close =          efi_close,
77           .d_ioctl =          efi_ioctl,
78           .d_read = noread,
79           .d_write =          nowrite,
80           .d_stop = nostop,
81           .d_tty =  notty,
82           .d_poll = nopoll,
83           .d_mmap = nommap,
84           .d_kqfilter =       nokqfilter,
85           .d_discard =        nodiscard,
86           .d_flag = D_OTHER | D_MPSAFE,
87 };
88 
89 static int
efi_open(dev_t dev,int flags,int type,struct lwp * l)90 efi_open(dev_t dev, int flags, int type, struct lwp *l)
91 {
92 
93           if (efi_ops == NULL) {
94                     return ENXIO;
95           }
96           if (atomic_swap_uint(&efi_isopen, 1) == 1) {
97                     return EBUSY;
98           }
99           membar_acquire();
100           return 0;
101 }
102 
103 static int
efi_close(dev_t dev,int flags,int type,struct lwp * l)104 efi_close(dev_t dev, int flags, int type, struct lwp *l)
105 {
106 
107           KASSERT(efi_isopen);
108           atomic_store_release(&efi_isopen, 0);
109           return 0;
110 }
111 
112 static int
efi_status_to_error(efi_status status)113 efi_status_to_error(efi_status status)
114 {
115           switch (status) {
116           case EFI_SUCCESS:
117                     return 0;
118           case EFI_INVALID_PARAMETER:
119                     return EINVAL;
120           case EFI_UNSUPPORTED:
121                     return EOPNOTSUPP;
122           case EFI_BUFFER_TOO_SMALL:
123                     return ERANGE;
124           case EFI_DEVICE_ERROR:
125                     return EIO;
126           case EFI_WRITE_PROTECTED:
127                     return EROFS;
128           case EFI_OUT_OF_RESOURCES:
129                     return ENOMEM;
130           case EFI_NOT_FOUND:
131                     return ENOENT;
132           case EFI_SECURITY_VIOLATION:
133                     return EACCES;
134           default:
135                     return EIO;
136           }
137 }
138 
139 /* XXX move to efi.h */
140 #define   EFI_SYSTEM_RESOURCE_TABLE_GUID                                                        \
141           {0xb122a263,0x3661,0x4f68,0x99,0x29,{0x78,0xf8,0xb0,0xd6,0x21,0x80}}
142 #define   EFI_PROPERTIES_TABLE                                                                  \
143           {0x880aaca3,0x4adc,0x4a04,0x90,0x79,{0xb7,0x47,0x34,0x08,0x25,0xe5}}
144 
145 #define   EFI_SYSTEM_RESOURCE_TABLE_FIRMWARE_RESOURCE_VERSION         1
146 
147 struct EFI_SYSTEM_RESOURCE_ENTRY {
148           struct uuid         FwClass;
149           uint32_t  FwType;
150           uint32_t  FwVersion;
151           uint32_t  LowestSupportedFwVersion;
152           uint32_t  CapsuleFlags;
153           uint32_t  LastAttemptVersion;
154           uint32_t  LastAttemptStatus;
155 };
156 
157 struct EFI_SYSTEM_RESOURCE_TABLE {
158           uint32_t  FwResourceCount;
159           uint32_t  FwResourceCountMax;
160           uint64_t  FwResourceVersion;
161           struct EFI_SYSTEM_RESOURCE_ENTRY        Entries[];
162 };
163 
164 static void *
efi_map_pa(uint64_t addr,bool * directp)165 efi_map_pa(uint64_t addr, bool *directp)
166 {
167           paddr_t pa = addr;
168           vaddr_t va;
169 
170           /*
171            * Verify the address is not truncated by conversion to
172            * paddr_t.  This might happen with a 64-bit EFI booting a
173            * 32-bit OS.
174            */
175           if (pa != addr)
176                     return NULL;
177 
178           /*
179            * Try direct-map if we have it.  If it works, note that it was
180            * direct-mapped for efi_unmap.
181            */
182 #ifdef __HAVE_MM_MD_DIRECT_MAPPED_PHYS
183           if (mm_md_direct_mapped_phys(pa, &va)) {
184                     *directp = true;
185                     return (void *)va;
186           }
187 #endif
188 
189           /*
190            * No direct map.  Reserve a page of kernel virtual address
191            * space, with no backing, to map to the physical address.
192            */
193           va = uvm_km_alloc(kernel_map, PAGE_SIZE, 0,
194               UVM_KMF_VAONLY|UVM_KMF_WAITVA);
195           KASSERT(va != 0);
196 
197           /*
198            * Map the kva page to the physical address and update the
199            * kernel pmap so we can use it.
200            */
201           pmap_kenter_pa(va, pa, VM_PROT_READ, 0);
202           pmap_update(pmap_kernel());
203 
204           /*
205            * Success!  Return the VA and note that it was not
206            * direct-mapped for efi_unmap.
207            */
208           *directp = false;
209           return (void *)va;
210 }
211 
212 static void
efi_unmap(void * ptr,bool direct)213 efi_unmap(void *ptr, bool direct)
214 {
215           vaddr_t va = (vaddr_t)ptr;
216 
217           /*
218            * If it was direct-mapped, nothing to do here.
219            */
220           if (direct)
221                     return;
222 
223           /*
224            * First remove the mapping from the kernel pmap so that it can
225            * be reused, before we free the kva and let anyone else reuse
226            * it.
227            */
228           pmap_kremove(va, PAGE_SIZE);
229           pmap_update(pmap_kernel());
230 
231           /*
232            * Next free the kva so it can be reused by someone else.
233            */
234           uvm_km_free(kernel_map, va, PAGE_SIZE, UVM_KMF_VAONLY);
235 }
236 
237 static int
efi_ioctl_got_table(struct efi_get_table_ioc * ioc,void * ptr,size_t len)238 efi_ioctl_got_table(struct efi_get_table_ioc *ioc, void *ptr, size_t len)
239 {
240 
241           /*
242            * Return the actual table length.
243            */
244           ioc->table_len = len;
245 
246           /*
247            * Copy out as much as we can into the user's allocated buffer.
248            */
249           return copyout(ptr, ioc->buf, MIN(ioc->buf_len, len));
250 }
251 
252 static int
efi_ioctl_get_esrt(struct efi_get_table_ioc * ioc,struct EFI_SYSTEM_RESOURCE_TABLE * tab)253 efi_ioctl_get_esrt(struct efi_get_table_ioc *ioc,
254     struct EFI_SYSTEM_RESOURCE_TABLE *tab)
255 {
256 
257           /*
258            * Verify the firmware resource version is one we understand.
259            */
260           if (tab->FwResourceVersion !=
261               EFI_SYSTEM_RESOURCE_TABLE_FIRMWARE_RESOURCE_VERSION)
262                     return ENOENT;
263 
264           /*
265            * Verify the resource count fits within the single page we
266            * have mapped.
267            *
268            * XXX What happens if it doesn't?  Are we expected to map more
269            * than one page, according to the table header?  The UEFI spec
270            * is unclear on this.
271            */
272           const size_t entry_space = PAGE_SIZE -
273               offsetof(struct EFI_SYSTEM_RESOURCE_TABLE, Entries);
274           if (tab->FwResourceCount > entry_space/sizeof(tab->Entries[0]))
275                     return ENOENT;
276 
277           /*
278            * Success!  Return everything through the last table entry.
279            */
280           const size_t len = offsetof(struct EFI_SYSTEM_RESOURCE_TABLE,
281               Entries[tab->FwResourceCount]);
282           return efi_ioctl_got_table(ioc, tab, len);
283 }
284 
285 static int
efi_ioctl_get_table(struct efi_get_table_ioc * ioc)286 efi_ioctl_get_table(struct efi_get_table_ioc *ioc)
287 {
288           uint64_t addr;
289           bool direct;
290           efi_status status;
291           int error;
292 
293           /*
294            * If the platform doesn't support it yet, fail now.
295            */
296           if (efi_ops->efi_gettab == NULL)
297                     return ENODEV;
298 
299           /*
300            * Get the address of the requested table out of the EFI
301            * configuration table.
302            */
303           status = efi_ops->efi_gettab(&ioc->uuid, &addr);
304           if (status != EFI_SUCCESS)
305                     return efi_status_to_error(status);
306 
307           /*
308            * UEFI provides no generic way to identify the size of the
309            * table, so we have to bake knowledge of every vendor GUID
310            * into this code to safely expose the right amount of data to
311            * userland.
312            *
313            * We even have to bake knowledge of which ones are physically
314            * addressed and which ones might be virtually addressed
315            * according to the vendor GUID into this code, although for
316            * the moment we never use RT->SetVirtualAddressMap so we only
317            * ever have to deal with physical addressing.
318            */
319           if (memcmp(&ioc->uuid, &(struct uuid)EFI_SYSTEM_RESOURCE_TABLE_GUID,
320                     sizeof(ioc->uuid)) == 0) {
321                     struct EFI_SYSTEM_RESOURCE_TABLE *tab;
322 
323                     if ((tab = efi_map_pa(addr, &direct)) == NULL)
324                               return ENOENT;
325                     error = efi_ioctl_get_esrt(ioc, tab);
326                     efi_unmap(tab, direct);
327           } else {
328                     error = ENOENT;
329           }
330 
331           return error;
332 }
333 
334 static int
efi_ioctl_var_get(struct efi_var_ioc * var)335 efi_ioctl_var_get(struct efi_var_ioc *var)
336 {
337           uint16_t *namebuf;
338           void *databuf = NULL;
339           size_t databufsize;
340           unsigned long datasize;
341           efi_status status;
342           int error;
343 
344           if (var->name == NULL || var->namesize == 0 ||
345               (var->data != NULL && var->datasize == 0)) {
346                     return EINVAL;
347           }
348           if (var->namesize > EFI_VARNAME_MAXBYTES) {
349                     return ENOMEM;
350           }
351           if (var->datasize > ULONG_MAX) { /* XXX stricter limit */
352                     return ENOMEM;
353           }
354 
355           namebuf = kmem_alloc(var->namesize, KM_SLEEP);
356           error = copyin(var->name, namebuf, var->namesize);
357           if (error != 0) {
358                     goto done;
359           }
360           if (namebuf[var->namesize / 2 - 1] != '\0') {
361                     error = EINVAL;
362                     goto done;
363           }
364           databufsize = var->datasize;
365           if (databufsize != 0) {
366                     databuf = kmem_alloc(databufsize, KM_SLEEP);
367                     error = copyin(var->data, databuf, databufsize);
368                     if (error != 0) {
369                               goto done;
370                     }
371           }
372 
373           datasize = databufsize;
374           status = efi_ops->efi_getvar(namebuf, &var->vendor, &var->attrib,
375               &datasize, databuf);
376           if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
377                     error = efi_status_to_error(status);
378                     goto done;
379           }
380           var->datasize = datasize;
381           if (status == EFI_SUCCESS && databufsize != 0) {
382                     error = copyout(databuf, var->data,
383                         MIN(datasize, databufsize));
384           } else {
385                     var->data = NULL;
386           }
387 
388 done:
389           kmem_free(namebuf, var->namesize);
390           if (databuf != NULL) {
391                     kmem_free(databuf, databufsize);
392           }
393           return error;
394 }
395 
396 static int
efi_ioctl_var_next(struct efi_var_ioc * var)397 efi_ioctl_var_next(struct efi_var_ioc *var)
398 {
399           efi_status status;
400           uint16_t *namebuf;
401           size_t namebufsize;
402           unsigned long namesize;
403           int error;
404 
405           if (var->name == NULL || var->namesize == 0) {
406                     return EINVAL;
407           }
408           if (var->namesize > EFI_VARNAME_MAXBYTES) {
409                     return ENOMEM;
410           }
411 
412           namebufsize = var->namesize;
413           namebuf = kmem_alloc(namebufsize, KM_SLEEP);
414           error = copyin(var->name, namebuf, namebufsize);
415           if (error != 0) {
416                     goto done;
417           }
418 
419           CTASSERT(EFI_VARNAME_MAXBYTES <= ULONG_MAX);
420           namesize = namebufsize;
421           status = efi_ops->efi_nextvar(&namesize, namebuf, &var->vendor);
422           if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
423                     error = efi_status_to_error(status);
424                     goto done;
425           }
426           var->namesize = namesize;
427           if (status == EFI_SUCCESS) {
428                     error = copyout(namebuf, var->name,
429                         MIN(namesize, namebufsize));
430           } else {
431                     var->name = NULL;
432           }
433 
434 done:
435           kmem_free(namebuf, namebufsize);
436           return error;
437 }
438 
439 static int
efi_ioctl_var_set(struct efi_var_ioc * var)440 efi_ioctl_var_set(struct efi_var_ioc *var)
441 {
442           efi_status status;
443           uint16_t *namebuf;
444           uint16_t *databuf = NULL;
445           int error;
446 
447           if (var->name == NULL || var->namesize == 0) {
448                     return EINVAL;
449           }
450 
451           namebuf = kmem_alloc(var->namesize, KM_SLEEP);
452           error = copyin(var->name, namebuf, var->namesize);
453           if (error != 0) {
454                     goto done;
455           }
456           if (namebuf[var->namesize / 2 - 1] != '\0') {
457                     error = EINVAL;
458                     goto done;
459           }
460           if (var->datasize != 0) {
461                     databuf = kmem_alloc(var->datasize, KM_SLEEP);
462                     error = copyin(var->data, databuf, var->datasize);
463                     if (error != 0) {
464                               goto done;
465                     }
466           }
467 
468           status = efi_ops->efi_setvar(namebuf, &var->vendor, var->attrib,
469               var->datasize, databuf);
470           error = efi_status_to_error(status);
471 
472 done:
473           kmem_free(namebuf, var->namesize);
474           if (databuf != NULL) {
475                     kmem_free(databuf, var->datasize);
476           }
477           return error;
478 }
479 
480 static int
efi_ioctl(dev_t dev,u_long cmd,void * data,int flags,struct lwp * l)481 efi_ioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l)
482 {
483           KASSERT(efi_ops != NULL);
484 
485           switch (cmd) {
486           case EFIIOC_GET_TABLE:
487                     return efi_ioctl_get_table(data);
488           case EFIIOC_VAR_GET:
489                     return efi_ioctl_var_get(data);
490           case EFIIOC_VAR_NEXT:
491                     return efi_ioctl_var_next(data);
492           case EFIIOC_VAR_SET:
493                     return efi_ioctl_var_set(data);
494           }
495 
496           return ENOTTY;
497 }
498 
499 void
efi_register_ops(const struct efi_ops * ops)500 efi_register_ops(const struct efi_ops *ops)
501 {
502           KASSERT(efi_ops == NULL);
503           efi_ops = ops;
504 }
505 
506 void
efiattach(int count)507 efiattach(int count)
508 {
509 }
510