> Date: Thu, 15 Sep 2022 11:32:02 +0200
> From: Paweł Cichowski <pawel.cichow...@3mdeb.com>
> 
> I am trying it out now, unfortunately I'm running into this error:
> fatal privileged instruction fault in supervisor mode
> [...]
> kernel: privileged instruction fault trap, code=0
> Stopped in pid 0.0 (system) at netbsd:xrstor+0xa:     fxsave1
> This happens in efi_runtime_exit, more specifically - during
> fpu_kern_leave.  It's probably due to me naively debugging by just
> calling it during efi_init, so now I'll try to do it properly and
> add efi_gettime to /dev/efi.

efi_init is probably too early to call this stuff -- not sure exactly
at what point fpu_kern_enter/leave becomes safe but efi_init may be
too early.

> How did you go about testing it?

I just wrote a little userland program, attached, to list the
variables and query their values using /dev/efi, which I've been
running in qemu booted with OVMF:

qemu-system-x86_64 \
        -machine pc,accel=nvmm \
        -bios /usr/pkg/share/ovmf/OVMFX64.fd \
        ...

> My main concern right now is not knowing where to try and start
> contributing to NetBSD, as it really sparked my interest. I found
> this project list https://wiki.netbsd.org/projects/all/#index4h1 and
> will probably look for some features of other BSDs which could be
> ported. Could you tell me where or how I could find some entry-level
> projects?

That's a reasonable place to start, although it's a good idea to ask
about any particular projects first -- many of those are a bit
outdated.

Also always a good strategy to try starting with something you want to
use or adjacent to something you want to use like this EFI business.
I noticed that fwupd needs to get at EFI configuration tables too so I
drafted a patch to expose this through /dev/efi (meant to be source-
compatible with FreeBSD) but I haven't tested it and it probably won't
work on the first try.
#include <sys/cdefs.h>
#include <sys/efiio.h>
#include <sys/ioctl.h>

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <uuid.h>

#define _PATH_EFI       "/dev/efi"

static const struct uuid EFI_GLOBAL_VARIABLE = {
        0x8BE4DF61,0x93CA,0x11d2,
        0xAA,0x0D,{0x00,0xE0,0x98,0x03,0x2B,0x8C},
};

static const unsigned char *const stdvars[] = {
        "AuditMode",
        "Boot0000",
        "Boot0001",
        "Boot0002",
        "Boot0003",
        "Boot0004",
        "Boot0005",
        "Boot0006",
        "Boot0007",
        "BootCurrent",
        "BootNext",
        "BootOrder",
        "BootOptionSupport",
        "ConIn",
        "ConInDev",
        "ConOut",
        "ConOutDev",
        "dbDefault",
        "dbrDefault",
        "dbtDefault",
        "dbxDefault",
        "DeployedMode",
        "Driver0000",
        "Driver0001",
        "Driver0002",
        "Driver0003",
        "DriverOrder",
        "ErrOut",
        "ErrOutDev",
        "HwErrRecSupport",
        "KEK",
        "KEKDefault",
        "Key0000",
        "Key0001",
        "Key0002",
        "Key0003",
        "Lang",
        "LangCodes",
        "OsIndications",
        "OsIndicationsSupported",
        "OsRecoveryOrder",
        "PK",
        "PKDefault",
        "PlatformLangCodes",
        "PlatformLang",
        "PlatformRecovery0000",
        "PlatformRecovery0001",
        "PlatformRecovery0002",
        "PlatformRecovery0003",
        "SignatureSupport",
        "SecureBoot",
        "SetupMode",
        "SysPrep0000",
        "SysPrep0001",
        "SysPrep0002",
        "SysPrep0003",
        "SysPrepOrder",
        "Timeout",
        "VendorKeys",
};

int
main(void)
{
        uint16_t name[128] = {0};
        uint8_t buf[1024];
        struct efi_var_ioc e = {
                .name = name,
                .namesize = __arraycount(name),
        };
        struct efi_var_ioc e1;
        int fd;
        unsigned i, j;

        if ((fd = open(_PATH_EFI, O_RDWR)) == -1)
                err(1, "open");
        for (;;) {
                if (ioctl(fd, EFIIOC_VAR_NEXT, &e) == -1) {
                        warn("ioctl(EFIIOC_VAR_NEXT)");
                        break;
                }
                if (e.name == NULL)
                        break;
                char *uuidstr;
                uint32_t uuidstatus;
                uuid_to_string(&e.vendor, &uuidstr, &uuidstatus);
                if (uuidstatus == uuid_s_ok) {
                        printf("[%s] ", uuidstr);
                        free(uuidstr);
                } else {
                        printf("[err 0x%x] ", uuidstatus);
                }
                for (i = 0; i < __arraycount(name); i++) {
                        if (name[i] == 0)
                                break;
                        if (name[i] >= 256)
                                printf("\\U+%04x", name[i]);
                        else
                                printf("%c", name[i]);
                }
                memset(buf, 0, sizeof(buf));
                e1 = e;
                e1.data = buf;
                e1.datasize = sizeof(buf);
                if (ioctl(fd, EFIIOC_VAR_GET, &e1) == -1) {
                        printf("\n");
                        fflush(stdout);
                        warn("ioctl(EFIIOC_VAR_GET)");
                        continue;
                }
                printf(" 0x%x\n", e1.attrib);
                for (i = 0; i < e1.datasize; i++) {
                        if ((i % 16) == 8)
                                printf(" ");
                        printf(" %02x", buf[i]);
                        if ((i % 16) == 15)
                                printf("\n");
                }
                if (e1.datasize % 16)
                        printf("\n");
        }

        for (i = 0; i < __arraycount(stdvars); i++) {
                printf("%s", stdvars[i]);
                for (j = 0; stdvars[i][j] != '\0'; j++)
                        name[j] = stdvars[i][j];
                name[j] = 0;
                e.name = name;
                e.namesize = 2*(j + 1);
                e.vendor = EFI_GLOBAL_VARIABLE;
                memset(buf, 0, sizeof(buf));
                e.data = buf;
                e.datasize = sizeof(buf);
                if (ioctl(fd, EFIIOC_VAR_GET, &e) == -1) {
                        printf("\n");
                        fflush(stdout);
                        warn("ioctl(EFIIOC_VAR_GET)");
                        continue;
                }
                printf(" 0x%x\n", e.attrib);
                for (j = 0; j < e.datasize; j++) {
                        if ((j % 16) == 0)
                                printf(" ");
                        printf(" %02x", buf[j]);
                        if ((j % 16) == 15)
                                printf("\n");
                }
                if (e.datasize % 16)
                        printf("\n");
        }

        fflush(stdout);
        return ferror(stdout);
}
>From cb0f9e34b035742fa323c43867fea1262e4b373c Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <riastr...@netbsd.org>
Date: Thu, 15 Sep 2022 07:54:19 +0000
Subject: [PATCH 1/2] efi(4): Implement MI parts of EFIIOC_GET_TABLE.

Intended to be compatible with FreeBSD.

Not yet supported on any architectures.
---
 sys/dev/efi.c    | 200 +++++++++++++++++++++++++++++++++++++++++++++++
 sys/dev/efivar.h |   8 +-
 sys/sys/efiio.h  |   8 ++
 3 files changed, 214 insertions(+), 2 deletions(-)

diff --git a/sys/dev/efi.c b/sys/dev/efi.c
index 49a1f2e387e5..67c9b17ddde4 100644
--- a/sys/dev/efi.c
+++ b/sys/dev/efi.c
@@ -40,7 +40,10 @@ __KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.3 2022/04/01 06:51:12 
skrll Exp $");
 #include <sys/atomic.h>
 #include <sys/efiio.h>
 
+#include <uvm/uvm_extern.h>
+
 #include <dev/efivar.h>
+#include <dev/mm.h>
 
 #ifdef _LP64
 #define        EFIERR(x)               (0x8000000000000000 | x)
@@ -149,6 +152,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(ioc->buf, ptr, 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)
 {
@@ -289,6 +487,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..72aeb8c6fbae 100644
--- a/sys/dev/efivar.h
+++ b/sys/dev/efivar.h
@@ -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 *);
diff --git a/sys/sys/efiio.h b/sys/sys/efiio.h
index 8f3a9a2d54e9..c50c2c416fa9 100644
--- a/sys/sys/efiio.h
+++ b/sys/sys/efiio.h
@@ -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)

>From f4b317934d0f1604190d8cc1cfe5dd8406bef5fe Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <riastr...@netbsd.org>
Date: Thu, 15 Sep 2022 07:55:02 +0000
Subject: [PATCH 2/2] efi(4): Implement EFIIOC_GET_TABLE on x86.

---
 sys/arch/x86/x86/efi_machdep.c | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/sys/arch/x86/x86/efi_machdep.c b/sys/arch/x86/x86/efi_machdep.c
index 22c96e460d6c..6f29511c484b 100644
--- a/sys/arch/x86/x86/efi_machdep.c
+++ b/sys/arch/x86/x86/efi_machdep.c
@@ -589,8 +589,10 @@ efi_get_e820memmap(void)
 #define        EFIERR(x)       (0x80000000ul | (x))
 #endif
 
+#define        EFI_SUCCESS             EFIERR(0)
 #define        EFI_UNSUPPORTED         EFIERR(3)
 #define        EFI_DEVICE_ERROR        EFIERR(7)
+#define        EFI_NOT_FOUND           EFIERR(14)
 
 /*
  * efi_runtime_init()
@@ -963,12 +965,29 @@ efi_runtime_setvar(efi_char *name, struct uuid *vendor, 
uint32_t attrib,
        return status;
 }
 
+static efi_status
+efi_runtime_gettab(const struct uuid *vendor, uint64_t *addrp)
+{
+       struct efi_cfgtbl *cfgtbl = efi_getcfgtblhead();
+       paddr_t pa;
+
+       if (cfgtbl == NULL)
+               return EFI_UNSUPPORTED;
+
+       pa = efi_getcfgtblpa(vendor);
+       if (pa == 0)
+               return EFI_NOT_FOUND;
+       *addrp = pa;
+       return EFI_SUCCESS;
+}
+
 static struct efi_ops efi_runtime_ops = {
        .efi_gettime = efi_runtime_gettime,
        .efi_settime = efi_runtime_settime,
        .efi_getvar = efi_runtime_getvar,
        .efi_setvar = efi_runtime_setvar,
        .efi_nextvar = efi_runtime_nextvar,
+       .efi_gettab = efi_runtime_gettab,
 };
 
 #endif /* EFI_RUNTIME */

Reply via email to