Module Name:    src
Committed By:   riastradh
Date:           Mon May 22 16:27:59 UTC 2023

Modified Files:
        src/sys/dev: efi.c efivar.h
        src/sys/sys: efiio.h

Log Message:
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


To generate a diff of this commit:
cvs rdiff -u -r1.6 -r1.7 src/sys/dev/efi.c
cvs rdiff -u -r1.1 -r1.2 src/sys/dev/efivar.h
cvs rdiff -u -r1.2 -r1.3 src/sys/sys/efiio.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/efi.c
diff -u src/sys/dev/efi.c:1.6 src/sys/dev/efi.c:1.7
--- src/sys/dev/efi.c:1.6	Mon May 22 16:27:49 2023
+++ src/sys/dev/efi.c	Mon May 22 16:27:58 2023
@@ -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 <jmcne...@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 
 #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 *d
 	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:

Index: src/sys/dev/efivar.h
diff -u src/sys/dev/efivar.h:1.1 src/sys/dev/efivar.h:1.2
--- src/sys/dev/efivar.h:1.1	Sun Oct 10 13:03:09 2021
+++ src/sys/dev/efivar.h	Mon May 22 16:27:58 2023
@@ -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 <jmcne...@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 *);

Index: src/sys/sys/efiio.h
diff -u src/sys/sys/efiio.h:1.2 src/sys/sys/efiio.h:1.3
--- src/sys/sys/efiio.h:1.2	Mon Oct 11 10:23:02 2021
+++ src/sys/sys/efiio.h	Mon May 22 16:27:58 2023
@@ -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)

Reply via email to