mail archive of the barebox mailing list
 help / color / mirror / Atom feed
From: Fabian Pflug <f.pflug@pengutronix.de>
To: Sascha Hauer <s.hauer@pengutronix.de>,
	 BAREBOX <barebox@lists.infradead.org>
Cc: Fabian Pflug <f.pflug@pengutronix.de>
Subject: [PATCH 3/5] drivers: usb: host: efi: add efi io driver
Date: Wed, 12 Aug 2026 18:36:51 +0200	[thread overview]
Message-ID: <20260812-v2026-06-0-topic-efi_usb-v1-3-9d2573c29f21@pengutronix.de> (raw)
In-Reply-To: <20260812-v2026-06-0-topic-efi_usb-v1-0-9d2573c29f21@pengutronix.de>

The driver is based on [1] with one "host" controller per device.
The host controller will hopefully not have any sub-devices and only
have the one root device, which functions as the only device needed and
registered for.

[1] https://uefi.org/specs/UEFI/2.11/17_Protocols_USB_Support.html#usb-driver-model

Signed-off-by: Fabian Pflug <f.pflug@pengutronix.de>
---
 drivers/usb/host/Kconfig           |  11 ++
 drivers/usb/host/Makefile          |   1 +
 drivers/usb/host/efi-io-protocol.c | 316 +++++++++++++++++++++++++++++++++++++
 efi/guid.c                         |   1 +
 include/efi/guid.h                 |   1 +
 5 files changed, 330 insertions(+)

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 58f276cdb4..66e320d163 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -22,6 +22,17 @@ config USB_EHCI_ZYNQ
 	help
 	  Enable support for Zynq on-chip EHCI USB controller
 
+config USB_EFI_IO_PROTOCOL
+	bool "EFI USB I/O Protocol"
+	depends on EFI_PAYLOAD
+	depends on USB_HOST
+	help
+	  Enable support for usb devices, initialized by the UEFI BIOS.
+	  Use this with caution, as devices may afterwards have multiple drivers.
+	  For example can a USB-Stick be treated as an IO Blockdevice by UEFI and
+	  have a driver for it, but can also be initiated as a USB-Storage device,
+	  which could lead to errors in handling the USB-Device.
+
 config USB_OHCI
 	bool "OHCI driver"
 	depends on !MMU && HAS_DMA
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index cbddfbe923..4f77680fc7 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_USB_EHCI_ZYNQ)	+= ehci-zynq.o
 obj-$(CONFIG_USB_OHCI)		+= ohci-hcd.o
 obj-$(CONFIG_USB_OHCI_AT91)	+= ohci-at91.o
 obj-$(CONFIG_USB_XHCI)		+= xhci.o xhci-mem.o xhci-ring.o
+obj-$(CONFIG_USB_EFI_IO_PROTOCOL)	+= efi-io-protocol.o
diff --git a/drivers/usb/host/efi-io-protocol.c b/drivers/usb/host/efi-io-protocol.c
new file mode 100644
index 0000000000..bd35628f59
--- /dev/null
+++ b/drivers/usb/host/efi-io-protocol.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <efi/payload.h>
+#include <efi/payload/init.h>
+#include <efi/payload/driver.h>
+#include <efi/protocol/usb.h>
+#include <efi/error.h>
+
+#include "../core/usb.h"
+
+struct efi_usb_io_priv {
+	struct efi_usb_io_protocol *protocol;
+	struct device *dev;
+	struct usb_host host;
+};
+
+#define usb_dev_to_efi_priv(ptr) \
+	container_of(ptr->host, struct efi_usb_io_priv, host)
+
+static int efi_usb_error_check(efi_status_t efiret, int status)
+{
+	if (efiret == EFI_DEVICE_ERROR) {
+		if (status & EFI_USB_ERR_TIMEOUT)
+			return -ETIMEDOUT;
+		if (status & EFI_USB_ERR_STALL)
+			return -ETIMEDOUT;
+		if (status & EFI_USB_ERR_NAK)
+			return -EPROTO;
+		if (status & EFI_USB_ERR_BUFFER)
+			return -EINVAL;
+		if (status & EFI_USB_ERR_NOTEXECUTE)
+			return -EIO;
+		if (status & EFI_USB_ERR_BABBLE)
+			return -EIO;
+		if (status & EFI_USB_ERR_CRC)
+			return -EIO;
+		if (status & EFI_USB_ERR_BITSTUFF)
+			return -EIO;
+		if (status & EFI_USB_ERR_SYSTEM)
+			return -EIO;
+	}
+
+	if (EFI_ERROR(efiret))
+		return -efi_errno(efiret);
+
+	return 0;
+}
+
+static int efi_usb_control_msg(struct usb_device *dev, unsigned long pipe,
+			       void *buffer, int length,
+			       struct devrequest *setup, int timeout)
+{
+	struct efi_usb_io_priv *priv = usb_dev_to_efi_priv(dev);
+	enum efi_usb_data_direction direction;
+	efi_status_t efiret;
+	efi_uintn_t efi_timeout = timeout;
+	u32 status;
+
+	if (usb_pipein(pipe))
+		direction = EFI_USB_DATA_IN;
+	else
+		direction = EFI_USB_DATA_OUT;
+	if (length == 0)
+		direction = EFI_USB_NO_DATA;
+
+	efiret = priv->protocol->control_transfer(priv->protocol, setup,
+						  direction, efi_timeout,
+						  buffer, length, &status);
+
+	dev->status = status; // dev-status is a long, status is u32
+	dev->act_len = length;
+
+	return efi_usb_error_check(efiret, status);
+}
+
+static int efi_usb_bulk_msg(struct usb_device *dev, unsigned long pipe,
+			    void *buffer, int length, int timeout)
+{
+	struct efi_usb_io_priv *priv = usb_dev_to_efi_priv(dev);
+	efi_uintn_t efi_length = length;
+	efi_status_t efiret;
+	u32 status;
+
+	u8 epnum = usb_pipeendpoint(pipe) | (usb_pipein(pipe) << 7);
+
+	efiret = priv->protocol->bulk_transfer(priv->protocol, epnum, buffer,
+					       &efi_length, timeout, &status);
+
+	dev->status = status;
+	dev->act_len = efi_length;
+
+	return efi_usb_error_check(efiret, status);
+}
+
+static int efi_usb_int_msg(struct usb_device *dev, unsigned long pipe,
+			   void *buffer, int length,
+			   int __always_unused interval)
+{
+	struct efi_usb_io_priv *priv = usb_dev_to_efi_priv(dev);
+	efi_status_t efiret;
+	u32 status;
+	efi_uintn_t efi_length = length;
+
+	u8 epnum = usb_pipeendpoint(pipe) | (usb_pipein(pipe) << 7);
+
+	efiret = priv->protocol->sync_interrupt_transfer(
+		priv->protocol, epnum, buffer, &efi_length, 100, &status);
+
+	dev->status = status;
+	dev->act_len = efi_length;
+
+	return efi_usb_error_check(efiret, status);
+}
+
+static int efi_get_usb_string(struct efi_usb_io_protocol *protocol, u16 lang_id,
+			      int index, char *buf, size_t size)
+{
+	char *efi_name;
+	efi_status_t efiret;
+	unsigned int u, idx;
+
+	memset(buf, 0, size);
+
+	if (!index)
+		return 0;
+
+	efiret = protocol->get_string_descriptor(protocol, lang_id, index,
+						 &efi_name);
+	if (EFI_ERROR(efiret))
+		return -efi_errno(efiret);
+
+	size--; /* leave room for trailing NULL char in output buffer */
+	for (idx = 0, u = 2;; u += 2) {
+		if (idx >= size)
+			break;
+		if (efi_name[u + 1]) /* high byte */
+			buf[idx++] = '?'; /* non-ASCII character */
+		else if (efi_name[u])
+			buf[idx++] = efi_name[u];
+		else
+			break;
+	}
+	buf[idx] = 0;
+
+	return 0;
+}
+
+static int create_usb_device(struct efi_usb_io_priv *priv)
+{
+	struct usb_host *host = &priv->host;
+	struct usb_device *dev;
+	efi_status_t efiret;
+	struct usb_interface *interface;
+	int err;
+	u16 *lang_ids;
+	u16 num_langs;
+
+	dev = usb_alloc_new_device();
+	dev->host = host;
+
+	dev_set_name(&dev->dev, "usb%d", dev->host->busnum);
+	dev->dev.id = DEVICE_ID_SINGLE;
+
+	efiret = priv->protocol->get_device_descriptor(priv->protocol,
+						       dev->descriptor);
+	if (EFI_ERROR(efiret)) {
+		err = -efi_errno(efiret);
+		goto out_err;
+	}
+
+	switch (dev->descriptor->bMaxPacketSize0) {
+	case 8:
+		dev->maxpacketsize = PACKET_SIZE_8;
+		break;
+	case 16:
+		dev->maxpacketsize = PACKET_SIZE_16;
+		break;
+	case 32:
+		dev->maxpacketsize = PACKET_SIZE_32;
+		break;
+	case 64:
+		dev->maxpacketsize = PACKET_SIZE_64;
+		break;
+	}
+
+	// There is only the possibility to access the current active configuration
+	// and not set the configuration.
+	// https://uefi.org/specs/UEFI/2.11/17_Protocols_USB_Support.html#efi-usb-io-protocol-usbgetconfigdescriptor
+	efiret = priv->protocol->get_config_descriptor(priv->protocol,
+						       &dev->config.desc);
+	if (EFI_ERROR(efiret)) {
+		err = -efi_errno(efiret);
+		goto out_err;
+	}
+
+	// UEFI has by definition only one interface per config
+	// https://uefi.org/specs/UEFI/2.11/17_Protocols_USB_Support.html#efi-usb-io-protocol-usbgetinterfacedescriptor
+	dev->config.no_of_if = 1;
+	interface = &dev->config.interface[0];
+
+	efiret = priv->protocol->get_interface_descriptor(priv->protocol,
+							  &interface->desc);
+	if (EFI_ERROR(efiret)) {
+		err = -efi_errno(efiret);
+		goto out_err;
+	}
+
+	interface->no_of_ep = interface->desc.bNumEndpoints;
+
+	if (interface->no_of_ep > USB_MAXENDPOINTS) {
+		err = -EPROTO;
+		goto out_err;
+	}
+
+	for (int i = 0; i < interface->no_of_ep; i++) {
+		efiret = priv->protocol->get_endpoint_descriptor(
+			priv->protocol, i, &interface->ep_desc[i]);
+		if (EFI_ERROR(efiret))
+			return -efi_errno(efiret);
+		usb_set_maxpacket_ep(dev, &interface->ep_desc[i]);
+	}
+
+	efiret = priv->protocol->get_supported_languages(priv->protocol,
+							 &lang_ids, &num_langs);
+	if (EFI_ERROR(efiret)) {
+		err = -efi_errno(efiret);
+		goto out_err;
+	}
+
+	num_langs = num_langs / sizeof(u16);
+	if (num_langs == 0) {
+		err = -EPROTO;
+		goto out_err;
+	}
+
+	dev->string_langid = lang_ids[0];
+	dev->have_langid = num_langs;
+
+	dev_info(&dev->dev, "new device: Mfr=%d, Product=%d, SerialNumber=%d\n",
+		 dev->descriptor->iManufacturer, dev->descriptor->iProduct,
+		 dev->descriptor->iSerialNumber);
+
+	err = efi_get_usb_string(priv->protocol, dev->string_langid,
+				 dev->descriptor->iManufacturer, dev->mf,
+				 sizeof(dev->mf));
+	if (err)
+		goto out_err;
+	err = efi_get_usb_string(priv->protocol, dev->string_langid,
+				 dev->descriptor->iProduct, dev->prod,
+				 sizeof(dev->prod));
+	if (err)
+		goto out_err;
+	err = efi_get_usb_string(priv->protocol, dev->string_langid,
+				 dev->descriptor->iSerialNumber, dev->serial,
+				 sizeof(dev->serial));
+	if (err)
+		goto out_err;
+
+	dev_info(&dev->dev, "Bus %03d Device %03d: ID %04x:%04x %s\n",
+		 dev->host->busnum, dev->devnum, dev->descriptor->idVendor,
+		 dev->descriptor->idProduct, dev->prod);
+
+	err = register_device(&dev->dev);
+	if (err) {
+		dev_err(&dev->dev, "Failed to register device: %pe\n",
+			ERR_PTR(err));
+		goto out_err;
+	}
+
+	// register as root device for host
+	host->root_dev = dev;
+
+	return 0;
+
+out_err:
+	dev_err(&dev->dev, "Failed to create UEFI-USB-IO device: %pe\n",
+		ERR_PTR(err));
+	usb_free_device(dev);
+	return err;
+}
+
+static int efi_usb_io_probe(struct efi_device *efidev)
+{
+	struct device *dev = &efidev->dev;
+	struct efi_usb_io_priv *priv;
+	struct usb_host *host;
+
+	priv = xzalloc(sizeof(*priv));
+
+	BS->handle_protocol(efidev->handle, &efi_usb_io_protocol_guid,
+			    (void **)&priv->protocol);
+	if (!priv->protocol)
+		return -ENODEV;
+
+	dev->priv = priv;
+	priv->dev = dev;
+
+	// EFI has one device per probe, which now needs its own host controller,
+	// since there is no shared host controller resource.
+
+	host = &priv->host;
+	host->submit_int_msg = efi_usb_int_msg;
+	host->submit_control_msg = efi_usb_control_msg;
+	host->submit_bulk_msg = efi_usb_bulk_msg;
+	usb_register_host(host);
+
+	return create_usb_device(priv);
+}
+
+static struct efi_driver efi_usb_io_driver = {
+	.driver = {
+		.name  = "efi-usb-io-protocol",
+	},
+	.probe = efi_usb_io_probe,
+	.guid = EFI_USB_IO_PROTOCOL_GUID,
+};
+device_efi_driver(efi_usb_io_driver);
diff --git a/efi/guid.c b/efi/guid.c
index 8853829d21..f438783fc1 100644
--- a/efi/guid.c
+++ b/efi/guid.c
@@ -15,6 +15,7 @@ efi_guid_t efi_null_guid = EFI_NULL_GUID;
 efi_guid_t efi_global_variable_guid = EFI_GLOBAL_VARIABLE_GUID;
 const efi_guid_t efi_guid_image_security_database = EFI_IMAGE_SECURITY_DATABASE_GUID;
 efi_guid_t efi_block_io_protocol_guid = EFI_BLOCK_IO_PROTOCOL_GUID;
+efi_guid_t efi_usb_io_protocol_guid = EFI_USB_IO_PROTOCOL_GUID;
 efi_guid_t efi_rng_protocol_guid = EFI_RNG_PROTOCOL_GUID;
 efi_guid_t efi_barebox_vendor_guid = EFI_BAREBOX_VENDOR_GUID;
 efi_guid_t efi_file_store_vars_guid = EFI_FILE_STORE_VARS_GUID;
diff --git a/include/efi/guid.h b/include/efi/guid.h
index 202300c74a..7d75842b04 100644
--- a/include/efi/guid.h
+++ b/include/efi/guid.h
@@ -27,6 +27,7 @@ extern efi_guid_t efi_null_guid;
 extern efi_guid_t efi_global_variable_guid;
 extern const efi_guid_t efi_guid_image_security_database;
 extern efi_guid_t efi_block_io_protocol_guid;
+extern efi_guid_t efi_usb_io_protocol_guid;
 extern efi_guid_t efi_rng_protocol_guid;
 extern efi_guid_t efi_barebox_vendor_guid;
 extern efi_guid_t efi_file_store_vars_guid;

-- 
2.47.3




  parent reply	other threads:[~2026-08-12 16:38 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-12 16:36 [PATCH 0/5] Add support for USB-EFI devices in EFI Playload Fabian Pflug
2026-08-12 16:36 ` [PATCH 1/5] efi: usb: add header for usb-efi Fabian Pflug
2026-08-12 16:36 ` [PATCH 2/5] usb: core: make usb_set_maxpacket_ep public Fabian Pflug
2026-08-12 16:36 ` Fabian Pflug [this message]
2026-08-12 16:36 ` [PATCH 4/5] efi: guid: add guid for usb host controller 2 Fabian Pflug
2026-08-12 16:36 ` [PATCH 5/5] test: x86: add test for uefi usb io Fabian Pflug

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260812-v2026-06-0-topic-efi_usb-v1-3-9d2573c29f21@pengutronix.de \
    --to=f.pflug@pengutronix.de \
    --cc=barebox@lists.infradead.org \
    --cc=s.hauer@pengutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox