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
next prev 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