[PATCH v3 3/5] drivers: usb: host: efi: add efi io driver
Fabian Pflug <[email protected]>
| Newsgroups | org.infradead.lists.barebox |
|---|---|
| Message-ID | <20260820-v2026-06-0-topic-efi_usb-v3-3-faf0b9267966@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 <[email protected]> --- v3: - change return type when EFI_USB_ERR_STALL - use goto in function on error instead of return. (Missed that...) - lang table size of 0 is now not an error. - failure in setting string descriptors are no errors. - set host-hw-dev - remove USB_HOST from Kconfig v2: - add missing free to string buffer - use 16bit data type for string buffer - include the first byte of string buffer - renamed num_langs to table_size and made usage clearer --- drivers/usb/host/Kconfig | 10 ++ drivers/usb/host/Makefile | 1 + drivers/usb/host/efi-io-protocol.c | 313 +++++++++++++++++++++++++++++++++++++ efi/guid.c | 1 + include/efi/guid.h | 1 + 5 files changed, 326 insertions(+) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 58f276cdb4..a2ddcb2bff 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -22,6 +22,16 @@ 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 + 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..cdba353e96 --- /dev/null +++ b/drivers/usb/host/efi-io-protocol.c @@ -0,0 +1,313 @@ +// 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 <linux/usb/usb.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 -EPIPE; + 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) +{ + wchar_t *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 = 0;; u++) { + if (idx >= size) + break; + if (efi_name[u] & 0xff00) /* high byte */ + buf[idx++] = '?'; /* non-ASCII character */ + else if (efi_name[u]) + buf[idx++] = efi_name[u] & 0xff; + else + break; + } + buf[idx] = 0; + BS->free_pool(efi_name); + + 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 table_size; + + 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)) { + err = -efi_errno(efiret); + goto out_err; + } + usb_set_maxpacket_ep(dev, &interface->ep_desc[i]); + } + + efiret = priv->protocol->get_supported_languages( + priv->protocol, &lang_ids, &table_size); + if (EFI_ERROR(efiret)) { + err = -efi_errno(efiret); + goto out_err; + } + + dev->string_langid = lang_ids[0]; + /* table size is given in bytes, not entries */ + dev->have_langid = table_size >= sizeof(*lang_ids); + + dev_info(&dev->dev, "new device: Mfr=%d, Product=%d, SerialNumber=%d\n", + dev->descriptor->iManufacturer, dev->descriptor->iProduct, + dev->descriptor->iSerialNumber); + + if (dev->have_langid) { + efi_get_usb_string(priv->protocol, dev->string_langid, + dev->descriptor->iManufacturer, dev->mf, + sizeof(dev->mf)); + efi_get_usb_string(priv->protocol, dev->string_langid, + dev->descriptor->iProduct, dev->prod, + sizeof(dev->prod)); + efi_get_usb_string(priv->protocol, dev->string_langid, + dev->descriptor->iSerialNumber, dev->serial, + sizeof(dev->serial)); + } + + 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; + + host->hw_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