]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[usb] Add basic support for USB hubs
authorMichael Brown <mcb30@ipxe.org>
Mon, 2 Feb 2015 14:38:42 +0000 (14:38 +0000)
committerMichael Brown <mcb30@ipxe.org>
Tue, 3 Feb 2015 12:19:52 +0000 (12:19 +0000)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/Makefile
src/drivers/bus/usb.c
src/drivers/usb/usbhub.c [new file with mode: 0644]
src/drivers/usb/usbhub.h [new file with mode: 0644]
src/include/ipxe/errfile.h

index 66d804b14cd466faf681ac9496e8942f93ce18c9..cf9cfd238f911498e1f9e639fbeec1f7009dd1ae 100644 (file)
@@ -83,6 +83,7 @@ SRCDIRS               += drivers/block
 SRCDIRS                += drivers/nvs
 SRCDIRS                += drivers/bitbash
 SRCDIRS                += drivers/infiniband
+SRCDIRS                += drivers/usb
 SRCDIRS                += interface/pxe interface/efi interface/smbios
 SRCDIRS                += interface/bofm
 SRCDIRS                += interface/xen
index 5209c4d0bc38a0ea906368ed9102c774e75bd057..1e76ffc9c0ddb46fad0718a91ef8b874353d43ae 100644 (file)
@@ -1646,3 +1646,6 @@ struct usb_port * usb_root_hub_port ( struct usb_device *usb ) {
 
        return usb->port;
 }
+
+/* Drag in hub driver */
+REQUIRE_OBJECT ( usbhub );
diff --git a/src/drivers/usb/usbhub.c b/src/drivers/usb/usbhub.c
new file mode 100644 (file)
index 0000000..ecac460
--- /dev/null
@@ -0,0 +1,552 @@
+/*
+ * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <byteswap.h>
+#include <ipxe/usb.h>
+#include "usbhub.h"
+
+/** @file
+ *
+ * USB hub driver
+ *
+ */
+
+/**
+ * Refill interrupt ring
+ *
+ * @v hubdev           Hub device
+ */
+static void hub_refill ( struct usb_hub_device *hubdev ) {
+       struct io_buffer *iobuf;
+       size_t mtu = hubdev->intr.mtu;
+       int rc;
+
+       /* Enqueue any available I/O buffers */
+       while ( ( iobuf = list_first_entry ( &hubdev->intrs, struct io_buffer,
+                                            list ) ) ) {
+
+               /* Reset size */
+               iob_put ( iobuf, ( mtu - iob_len ( iobuf ) ) );
+
+               /* Enqueue I/O buffer */
+               if ( ( rc = usb_stream ( &hubdev->intr, iobuf ) ) != 0 ) {
+                       DBGC ( hubdev, "HUB %s could not enqueue interrupt: "
+                              "%s\n", hubdev->name, strerror ( rc ) );
+                       /* Leave in available list and wait for next refill */
+                       return;
+               }
+
+               /* Remove from available list */
+               list_del ( &iobuf->list );
+       }
+
+       /* Stop refill process */
+       process_del ( &hubdev->refill );
+}
+
+/** Refill process descriptor */
+static struct process_descriptor hub_refill_desc =
+       PROC_DESC ( struct usb_hub_device, refill, hub_refill );
+
+/**
+ * Complete interrupt transfer
+ *
+ * @v ep               USB endpoint
+ * @v iobuf            I/O buffer
+ * @v rc               Completion status code
+ */
+static void hub_complete ( struct usb_endpoint *ep,
+                          struct io_buffer *iobuf, int rc ) {
+       struct usb_hub_device *hubdev =
+               container_of ( ep, struct usb_hub_device, intr );
+       struct usb_hub *hub = hubdev->hub;
+       uint8_t *data = iobuf->data;
+       unsigned int bits = ( 8 * iob_len ( iobuf ) );
+       unsigned int i;
+
+       /* Ignore packets cancelled when the endpoint closes */
+       if ( ! ep->open )
+               goto done;
+
+       /* Ignore packets with errors */
+       if ( rc != 0 ) {
+               DBGC ( hubdev, "HUB %s interrupt failed: %s\n",
+                      hubdev->name, strerror ( rc ) );
+               DBGC_HDA ( hubdev, 0, iobuf->data, iob_len ( iobuf ) );
+               goto done;
+       }
+
+       /* Report any port status changes */
+       for ( i = 1 ; i <= hub->ports ; i++ ) {
+
+               /* Sanity check */
+               if ( i > bits ) {
+                       DBGC ( hubdev, "HUB %s underlength interrupt:\n",
+                              hubdev->name );
+                       DBGC_HDA ( hubdev, 0, iobuf->data, iob_len ( iobuf ) );
+                       goto done;
+               }
+
+               /* Report port status change if applicable */
+               if ( data[ i / 8 ] & ( 1 << ( i % 8 ) ) ) {
+                       DBGC2 ( hubdev, "HUB %s port %d status changed\n",
+                               hubdev->name, i );
+                       usb_port_changed ( usb_port ( hub, i ) );
+               }
+       }
+
+ done:
+       /* Return I/O buffer to available list */
+       list_add_tail ( &iobuf->list, &hubdev->intrs );
+
+       /* Start refill process */
+       process_add ( &hubdev->refill );
+}
+
+/** Interrupt endpoint operations */
+static struct usb_endpoint_driver_operations usb_hub_intr_operations = {
+       .complete = hub_complete,
+};
+
+/**
+ * Open hub
+ *
+ * @v hub              USB hub
+ * @ret rc             Return status code
+ */
+static int hub_open ( struct usb_hub *hub ) {
+       struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub );
+       struct usb_device *usb = hubdev->usb;
+       struct io_buffer *iobuf;
+       struct io_buffer *tmp;
+       unsigned int i;
+       int rc;
+
+       /* Ensure ports are powered */
+       for ( i = 1 ; i <= hub->ports ; i++ ) {
+               if ( ( rc = usb_hub_set_port_feature ( usb, i,
+                                                      USB_HUB_PORT_POWER,
+                                                      0 ) ) != 0 ) {
+                       DBGC ( hubdev, "HUB %s port %d could not apply power: "
+                              "%s\n", hubdev->name, i, strerror ( rc ) );
+                       goto err_power;
+               }
+       }
+
+       /* Allocate I/O buffers */
+       for ( i = 0 ; i < USB_HUB_INTR_FILL ; i++ ) {
+               iobuf = alloc_iob ( hubdev->intr.mtu );
+               if ( ! iobuf ) {
+                       rc = -ENOMEM;
+                       goto err_alloc_iob;
+               }
+               list_add ( &iobuf->list, &hubdev->intrs );
+       }
+
+       /* Open interrupt endpoint */
+       if ( ( rc = usb_endpoint_open ( &hubdev->intr ) ) != 0 ) {
+               DBGC ( hubdev, "HUB %s could not register interrupt: %s\n",
+                      hubdev->name, strerror ( rc ) );
+               goto err_open;
+       }
+
+       /* Start refill process */
+       process_add ( &hubdev->refill );
+
+       /* Refill interrupt ring */
+       hub_refill ( hubdev );
+
+       return 0;
+
+       usb_endpoint_close ( &hubdev->intr );
+ err_open:
+ err_alloc_iob:
+       list_for_each_entry_safe ( iobuf, tmp, &hubdev->intrs, list ) {
+               list_del ( &iobuf->list );
+               free_iob ( iobuf );
+       }
+ err_power:
+       return rc;
+}
+
+/**
+ * Close hub
+ *
+ * @v hub              USB hub
+ */
+static void hub_close ( struct usb_hub *hub ) {
+       struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub );
+       struct io_buffer *iobuf;
+       struct io_buffer *tmp;
+
+       /* Close interrupt endpoint */
+       usb_endpoint_close ( &hubdev->intr );
+
+       /* Stop refill process */
+       process_del ( &hubdev->refill );
+
+       /* Free I/O buffers */
+       list_for_each_entry_safe ( iobuf, tmp, &hubdev->intrs, list ) {
+               list_del ( &iobuf->list );
+               free_iob ( iobuf );
+       }
+}
+
+/**
+ * Enable port
+ *
+ * @v hub              USB hub
+ * @v port             USB port
+ * @ret rc             Return status code
+ */
+static int hub_enable ( struct usb_hub *hub, struct usb_port *port ) {
+       struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub );
+       struct usb_device *usb = hubdev->usb;
+       struct usb_hub_port_status status;
+       unsigned int current;
+       unsigned int i;
+       int rc;
+
+       /* Initiate reset if applicable */
+       if ( ( hub->protocol < USB_PROTO_3_0 ) &&
+            ( ( rc = usb_hub_set_port_feature ( usb, port->address,
+                                                USB_HUB_PORT_RESET, 0 ) )!=0)){
+               DBGC ( hubdev, "HUB %s port %d could not initiate reset: %s\n",
+                      hubdev->name, port->address, strerror ( rc ) );
+               return rc;
+       }
+
+       /* Wait for port to become enabled */
+       for ( i = 0 ; i < USB_HUB_ENABLE_MAX_WAIT_MS ; i++ ) {
+
+               /* Check for port being enabled */
+               if ( ( rc = usb_hub_get_port_status ( usb, port->address,
+                                                     &status ) ) != 0 ) {
+                       DBGC ( hubdev, "HUB %s port %d could not get status: "
+                              "%s\n", hubdev->name, port->address,
+                              strerror ( rc ) );
+                       return rc;
+               }
+               current = le16_to_cpu ( status.current );
+               if ( current & ( 1 << USB_HUB_PORT_ENABLE ) )
+                       return 0;
+
+               /* Delay */
+               mdelay ( 1 );
+       }
+
+       DBGC ( hubdev, "HUB %s port %d timed out waiting for enable\n",
+              hubdev->name, port->address );
+       return -ETIMEDOUT;
+}
+
+/**
+ * Disable port
+ *
+ * @v hub              USB hub
+ * @v port             USB port
+ * @ret rc             Return status code
+ */
+static int hub_disable ( struct usb_hub *hub, struct usb_port *port ) {
+       struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub );
+       struct usb_device *usb = hubdev->usb;
+       int rc;
+
+       /* Disable port */
+       if ( ( rc = usb_hub_clear_port_feature ( usb, port->address,
+                                                USB_HUB_PORT_ENABLE, 0 ) )!=0){
+               DBGC ( hubdev, "HUB %s port %d could not disable: %s\n",
+                      hubdev->name, port->address, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Clear port status change bits
+ *
+ * @v hubdev           USB hub device
+ * @v port             Port number
+ * @v changed          Port status change bits
+ * @ret rc             Return status code
+ */
+static int hub_clear_changes ( struct usb_hub_device *hubdev,
+                              unsigned int port, uint16_t changed ) {
+       struct usb_device *usb = hubdev->usb;
+       unsigned int bit;
+       unsigned int feature;
+       int rc;
+
+       /* Clear each set bit */
+       for ( bit = 0 ; bit < 16 ; bit++ ) {
+
+               /* Skip unset bits */
+               if ( ! ( changed & ( 1 << bit ) ) )
+                       continue;
+
+               /* Skip unused features */
+               feature = USB_HUB_C_FEATURE ( bit );
+               if ( ! ( hubdev->features & ( 1 << feature ) ) )
+                       continue;
+
+               /* Clear bit */
+               if ( ( rc = usb_hub_clear_port_feature ( usb, port,
+                                                        feature, 0 ) ) != 0 ) {
+                       DBGC ( hubdev, "HUB %s port %d could not clear feature "
+                              "%d: %s\n", hubdev->name, port, feature,
+                              strerror ( rc ) );
+                       return rc;
+               }
+       }
+
+       return 0;
+}
+
+/**
+ * Update port speed
+ *
+ * @v hub              USB hub
+ * @v port             USB port
+ * @ret rc             Return status code
+ */
+static int hub_speed ( struct usb_hub *hub, struct usb_port *port ) {
+       struct usb_hub_device *hubdev = usb_hub_get_drvdata ( hub );
+       struct usb_device *usb = hubdev->usb;
+       struct usb_hub_port_status status;
+       unsigned int current;
+       unsigned int changed;
+       int rc;
+
+       /* Get port status */
+       if ( ( rc = usb_hub_get_port_status ( usb, port->address,
+                                             &status ) ) != 0 ) {
+               DBGC ( hubdev, "HUB %s port %d could not get status: %s\n",
+                      hubdev->name, port->address, strerror ( rc ) );
+               return rc;
+       }
+       current = le16_to_cpu ( status.current );
+       changed = le16_to_cpu ( status.changed );
+       DBGC2 ( hubdev, "HUB %s port %d status is %04x:%04x\n",
+               hubdev->name, port->address, changed, current );
+
+       /* Update port speed */
+       if ( current & ( 1 << USB_HUB_PORT_CONNECTION ) ) {
+               if ( hub->protocol >= USB_PROTO_3_0 ) {
+                       port->speed = USB_SPEED_SUPER;
+               } else if ( current & ( 1 << USB_HUB_PORT_LOW_SPEED ) ) {
+                       port->speed = USB_SPEED_LOW;
+               } else if ( current & ( 1 << USB_HUB_PORT_HIGH_SPEED ) ) {
+                       port->speed = USB_SPEED_HIGH;
+               } else {
+                       port->speed = USB_SPEED_FULL;
+               }
+       } else {
+               port->speed = USB_SPEED_NONE;
+       }
+
+       /* Clear port status change bits */
+       if ( ( rc = hub_clear_changes ( hubdev, port->address, changed ) ) != 0)
+               return rc;
+
+       return 0;
+}
+
+/** USB hub operations */
+static struct usb_hub_driver_operations hub_operations = {
+       .open = hub_open,
+       .close = hub_close,
+       .enable = hub_enable,
+       .disable = hub_disable,
+       .speed = hub_speed,
+};
+
+/**
+ * Probe USB hub
+ *
+ * @v func             USB function
+ * @v config           Configuration descriptor
+ * @ret rc             Return status code
+ */
+static int hub_probe ( struct usb_function *func,
+                      struct usb_configuration_descriptor *config ) {
+       struct usb_device *usb = func->usb;
+       struct usb_bus *bus = usb->port->hub->bus;
+       struct usb_hub_device *hubdev;
+       struct usb_interface_descriptor *interface;
+       union usb_hub_descriptor desc;
+       unsigned int depth;
+       unsigned int ports;
+       int enhanced;
+       int rc;
+
+       /* Allocate and initialise structure */
+       hubdev = zalloc ( sizeof ( *hubdev ) );
+       if ( ! hubdev ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       enhanced = ( usb->port->protocol >= USB_PROTO_3_0 );
+       hubdev->name = func->name;
+       hubdev->usb = usb;
+       hubdev->features =
+               ( enhanced ? USB_HUB_FEATURES_ENHANCED : USB_HUB_FEATURES );
+       usb_endpoint_init ( &hubdev->intr, usb, &usb_hub_intr_operations );
+       INIT_LIST_HEAD ( &hubdev->intrs );
+       process_init_stopped ( &hubdev->refill, &hub_refill_desc, NULL );
+
+       /* Locate hub interface descriptor */
+       interface = usb_interface_descriptor ( config, func->interface[0], 0 );
+       if ( ! interface ) {
+               DBGC ( hubdev, "HUB %s has no interface descriptor\n",
+                      hubdev->name );
+               rc = -EINVAL;
+               goto err_interface;
+       }
+
+       /* Locate interrupt endpoint descriptor */
+       if ( ( rc = usb_endpoint_described ( &hubdev->intr, config, interface,
+                                            USB_INTERRUPT, 0 ) ) != 0 ) {
+               DBGC ( hubdev, "HUB %s could not describe interrupt endpoint: "
+                      "%s\n", hubdev->name, strerror ( rc ) );
+               goto err_endpoint;
+       }
+
+       /* Set hub depth */
+       depth = usb_depth ( usb );
+       if ( enhanced ) {
+               if ( ( rc = usb_hub_set_hub_depth ( usb, depth ) ) != 0 ) {
+                       DBGC ( hubdev, "HUB %s could not set hub depth to %d: "
+                              "%s\n", hubdev->name, depth, strerror ( rc ) );
+                       goto err_set_hub_depth;
+               }
+       }
+
+       /* Get hub descriptor */
+       if ( ( rc = usb_hub_get_descriptor ( usb, enhanced, &desc ) ) != 0 ) {
+               DBGC ( hubdev, "HUB %s could not get hub descriptor: %s\n",
+                      hubdev->name, strerror ( rc ) );
+               goto err_hub_descriptor;
+       }
+       ports = desc.basic.ports;
+       DBGC ( hubdev, "HUB %s has %d ports at depth %d%s\n", hubdev->name,
+              ports, depth, ( enhanced ? " (enhanced)" : "" ) );
+
+       /* Allocate hub */
+       hubdev->hub = alloc_usb_hub ( bus, usb, ports, &hub_operations );
+       if ( ! hubdev->hub ) {
+               rc = -ENOMEM;
+               goto err_alloc_hub;
+       }
+       usb_hub_set_drvdata ( hubdev->hub, hubdev );
+
+       /* Register hub */
+       if ( ( rc = register_usb_hub ( hubdev->hub ) ) != 0 ) {
+               DBGC ( hubdev, "HUB %s could not register: %s\n",
+                      hubdev->name, strerror ( rc ) );
+               goto err_register_hub;
+       }
+
+       usb_func_set_drvdata ( func, hubdev );
+       return 0;
+
+       unregister_usb_hub ( hubdev->hub );
+ err_register_hub:
+       free_usb_hub ( hubdev->hub );
+ err_alloc_hub:
+ err_hub_descriptor:
+ err_set_hub_depth:
+ err_endpoint:
+ err_interface:
+       free ( hubdev );
+ err_alloc:
+       return rc;
+}
+
+/**
+ * Remove USB hub
+ *
+ * @v func             USB function
+ * @ret rc             Return status code
+ */
+static void hub_remove ( struct usb_function *func ) {
+       struct usb_hub_device *hubdev = usb_func_get_drvdata ( func );
+       struct usb_hub *hub = hubdev->hub;
+       struct usb_device *usb = hubdev->usb;
+       struct usb_port *port;
+       unsigned int i;
+
+       /* If hub has been unplugged, mark all ports as unplugged */
+       if ( usb->port->speed == USB_SPEED_NONE ) {
+               for ( i = 1 ; i <= hub->ports ; i++ ) {
+                       port = usb_port ( hub, i );
+                       port->speed = USB_SPEED_NONE;
+               }
+       }
+
+       /* Unregister hub */
+       unregister_usb_hub ( hubdev->hub );
+       assert ( ! process_running ( &hubdev->refill ) );
+       assert ( list_empty ( &hubdev->intrs ) );
+
+       /* Free hub */
+       free_usb_hub ( hubdev->hub );
+
+       /* Free hub device */
+       free ( hubdev );
+}
+
+/** USB hub device IDs */
+static struct usb_device_id hub_ids[] = {
+       {
+               .name = "hub-1",
+               .vendor = USB_ANY_ID,
+               .product = USB_ANY_ID,
+               .class = {
+                       .class = USB_CLASS_HUB,
+                       .subclass = 0,
+                       .protocol = 0,
+               },
+       },
+       {
+               .name = "hub-2",
+               .vendor = USB_ANY_ID,
+               .product = USB_ANY_ID,
+               .class = {
+                       .class = USB_CLASS_HUB,
+                       .subclass = 0,
+                       .protocol = 1,
+               },
+       },
+};
+
+/** USB hub driver */
+struct usb_driver usb_hub_driver __usb_driver = {
+       .ids = hub_ids,
+       .id_count = ( sizeof ( hub_ids ) / sizeof ( hub_ids[0] ) ),
+       .probe = hub_probe,
+       .remove = hub_remove,
+};
diff --git a/src/drivers/usb/usbhub.h b/src/drivers/usb/usbhub.h
new file mode 100644 (file)
index 0000000..a59ecae
--- /dev/null
@@ -0,0 +1,248 @@
+#ifndef _USBHUB_H
+#define _USBHUB_H
+
+/** @file
+ *
+ * USB hubs
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <ipxe/usb.h>
+#include <ipxe/list.h>
+#include <ipxe/process.h>
+
+/** Request recipient is a port */
+#define USB_HUB_RECIP_PORT ( 3 << 0 )
+
+/** A basic USB hub descriptor */
+struct usb_hub_descriptor_basic {
+       /** Descriptor header */
+       struct usb_descriptor_header header;
+       /** Number of ports */
+       uint8_t ports;
+       /** Characteristics */
+       uint16_t characteristics;
+       /** Power-on delay (in 2ms intervals */
+       uint8_t delay;
+       /** Controller current (in mA) */
+       uint8_t current;
+} __attribute__ (( packed ));
+
+/** A basic USB hub descriptor */
+#define USB_HUB_DESCRIPTOR 41
+
+/** An enhanced USB hub descriptor */
+struct usb_hub_descriptor_enhanced {
+       /** Basic USB hub descriptor */
+       struct usb_hub_descriptor_basic basic;
+       /** Header decode latency */
+       uint8_t latency;
+       /** Maximum delay */
+       uint16_t delay;
+       /** Removable device bitmask */
+       uint16_t removable;
+} __attribute__ (( packed ));
+
+/** An enhanced USB hub descriptor */
+#define USB_HUB_DESCRIPTOR_ENHANCED 42
+
+/** A USB hub descriptor */
+union usb_hub_descriptor {
+       /** Descriptor header */
+       struct usb_descriptor_header header;
+       /** Basic hub descriptor */
+       struct usb_hub_descriptor_basic basic;
+       /** Enhanced hub descriptor */
+       struct usb_hub_descriptor_enhanced enhanced;
+} __attribute__ (( packed ));
+
+/** Port status */
+struct usb_hub_port_status {
+       /** Current status */
+       uint16_t current;
+       /** Changed status */
+       uint16_t changed;
+} __attribute__ (( packed ));
+
+/** Current connect status feature */
+#define USB_HUB_PORT_CONNECTION 0
+
+/** Port enabled/disabled feature */
+#define USB_HUB_PORT_ENABLE 1
+
+/** Port reset feature */
+#define USB_HUB_PORT_RESET 4
+
+/** Port power feature */
+#define USB_HUB_PORT_POWER 8
+
+/** Low-speed device attached */
+#define USB_HUB_PORT_LOW_SPEED 9
+
+/** High-speed device attached */
+#define USB_HUB_PORT_HIGH_SPEED 10
+
+/** Connect status changed */
+#define USB_HUB_C_PORT_CONNECTION 16
+
+/** Port enable/disable changed */
+#define        USB_HUB_C_PORT_ENABLE 17
+
+/** Suspend changed */
+#define USB_HUB_C_PORT_SUSPEND 18
+
+/** Over-current indicator changed */
+#define USB_HUB_C_PORT_OVER_CURRENT 19
+
+/** Reset changed */
+#define USB_HUB_C_PORT_RESET 20
+
+/** Link state changed */
+#define USB_HUB_C_PORT_LINK_STATE 25
+
+/** Configuration error */
+#define USB_HUB_C_PORT_CONFIG_ERROR 26
+
+/** Calculate feature from change bit number */
+#define USB_HUB_C_FEATURE( bit ) ( 16 + (bit) )
+
+/** USB features */
+#define USB_HUB_FEATURES                                               \
+       ( ( 1 << USB_HUB_C_PORT_CONNECTION ) |                          \
+         ( 1 << USB_HUB_C_PORT_ENABLE ) |                              \
+         ( 1 << USB_HUB_C_PORT_SUSPEND ) |                             \
+         ( 1 << USB_HUB_C_PORT_OVER_CURRENT ) |                        \
+         ( 1 << USB_HUB_C_PORT_RESET ) )
+
+/** USB features for enhanced hubs */
+#define USB_HUB_FEATURES_ENHANCED                                      \
+       ( ( 1 << USB_HUB_C_PORT_CONNECTION ) |                          \
+         ( 1 << USB_HUB_C_PORT_OVER_CURRENT ) |                        \
+         ( 1 << USB_HUB_C_PORT_RESET ) |                               \
+         ( 1 << USB_HUB_C_PORT_LINK_STATE ) |                          \
+         ( 1 << USB_HUB_C_PORT_CONFIG_ERROR ) )
+
+/** Set hub depth */
+#define USB_HUB_SET_HUB_DEPTH                                          \
+       ( USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE |             \
+         USB_REQUEST_TYPE ( 12 ) )
+
+/**
+ * Get hub descriptor
+ *
+ * @v usb              USB device
+ * @v enhanced         Hub is an enhanced hub
+ * @v data             Hub descriptor to fill in
+ * @ret rc             Return status code
+ */
+static inline __attribute__ (( always_inline )) int
+usb_hub_get_descriptor ( struct usb_device *usb, int enhanced,
+                        union usb_hub_descriptor *data ) {
+       unsigned int desc;
+       size_t len;
+
+       /* Determine descriptor type and length */
+       desc = ( enhanced ? USB_HUB_DESCRIPTOR_ENHANCED : USB_HUB_DESCRIPTOR );
+       len = ( enhanced ? sizeof ( data->enhanced ) : sizeof ( data->basic ) );
+
+       return usb_get_descriptor ( usb, USB_TYPE_CLASS, desc, 0, 0,
+                                   &data->header, len );
+}
+
+/**
+ * Get port status
+ *
+ * @v usb              USB device
+ * @v port             Port address
+ * @v status           Port status descriptor to fill in
+ * @ret rc             Return status code
+ */
+static inline __attribute__ (( always_inline )) int
+usb_hub_get_port_status ( struct usb_device *usb, unsigned int port,
+                         struct usb_hub_port_status *status ) {
+
+       return usb_get_status ( usb, ( USB_TYPE_CLASS | USB_HUB_RECIP_PORT ),
+                               port, status, sizeof ( *status ) );
+}
+
+/**
+ * Clear port feature
+ *
+ * @v usb              USB device
+ * @v port             Port address
+ * @v feature          Feature to clear
+ * @v index            Index (when clearing a port indicator)
+ * @ret rc             Return status code
+ */
+static inline __attribute__ (( always_inline )) int
+usb_hub_clear_port_feature ( struct usb_device *usb, unsigned int port,
+                            unsigned int feature, unsigned int index ) {
+
+       return usb_clear_feature ( usb, ( USB_TYPE_CLASS | USB_HUB_RECIP_PORT ),
+                                  feature, ( ( index << 8 ) | port ) );
+}
+
+/**
+ * Set port feature
+ *
+ * @v usb              USB device
+ * @v port             Port address
+ * @v feature          Feature to clear
+ * @v index            Index (when clearing a port indicator)
+ * @ret rc             Return status code
+ */
+static inline __attribute__ (( always_inline )) int
+usb_hub_set_port_feature ( struct usb_device *usb, unsigned int port,
+                          unsigned int feature, unsigned int index ) {
+
+       return usb_set_feature ( usb, ( USB_TYPE_CLASS | USB_HUB_RECIP_PORT ),
+                                feature, ( ( index << 8 ) | port ) );
+}
+
+/**
+ * Set hub depth
+ *
+ * @v usb              USB device
+ * @v depth            Hub depth
+ * @ret rc             Return status code
+ */
+static inline __attribute__ (( always_inline )) int
+usb_hub_set_hub_depth ( struct usb_device *usb, unsigned int depth ) {
+
+       return usb_control ( usb, USB_HUB_SET_HUB_DEPTH, depth, 0, NULL, 0 );
+}
+
+/** A USB hub device */
+struct usb_hub_device {
+       /** Name */
+       const char *name;
+       /** USB device */
+       struct usb_device *usb;
+       /** USB hub */
+       struct usb_hub *hub;
+       /** Features */
+       unsigned int features;
+
+       /** Interrupt endpoint */
+       struct usb_endpoint intr;
+       /** Recycled interrupt I/O buffers */
+       struct list_head intrs;
+       /** Interrupt endpoint refill process */
+       struct process refill;
+};
+
+/** Interrupt ring fill level
+ *
+ * This is a policy decision.
+ */
+#define USB_HUB_INTR_FILL 4
+
+/** Maximum time to wait for port to become enabled
+ *
+ * This is a policy decision.
+ */
+#define USB_HUB_ENABLE_MAX_WAIT_MS 100
+
+#endif /* _USBHUB_H */
index d84cd712979311d25320ea57afd0be1278e5cc2b..e8a2b5ee070fbb25d0ea20ab49bf016e4e62d5b2 100644 (file)
@@ -77,6 +77,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define ERRFILE_linux               ( ERRFILE_DRIVER | 0x00050000 )
 #define ERRFILE_pcivpd              ( ERRFILE_DRIVER | 0x00060000 )
 #define ERRFILE_usb                 ( ERRFILE_DRIVER | 0x00070000 )
+#define ERRFILE_usbhub              ( ERRFILE_DRIVER | 0x00080000 )
 
 #define ERRFILE_nvs                 ( ERRFILE_DRIVER | 0x00100000 )
 #define ERRFILE_spi                 ( ERRFILE_DRIVER | 0x00110000 )