summaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/efi.c306
-rw-r--r--sys/dev/efivar.h46
-rw-r--r--sys/dev/files.dev5
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