diff options
| author | riastradh <riastradh@NetBSD.org> | 2023-05-22 16:27:58 +0000 |
|---|---|---|
| committer | riastradh <riastradh@NetBSD.org> | 2023-05-22 16:27:58 +0000 |
| commit | 7a20a79b976e40a8cd5ddbfc7d4515ec5b4432b3 (patch) | |
| tree | 572e711ce1f49ad32814907ea90522fabe136557 /sys/dev | |
| parent | 80fbbf7d5697b81a69b0dfb89d796e2c9fd74a9e (diff) | |
efi(4): Implement MI parts of EFIIOC_GET_TABLE.
Intended to be compatible with FreeBSD.
Not yet supported on any architectures.
PR kern/57076
XXX pullup-10
Diffstat (limited to 'sys/dev')
| -rw-r--r-- | sys/dev/efi.c | 204 | ||||
| -rw-r--r-- | sys/dev/efivar.h | 10 |
2 files changed, 209 insertions, 5 deletions
diff --git a/sys/dev/efi.c b/sys/dev/efi.c index e6bd1b5ab94..4333f3962e4 100644 --- a/sys/dev/efi.c +++ b/sys/dev/efi.c @@ -1,4 +1,4 @@ -/* $NetBSD: efi.c,v 1.6 2023/05/22 16:27:49 riastradh Exp $ */ +/* $NetBSD: efi.c,v 1.7 2023/05/22 16:27:58 riastradh Exp $ */ /*- * Copyright (c) 2021 Jared McNeill <jmcneill@invisible.ca> @@ -32,7 +32,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.6 2023/05/22 16:27:49 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.7 2023/05/22 16:27:58 riastradh Exp $"); #include <sys/param.h> #include <sys/conf.h> @@ -40,7 +40,10 @@ __KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.6 2023/05/22 16:27:49 riastradh Exp $"); #include <sys/atomic.h> #include <sys/efiio.h> +#include <uvm/uvm_extern.h> + #include <dev/efivar.h> +#include <dev/mm.h> #include "ioconf.h" @@ -133,6 +136,201 @@ efi_status_to_error(efi_status status) } } +/* XXX move to efi.h */ +#define EFI_SYSTEM_RESOURCE_TABLE_GUID \ + {0xb122a263,0x3661,0x4f68,0x99,0x29,{0x78,0xf8,0xb0,0xd6,0x21,0x80}} +#define EFI_PROPERTIES_TABLE \ + {0x880aaca3,0x4adc,0x4a04,0x90,0x79,{0xb7,0x47,0x34,0x08,0x25,0xe5}} + +#define EFI_SYSTEM_RESOURCE_TABLE_FIRMWARE_RESOURCE_VERSION 1 + +struct EFI_SYSTEM_RESOURCE_ENTRY { + struct uuid FwClass; + uint32_t FwType; + uint32_t FwVersion; + uint32_t LowestSupportedFwVersion; + uint32_t CapsuleFlags; + uint32_t LastAttemptVersion; + uint32_t LastAttemptStatus; +}; + +struct EFI_SYSTEM_RESOURCE_TABLE { + uint32_t FwResourceCount; + uint32_t FwResourceCountMax; + uint64_t FwResourceVersion; + struct EFI_SYSTEM_RESOURCE_ENTRY Entries[]; +}; + +static void * +efi_map_pa(uint64_t addr, bool *directp) +{ + paddr_t pa = addr; + vaddr_t va; + + /* + * Verify the address is not truncated by conversion to + * paddr_t. This might happen with a 64-bit EFI booting a + * 32-bit OS. + */ + if (pa != addr) + return NULL; + + /* + * Try direct-map if we have it. If it works, note that it was + * direct-mapped for efi_unmap. + */ +#ifdef __HAVE_MM_MD_DIRECT_MAPPED_PHYS + if (mm_md_direct_mapped_phys(pa, &va)) { + *directp = true; + return (void *)va; + } +#endif + + /* + * No direct map. Reserve a page of kernel virtual address + * space, with no backing, to map to the physical address. + */ + va = uvm_km_alloc(kernel_map, PAGE_SIZE, 0, + UVM_KMF_VAONLY|UVM_KMF_WAITVA); + KASSERT(va != 0); + + /* + * Map the kva page to the physical address and update the + * kernel pmap so we can use it. + */ + pmap_kenter_pa(va, pa, VM_PROT_READ, 0); + pmap_update(pmap_kernel()); + + /* + * Success! Return the VA and note that it was not + * direct-mapped for efi_unmap. + */ + *directp = false; + return (void *)va; +} + +static void +efi_unmap(void *ptr, bool direct) +{ + vaddr_t va = (vaddr_t)ptr; + + /* + * If it was direct-mapped, nothing to do here. + */ + if (direct) + return; + + /* + * First remove the mapping from the kernel pmap so that it can + * be reused, before we free the kva and let anyone else reuse + * it. + */ + pmap_kremove(va, PAGE_SIZE); + pmap_update(pmap_kernel()); + + /* + * Next free the kva so it can be reused by someone else. + */ + uvm_km_free(kernel_map, va, PAGE_SIZE, UVM_KMF_VAONLY); +} + +static int +efi_ioctl_got_table(struct efi_get_table_ioc *ioc, void *ptr, size_t len) +{ + + /* + * Return the actual table length. + */ + ioc->table_len = len; + + /* + * Copy out as much as we can into the user's allocated buffer. + */ + return copyout(ptr, ioc->buf, MIN(ioc->buf_len, len)); +} + +static int +efi_ioctl_get_esrt(struct efi_get_table_ioc *ioc, + struct EFI_SYSTEM_RESOURCE_TABLE *tab) +{ + + /* + * Verify the firmware resource version is one we understand. + */ + if (tab->FwResourceVersion != + EFI_SYSTEM_RESOURCE_TABLE_FIRMWARE_RESOURCE_VERSION) + return ENOENT; + + /* + * Verify the resource count fits within the single page we + * have mapped. + * + * XXX What happens if it doesn't? Are we expected to map more + * than one page, according to the table header? The UEFI spec + * is unclear on this. + */ + const size_t entry_space = PAGE_SIZE - + offsetof(struct EFI_SYSTEM_RESOURCE_TABLE, Entries); + if (tab->FwResourceCount > entry_space/sizeof(tab->Entries[0])) + return ENOENT; + + /* + * Success! Return everything through the last table entry. + */ + const size_t len = offsetof(struct EFI_SYSTEM_RESOURCE_TABLE, + Entries[tab->FwResourceCount]); + return efi_ioctl_got_table(ioc, tab, len); +} + +static int +efi_ioctl_get_table(struct efi_get_table_ioc *ioc) +{ + uint64_t addr; + bool direct; + efi_status status; + int error; + + /* + * If the platform doesn't support it yet, fail now. + */ + if (efi_ops->efi_gettab == NULL) + return ENODEV; + + /* + * Get the address of the requested table out of the EFI + * configuration table. + */ + status = efi_ops->efi_gettab(&ioc->uuid, &addr); + if (status != EFI_SUCCESS) + return efi_status_to_error(status); + + /* + * UEFI provides no generic way to identify the size of the + * table, so we have to bake knowledge of every vendor GUID + * into this code to safely expose the right amount of data to + * userland. + * + * We even have to bake knowledge of which ones are physically + * addressed and which ones might be virtually addressed + * according to the vendor GUID into this code, although for + * the moment we never use RT->SetVirtualAddressMap so we only + * ever have to deal with physical addressing. + */ + if (memcmp(&ioc->uuid, &(struct uuid)EFI_SYSTEM_RESOURCE_TABLE_GUID, + sizeof(ioc->uuid)) == 0) { + struct EFI_SYSTEM_RESOURCE_TABLE *tab; + + if ((tab = efi_map_pa(addr, &direct)) == NULL) + return ENOENT; + error = efi_ioctl_get_esrt(ioc, tab); + efi_unmap(tab, direct); + } else { + error = ENOENT; + } + + return error; +} + static int efi_ioctl_var_get(struct efi_var_ioc *var) { @@ -273,6 +471,8 @@ efi_ioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l) KASSERT(efi_ops != NULL); switch (cmd) { + case EFIIOC_GET_TABLE: + return efi_ioctl_get_table(data); case EFIIOC_VAR_GET: return efi_ioctl_var_get(data); case EFIIOC_VAR_NEXT: diff --git a/sys/dev/efivar.h b/sys/dev/efivar.h index 21d6d61fd26..cbaf2cbcb5c 100644 --- a/sys/dev/efivar.h +++ b/sys/dev/efivar.h @@ -1,4 +1,4 @@ -/* $NetBSD: efivar.h,v 1.1 2021/10/10 13:03:09 jmcneill Exp $ */ +/* $NetBSD: efivar.h,v 1.2 2023/05/22 16:27:58 riastradh Exp $ */ /*- * Copyright (c) 2021 Jared McNeill <jmcneill@invisible.ca> @@ -29,16 +29,20 @@ #ifndef _DEV_EFIVAR_H #define _DEV_EFIVAR_H +#include <sys/uuid.h> +#include <sys/types.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 *); + u_long *, void *); efi_status (*efi_setvar)(uint16_t *, struct uuid *, uint32_t, - u_long, void *); + u_long, void *); efi_status (*efi_nextvar)(u_long *, uint16_t *, struct uuid *); + efi_status (*efi_gettab)(const struct uuid *, uint64_t *); }; void efi_register_ops(const struct efi_ops *); |
