aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/efidev
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/efidev')
-rw-r--r--sys/dev/efidev/efidev.c248
-rw-r--r--sys/dev/efidev/efirt.c878
-rw-r--r--sys/dev/efidev/efirtc.c203
3 files changed, 1329 insertions, 0 deletions
diff --git a/sys/dev/efidev/efidev.c b/sys/dev/efidev/efidev.c
new file mode 100644
index 000000000000..18bdaaa234f4
--- /dev/null
+++ b/sys/dev/efidev/efidev.c
@@ -0,0 +1,248 @@
+/*-
+ * Copyright (c) 2016 Netflix, Inc.
+ *
+ * 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
+ * in this position and unchanged.
+ * 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.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+
+#include <machine/efi.h>
+#include <sys/efiio.h>
+
+static d_ioctl_t efidev_ioctl;
+
+static struct cdevsw efi_cdevsw = {
+ .d_name = "efi",
+ .d_version = D_VERSION,
+ .d_ioctl = efidev_ioctl,
+};
+
+static int
+efidev_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
+ int flags __unused, struct thread *td __unused)
+{
+ int error;
+
+ switch (cmd) {
+ case EFIIOC_GET_TABLE:
+ {
+ struct efi_get_table_ioctl *egtioc =
+ (struct efi_get_table_ioctl *)addr;
+ void *buf = NULL;
+
+ error = efi_copy_table(&egtioc->guid,
+ egtioc->buf != NULL ? &buf : NULL, egtioc->buf_len,
+ &egtioc->table_len);
+
+ if (error != 0 || egtioc->buf == NULL)
+ break;
+
+ if (egtioc->buf_len < egtioc->table_len) {
+ error = EINVAL;
+ free(buf, M_TEMP);
+ break;
+ }
+
+ error = copyout(buf, egtioc->buf, egtioc->buf_len);
+ free(buf, M_TEMP);
+
+ break;
+ }
+ case EFIIOC_GET_TIME:
+ {
+ struct efi_tm *tm = (struct efi_tm *)addr;
+
+ error = efi_get_time(tm);
+ break;
+ }
+ case EFIIOC_SET_TIME:
+ {
+ struct efi_tm *tm = (struct efi_tm *)addr;
+
+ error = efi_set_time(tm);
+ break;
+ }
+ case EFIIOC_GET_WAKETIME:
+ {
+ struct efi_waketime_ioctl *wt = (struct efi_waketime_ioctl *)addr;
+
+ error = efi_get_waketime(&wt->enabled, &wt->pending,
+ &wt->waketime);
+ break;
+ }
+ case EFIIOC_SET_WAKETIME:
+ {
+ struct efi_waketime_ioctl *wt = (struct efi_waketime_ioctl *)addr;
+
+ error = efi_set_waketime(wt->enabled, &wt->waketime);
+ break;
+ }
+ case EFIIOC_VAR_GET:
+ {
+ struct efi_var_ioctl *ev = (struct efi_var_ioctl *)addr;
+ void *data;
+ efi_char *name;
+
+ data = malloc(ev->datasize, M_TEMP, M_WAITOK);
+ name = malloc(ev->namesize, M_TEMP, M_WAITOK);
+ error = copyin(ev->name, name, ev->namesize);
+ if (error)
+ goto vg_out;
+ if (name[ev->namesize / sizeof(efi_char) - 1] != 0) {
+ error = EINVAL;
+ goto vg_out;
+ }
+
+ error = efi_var_get(name, &ev->vendor, &ev->attrib,
+ &ev->datasize, data);
+
+ if (error == 0) {
+ error = copyout(data, ev->data, ev->datasize);
+ } else if (error == EOVERFLOW) {
+ /*
+ * Pass back the size we really need, but
+ * convert the error to 0 so the copyout
+ * happens. datasize was updated in the
+ * efi_var_get call.
+ */
+ ev->data = NULL;
+ error = 0;
+ }
+vg_out:
+ free(data, M_TEMP);
+ free(name, M_TEMP);
+ break;
+ }
+ case EFIIOC_VAR_NEXT:
+ {
+ struct efi_var_ioctl *ev = (struct efi_var_ioctl *)addr;
+ efi_char *name;
+
+ name = malloc(ev->namesize, M_TEMP, M_WAITOK);
+ error = copyin(ev->name, name, ev->namesize);
+ if (error)
+ goto vn_out;
+ /* Note: namesize is the buffer size, not the string lenght */
+
+ error = efi_var_nextname(&ev->namesize, name, &ev->vendor);
+ if (error == 0) {
+ error = copyout(name, ev->name, ev->namesize);
+ } else if (error == EOVERFLOW) {
+ ev->name = NULL;
+ error = 0;
+ }
+ vn_out:
+ free(name, M_TEMP);
+ break;
+ }
+ case EFIIOC_VAR_SET:
+ {
+ struct efi_var_ioctl *ev = (struct efi_var_ioctl *)addr;
+ void *data = NULL;
+ efi_char *name;
+
+ /* datasize == 0 -> delete (more or less) */
+ if (ev->datasize > 0)
+ data = malloc(ev->datasize, M_TEMP, M_WAITOK);
+ name = malloc(ev->namesize, M_TEMP, M_WAITOK);
+ if (ev->datasize) {
+ error = copyin(ev->data, data, ev->datasize);
+ if (error)
+ goto vs_out;
+ }
+ error = copyin(ev->name, name, ev->namesize);
+ if (error)
+ goto vs_out;
+ if (name[ev->namesize / sizeof(efi_char) - 1] != 0) {
+ error = EINVAL;
+ goto vs_out;
+ }
+
+ error = efi_var_set(name, &ev->vendor, ev->attrib, ev->datasize,
+ data);
+vs_out:
+ free(data, M_TEMP);
+ free(name, M_TEMP);
+ break;
+ }
+ default:
+ error = ENOTTY;
+ break;
+ }
+
+ return (error);
+}
+
+static struct cdev *efidev;
+
+static int
+efidev_modevents(module_t m, int event, void *arg __unused)
+{
+ struct make_dev_args mda;
+ int error;
+
+ switch (event) {
+ case MOD_LOAD:
+ /*
+ * If we have no efi environment, then don't create the device.
+ */
+ if (efi_rt_ok() != 0)
+ return (0);
+ make_dev_args_init(&mda);
+ mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
+ mda.mda_devsw = &efi_cdevsw;
+ mda.mda_uid = UID_ROOT;
+ mda.mda_gid = GID_WHEEL;
+ mda.mda_mode = 0700;
+ error = make_dev_s(&mda, &efidev, "efi");
+ return (error);
+
+ case MOD_UNLOAD:
+ if (efidev != NULL)
+ destroy_dev(efidev);
+ efidev = NULL;
+ return (0);
+
+ case MOD_SHUTDOWN:
+ return (0);
+
+ default:
+ return (EOPNOTSUPP);
+ }
+}
+
+static moduledata_t efidev_moddata = {
+ .name = "efidev",
+ .evhand = efidev_modevents,
+ .priv = NULL,
+};
+
+DECLARE_MODULE(efidev, efidev_moddata, SI_SUB_DRIVERS, SI_ORDER_ANY);
+MODULE_VERSION(efidev, 1);
+MODULE_DEPEND(efidev, efirt, 1, 1, 1);
diff --git a/sys/dev/efidev/efirt.c b/sys/dev/efidev/efirt.c
new file mode 100644
index 000000000000..b55c1c191077
--- /dev/null
+++ b/sys/dev/efidev/efirt.c
@@ -0,0 +1,878 @@
+/*-
+ * Copyright (c) 2004 Marcel Moolenaar
+ * Copyright (c) 2001 Doug Rabson
+ * Copyright (c) 2016, 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Konstantin Belousov
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+
+#include <sys/cdefs.h>
+#include "opt_acpi.h"
+
+#include <sys/param.h>
+#include <sys/efi.h>
+#include <sys/eventhandler.h>
+#include <sys/kernel.h>
+#include <sys/linker.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/msan.h>
+#include <sys/mutex.h>
+#include <sys/clock.h>
+#include <sys/proc.h>
+#include <sys/reboot.h>
+#include <sys/rwlock.h>
+#include <sys/sched.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/uio.h>
+#include <sys/vmmeter.h>
+
+#include <machine/fpu.h>
+#include <machine/efi.h>
+#include <machine/metadata.h>
+#include <machine/vmparam.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+#include <vm/vm_map.h>
+
+#ifdef DEV_ACPI
+#include <contrib/dev/acpica/include/acpi.h>
+#endif
+
+#define EFI_TABLE_ALLOC_MAX 0x800000
+
+static struct efi_systbl *efi_systbl;
+static eventhandler_tag efi_shutdown_tag;
+/*
+ * The following pointers point to tables in the EFI runtime service data pages.
+ * Care should be taken to make sure that we've properly entered the EFI runtime
+ * environment (efi_enter()) before dereferencing them.
+ */
+static struct efi_cfgtbl *efi_cfgtbl;
+static struct efi_rt *efi_runtime;
+
+static int efi_status2err[25] = {
+ 0, /* EFI_SUCCESS */
+ ENOEXEC, /* EFI_LOAD_ERROR */
+ EINVAL, /* EFI_INVALID_PARAMETER */
+ ENOSYS, /* EFI_UNSUPPORTED */
+ EMSGSIZE, /* EFI_BAD_BUFFER_SIZE */
+ EOVERFLOW, /* EFI_BUFFER_TOO_SMALL */
+ EBUSY, /* EFI_NOT_READY */
+ EIO, /* EFI_DEVICE_ERROR */
+ EROFS, /* EFI_WRITE_PROTECTED */
+ EAGAIN, /* EFI_OUT_OF_RESOURCES */
+ EIO, /* EFI_VOLUME_CORRUPTED */
+ ENOSPC, /* EFI_VOLUME_FULL */
+ ENXIO, /* EFI_NO_MEDIA */
+ ESTALE, /* EFI_MEDIA_CHANGED */
+ ENOENT, /* EFI_NOT_FOUND */
+ EACCES, /* EFI_ACCESS_DENIED */
+ ETIMEDOUT, /* EFI_NO_RESPONSE */
+ EADDRNOTAVAIL, /* EFI_NO_MAPPING */
+ ETIMEDOUT, /* EFI_TIMEOUT */
+ EDOOFUS, /* EFI_NOT_STARTED */
+ EALREADY, /* EFI_ALREADY_STARTED */
+ ECANCELED, /* EFI_ABORTED */
+ EPROTO, /* EFI_ICMP_ERROR */
+ EPROTO, /* EFI_TFTP_ERROR */
+ EPROTO /* EFI_PROTOCOL_ERROR */
+};
+
+enum efi_table_type {
+ TYPE_ESRT = 0,
+ TYPE_PROP,
+ TYPE_MEMORY_ATTR
+};
+
+static int efi_enter(void);
+static void efi_leave(void);
+
+int
+efi_status_to_errno(efi_status status)
+{
+ u_long code;
+
+ code = status & 0x3ffffffffffffffful;
+ return (code < nitems(efi_status2err) ? efi_status2err[code] : EDOOFUS);
+}
+
+static struct mtx efi_lock;
+SYSCTL_NODE(_hw, OID_AUTO, efi, CTLFLAG_RWTUN | CTLFLAG_MPSAFE, NULL,
+ "EFI");
+static bool efi_poweroff = true;
+SYSCTL_BOOL(_hw_efi, OID_AUTO, poweroff, CTLFLAG_RWTUN, &efi_poweroff, 0,
+ "If true, use EFI runtime services to power off in preference to ACPI");
+extern int print_efirt_faults;
+SYSCTL_INT(_hw_efi, OID_AUTO, print_faults, CTLFLAG_RWTUN,
+ &print_efirt_faults, 0,
+ "Print fault information upon trap from EFIRT calls: "
+ "0 - never, 1 - once, 2 - always");
+extern u_long cnt_efirt_faults;
+SYSCTL_ULONG(_hw_efi, OID_AUTO, total_faults, CTLFLAG_RD,
+ &cnt_efirt_faults, 0,
+ "Total number of faults that occurred during EFIRT calls");
+
+static bool
+efi_is_in_map(struct efi_md *map, int ndesc, int descsz, vm_offset_t addr)
+{
+ struct efi_md *p;
+ int i;
+
+ for (i = 0, p = map; i < ndesc; i++, p = efi_next_descriptor(p,
+ descsz)) {
+ if ((p->md_attr & EFI_MD_ATTR_RT) == 0)
+ continue;
+
+ if (addr >= p->md_virt &&
+ addr < p->md_virt + p->md_pages * EFI_PAGE_SIZE)
+ return (true);
+ }
+
+ return (false);
+}
+
+static void
+efi_shutdown_final(void *dummy __unused, int howto)
+{
+
+ /*
+ * On some systems, ACPI S5 is missing or does not function properly.
+ * When present, shutdown via EFI Runtime Services instead, unless
+ * disabled.
+ */
+ if ((howto & RB_POWEROFF) != 0 && efi_poweroff)
+ (void)efi_reset_system(EFI_RESET_SHUTDOWN);
+}
+
+static int
+efi_init(void)
+{
+ struct efi_map_header *efihdr;
+ struct efi_md *map;
+ struct efi_rt *rtdm;
+ size_t efisz;
+ int ndesc, rt_disabled;
+
+ rt_disabled = 0;
+ TUNABLE_INT_FETCH("efi.rt.disabled", &rt_disabled);
+ if (rt_disabled == 1)
+ return (0);
+ mtx_init(&efi_lock, "efi", NULL, MTX_DEF);
+
+ if (efi_systbl_phys == 0) {
+ if (bootverbose)
+ printf("EFI systbl not available\n");
+ return (0);
+ }
+
+ efi_systbl = (struct efi_systbl *)efi_phys_to_kva(efi_systbl_phys);
+ if (efi_systbl == NULL || efi_systbl->st_hdr.th_sig != EFI_SYSTBL_SIG) {
+ efi_systbl = NULL;
+ if (bootverbose)
+ printf("EFI systbl signature invalid\n");
+ return (0);
+ }
+ efi_cfgtbl = (efi_systbl->st_cfgtbl == 0) ? NULL :
+ (struct efi_cfgtbl *)efi_systbl->st_cfgtbl;
+ if (efi_cfgtbl == NULL) {
+ if (bootverbose)
+ printf("EFI config table is not present\n");
+ }
+
+ efihdr = (struct efi_map_header *)preload_search_info(preload_kmdp,
+ MODINFO_METADATA | MODINFOMD_EFI_MAP);
+ if (efihdr == NULL) {
+ if (bootverbose)
+ printf("EFI map is not present\n");
+ return (0);
+ }
+ efisz = (sizeof(struct efi_map_header) + 0xf) & ~0xf;
+ map = (struct efi_md *)((uint8_t *)efihdr + efisz);
+ if (efihdr->descriptor_size == 0)
+ return (ENOMEM);
+
+ ndesc = efihdr->memory_size / efihdr->descriptor_size;
+ if (!efi_create_1t1_map(map, ndesc, efihdr->descriptor_size)) {
+ if (bootverbose)
+ printf("EFI cannot create runtime map\n");
+ return (ENOMEM);
+ }
+
+ efi_runtime = (efi_systbl->st_rt == 0) ? NULL :
+ (struct efi_rt *)efi_systbl->st_rt;
+ if (efi_runtime == NULL) {
+ if (bootverbose)
+ printf("EFI runtime services table is not present\n");
+ efi_destroy_1t1_map();
+ return (ENXIO);
+ }
+
+#if defined(__aarch64__) || defined(__amd64__)
+ /*
+ * Some UEFI implementations have multiple implementations of the
+ * RS->GetTime function. They switch from one we can only use early
+ * in the boot process to one valid as a RunTime service only when we
+ * call RS->SetVirtualAddressMap. As this is not always the case, e.g.
+ * with an old loader.efi, check if the RS->GetTime function is within
+ * the EFI map, and fail to attach if not.
+ */
+ rtdm = (struct efi_rt *)efi_phys_to_kva((uintptr_t)efi_runtime);
+ if (rtdm == NULL || !efi_is_in_map(map, ndesc, efihdr->descriptor_size,
+ (vm_offset_t)rtdm->rt_gettime)) {
+ if (bootverbose)
+ printf(
+ "EFI runtime services table has an invalid pointer\n");
+ efi_runtime = NULL;
+ efi_destroy_1t1_map();
+ return (ENXIO);
+ }
+#endif
+
+ /*
+ * We use SHUTDOWN_PRI_LAST - 1 to trigger after IPMI, but before ACPI.
+ */
+ efi_shutdown_tag = EVENTHANDLER_REGISTER(shutdown_final,
+ efi_shutdown_final, NULL, SHUTDOWN_PRI_LAST - 1);
+
+ return (0);
+}
+
+static void
+efi_uninit(void)
+{
+
+ /* Most likely disabled by tunable */
+ if (efi_runtime == NULL)
+ return;
+ if (efi_shutdown_tag != NULL)
+ EVENTHANDLER_DEREGISTER(shutdown_final, efi_shutdown_tag);
+ efi_destroy_1t1_map();
+
+ efi_systbl = NULL;
+ efi_cfgtbl = NULL;
+ efi_runtime = NULL;
+
+ mtx_destroy(&efi_lock);
+}
+
+static int
+rt_ok(void)
+{
+
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ return (0);
+}
+
+/*
+ * The fpu_kern_enter() call in allows firmware to use FPU, as
+ * mandated by the specification. It also enters a critical section,
+ * giving us neccessary protection against context switches.
+ */
+static int
+efi_enter(void)
+{
+ struct thread *td;
+ pmap_t curpmap;
+ int error;
+
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ td = curthread;
+ curpmap = &td->td_proc->p_vmspace->vm_pmap;
+ PMAP_LOCK(curpmap);
+ mtx_lock(&efi_lock);
+ fpu_kern_enter(td, NULL, FPU_KERN_NOCTX);
+ error = efi_arch_enter();
+ if (error != 0) {
+ fpu_kern_leave(td, NULL);
+ mtx_unlock(&efi_lock);
+ PMAP_UNLOCK(curpmap);
+ } else {
+ MPASS((td->td_pflags & TDP_EFIRT) == 0);
+ td->td_pflags |= TDP_EFIRT;
+ }
+ return (error);
+}
+
+static void
+efi_leave(void)
+{
+ struct thread *td;
+ pmap_t curpmap;
+
+ td = curthread;
+ MPASS((td->td_pflags & TDP_EFIRT) != 0);
+ td->td_pflags &= ~TDP_EFIRT;
+
+ efi_arch_leave();
+
+ curpmap = &curproc->p_vmspace->vm_pmap;
+ fpu_kern_leave(td, NULL);
+ mtx_unlock(&efi_lock);
+ PMAP_UNLOCK(curpmap);
+}
+
+static int
+get_table(efi_guid_t *guid, void **ptr)
+{
+ struct efi_cfgtbl *ct;
+ u_long count;
+ int error;
+
+ if (efi_cfgtbl == NULL || efi_systbl == NULL)
+ return (ENXIO);
+ error = efi_enter();
+ if (error != 0)
+ return (error);
+ count = efi_systbl->st_entries;
+ ct = efi_cfgtbl;
+ while (count--) {
+ if (!bcmp(&ct->ct_guid, guid, sizeof(*guid))) {
+ *ptr = ct->ct_data;
+ efi_leave();
+ return (0);
+ }
+ ct++;
+ }
+
+ efi_leave();
+ return (ENOENT);
+}
+
+static int
+get_table_length(enum efi_table_type type, size_t *table_len, void **taddr)
+{
+ switch (type) {
+ case TYPE_ESRT:
+ {
+ struct efi_esrt_table *esrt = NULL;
+ efi_guid_t guid = EFI_TABLE_ESRT;
+ uint32_t fw_resource_count = 0;
+ size_t len = sizeof(*esrt);
+ int error;
+ void *buf;
+
+ error = efi_get_table(&guid, (void **)&esrt);
+ if (error != 0)
+ return (error);
+
+ buf = malloc(len, M_TEMP, M_WAITOK);
+ error = physcopyout((vm_paddr_t)esrt, buf, len);
+ if (error != 0) {
+ free(buf, M_TEMP);
+ return (error);
+ }
+
+ /* Check ESRT version */
+ if (((struct efi_esrt_table *)buf)->fw_resource_version !=
+ ESRT_FIRMWARE_RESOURCE_VERSION) {
+ free(buf, M_TEMP);
+ return (ENODEV);
+ }
+
+ fw_resource_count = ((struct efi_esrt_table *)buf)->
+ fw_resource_count;
+ if (fw_resource_count > EFI_TABLE_ALLOC_MAX /
+ sizeof(struct efi_esrt_entry_v1)) {
+ free(buf, M_TEMP);
+ return (ENOMEM);
+ }
+
+ len += fw_resource_count * sizeof(struct efi_esrt_entry_v1);
+ *table_len = len;
+
+ if (taddr != NULL)
+ *taddr = esrt;
+ free(buf, M_TEMP);
+ return (0);
+ }
+ case TYPE_PROP:
+ {
+ efi_guid_t guid = EFI_PROPERTIES_TABLE;
+ struct efi_prop_table *prop;
+ size_t len = sizeof(*prop);
+ uint32_t prop_len;
+ int error;
+ void *buf;
+
+ error = efi_get_table(&guid, (void **)&prop);
+ if (error != 0)
+ return (error);
+
+ buf = malloc(len, M_TEMP, M_WAITOK);
+ error = physcopyout((vm_paddr_t)prop, buf, len);
+ if (error != 0) {
+ free(buf, M_TEMP);
+ return (error);
+ }
+
+ prop_len = ((struct efi_prop_table *)buf)->length;
+ if (prop_len > EFI_TABLE_ALLOC_MAX) {
+ free(buf, M_TEMP);
+ return (ENOMEM);
+ }
+ *table_len = prop_len;
+
+ if (taddr != NULL)
+ *taddr = prop;
+ free(buf, M_TEMP);
+ return (0);
+ }
+ case TYPE_MEMORY_ATTR:
+ {
+ efi_guid_t guid = EFI_MEMORY_ATTRIBUTES_TABLE;
+ struct efi_memory_attribute_table *tbl_addr, *mem_addr;
+ int error;
+ void *buf;
+ size_t len = sizeof(struct efi_memory_attribute_table);
+
+ error = efi_get_table(&guid, (void **)&tbl_addr);
+ if (error)
+ return (error);
+
+ buf = malloc(len, M_TEMP, M_WAITOK);
+ error = physcopyout((vm_paddr_t)tbl_addr, buf, len);
+ if (error) {
+ free(buf, M_TEMP);
+ return (error);
+ }
+
+ mem_addr = (struct efi_memory_attribute_table *)buf;
+ if (mem_addr->version != 2) {
+ free(buf, M_TEMP);
+ return (EINVAL);
+ }
+ len += mem_addr->descriptor_size * mem_addr->num_ents;
+ if (len > EFI_TABLE_ALLOC_MAX) {
+ free(buf, M_TEMP);
+ return (ENOMEM);
+ }
+
+ *table_len = len;
+ if (taddr != NULL)
+ *taddr = tbl_addr;
+ free(buf, M_TEMP);
+ return (0);
+ }
+ }
+ return (ENOENT);
+}
+
+static int
+copy_table(efi_guid_t *guid, void **buf, size_t buf_len, size_t *table_len)
+{
+ static const struct known_table {
+ efi_guid_t guid;
+ enum efi_table_type type;
+ } tables[] = {
+ { EFI_TABLE_ESRT, TYPE_ESRT },
+ { EFI_PROPERTIES_TABLE, TYPE_PROP },
+ { EFI_MEMORY_ATTRIBUTES_TABLE, TYPE_MEMORY_ATTR }
+ };
+ size_t table_idx;
+ void *taddr;
+ int rc;
+
+ for (table_idx = 0; table_idx < nitems(tables); table_idx++) {
+ if (!bcmp(&tables[table_idx].guid, guid, sizeof(*guid)))
+ break;
+ }
+
+ if (table_idx == nitems(tables))
+ return (EINVAL);
+
+ rc = get_table_length(tables[table_idx].type, table_len, &taddr);
+ if (rc != 0)
+ return rc;
+
+ /* return table length to userspace */
+ if (buf == NULL)
+ return (0);
+
+ *buf = malloc(*table_len, M_TEMP, M_WAITOK);
+ rc = physcopyout((vm_paddr_t)taddr, *buf, *table_len);
+ return (rc);
+}
+
+static int efi_rt_handle_faults = EFI_RT_HANDLE_FAULTS_DEFAULT;
+SYSCTL_INT(_machdep, OID_AUTO, efi_rt_handle_faults, CTLFLAG_RWTUN,
+ &efi_rt_handle_faults, 0,
+ "Call EFI RT methods with fault handler wrapper around");
+
+static int
+efi_rt_arch_call_nofault(struct efirt_callinfo *ec)
+{
+
+ switch (ec->ec_argcnt) {
+ case 0:
+ ec->ec_efi_status = ((register_t EFIABI_ATTR (*)(void))
+ ec->ec_fptr)();
+ break;
+ case 1:
+ ec->ec_efi_status = ((register_t EFIABI_ATTR (*)(register_t))
+ ec->ec_fptr)(ec->ec_arg1);
+ break;
+ case 2:
+ ec->ec_efi_status = ((register_t EFIABI_ATTR (*)(register_t,
+ register_t))ec->ec_fptr)(ec->ec_arg1, ec->ec_arg2);
+ break;
+ case 3:
+ ec->ec_efi_status = ((register_t EFIABI_ATTR (*)(register_t,
+ register_t, register_t))ec->ec_fptr)(ec->ec_arg1,
+ ec->ec_arg2, ec->ec_arg3);
+ break;
+ case 4:
+ ec->ec_efi_status = ((register_t EFIABI_ATTR (*)(register_t,
+ register_t, register_t, register_t))ec->ec_fptr)(
+ ec->ec_arg1, ec->ec_arg2, ec->ec_arg3, ec->ec_arg4);
+ break;
+ case 5:
+ ec->ec_efi_status = ((register_t EFIABI_ATTR (*)(register_t,
+ register_t, register_t, register_t, register_t))
+ ec->ec_fptr)(ec->ec_arg1, ec->ec_arg2, ec->ec_arg3,
+ ec->ec_arg4, ec->ec_arg5);
+ break;
+ default:
+ panic("efi_rt_arch_call: %d args", (int)ec->ec_argcnt);
+ }
+
+ return (0);
+}
+
+static int
+efi_call(struct efirt_callinfo *ecp)
+{
+ int error;
+
+ error = efi_enter();
+ if (error != 0)
+ return (error);
+ error = efi_rt_handle_faults ? efi_rt_arch_call(ecp) :
+ efi_rt_arch_call_nofault(ecp);
+ efi_leave();
+ if (error == 0)
+ error = efi_status_to_errno(ecp->ec_efi_status);
+ else if (bootverbose)
+ printf("EFI %s call faulted, error %d\n", ecp->ec_name, error);
+ return (error);
+}
+
+#define EFI_RT_METHOD_PA(method) \
+ ((uintptr_t)((struct efi_rt *)efi_phys_to_kva((uintptr_t) \
+ efi_runtime))->method)
+
+static int
+efi_get_time_locked(struct efi_tm *tm, struct efi_tmcap *tmcap)
+{
+ struct efirt_callinfo ec;
+ int error;
+
+ EFI_TIME_OWNED();
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ bzero(&ec, sizeof(ec));
+ ec.ec_name = "rt_gettime";
+ ec.ec_argcnt = 2;
+ ec.ec_arg1 = (uintptr_t)tm;
+ ec.ec_arg2 = (uintptr_t)tmcap;
+ ec.ec_fptr = EFI_RT_METHOD_PA(rt_gettime);
+ error = efi_call(&ec);
+ if (error == 0)
+ kmsan_mark(tm, sizeof(*tm), KMSAN_STATE_INITED);
+ return (error);
+}
+
+static int
+get_time(struct efi_tm *tm)
+{
+ struct efi_tmcap dummy;
+ int error;
+
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ EFI_TIME_LOCK();
+ /*
+ * UEFI spec states that the Capabilities argument to GetTime is
+ * optional, but some UEFI implementations choke when passed a NULL
+ * pointer. Pass a dummy efi_tmcap, even though we won't use it,
+ * to workaround such implementations.
+ */
+ error = efi_get_time_locked(tm, &dummy);
+ EFI_TIME_UNLOCK();
+ return (error);
+}
+
+static int
+get_waketime(uint8_t *enabled, uint8_t *pending, struct efi_tm *tm)
+{
+ struct efirt_callinfo ec;
+ int error;
+#ifdef DEV_ACPI
+ UINT32 acpiRtcEnabled;
+#endif
+
+ if (efi_runtime == NULL)
+ return (ENXIO);
+
+ EFI_TIME_LOCK();
+ bzero(&ec, sizeof(ec));
+ ec.ec_name = "rt_getwaketime";
+ ec.ec_argcnt = 3;
+ ec.ec_arg1 = (uintptr_t)enabled;
+ ec.ec_arg2 = (uintptr_t)pending;
+ ec.ec_arg3 = (uintptr_t)tm;
+ ec.ec_fptr = EFI_RT_METHOD_PA(rt_getwaketime);
+ error = efi_call(&ec);
+ EFI_TIME_UNLOCK();
+
+#ifdef DEV_ACPI
+ if (error == 0) {
+ error = AcpiReadBitRegister(ACPI_BITREG_RT_CLOCK_ENABLE,
+ &acpiRtcEnabled);
+ if (ACPI_SUCCESS(error)) {
+ *enabled = *enabled && acpiRtcEnabled;
+ } else
+ error = EIO;
+ }
+#endif
+
+ return (error);
+}
+
+static int
+set_waketime(uint8_t enable, struct efi_tm *tm)
+{
+ struct efirt_callinfo ec;
+ int error;
+
+ if (efi_runtime == NULL)
+ return (ENXIO);
+
+ EFI_TIME_LOCK();
+ bzero(&ec, sizeof(ec));
+ ec.ec_name = "rt_setwaketime";
+ ec.ec_argcnt = 2;
+ ec.ec_arg1 = (uintptr_t)enable;
+ ec.ec_arg2 = (uintptr_t)tm;
+ ec.ec_fptr = EFI_RT_METHOD_PA(rt_setwaketime);
+ error = efi_call(&ec);
+ EFI_TIME_UNLOCK();
+
+#ifdef DEV_ACPI
+ if (error == 0) {
+ error = AcpiWriteBitRegister(ACPI_BITREG_RT_CLOCK_ENABLE,
+ (enable != 0) ? 1 : 0);
+ if (ACPI_FAILURE(error))
+ error = EIO;
+ }
+#endif
+
+ return (error);
+}
+
+static int
+get_time_capabilities(struct efi_tmcap *tmcap)
+{
+ struct efi_tm dummy;
+ int error;
+
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ EFI_TIME_LOCK();
+ error = efi_get_time_locked(&dummy, tmcap);
+ EFI_TIME_UNLOCK();
+ return (error);
+}
+
+static int
+reset_system(enum efi_reset type)
+{
+ struct efirt_callinfo ec;
+
+ switch (type) {
+ case EFI_RESET_COLD:
+ case EFI_RESET_WARM:
+ case EFI_RESET_SHUTDOWN:
+ break;
+ default:
+ return (EINVAL);
+ }
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ bzero(&ec, sizeof(ec));
+ ec.ec_name = "rt_reset";
+ ec.ec_argcnt = 4;
+ ec.ec_arg1 = (uintptr_t)type;
+ ec.ec_arg2 = (uintptr_t)0;
+ ec.ec_arg3 = (uintptr_t)0;
+ ec.ec_arg4 = (uintptr_t)NULL;
+ ec.ec_fptr = EFI_RT_METHOD_PA(rt_reset);
+ return (efi_call(&ec));
+}
+
+static int
+efi_set_time_locked(struct efi_tm *tm)
+{
+ struct efirt_callinfo ec;
+
+ EFI_TIME_OWNED();
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ bzero(&ec, sizeof(ec));
+ ec.ec_name = "rt_settime";
+ ec.ec_argcnt = 1;
+ ec.ec_arg1 = (uintptr_t)tm;
+ ec.ec_fptr = EFI_RT_METHOD_PA(rt_settime);
+ return (efi_call(&ec));
+}
+
+static int
+set_time(struct efi_tm *tm)
+{
+ int error;
+
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ EFI_TIME_LOCK();
+ error = efi_set_time_locked(tm);
+ EFI_TIME_UNLOCK();
+ return (error);
+}
+
+static int
+var_get(efi_char *name, efi_guid_t *vendor, uint32_t *attrib,
+ size_t *datasize, void *data)
+{
+ struct efirt_callinfo ec;
+ int error;
+
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ bzero(&ec, sizeof(ec));
+ ec.ec_argcnt = 5;
+ ec.ec_name = "rt_getvar";
+ ec.ec_arg1 = (uintptr_t)name;
+ ec.ec_arg2 = (uintptr_t)vendor;
+ ec.ec_arg3 = (uintptr_t)attrib;
+ ec.ec_arg4 = (uintptr_t)datasize;
+ ec.ec_arg5 = (uintptr_t)data;
+ ec.ec_fptr = EFI_RT_METHOD_PA(rt_getvar);
+ error = efi_call(&ec);
+ if (error == 0)
+ kmsan_mark(data, *datasize, KMSAN_STATE_INITED);
+ return (error);
+}
+
+static int
+var_nextname(size_t *namesize, efi_char *name, efi_guid_t *vendor)
+{
+ struct efirt_callinfo ec;
+ int error;
+
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ bzero(&ec, sizeof(ec));
+ ec.ec_argcnt = 3;
+ ec.ec_name = "rt_scanvar";
+ ec.ec_arg1 = (uintptr_t)namesize;
+ ec.ec_arg2 = (uintptr_t)name;
+ ec.ec_arg3 = (uintptr_t)vendor;
+ ec.ec_fptr = EFI_RT_METHOD_PA(rt_scanvar);
+ error = efi_call(&ec);
+ if (error == 0)
+ kmsan_mark(name, *namesize, KMSAN_STATE_INITED);
+ return (error);
+}
+
+static int
+var_set(efi_char *name, efi_guid_t *vendor, uint32_t attrib,
+ size_t datasize, void *data)
+{
+ struct efirt_callinfo ec;
+
+ if (efi_runtime == NULL)
+ return (ENXIO);
+ bzero(&ec, sizeof(ec));
+ ec.ec_argcnt = 5;
+ ec.ec_name = "rt_setvar";
+ ec.ec_arg1 = (uintptr_t)name;
+ ec.ec_arg2 = (uintptr_t)vendor;
+ ec.ec_arg3 = (uintptr_t)attrib;
+ ec.ec_arg4 = (uintptr_t)datasize;
+ ec.ec_arg5 = (uintptr_t)data;
+ ec.ec_fptr = EFI_RT_METHOD_PA(rt_setvar);
+ return (efi_call(&ec));
+}
+
+const static struct efi_ops efi_ops = {
+ .rt_ok = rt_ok,
+ .get_table = get_table,
+ .copy_table = copy_table,
+ .get_time = get_time,
+ .get_time_capabilities = get_time_capabilities,
+ .reset_system = reset_system,
+ .set_time = set_time,
+ .get_waketime = get_waketime,
+ .set_waketime = set_waketime,
+ .var_get = var_get,
+ .var_nextname = var_nextname,
+ .var_set = var_set,
+};
+const struct efi_ops *active_efi_ops = &efi_ops;
+
+static int
+efirt_modevents(module_t m, int event, void *arg __unused)
+{
+
+ switch (event) {
+ case MOD_LOAD:
+ return (efi_init());
+
+ case MOD_UNLOAD:
+ efi_uninit();
+ return (0);
+
+ case MOD_SHUTDOWN:
+ return (0);
+
+ default:
+ return (EOPNOTSUPP);
+ }
+}
+
+static moduledata_t efirt_moddata = {
+ .name = "efirt",
+ .evhand = efirt_modevents,
+ .priv = NULL,
+};
+/* After fpuinitstate, before efidev */
+DECLARE_MODULE(efirt, efirt_moddata, SI_SUB_DRIVERS, SI_ORDER_SECOND);
+MODULE_VERSION(efirt, 1);
diff --git a/sys/dev/efidev/efirtc.c b/sys/dev/efidev/efirtc.c
new file mode 100644
index 000000000000..69d2c0b1af9f
--- /dev/null
+++ b/sys/dev/efidev/efirtc.c
@@ -0,0 +1,203 @@
+/*-
+ * Copyright (c) 2017 Andrew Turner
+ * All rights reserved.
+ *
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory under DARPA/AFRL contract FA8750-10-C-0237
+ * ("CTSRD"), as part of the DARPA CRASH research programme.
+ *
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/clock.h>
+#include <sys/efi.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include "clock_if.h"
+
+static bool efirtc_zeroes_subseconds;
+static struct timespec efirtc_resadj;
+
+static const u_int us_per_s = 1000000;
+static const u_int ns_per_s = 1000000000;
+static const u_int ns_per_us = 1000;
+
+static void
+efirtc_identify(driver_t *driver, device_t parent)
+{
+
+ /* Don't add the driver unless we have working runtime services. */
+ if (efi_rt_ok() != 0)
+ return;
+ if (device_find_child(parent, "efirtc", DEVICE_UNIT_ANY) != NULL)
+ return;
+ if (BUS_ADD_CHILD(parent, 0, "efirtc", DEVICE_UNIT_ANY) == NULL)
+ device_printf(parent, "add child failed\n");
+}
+
+static int
+efirtc_probe(device_t dev)
+{
+ struct efi_tm tm;
+ int error;
+
+ /*
+ * Check whether we can read the time. This will stop us from attaching
+ * when there is EFI Runtime support but the gettime function is
+ * unimplemented, e.g. on some builds of U-Boot.
+ */
+ if ((error = efi_get_time(&tm)) != 0) {
+ if (bootverbose)
+ device_printf(dev, "cannot read EFI realtime clock, "
+ "error %d\n", error);
+ return (error);
+ }
+ device_set_desc(dev, "EFI Realtime Clock");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+efirtc_attach(device_t dev)
+{
+ struct efi_tmcap tmcap;
+ long res;
+ int error;
+
+ bzero(&tmcap, sizeof(tmcap));
+ if ((error = efi_get_time_capabilities(&tmcap)) != 0) {
+ device_printf(dev, "cannot get EFI time capabilities");
+ return (error);
+ }
+
+ /* Translate resolution in Hz to tick length in usec. */
+ if (tmcap.tc_res == 0)
+ res = us_per_s; /* 0 is insane, assume 1 Hz. */
+ else if (tmcap.tc_res > us_per_s)
+ res = 1; /* 1us is the best we can represent */
+ else
+ res = us_per_s / tmcap.tc_res;
+
+ /* Clock rounding adjustment is 1/2 of resolution, in nsec. */
+ efirtc_resadj.tv_nsec = (res * ns_per_us) / 2;
+
+ /* Does the clock zero the subseconds when time is set? */
+ efirtc_zeroes_subseconds = tmcap.tc_stz;
+
+ /*
+ * Register. If the clock zeroes out the subseconds when it's set,
+ * schedule the SetTime calls to happen just before top-of-second.
+ */
+ clock_register_flags(dev, res, CLOCKF_SETTIME_NO_ADJ);
+ if (efirtc_zeroes_subseconds)
+ clock_schedule(dev, ns_per_s - ns_per_us);
+
+ return (0);
+}
+
+static int
+efirtc_detach(device_t dev)
+{
+
+ clock_unregister(dev);
+ return (0);
+}
+
+static int
+efirtc_gettime(device_t dev, struct timespec *ts)
+{
+ struct clocktime ct;
+ struct efi_tm tm;
+ int error;
+
+ error = efi_get_time(&tm);
+ if (error != 0)
+ return (error);
+
+ ct.sec = tm.tm_sec;
+ ct.min = tm.tm_min;
+ ct.hour = tm.tm_hour;
+ ct.day = tm.tm_mday;
+ ct.mon = tm.tm_mon;
+ ct.year = tm.tm_year;
+ ct.nsec = tm.tm_nsec;
+
+ clock_dbgprint_ct(dev, CLOCK_DBG_READ, &ct);
+ return (clock_ct_to_ts(&ct, ts));
+}
+
+static int
+efirtc_settime(device_t dev, struct timespec *ts)
+{
+ struct clocktime ct;
+ struct efi_tm tm;
+
+ /*
+ * We request a timespec with no resolution-adjustment so that we can
+ * apply it ourselves based on whether or not the clock zeroes the
+ * sub-second part of the time when setting the time.
+ */
+ ts->tv_sec -= utc_offset();
+ if (!efirtc_zeroes_subseconds)
+ timespecadd(ts, &efirtc_resadj, ts);
+
+ clock_ts_to_ct(ts, &ct);
+ clock_dbgprint_ct(dev, CLOCK_DBG_WRITE, &ct);
+
+ bzero(&tm, sizeof(tm));
+ tm.tm_sec = ct.sec;
+ tm.tm_min = ct.min;
+ tm.tm_hour = ct.hour;
+ tm.tm_mday = ct.day;
+ tm.tm_mon = ct.mon;
+ tm.tm_year = ct.year;
+ tm.tm_nsec = ct.nsec;
+
+ return (efi_set_time(&tm));
+}
+
+static device_method_t efirtc_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_identify, efirtc_identify),
+ DEVMETHOD(device_probe, efirtc_probe),
+ DEVMETHOD(device_attach, efirtc_attach),
+ DEVMETHOD(device_detach, efirtc_detach),
+
+ /* Clock interface */
+ DEVMETHOD(clock_gettime, efirtc_gettime),
+ DEVMETHOD(clock_settime, efirtc_settime),
+
+ DEVMETHOD_END
+};
+
+static driver_t efirtc_driver = {
+ "efirtc",
+ efirtc_methods,
+ 0
+};
+
+DRIVER_MODULE(efirtc, nexus, efirtc_driver, 0, 0);
+MODULE_VERSION(efirtc, 1);
+MODULE_DEPEND(efirtc, efirt, 1, 1, 1);