diff --git a/sys/dev/efi.c b/sys/dev/efi.c index e6bd1b5ab94b..4333f3962e4e 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 @@ -32,7 +32,7 @@ */ #include -__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 #include @@ -40,7 +40,10 @@ __KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.6 2023/05/22 16:27:49 riastradh Exp $"); #include #include +#include + #include +#include #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 21d6d61fd26a..cbaf2cbcb5c4 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 @@ -29,16 +29,20 @@ #ifndef _DEV_EFIVAR_H #define _DEV_EFIVAR_H +#include +#include + #include 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 *); diff --git a/sys/sys/efiio.h b/sys/sys/efiio.h index 8f3a9a2d54e9..3e467f40c953 100644 --- a/sys/sys/efiio.h +++ b/sys/sys/efiio.h @@ -1,4 +1,4 @@ -/* $NetBSD: efiio.h,v 1.2 2021/10/11 10:23:02 jmcneill Exp $ */ +/* $NetBSD: efiio.h,v 1.3 2023/05/22 16:27:58 riastradh Exp $ */ /*- * Copyright (c) 2021 The NetBSD Foundation, Inc. @@ -48,6 +48,13 @@ #define EFI_VARIABLE_APPEND_WRITE 0x00000040 #define EFI_VARIABLE_ENHANCED_AUTHENTICATED_ACCESS 0x00000080 +struct efi_get_table_ioc { + void * buf; + struct uuid uuid; + size_t table_len; + size_t buf_len; +}; + struct efi_var_ioc { uint16_t * name; /* vendor's variable name */ size_t namesize; /* size in bytes of the name buffer */ @@ -57,6 +64,7 @@ struct efi_var_ioc { size_t datasize; /* size in bytes of the data buffer */ }; +#define EFIIOC_GET_TABLE _IOWR('e', 1, struct efi_get_table_ioc) #define EFIIOC_VAR_GET _IOWR('e', 4, struct efi_var_ioc) #define EFIIOC_VAR_NEXT _IOWR('e', 5, struct efi_var_ioc) #define EFIIOC_VAR_SET _IOWR('e', 7, struct efi_var_ioc)