NetBSD Problem Report #57076

From www@netbsd.org  Thu Oct 27 16:48:22 2022
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits))
	(Client CN "mail.NetBSD.org", Issuer "mail.NetBSD.org CA" (not verified))
	by mollari.NetBSD.org (Postfix) with ESMTPS id F39F21A921F
	for <gnats-bugs@gnats.NetBSD.org>; Thu, 27 Oct 2022 16:48:21 +0000 (UTC)
Message-Id: <20221027164821.152561A9239@mollari.NetBSD.org>
Date: Thu, 27 Oct 2022 16:48:21 +0000 (UTC)
From: sergii.dmytruk@3mdeb.com
Reply-To: sergii.dmytruk@3mdeb.com
To: gnats-bugs@NetBSD.org
Subject: Add efivar port to NetBSD
X-Send-Pr-Version: www-1.0

>Number:         57076
>Category:       pkg
>Synopsis:       Add efivar port to NetBSD
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    pkg-manager
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Thu Oct 27 16:50:00 +0000 2022
>Last-Modified:  Sun Nov 06 21:20:02 +0000 2022
>Originator:     Sergii Dmytruk
>Release:        NetBSD 9.99.100
>Organization:
3mdeb
>Environment:
NetBSD localhost 9.99.100 NetBSD 9.99.100 (MYKERNEL) #0: Wed Sep 28 18:19:26 UTC 2022 root@localhost:/usr/obj/sys/arch/amd64/compile/MYKERNEL amd64
>Description:
3mdeb made a port of efivar [1] for *BSD systems [2], which is a
dependency of fwupd project [3].  We want to upstream efivar as
a prerequisite for adding fwupd later.

[1]: https://github.com/rhboot/efivar
[2]: https://github.com/3mdeb/efivar/blob/bsd/BSD_PORTING.md
[3]: https://fwupd.org/

It's in pkgsrc-wip repo [4] at the moment.

[4]: https://github.com/NetBSD/pkgsrc-wip/tree/master/efivar

Please note that latest NetBSD release (9.3) doesn't have EFI
ioctls nor relevant headers which are necessary to build the
port even though the changes were accepted quite some time ago.
Does it need to be enabled for the platform first?  Any plans
for that?
>How-To-Repeat:

>Fix:

>Audit-Trail:
From: Taylor R Campbell <riastradh@NetBSD.org>
To: sergii.dmytruk@3mdeb.com
Cc: gnats-bugs@NetBSD.org
Subject: Re: pkg/57076: Add efivar port to NetBSD
Date: Thu, 27 Oct 2022 17:41:25 +0000

 This is a multi-part message in MIME format.
 --=_3GH/d8a4GiEuk6sY511/abKYWvHzDwXu

 We have /dev/efi on amd64 and aarch64 (and maybe 32-bit arm? I forget)
 -- it will be in NetBSD 10.

 Currently the implementation supports listing, reading, and writing
 EFI variables with EFIIOC_VAR_LIST, EFIIOC_VAR_GET, EFIIOC_VAR_SET.

 I have a draft patch for getting EFI tables (EFIIOC_GET_TABLE), but I
 haven't committed it yet -- EFI's table interface is badly designed,
 so we have to encoded knowledge of every possible table by its UUID in
 the kernel to do it safely, and I haven't gotten around to testing it
 because of that.

 The patch for EFIIOC_GET_TABLE is attached if you'd like to give it a
 try -- it knows about the EFI system resources table and that's it.
 (Memory is fuzzy now but I think fwupd might require access to that
 table and no others.)

 A good start for efivar would be to package it up in pkgsrc, maybe as
 sysutils/efivar.  Would you like to give that a try?

 --=_3GH/d8a4GiEuk6sY511/abKYWvHzDwXu
 Content-Type: text/plain; charset="ISO-8859-1"; name="efitab"
 Content-Transfer-Encoding: quoted-printable
 Content-Disposition: attachment; filename="efitab.patch"

 From 0c5ceaa01f92b2dc5a9c3f99265fedd6b81734a4 Mon Sep 17 00:00:00 2001
 From: Taylor R Campbell <riastradh@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 91f0e1365d7e..f29a89d0a35f 100644
 --- a/sys/dev/efi.c
 +++ b/sys/dev/efi.c
 @@ -40,7 +40,10 @@ __KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.4 2022/09/24 11:06=
 :03 riastradh Exp $");
  #include <sys/atomic.h>
  #include <sys/efiio.h>
 =20
 +#include <uvm/uvm_extern.h>
 +
  #include <dev/efivar.h>
 +#include <dev/mm.h>
 =20
  #ifdef _LP64
  #define	EFIERR(x)		(0x8000000000000000 | x)
 @@ -149,6 +152,201 @@ efi_status_to_error(efi_status status)
  	}
  }
 =20
 +/* 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 =3D 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 !=3D 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 =3D 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 =3D uvm_km_alloc(kernel_map, PAGE_SIZE, 0,
 +	    UVM_KMF_VAONLY|UVM_KMF_WAITVA);
 +	KASSERT(va !=3D 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 =3D false;
 +	return (void *)va;
 +}
 +
 +static void
 +efi_unmap(void *ptr, bool direct)
 +{
 +	vaddr_t va =3D (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 =3D 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 !=3D
 +	    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 =3D 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 =3D 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 =3D=3D NULL)
 +		return ENODEV;
 +
 +	/*
 +	 * Get the address of the requested table out of the EFI
 +	 * configuration table.
 +	 */
 +	status =3D efi_ops->efi_gettab(&ioc->uuid, &addr);
 +	if (status !=3D 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)) =3D=3D 0) {
 +		struct EFI_SYSTEM_RESOURCE_TABLE *tab;
 +
 +		if ((tab =3D efi_map_pa(addr, &direct)) =3D=3D NULL)
 +			return ENOENT;
 +		error =3D efi_ioctl_get_esrt(ioc, tab);
 +		efi_unmap(tab, direct);
 +	} else {
 +		error =3D 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 !=3D NULL);
 =20
  	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
 =20
 +#include <sys/uuid.h>
 +#include <sys/types.h>
 +
  #include <machine/efi.h>
 =20
  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 *);
  };
 =20
  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
 =20
 +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 */
  };
 =20
 +#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 6a19066c80d0518b48625bc5e24102512d5914a0 Mon Sep 17 00:00:00 2001
 From: Taylor R Campbell <riastradh@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 3a40a8eee043..a025b2a39a52 100644
 --- a/sys/arch/x86/x86/efi_machdep.c
 +++ b/sys/arch/x86/x86/efi_machdep.c
 @@ -590,8 +590,10 @@ efi_get_e820memmap(void)
  #define	EFIERR(x)	(0x80000000ul | (x))
  #endif
 =20
 +#define	EFI_SUCCESS		EFIERR(0)
  #define	EFI_UNSUPPORTED		EFIERR(3)
  #define	EFI_DEVICE_ERROR	EFIERR(7)
 +#define	EFI_NOT_FOUND		EFIERR(14)
 =20
  /*
   * efi_runtime_init()
 @@ -985,12 +987,29 @@ efi_runtime_setvar(efi_char *name, struct uuid *vendo=
 r, uint32_t attrib,
  	return status;
  }
 =20
 +static efi_status
 +efi_runtime_gettab(const struct uuid *vendor, uint64_t *addrp)
 +{
 +	struct efi_cfgtbl *cfgtbl =3D efi_getcfgtblhead();
 +	paddr_t pa;
 +
 +	if (cfgtbl =3D=3D NULL)
 +		return EFI_UNSUPPORTED;
 +
 +	pa =3D efi_getcfgtblpa(vendor);
 +	if (pa =3D=3D 0)
 +		return EFI_NOT_FOUND;
 +	*addrp =3D pa;
 +	return EFI_SUCCESS;
 +}
 +
  static struct efi_ops efi_runtime_ops =3D {
  	.efi_gettime =3D efi_runtime_gettime,
  	.efi_settime =3D efi_runtime_settime,
  	.efi_getvar =3D efi_runtime_getvar,
  	.efi_setvar =3D efi_runtime_setvar,
  	.efi_nextvar =3D efi_runtime_nextvar,
 +	.efi_gettab =3D efi_runtime_gettab,
  };
 =20
  #endif	/* EFI_RUNTIME */

 --=_3GH/d8a4GiEuk6sY511/abKYWvHzDwXu--

From: Sergii Dmytruk <sergii.dmytruk@3mdeb.com>
To: Taylor R Campbell <riastradh@NetBSD.org>
Cc: gnats-bugs@NetBSD.org
Subject: Re: pkg/57076: Add efivar port to NetBSD
Date: Thu, 27 Oct 2022 21:47:19 +0300

 On Thu, Oct 27, 2022 at 05:41:25PM +0000, Taylor R Campbell wrote:
 > We have /dev/efi on amd64 and aarch64 (and maybe 32-bit arm? I forget)
 > -- it will be in NetBSD 10.

 Oh, got it now, 9.3 isn't a standalone release, it's an update.

 > The patch for EFIIOC_GET_TABLE is attached if you'd like to give it a
 > try -- it knows about the EFI system resources table and that's it.

 I don't know any other table that's of any use in user space.  I get EIO
 on ioctl(EFIIOC_GET_TABLE), don't see the patch returning it explicitly,
 but in here:

 >+	/*
 >+	 * Copy out as much as we can into the user's allocated buffer.
 >+	 */
 >+	return copyout(ioc->buf, ptr, MIN(ioc->buf_len, len));

 It looks like first two parameters must be swapped.  However, I still
 get EIO after doing this.

 > (Memory is fuzzy now but I think fwupd might require access to that
 > table and no others.)

 Yes, that's correct.

 > A good start for efivar would be to package it up in pkgsrc, maybe as
 > sysutils/efivar.  Would you like to give that a try?

 That's essentially what this report is about.  Docs say "push to
 pkgsrc-wip then send bug report to move to pkgsrc", the port is already
 in pkgsrc-wip.

From: Sergii Dmytruk <sergii.dmytruk@3mdeb.com>
To: Taylor R Campbell <riastradh@NetBSD.org>
Cc: gnats-bugs@NetBSD.org
Subject: Re: pkg/57076: Add efivar port to NetBSD
Date: Fri, 28 Oct 2022 00:20:28 +0300

 By the way, looks like we've used code derived from your patch to get
 ESRT working here: https://github.com/3mdeb/NetBSD-src/pull/5

 Weirdly enough, that version actually works as expected (ioctl call is
 named differently for some reason).

From: Sergii Dmytruk <sergii.dmytruk@3mdeb.com>
To: Taylor R Campbell <riastradh@NetBSD.org>
Cc: gnats-bugs@NetBSD.org
Subject: Re: pkg/57076: Add efivar port to NetBSD
Date: Sun, 6 Nov 2022 19:42:24 +0200

 --e94Sh53BmyHhQjjd
 Content-Type: text/plain; charset=us-ascii
 Content-Disposition: inline

 Apart from swapped arguments, there was incorrect duplicated definition of
 EFI_SUCCESS:

     #define	EFI_SUCCESS		EFIERR(0)

 It's just 0, since this isn't an error.

 With these fixes, everything works.  I've attached modified patch and a
 client for the API which I used for testing.

 --e94Sh53BmyHhQjjd
 Content-Type: text/plain; charset=us-ascii
 Content-Disposition: attachment; filename="efitab-fixed.patch"

  sys/arch/x86/x86/efi_machdep.c |  29 +++---
  sys/dev/efi.c                  | 216 ++++++++++++++++++++++++++++++++++++++---
  sys/dev/efi/efi.h              |  16 +++
  sys/dev/efivar.h               |   8 +-
  sys/sys/efiio.h                |   8 ++
  5 files changed, 247 insertions(+), 30 deletions(-)

 diff --git a/sys/arch/x86/x86/efi_machdep.c b/sys/arch/x86/x86/efi_machdep.c
 index 3a40a8eee04..57710c799dd 100644
 --- a/sys/arch/x86/x86/efi_machdep.c
 +++ b/sys/arch/x86/x86/efi_machdep.c
 @@ -581,18 +581,6 @@ efi_get_e820memmap(void)

  #ifdef EFI_RUNTIME

 -/*
 - * XXX move to sys/dev/efi/efi.h
 - */
 -#ifdef _LP64
 -#define	EFIERR(x)	(0x8000000000000000ul | (x))
 -#else
 -#define	EFIERR(x)	(0x80000000ul | (x))
 -#endif
 -
 -#define	EFI_UNSUPPORTED		EFIERR(3)
 -#define	EFI_DEVICE_ERROR	EFIERR(7)
 -
  /*
   * efi_runtime_init()
   *
 @@ -985,12 +973,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 */
 diff --git a/sys/dev/efi.c b/sys/dev/efi.c
 index 91f0e1365d7..cba8c0f3707 100644
 --- a/sys/dev/efi.c
 +++ b/sys/dev/efi.c
 @@ -40,23 +40,10 @@ __KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.4 2022/09/24 11:06:03 riastradh Exp $");
  #include <sys/atomic.h>
  #include <sys/efiio.h>

 -#include <dev/efivar.h>
 -
 -#ifdef _LP64
 -#define	EFIERR(x)		(0x8000000000000000 | x)
 -#else
 -#define	EFIERR(x)		(0x80000000 | x)
 -#endif
 +#include <uvm/uvm_extern.h>

 -#define	EFI_SUCCESS		0
 -#define	EFI_INVALID_PARAMETER	EFIERR(2)
 -#define	EFI_UNSUPPORTED		EFIERR(3)
 -#define	EFI_BUFFER_TOO_SMALL	EFIERR(5)
 -#define	EFI_DEVICE_ERROR	EFIERR(7)
 -#define	EFI_WRITE_PROTECTED	EFIERR(8)
 -#define	EFI_OUT_OF_RESOURCES	EFIERR(9)
 -#define	EFI_NOT_FOUND		EFIERR(14)
 -#define	EFI_SECURITY_VIOLATION	EFIERR(26)
 +#include <dev/efivar.h>
 +#include <dev/mm.h>

  #include "ioconf.h"

 @@ -149,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)
  {
 @@ -289,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/efi/efi.h b/sys/dev/efi/efi.h
 index 1f7fe910d06..544c81c0ca5 100644
 --- a/sys/dev/efi/efi.h
 +++ b/sys/dev/efi/efi.h
 @@ -44,6 +44,22 @@
  #define	EFIAPI	/* empty */
  #endif

 +#ifdef _LP64
 +#define	EFIERR(x)		(0x8000000000000000ul | (x))
 +#else
 +#define	EFIERR(x)		(0x80000000ul | (x))
 +#endif
 +
 +#define	EFI_SUCCESS		0
 +#define	EFI_INVALID_PARAMETER	EFIERR(2)
 +#define	EFI_UNSUPPORTED		EFIERR(3)
 +#define	EFI_BUFFER_TOO_SMALL	EFIERR(5)
 +#define	EFI_DEVICE_ERROR	EFIERR(7)
 +#define	EFI_WRITE_PROTECTED	EFIERR(8)
 +#define	EFI_OUT_OF_RESOURCES	EFIERR(9)
 +#define	EFI_NOT_FOUND		EFIERR(14)
 +#define	EFI_SECURITY_VIOLATION	EFIERR(26)
 +
  enum efi_reset {
  	EFI_RESET_COLD,
  	EFI_RESET_WARM,
 diff --git a/sys/dev/efivar.h b/sys/dev/efivar.h
 index 21d6d61fd26..72aeb8c6fba 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 8f3a9a2d54e..c50c2c416fa 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)

 --e94Sh53BmyHhQjjd
 Content-Type: text/plain; charset=us-ascii
 Content-Disposition: attachment; filename="efitable.c"

 /*-
  * Copyright (c) 2022 3mdeb <contact@3mdeb.com>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  */

 #include <sys/cdefs.h>

 #include <sys/types.h>
 #include <sys/efiio.h>
 #include <sys/ioctl.h>
 #include <sys/param.h>
 #include <sys/stat.h>
 #include <err.h>
 #include <fcntl.h>
 #include <getopt.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sysexits.h>
 #include <unistd.h>
 #include <uuid.h>
 #include <errno.h>

 #define TABLE_MAX_LEN 30

 #ifdef EFI_TABLE_ESRT
 #undef EFI_TABLE_ESRT
 #endif

 #define nitems(_a)      (sizeof((_a)) / sizeof((_a)[0]))

 #define	EFI_TABLE_ESRT					\
 			{0xb122a263,0x3661,0x4f68,0x99,0x29,{0x78,0xf8,0xb0,0xd6,0x21,0x80}}

 static void efi_table_print_esrt(void *data);
 static void usage(void);
 static void fail(const char msg[]);

 struct efi_table_op {
 	char name[TABLE_MAX_LEN];
 	void (*parse) (void *);
 	struct uuid uuid;
 };

 static const struct efi_table_op efi_table_ops[] = {
 	{ .name = "esrt",
 	  .parse = efi_table_print_esrt,
 	  .uuid = EFI_TABLE_ESRT },
 };

 struct efi_esrt_table {
 	uint32_t	fw_resource_count;
 	uint32_t	fw_resource_count_max;
 	uint64_t	fw_resource_version;
 	uint8_t	entries[];
 };

 struct efi_esrt_entry_v1 {
 	struct uuid	fw_class;
 	uint32_t 	fw_type;
 	uint32_t	fw_version;
 	uint32_t	lowest_supported_fw_version;
 	uint32_t	capsule_flags;
 	uint32_t	last_attempt_version;
 	uint32_t	last_attempt_status;
 };

 int
 main(int argc, char **argv)
 {
 	struct efi_get_table_ioc table = {
 		.buf = NULL,
 		.buf_len = 0,
 		.table_len = 0
 	};
 	int efi_fd, ch, rc = 1, efi_idx = -1;
 	bool got_table = false;
 	bool table_set = false;
 	bool uuid_set = false;
 	struct option longopts[] = {
 		{ "uuid",  required_argument, NULL, 'u' },
 		{ "table", required_argument, NULL, 't' },
 		{ NULL,    0,                 NULL,  0  }
 	};

 	while ((ch = getopt_long(argc, argv, "u:t:", longopts, NULL)) != -1) {
 		switch (ch) {
 			case 'u': {
 					  char *uuid_str = optarg;
 					  struct uuid uuid;
 					  uint32_t status;
 					  size_t n;

 					  uuid_set = 1;

 					  uuid_from_string(uuid_str, &uuid, &status);
 					  if (status != uuid_s_ok)
 						  fail("invalid UUID");

 					  for (n = 0; n < nitems(efi_table_ops); n++) {
 						  if (!memcmp(&uuid,
 									  &efi_table_ops[n].uuid,
 									  sizeof(uuid))) {
 							  efi_idx = n;
 							  got_table = true;
 							  break;
 						  }
 					  }
 					  break;
 				  }
 			case 't': {
 					  char *table_name = optarg;
 					  size_t n;

 					  table_set = true;

 					  for (n = 0; n < nitems(efi_table_ops); n++) {
 						  if (!strcmp(table_name,
 									  efi_table_ops[n].name)) {
 							  efi_idx = n;
 							  got_table = true;
 							  break;
 						  }
 					  }

 					  if (!got_table)
 						  fail("unsupported efi table");
 					  break;
 				  }
 			default:
 				  usage();
 		}
 	}

 	if (!table_set && !uuid_set)
 		fail("table is not set");

 	if (!got_table)
 		fail("unsupported table");

 	efi_fd = open("/dev/efi", O_RDWR);
 	if (efi_fd < 0)
 		fail("/dev/efi");

 	table.uuid = efi_table_ops[efi_idx].uuid;
 	if (ioctl(efi_fd, EFIIOC_GET_TABLE, &table) == -1)
 		fail(NULL);

 	table.buf = malloc(table.table_len);
 	table.buf_len = table.table_len;

 	if (ioctl(efi_fd, EFIIOC_GET_TABLE, &table) == -1)
 		fail(NULL);
 	close(efi_fd);

 	efi_table_ops[efi_idx].parse(table.buf);

 	return (rc);
 }

 static void
 efi_table_print_esrt(void *data)
 {
 	struct efi_esrt_table *esrt = data;
 	struct efi_esrt_entry_v1 *esre = (void *)esrt->entries;
 	printf("ESRT FwResourceCount = %d\n", esrt->fw_resource_count);

 	uint32_t i;
 	for (i = 0; i < esrt->fw_resource_count; i++) {
 		char *uuid = NULL;
 		uuid_to_string(&esre[i].fw_class, &uuid, NULL);

 		printf("ESRT[%d]:\n", i);
 		printf("  FwClass: %s\n", uuid);
 		printf("  FwType: 0x%08x\n", esre[i].fw_type);
 		printf("  FwVersion: 0x%08x\n", esre[i].fw_version);
 		printf("  LowestSupportedFwVersion: 0x%08x\n",
 				esre[i].lowest_supported_fw_version);
 		printf("  CapsuleFlags: 0x%08x\n",
 				esre[i].capsule_flags);
 		printf("  LastAttemptVersion: 0x%08x\n",
 				esre[i].last_attempt_version);
 		printf("  LastAttemptStatus: 0x%08x\n",
 				esre[i].last_attempt_status);

 		free(uuid);
 	}
 }

 static void usage(void)
 {
 	printf("usage: efitable [-u uuid | -t name]\n");
 	exit(1);
 }

 static void fail(const char msg[])
 {
 	if (msg != NULL)
 		fprintf(stderr, "%s\n", msg);
 	else
 		fprintf(stderr, "errno: %s\n", strerror(errno));
 	exit(1);
 }

 --e94Sh53BmyHhQjjd--

NetBSD Home
NetBSD PR Database Search

(Contact us) $NetBSD: query-full-pr,v 1.47 2022/09/11 19:34:41 kim Exp $
$NetBSD: gnats_config.sh,v 1.9 2014/08/02 14:16:04 spz Exp $
Copyright © 1994-2022 The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.