diff options
Diffstat (limited to 'sys/dev')
| -rw-r--r-- | sys/dev/efi.c | 306 | ||||
| -rw-r--r-- | sys/dev/efivar.h | 46 | ||||
| -rw-r--r-- | sys/dev/files.dev | 5 |
3 files changed, 356 insertions, 1 deletions
diff --git a/sys/dev/efi.c b/sys/dev/efi.c new file mode 100644 index 00000000000..3d5affab0fa --- /dev/null +++ b/sys/dev/efi.c @@ -0,0 +1,306 @@ +/* $NetBSD: efi.c,v 1.1 2021/10/10 13:03:09 jmcneill Exp $ */ + +/*- + * Copyright (c) 2021 Jared McNeill <jmcneill@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This pseudo-driver implements a /dev/efi character device that provides + * ioctls for using UEFI runtime time and variable services. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.1 2021/10/10 13:03:09 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/kmem.h> +#include <sys/atomic.h> +#include <sys/efiio.h> + +#include <dev/efivar.h> + +#ifdef _LP64 +#define EFIERR(x) (0x8000000000000000 | x) +#else +#define EFIERR(x) (0x80000000 | x) +#endif + +#define EFI_SUCCESS 0 +#define EFI_INVALID_PARAMETER EFIERR(2) +#define EFI_UNSUPPORTED EFIERR(3) +#define EFI_BUFFER_TOO_SMALL EFIERR(5) +#define EFI_DEVICE_ERROR EFIERR(7) +#define EFI_WRITE_PROTECTED EFIERR(8) +#define EFI_OUT_OF_RESOURCES EFIERR(9) +#define EFI_NOT_FOUND EFIERR(14) +#define EFI_SECURITY_VIOLATION EFIERR(26) + +#include "ioconf.h" + +/* + * Maximum length of an EFI variable name. The UEFI spec doesn't specify a + * constraint, but we want to limit the size to act as a guard rail against + * allocating too much kernel memory. + */ +#define EFI_VARNAME_MAXLENGTH EFI_PAGE_SIZE + +/* + * Pointer to arch specific EFI backend. + */ +static const struct efi_ops *efi_ops = NULL; + +/* + * Only allow one user of /dev/efi at a time. Even though the MD EFI backends + * should serialize individual UEFI RT calls, the UEFI specification says + * that a SetVariable() call between calls to GetNextVariableName() may + * produce unpredictable results, and we want to avoid this. + */ +static u_int efi_isopen = 0; + +static dev_type_open(efi_open); +static dev_type_close(efi_close); +static dev_type_ioctl(efi_ioctl); + +const struct cdevsw efi_cdevsw = { + .d_open = efi_open, + .d_close = efi_close, + .d_ioctl = efi_ioctl, + .d_read = noread, + .d_write = nowrite, + .d_stop = nostop, + .d_tty = notty, + .d_poll = nopoll, + .d_mmap = nommap, + .d_kqfilter = nokqfilter, + .d_discard = nodiscard, + .d_flag = D_OTHER | D_MPSAFE, +}; + +static int +efi_open(dev_t dev, int flags, int type, struct lwp *l) +{ + if (efi_ops == NULL) { + return ENXIO; + } + if (atomic_cas_uint(&efi_isopen, 0, 1) == 1) { + return EBUSY; + } + return 0; +} + +static int +efi_close(dev_t dev, int flags, int type, struct lwp *l) +{ + KASSERT(efi_isopen); + atomic_swap_uint(&efi_isopen, 0); + return 0; +} + +static int +efi_status_to_error(efi_status status) +{ + switch (status) { + case EFI_SUCCESS: + return 0; + case EFI_INVALID_PARAMETER: + return EINVAL; + case EFI_UNSUPPORTED: + return EOPNOTSUPP; + case EFI_BUFFER_TOO_SMALL: + return ERANGE; + case EFI_DEVICE_ERROR: + return EIO; + case EFI_WRITE_PROTECTED: + return EROFS; + case EFI_OUT_OF_RESOURCES: + return ENOMEM; + case EFI_NOT_FOUND: + return ENOENT; + case EFI_SECURITY_VIOLATION: + return EACCES; + default: + return EIO; + } +} + +static int +efi_ioctl_var_get(struct efi_var_ioc *var) +{ + uint16_t *namebuf; + void *databuf = NULL; + efi_status status; + int error; + + if (var->name == NULL || var->namesize == 0 || + (var->data != NULL && var->datasize == 0)) { + return EINVAL; + } + if (var->namesize > EFI_VARNAME_MAXLENGTH) { + return ENOMEM; + } + + namebuf = kmem_alloc(var->namesize, KM_SLEEP); + error = copyin(var->name, namebuf, var->namesize); + if (error != 0) { + goto done; + } + if (namebuf[var->namesize / 2 - 1] != '\0') { + error = EINVAL; + goto done; + } + if (var->datasize != 0) { + databuf = kmem_alloc(var->datasize, KM_SLEEP); + error = copyin(var->data, databuf, var->datasize); + if (error != 0) { + goto done; + } + } + + status = efi_ops->efi_getvar(namebuf, &var->vendor, &var->attrib, + &var->datasize, databuf); + if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) { + error = efi_status_to_error(status); + goto done; + } + if (status == EFI_SUCCESS && databuf != NULL) { + error = copyout(databuf, var->data, var->datasize); + } else { + var->data = NULL; + } + +done: + kmem_free(namebuf, var->namesize); + if (databuf != NULL) { + kmem_free(databuf, var->datasize); + } + return error; +} + +static int +efi_ioctl_var_next(struct efi_var_ioc *var) +{ + efi_status status; + uint16_t *namebuf; + int error; + + if (var->name == NULL || var->namesize == 0) { + return EINVAL; + } + if (var->namesize > EFI_VARNAME_MAXLENGTH) { + return ENOMEM; + } + + namebuf = kmem_alloc(var->namesize, KM_SLEEP); + error = copyin(var->name, namebuf, var->namesize); + if (error != 0) { + goto done; + } + + status = efi_ops->efi_nextvar(&var->namesize, namebuf, &var->vendor); + if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) { + error = efi_status_to_error(status); + goto done; + } + if (status == EFI_SUCCESS) { + error = copyout(namebuf, var->name, var->namesize); + } else { + var->name = NULL; + } + +done: + kmem_free(namebuf, var->namesize); + return error; +} + +static int +efi_ioctl_var_set(struct efi_var_ioc *var) +{ + efi_status status; + uint16_t *namebuf; + uint16_t *databuf = NULL; + int error; + + if (var->name == NULL || var->namesize == 0) { + return EINVAL; + } + + namebuf = kmem_alloc(var->namesize, KM_SLEEP); + error = copyin(var->name, namebuf, var->namesize); + if (error != 0) { + goto done; + } + if (namebuf[var->namesize / 2 - 1] != '\0') { + error = EINVAL; + goto done; + } + if (var->datasize != 0) { + databuf = kmem_alloc(var->datasize, KM_SLEEP); + error = copyin(var->data, databuf, var->datasize); + if (error != 0) { + goto done; + } + } + + status = efi_ops->efi_setvar(namebuf, &var->vendor, var->attrib, + var->datasize, databuf); + error = efi_status_to_error(status); + +done: + kmem_free(namebuf, var->namesize); + if (databuf != NULL) { + kmem_free(databuf, var->datasize); + } + return error; +} + +static int +efi_ioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l) +{ + KASSERT(efi_ops != NULL); + + switch (cmd) { + case EFIIOC_VAR_GET: + return efi_ioctl_var_get(data); + case EFIIOC_VAR_NEXT: + return efi_ioctl_var_next(data); + case EFIIOC_VAR_SET: + return efi_ioctl_var_set(data); + } + + return ENOTTY; +} + +void +efi_register_ops(const struct efi_ops *ops) +{ + KASSERT(efi_ops == NULL); + efi_ops = ops; +} + +void +efiattach(int count) +{ +} diff --git a/sys/dev/efivar.h b/sys/dev/efivar.h new file mode 100644 index 00000000000..21d6d61fd26 --- /dev/null +++ b/sys/dev/efivar.h @@ -0,0 +1,46 @@ +/* $NetBSD: efivar.h,v 1.1 2021/10/10 13:03:09 jmcneill Exp $ */ + +/*- + * Copyright (c) 2021 Jared McNeill <jmcneill@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _DEV_EFIVAR_H +#define _DEV_EFIVAR_H + +#include <machine/efi.h> + +struct efi_ops { + efi_status (*efi_gettime)(struct efi_tm *, struct efi_tmcap *); + efi_status (*efi_settime)(struct efi_tm *); + efi_status (*efi_getvar)(uint16_t *, struct uuid *, uint32_t *, + u_long *, void *); + efi_status (*efi_setvar)(uint16_t *, struct uuid *, uint32_t, + u_long, void *); + efi_status (*efi_nextvar)(u_long *, uint16_t *, struct uuid *); +}; + +void efi_register_ops(const struct efi_ops *); + +#endif /* !_DEV_EFIVAR_H */ diff --git a/sys/dev/files.dev b/sys/dev/files.dev index 5b5c00eb413..902363c0d72 100644 --- a/sys/dev/files.dev +++ b/sys/dev/files.dev @@ -1,4 +1,4 @@ -# $NetBSD: files.dev,v 1.7 2021/07/21 23:16:09 jmcneill Exp $ +# $NetBSD: files.dev,v 1.8 2021/10/10 13:03:09 jmcneill Exp $ file dev/bio.c bio needs-flag file dev/ccd.c ccd @@ -27,3 +27,6 @@ file dev/video.c video needs-flag file dev/vnd.c vnd file dev/ipmi.c ipmi needs-flag file dev/smbios.c smbios + +defpseudo efi +file dev/efi.c efi needs-flag |
