Home | History | Annotate | Line # | Download | only in dev
efi.c revision 1.5
      1 /* $NetBSD: efi.c,v 1.5 2023/05/22 16:27:39 riastradh Exp $ */
      2 
      3 /*-
      4  * Copyright (c) 2021 Jared McNeill <jmcneill (at) 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.5 2023/05/22 16:27:39 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