Home | History | Annotate | Line # | Download | only in dev
efi.c revision 1.1
      1 /* $NetBSD: efi.c,v 1.1 2021/10/10 13:03:09 jmcneill 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.1 2021/10/10 13:03:09 jmcneill 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 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 	if (efi_ops == NULL) {
    106 		return ENXIO;
    107 	}
    108 	if (atomic_cas_uint(&efi_isopen, 0, 1) == 1) {
    109 		return EBUSY;
    110 	}
    111 	return 0;
    112 }
    113 
    114 static int
    115 efi_close(dev_t dev, int flags, int type, struct lwp *l)
    116 {
    117 	KASSERT(efi_isopen);
    118 	atomic_swap_uint(&efi_isopen, 0);
    119 	return 0;
    120 }
    121 
    122 static int
    123 efi_status_to_error(efi_status status)
    124 {
    125 	switch (status) {
    126 	case EFI_SUCCESS:
    127 		return 0;
    128 	case EFI_INVALID_PARAMETER:
    129 		return EINVAL;
    130 	case EFI_UNSUPPORTED:
    131 		return EOPNOTSUPP;
    132 	case EFI_BUFFER_TOO_SMALL:
    133 		return ERANGE;
    134 	case EFI_DEVICE_ERROR:
    135 		return EIO;
    136 	case EFI_WRITE_PROTECTED:
    137 		return EROFS;
    138 	case EFI_OUT_OF_RESOURCES:
    139 		return ENOMEM;
    140 	case EFI_NOT_FOUND:
    141 		return ENOENT;
    142 	case EFI_SECURITY_VIOLATION:
    143 		return EACCES;
    144 	default:
    145 		return EIO;
    146 	}
    147 }
    148 
    149 static int
    150 efi_ioctl_var_get(struct efi_var_ioc *var)
    151 {
    152 	uint16_t *namebuf;
    153 	void *databuf = NULL;
    154 	efi_status status;
    155 	int error;
    156 
    157 	if (var->name == NULL || var->namesize == 0 ||
    158 	    (var->data != NULL && var->datasize == 0)) {
    159 		return EINVAL;
    160 	}
    161 	if (var->namesize > EFI_VARNAME_MAXLENGTH) {
    162 		return ENOMEM;
    163 	}
    164 
    165 	namebuf = kmem_alloc(var->namesize, KM_SLEEP);
    166 	error = copyin(var->name, namebuf, var->namesize);
    167 	if (error != 0) {
    168 		goto done;
    169 	}
    170 	if (namebuf[var->namesize / 2 - 1] != '\0') {
    171 		error = EINVAL;
    172 		goto done;
    173 	}
    174 	if (var->datasize != 0) {
    175 		databuf = kmem_alloc(var->datasize, KM_SLEEP);
    176 		error = copyin(var->data, databuf, var->datasize);
    177 		if (error != 0) {
    178 			goto done;
    179 		}
    180 	}
    181 
    182 	status = efi_ops->efi_getvar(namebuf, &var->vendor, &var->attrib,
    183 	    &var->datasize, databuf);
    184 	if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
    185 		error = efi_status_to_error(status);
    186 		goto done;
    187 	}
    188 	if (status == EFI_SUCCESS && databuf != NULL) {
    189 		error = copyout(databuf, var->data, var->datasize);
    190 	} else {
    191 		var->data = NULL;
    192 	}
    193 
    194 done:
    195 	kmem_free(namebuf, var->namesize);
    196 	if (databuf != NULL) {
    197 		kmem_free(databuf, var->datasize);
    198 	}
    199 	return error;
    200 }
    201 
    202 static int
    203 efi_ioctl_var_next(struct efi_var_ioc *var)
    204 {
    205 	efi_status status;
    206 	uint16_t *namebuf;
    207 	int error;
    208 
    209 	if (var->name == NULL || var->namesize == 0) {
    210 		return EINVAL;
    211 	}
    212 	if (var->namesize > EFI_VARNAME_MAXLENGTH) {
    213 		return ENOMEM;
    214 	}
    215 
    216 	namebuf = kmem_alloc(var->namesize, KM_SLEEP);
    217 	error = copyin(var->name, namebuf, var->namesize);
    218 	if (error != 0) {
    219 		goto done;
    220 	}
    221 
    222 	status = efi_ops->efi_nextvar(&var->namesize, namebuf, &var->vendor);
    223 	if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
    224 		error = efi_status_to_error(status);
    225 		goto done;
    226 	}
    227 	if (status == EFI_SUCCESS) {
    228 		error = copyout(namebuf, var->name, var->namesize);
    229 	} else {
    230 		var->name = NULL;
    231 	}
    232 
    233 done:
    234 	kmem_free(namebuf, var->namesize);
    235 	return error;
    236 }
    237 
    238 static int
    239 efi_ioctl_var_set(struct efi_var_ioc *var)
    240 {
    241 	efi_status status;
    242 	uint16_t *namebuf;
    243 	uint16_t *databuf = NULL;
    244 	int error;
    245 
    246 	if (var->name == NULL || var->namesize == 0) {
    247 		return EINVAL;
    248 	}
    249 
    250 	namebuf = kmem_alloc(var->namesize, KM_SLEEP);
    251 	error = copyin(var->name, namebuf, var->namesize);
    252 	if (error != 0) {
    253 		goto done;
    254 	}
    255 	if (namebuf[var->namesize / 2 - 1] != '\0') {
    256 		error = EINVAL;
    257 		goto done;
    258 	}
    259 	if (var->datasize != 0) {
    260 		databuf = kmem_alloc(var->datasize, KM_SLEEP);
    261 		error = copyin(var->data, databuf, var->datasize);
    262 		if (error != 0) {
    263 			goto done;
    264 		}
    265 	}
    266 
    267 	status = efi_ops->efi_setvar(namebuf, &var->vendor, var->attrib,
    268 	    var->datasize, databuf);
    269 	error = efi_status_to_error(status);
    270 
    271 done:
    272 	kmem_free(namebuf, var->namesize);
    273 	if (databuf != NULL) {
    274 		kmem_free(databuf, var->datasize);
    275 	}
    276 	return error;
    277 }
    278 
    279 static int
    280 efi_ioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l)
    281 {
    282 	KASSERT(efi_ops != NULL);
    283 
    284 	switch (cmd) {
    285 	case EFIIOC_VAR_GET:
    286 		return efi_ioctl_var_get(data);
    287 	case EFIIOC_VAR_NEXT:
    288 		return efi_ioctl_var_next(data);
    289 	case EFIIOC_VAR_SET:
    290 		return efi_ioctl_var_set(data);
    291 	}
    292 
    293 	return ENOTTY;
    294 }
    295 
    296 void
    297 efi_register_ops(const struct efi_ops *ops)
    298 {
    299 	KASSERT(efi_ops == NULL);
    300 	efi_ops = ops;
    301 }
    302 
    303 void
    304 efiattach(int count)
    305 {
    306 }
    307