tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: Problems with implementing EFI runtime support for x86



> Date: Thu, 15 Sep 2022 11:32:02 +0200
> From: Paweł Cichowski <pawel.cichowski%3mdeb.com@localhost>
> 
> 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 <riastradh%NetBSD.org@localhost>
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 <riastradh%NetBSD.org@localhost>
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 */


Home | Main Index | Thread Index | Old Index