[U-Boot] [PATCH v3 00/15] usb: hub: Support USB 3.0 hubs

This series is a follow-up series to previous series [1] that adds support for USB 3.0 hubs.
USB 3.0 hubs have slightly different hub descriptor format, as well as different port status bit positions. These needs to be properly handled by U-Boot USB core stack codes. xHCI driver has also been updated to correctly set up context structures that are required for devices behind a 3.0 hub to work.
Besides USB 3.0 hubs support, this series also adds support for low speed/full speed devices connected behind a high speed hub with the xHC controller. It turns out the 'Transaction Translator' part in the xHC data structure is missing.
Note there is one error during testing USB keyboard when CONFIG_USB_KEYBOARD is on.
"Failed to get keyboard state from device 413c:2105"
The enumeration process did pass and the error was thrown in the USB keyboard driver. This was due to the interrupt transfer is still not supported by xHCI driver, as of today.
This series is available at u-boot-x86/xhci-working2 for testing.
[1] https://lists.denx.de/pipermail/u-boot/2017-June/296166.html
Changes in v3: - fix build warnings for non-DM configuration
Changes in v2: - update the xhci-pci driver Kconfig to make it depend on DM_USB - nuke xhci_pci_remove() altogether - trim down the ifdefs by adding usb_get_hub_device() and limit ifdefs only in that function - new patch to "remove hub_port_reset()" - squash the xchi is_root_port() changes into previous commit - change to use 'packets' in the comments - handle port number is greater than 15 in route string - fix several nits in the commit message - add a comment for 666 in TT think time
Bin Meng (15): usb: xhci-pci: Drop non-DM version of xhci-pci driver usb: xhci-pci: Clean up the driver a little bit usb: hub: Use 'struct usb_hub_device' as hub device's uclass_priv usb: hub: Remove hub_port_reset() usb: hub: Add a new API to test if a hub device is root hub usb: hub: Translate USB 3.0 hub port status into old version usb: hub: Support 'set hub depth' request for USB 3.0 hubs usb: xhci: Change xhci_setup_addressable_virt_dev() signature usb: xhci: Program 'route string' in the input slot context usb: hub: Parse and save TT details from device descriptor dm: usb: Add a new USB controller operation 'update_hub_device' usb: hub: Call usb_update_hub_device() after hub descriptor is fetched usb: xhci: Implement update_hub_device() operation usb: xhci: Correct TT_SLOT and TT_PORT macros usb: xhci: Enable TT to support LS/FS devices behind a HS hub
common/usb_hub.c | 208 ++++++++++++++++++++++++++++++++++++++---- drivers/usb/host/Kconfig | 1 + drivers/usb/host/usb-uclass.c | 13 ++- drivers/usb/host/xhci-mem.c | 52 ++++++++++- drivers/usb/host/xhci-pci.c | 73 +-------------- drivers/usb/host/xhci.c | 86 ++++++++++++----- drivers/usb/host/xhci.h | 8 +- include/usb.h | 64 +++++++++---- include/usb_defs.h | 15 +++ 9 files changed, 379 insertions(+), 141 deletions(-)

As there is no board that currently uses xhci-pci driver without DM USB, drop its support and leave only DM support.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org
---
Changes in v3: None Changes in v2: - update the xhci-pci driver Kconfig to make it depend on DM_USB
drivers/usb/host/Kconfig | 1 + drivers/usb/host/xhci-pci.c | 52 --------------------------------------------- 2 files changed, 1 insertion(+), 52 deletions(-)
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 9edc589..67ad72b 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -33,6 +33,7 @@ config USB_XHCI_MVEBU
config USB_XHCI_PCI bool "Support for PCI-based xHCI USB controller" + depends on DM_USB default y if X86 help Enables support for the PCI-based xHCI controller. diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 63daaa6..5ad8452 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -14,56 +14,6 @@
#include "xhci.h"
-#ifndef CONFIG_DM_USB - -/* - * Create the appropriate control structures to manage a new XHCI host - * controller. - */ -int xhci_hcd_init(int index, struct xhci_hccr **ret_hccr, - struct xhci_hcor **ret_hcor) -{ - struct xhci_hccr *hccr; - struct xhci_hcor *hcor; - pci_dev_t pdev; - uint32_t cmd; - int len; - - pdev = pci_find_class(PCI_CLASS_SERIAL_USB_XHCI, index); - if (pdev < 0) { - printf("XHCI host controller not found\n"); - return -1; - } - - hccr = (struct xhci_hccr *)pci_map_bar(pdev, - PCI_BASE_ADDRESS_0, PCI_REGION_MEM); - len = HC_LENGTH(xhci_readl(&hccr->cr_capbase)); - hcor = (struct xhci_hcor *)((uint32_t)hccr + len); - - debug("XHCI-PCI init hccr 0x%x and hcor 0x%x hc_length %d\n", - (uint32_t)hccr, (uint32_t)hcor, len); - - *ret_hccr = hccr; - *ret_hcor = hcor; - - /* enable busmaster */ - pci_read_config_dword(pdev, PCI_COMMAND, &cmd); - cmd |= PCI_COMMAND_MASTER; - pci_write_config_dword(pdev, PCI_COMMAND, cmd); - - return 0; -} - -/* - * Destroy the appropriate control structures corresponding * to the XHCI host - * controller - */ -void xhci_hcd_stop(int index) -{ -} - -#else - struct xhci_pci_priv { struct xhci_ctrl ctrl; /* Needs to come first in this struct! */ }; @@ -137,5 +87,3 @@ static struct pci_device_id xhci_pci_supported[] = { };
U_BOOT_PCI_DEVICE(xhci_pci, xhci_pci_supported); - -#endif /* CONFIG_DM_USB */

This cleans up the driver a little bit.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org
---
Changes in v3: None Changes in v2: - nuke xhci_pci_remove() altogether
drivers/usb/host/xhci-pci.c | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-)
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 5ad8452..e4a0ef4 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -8,16 +8,10 @@
#include <common.h> #include <dm.h> -#include <errno.h> #include <pci.h> #include <usb.h> - #include "xhci.h"
-struct xhci_pci_priv { - struct xhci_ctrl ctrl; /* Needs to come first in this struct! */ -}; - static void xhci_pci_init(struct udevice *dev, struct xhci_hccr **ret_hccr, struct xhci_hcor **ret_hcor) { @@ -53,17 +47,6 @@ static int xhci_pci_probe(struct udevice *dev) return xhci_register(dev, hccr, hcor); }
-static int xhci_pci_remove(struct udevice *dev) -{ - int ret; - - ret = xhci_deregister(dev); - if (ret) - return ret; - - return 0; -} - static const struct udevice_id xhci_pci_ids[] = { { .compatible = "xhci-pci" }, { } @@ -73,11 +56,11 @@ U_BOOT_DRIVER(xhci_pci) = { .name = "xhci_pci", .id = UCLASS_USB, .probe = xhci_pci_probe, - .remove = xhci_pci_remove, + .remove = xhci_deregister, .of_match = xhci_pci_ids, .ops = &xhci_usb_ops, .platdata_auto_alloc_size = sizeof(struct usb_platdata), - .priv_auto_alloc_size = sizeof(struct xhci_pci_priv), + .priv_auto_alloc_size = sizeof(struct xhci_ctrl), .flags = DM_FLAG_ALLOC_PRIV_DMA, };

Use USB hub device's dev->uclass_priv to point to 'usb_hub_device' so that with driver model usb_hub_reset() and usb_hub_allocate() are no longer needed.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org
---
Changes in v3: None Changes in v2: - trim down the ifdefs by adding usb_get_hub_device() and limit ifdefs only in that function
common/usb_hub.c | 27 ++++++++++++++++++++++----- drivers/usb/host/usb-uclass.c | 2 -- 2 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/common/usb_hub.c b/common/usb_hub.c index a46d26a..37b358b 100644 --- a/common/usb_hub.c +++ b/common/usb_hub.c @@ -55,9 +55,6 @@ struct usb_device_scan { struct list_head list; };
-/* TODO(sjg@chromium.org): Remove this when CONFIG_DM_USB is defined */ -static struct usb_hub_device hub_dev[USB_MAX_HUB]; -static int usb_hub_index; static LIST_HEAD(usb_scan_list);
__weak void usb_hub_reset_devices(int port) @@ -164,6 +161,10 @@ static void usb_hub_power_on(struct usb_hub_device *hub) max(100, (int)pgood_delay) + 1000); }
+#ifndef CONFIG_DM_USB +static struct usb_hub_device hub_dev[USB_MAX_HUB]; +static int usb_hub_index; + void usb_hub_reset(void) { usb_hub_index = 0; @@ -180,6 +181,7 @@ static struct usb_hub_device *usb_hub_allocate(void) printf("ERROR: USB_MAX_HUB (%d) reached\n", USB_MAX_HUB); return NULL; } +#endif
#define MAX_TRIES 5
@@ -543,6 +545,20 @@ out: return ret; }
+static struct usb_hub_device *usb_get_hub_device(struct usb_device *dev) +{ + struct usb_hub_device *hub; + +#ifndef CONFIG_DM_USB + /* "allocate" Hub device */ + hub = usb_hub_allocate(); +#else + hub = dev_get_uclass_priv(dev->dev); +#endif + + return hub; +} + static int usb_hub_configure(struct usb_device *dev) { int i, length; @@ -554,11 +570,11 @@ static int usb_hub_configure(struct usb_device *dev) __maybe_unused struct usb_hub_status *hubsts; int ret;
- /* "allocate" Hub device */ - hub = usb_hub_allocate(); + hub = usb_get_hub_device(dev); if (hub == NULL) return -ENOMEM; hub->pusb_dev = dev; + /* Get the the hub descriptor */ ret = usb_get_hub_descriptor(dev, buffer, 4); if (ret < 0) { @@ -792,6 +808,7 @@ UCLASS_DRIVER(usb_hub) = { .child_pre_probe = usb_child_pre_probe, .per_child_auto_alloc_size = sizeof(struct usb_device), .per_child_platdata_auto_alloc_size = sizeof(struct usb_dev_platdata), + .per_device_auto_alloc_size = sizeof(struct usb_hub_device), };
static const struct usb_device_id hub_id_table[] = { diff --git a/drivers/usb/host/usb-uclass.c b/drivers/usb/host/usb-uclass.c index efc5d4e..d8d74bd 100644 --- a/drivers/usb/host/usb-uclass.c +++ b/drivers/usb/host/usb-uclass.c @@ -177,7 +177,6 @@ int usb_stop(void) #ifdef CONFIG_USB_STORAGE usb_stor_reset(); #endif - usb_hub_reset(); uc_priv->companion_device_count = 0; usb_started = 0;
@@ -230,7 +229,6 @@ int usb_init(void) int ret;
asynch_allowed = 1; - usb_hub_reset();
ret = uclass_get(UCLASS_USB, &uc); if (ret)

At present hub_port_reset() is defined in DM USB, but it is never called hence remove it (removing another ifdefs).
While we are here, change legacy_hub_port_reset() name to usb_hub_port_reset() to better match other function names in the same hub module.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org
---
Changes in v3: None Changes in v2: - new patch to "remove hub_port_reset()"
common/usb_hub.c | 25 +++++++++++++------------ include/usb.h | 18 ------------------ 2 files changed, 13 insertions(+), 30 deletions(-)
diff --git a/common/usb_hub.c b/common/usb_hub.c index 37b358b..086b155 100644 --- a/common/usb_hub.c +++ b/common/usb_hub.c @@ -207,8 +207,18 @@ static inline char *portspeed(int portstatus) return speed_str; }
-int legacy_hub_port_reset(struct usb_device *dev, int port, - unsigned short *portstat) +/** + * usb_hub_port_reset() - reset a port given its usb_device pointer + * + * Reset a hub port and see if a device is present on that port, providing + * sufficient time for it to show itself. The port status is returned. + * + * @dev: USB device to reset + * @port: Port number to reset (note ports are numbered from 0 here) + * @portstat: Returns port status + */ +static int usb_hub_port_reset(struct usb_device *dev, int port, + unsigned short *portstat) { int err, tries; ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1); @@ -281,15 +291,6 @@ int legacy_hub_port_reset(struct usb_device *dev, int port, return 0; }
-#ifdef CONFIG_DM_USB -int hub_port_reset(struct udevice *dev, int port, unsigned short *portstat) -{ - struct usb_device *udev = dev_get_parent_priv(dev); - - return legacy_hub_port_reset(udev, port, portstat); -} -#endif - int usb_hub_port_connect_change(struct usb_device *dev, int port) { ALLOC_CACHE_ALIGN_BUFFER(struct usb_port_status, portsts, 1); @@ -323,7 +324,7 @@ int usb_hub_port_connect_change(struct usb_device *dev, int port) }
/* Reset the port */ - ret = legacy_hub_port_reset(dev, port, &portstatus); + ret = usb_hub_port_reset(dev, port, &portstatus); if (ret < 0) { if (ret != -ENXIO) printf("cannot reset port %i!?\n", port + 1); diff --git a/include/usb.h b/include/usb.h index eb82cc2..a266677 100644 --- a/include/usb.h +++ b/include/usb.h @@ -871,24 +871,6 @@ bool usb_device_has_child_on_port(struct usb_device *parent, int port); int usb_hub_probe(struct usb_device *dev, int ifnum); void usb_hub_reset(void);
-/** - * legacy_hub_port_reset() - reset a port given its usb_device pointer - * - * Reset a hub port and see if a device is present on that port, providing - * sufficient time for it to show itself. The port status is returned. - * - * With driver model this moves to hub_port_reset() and is passed a struct - * udevice. - * - * @dev: USB device to reset - * @port: Port number to reset (note ports are numbered from 0 here) - * @portstat: Returns port status - */ -int legacy_hub_port_reset(struct usb_device *dev, int port, - unsigned short *portstat); - -int hub_port_reset(struct udevice *dev, int port, unsigned short *portstat); - /* * usb_find_usb2_hub_address_port() - Get hub address and port for TT setting *

Sometimes we need know if a given hub device is root hub or not. Add a new API to test this. This removes the xHCI driver's own version is_root_hub() and change to use the new API.
While we are here, remove the unused/commented out get_usb_device() in the xHCI driver too.
Signed-off-by: Bin Meng bmeng.cn@gmail.com
---
Changes in v3: None Changes in v2: - squash the xchi is_root_port() changes into previous commit
common/usb_hub.c | 10 ++++++++++ drivers/usb/host/xhci.c | 24 ++---------------------- include/usb.h | 8 ++++++++ 3 files changed, 20 insertions(+), 22 deletions(-)
diff --git a/common/usb_hub.c b/common/usb_hub.c index 086b155..a8c2f56 100644 --- a/common/usb_hub.c +++ b/common/usb_hub.c @@ -67,6 +67,16 @@ static inline bool usb_hub_is_superspeed(struct usb_device *hdev) return hdev->descriptor.bDeviceProtocol == 3; }
+#ifdef CONFIG_DM_USB +bool usb_hub_is_root_hub(struct udevice *hub) +{ + if (device_get_uclass_id(hub->parent) != UCLASS_USB_HUB) + return true; + + return false; +} +#endif + static int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size) { unsigned short dtype = USB_DT_HUB; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 62ab62b..0c88980 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1116,26 +1116,6 @@ int usb_lowlevel_stop(int index) #endif /* CONFIG_DM_USB */
#ifdef CONFIG_DM_USB -/* -static struct usb_device *get_usb_device(struct udevice *dev) -{ - struct usb_device *udev; - - if (device_get_uclass_id(dev) == UCLASS_USB) - udev = dev_get_uclass_priv(dev); - else - udev = dev_get_parent_priv(dev); - - return udev; -} -*/ -static bool is_root_hub(struct udevice *dev) -{ - if (device_get_uclass_id(dev->parent) != UCLASS_USB_HUB) - return true; - - return false; -}
static int xhci_submit_control_msg(struct udevice *dev, struct usb_device *udev, unsigned long pipe, void *buffer, int length, @@ -1150,10 +1130,10 @@ static int xhci_submit_control_msg(struct udevice *dev, struct usb_device *udev, hub = udev->dev; if (device_get_uclass_id(hub) == UCLASS_USB_HUB) { /* Figure out our port number on the root hub */ - if (is_root_hub(hub)) { + if (usb_hub_is_root_hub(hub)) { root_portnr = udev->portnr; } else { - while (!is_root_hub(hub->parent)) + while (!usb_hub_is_root_hub(hub->parent)) hub = hub->parent; uhop = dev_get_parent_priv(hub); root_portnr = uhop->portnr; diff --git a/include/usb.h b/include/usb.h index a266677..64dfa84 100644 --- a/include/usb.h +++ b/include/usb.h @@ -776,6 +776,14 @@ int usb_setup_device(struct usb_device *dev, bool do_read, struct usb_device *parent);
/** + * usb_hub_is_root_hub() - Test whether a hub device is root hub or not + * + * @hub: USB hub device to test + * @return: true if the hub device is root hub, false otherwise. + */ +bool usb_hub_is_root_hub(struct udevice *hub); + +/** * usb_hub_scan() - Scan a hub and find its devices * * @hub: Hub device to scan

On 19 July 2017 at 07:51, Bin Meng bmeng.cn@gmail.com wrote:
Sometimes we need know if a given hub device is root hub or not. Add a new API to test this. This removes the xHCI driver's own version is_root_hub() and change to use the new API.
While we are here, remove the unused/commented out get_usb_device() in the xHCI driver too.
Signed-off-by: Bin Meng bmeng.cn@gmail.com
Changes in v3: None Changes in v2:
- squash the xchi is_root_port() changes into previous commit
common/usb_hub.c | 10 ++++++++++ drivers/usb/host/xhci.c | 24 ++---------------------- include/usb.h | 8 ++++++++ 3 files changed, 20 insertions(+), 22 deletions(-)
Reviewed-by: Simon Glass sjg@chromium.org

USB 3.0 hub port status field has different bit positions from 2.0 hubs. Since U-Boot only understands the old version, translate the new one into the old one.
Since we are going to add USB 3.0 hub support, this feature is only available with driver model USB.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org ---
Changes in v3: None Changes in v2: None
common/usb_hub.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-)
diff --git a/common/usb_hub.c b/common/usb_hub.c index a8c2f56..b0ff159 100644 --- a/common/usb_hub.c +++ b/common/usb_hub.c @@ -112,9 +112,40 @@ static int usb_get_hub_status(struct usb_device *dev, void *data)
int usb_get_port_status(struct usb_device *dev, int port, void *data) { - return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + int ret; + + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port, data, sizeof(struct usb_port_status), USB_CNTL_TIMEOUT); + +#ifdef CONFIG_DM_USB + if (ret < 0) + return ret; + + /* + * Translate the USB 3.0 hub port status field into the old version + * that U-Boot understands. Do this only when the hub is not root hub. + * For root hub, the port status field has already been translated + * in the host controller driver (see xhci_submit_root() in xhci.c). + * + * Note: this only supports driver model. + */ + + if (!usb_hub_is_root_hub(dev->dev) && usb_hub_is_superspeed(dev)) { + struct usb_port_status *status = (struct usb_port_status *)data; + u16 tmp = (status->wPortStatus) & USB_SS_PORT_STAT_MASK; + + if (status->wPortStatus & USB_SS_PORT_STAT_POWER) + tmp |= USB_PORT_STAT_POWER; + if ((status->wPortStatus & USB_SS_PORT_STAT_SPEED) == + USB_SS_PORT_STAT_SPEED_5GBPS) + tmp |= USB_PORT_STAT_SUPER_SPEED; + + status->wPortStatus = tmp; + } +#endif + + return ret; }

USB 3.0 hub uses a hub depth value multiplied by four as an offset into the 'route string' to locate the bits it uses to determine the downstream port number. We shall set the hub depth value of a USB 3.0 hub after it is configured.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org
---
Changes in v3: None Changes in v2: - change to use 'packets' in the comments
common/usb_hub.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ include/usb.h | 1 + include/usb_defs.h | 3 +++ 3 files changed, 56 insertions(+)
diff --git a/common/usb_hub.c b/common/usb_hub.c index b0ff159..18c366a 100644 --- a/common/usb_hub.c +++ b/common/usb_hub.c @@ -75,6 +75,16 @@ bool usb_hub_is_root_hub(struct udevice *hub)
return false; } + +static int usb_set_hub_depth(struct usb_device *dev, int depth) +{ + if (depth < 0 || depth > 4) + return -EINVAL; + + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_HUB_DEPTH, USB_DIR_OUT | USB_RT_HUB, + depth, 0, NULL, 0, USB_CNTL_TIMEOUT); +} #endif
static int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size) @@ -726,6 +736,48 @@ static int usb_hub_configure(struct usb_device *dev) debug("%sover-current condition exists\n", (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? \ "" : "no "); + +#ifdef CONFIG_DM_USB + /* + * A maximum of seven tiers are allowed in a USB topology, and the + * root hub occupies the first tier. The last tier ends with a normal + * USB device. USB 3.0 hubs use a 20-bit field called 'route string' + * to route packets to the designated downstream port. The hub uses a + * hub depth value multiplied by four as an offset into the 'route + * string' to locate the bits it uses to determine the downstream + * port number. + */ + if (usb_hub_is_root_hub(dev->dev)) { + hub->hub_depth = -1; + } else { + struct udevice *hdev; + int depth = 0; + + hdev = dev->dev->parent; + while (!usb_hub_is_root_hub(hdev)) { + depth++; + hdev = hdev->parent; + } + + hub->hub_depth = depth; + + if (usb_hub_is_superspeed(dev)) { + debug("set hub (%p) depth to %d\n", dev, depth); + /* + * This request sets the value that the hub uses to + * determine the index into the 'route string index' + * for this hub. + */ + ret = usb_set_hub_depth(dev, depth); + if (ret < 0) { + debug("%s: failed to set hub depth (%lX)\n", + __func__, dev->status); + return ret; + } + } + } +#endif + usb_hub_power_on(hub);
/* diff --git a/include/usb.h b/include/usb.h index 64dfa84..f71da52 100644 --- a/include/usb.h +++ b/include/usb.h @@ -570,6 +570,7 @@ struct usb_hub_device { ulong connect_timeout; /* Device connection timeout in ms */ ulong query_delay; /* Device query delay in ms */ int overcurrent_count[USB_MAXCHILDREN]; /* Over-current counter */ + int hub_depth; /* USB 3.0 hub depth */ };
#ifdef CONFIG_DM_USB diff --git a/include/usb_defs.h b/include/usb_defs.h index 6b4385a..273337f 100644 --- a/include/usb_defs.h +++ b/include/usb_defs.h @@ -306,6 +306,9 @@ /* Mask for wIndex in get/set port feature */ #define USB_HUB_PORT_MASK 0xf
+/* Hub class request codes */ +#define USB_REQ_SET_HUB_DEPTH 0x0c + /* * CBI style */

For future extension, change xhci_setup_addressable_virt_dev() signature to accept a pointer to 'struct usb_device', instead of its members slot_id & speed, as the struct already contains these two plus some other useful information of the device.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org ---
Changes in v3: None Changes in v2: None
drivers/usb/host/xhci-mem.c | 6 ++++-- drivers/usb/host/xhci.c | 3 +-- drivers/usb/host/xhci.h | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 12e277a..9aa3092 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -713,14 +713,16 @@ void xhci_slot_copy(struct xhci_ctrl *ctrl, struct xhci_container_ctx *in_ctx, * @param udev pointer to the Device Data Structure * @return returns negative value on failure else 0 on success */ -void xhci_setup_addressable_virt_dev(struct xhci_ctrl *ctrl, int slot_id, - int speed, int hop_portnr) +void xhci_setup_addressable_virt_dev(struct xhci_ctrl *ctrl, + struct usb_device *udev, int hop_portnr) { struct xhci_virt_device *virt_dev; struct xhci_ep_ctx *ep0_ctx; struct xhci_slot_ctx *slot_ctx; u32 port_num = 0; u64 trb_64 = 0; + int slot_id = udev->slot_id; + int speed = udev->speed;
virt_dev = ctrl->devs[slot_id];
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 0c88980..336613f 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -415,8 +415,7 @@ static int xhci_address_device(struct usb_device *udev, int root_portnr) * so setting up the slot context. */ debug("Setting up addressable devices %p\n", ctrl->dcbaa); - xhci_setup_addressable_virt_dev(ctrl, udev->slot_id, udev->speed, - root_portnr); + xhci_setup_addressable_virt_dev(ctrl, udev, root_portnr);
ctrl_ctx = xhci_get_input_control_ctx(virt_dev->in_ctx); ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index b9602ba..cdce67c 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1247,8 +1247,8 @@ void xhci_endpoint_copy(struct xhci_ctrl *ctrl, void xhci_slot_copy(struct xhci_ctrl *ctrl, struct xhci_container_ctx *in_ctx, struct xhci_container_ctx *out_ctx); -void xhci_setup_addressable_virt_dev(struct xhci_ctrl *ctrl, int slot_id, - int speed, int hop_portnr); +void xhci_setup_addressable_virt_dev(struct xhci_ctrl *ctrl, + struct usb_device *udev, int hop_portnr); void xhci_queue_command(struct xhci_ctrl *ctrl, u8 *ptr, u32 slot_id, u32 ep_index, trb_type cmd); void xhci_acknowledge_event(struct xhci_ctrl *ctrl);

xHCI spec says: the values of the 'route string' field shall be initialized by the first 'Address Device' command issued to a device slot, and shall not be modified by any other command.
So far U-Boot does not program this field, and it does not prevent SS device directly attached to root port, or HS device behind an HS hub, from working, due to the fact that 'route string' is used by the xHC to target SS packets. But in order to enumerate devices behind an SS hub, this field must be programmed.
With this commit and along with previous commits, now SS & HS devices attached to a USB 3.0 hub can be enumerated by U-Boot.
As usual, this new feature is only available when DM is on.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org
--- Test logs: two USB 3.0 hubs (one tier2, one tier3) => usb tree USB device tree: 1 Hub (5 Gb/s, 0mA) | U-Boot XHCI Host Controller | +-2 Hub (5 Gb/s, 0mA) | | GenesysLogic USB3.0 Hub | | | +-4 Hub (5 Gb/s, 0mA) | | | VIA Labs, Inc. USB3.0 Hub | | | | | +-7 Mass Storage (5 Gb/s, 76mA) | | JetFlash Mass Storage Device 16Q6ZPH20GF3E8UQ | | | +-8 Vendor specific (5 Gb/s, 36mA) | Realtek USB 10/100/1000 LAN 00E04C680977 | +-3 Hub (480 Mb/s, 100mA) | GenesysLogic USB2.0 Hub | +-5 Mass Storage (480 Mb/s, 200mA) | Netac OnlyDisk FF00ECB60800FFFF1526 | +-6 Hub (480 Mb/s, 0mA) VIA Labs, Inc. USB2.0 Hub
Changes in v3: - fix build warnings for non-DM configuration
Changes in v2: - handle port number is greater than 15 in route string
drivers/usb/host/xhci-mem.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 9aa3092..c02059e 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -723,6 +723,11 @@ void xhci_setup_addressable_virt_dev(struct xhci_ctrl *ctrl, u64 trb_64 = 0; int slot_id = udev->slot_id; int speed = udev->speed; + int route = 0; +#ifdef CONFIG_DM_USB + struct usb_device *dev = udev; + struct usb_hub_device *hub; +#endif
virt_dev = ctrl->devs[slot_id];
@@ -733,7 +738,32 @@ void xhci_setup_addressable_virt_dev(struct xhci_ctrl *ctrl, slot_ctx = xhci_get_slot_ctx(ctrl, virt_dev->in_ctx);
/* Only the control endpoint is valid - one endpoint context */ - slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(1) | 0); + slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(1)); + +#ifdef CONFIG_DM_USB + /* Calculate the route string for this device */ + port_num = dev->portnr; + while (!usb_hub_is_root_hub(dev->dev)) { + hub = dev_get_uclass_priv(dev->dev); + /* + * Each hub in the topology is expected to have no more than + * 15 ports in order for the route string of a device to be + * unique. SuperSpeed hubs are restricted to only having 15 + * ports, but FS/LS/HS hubs are not. The xHCI specification + * says that if the port number the device is greater than 15, + * that portion of the route string shall be set to 15. + */ + if (port_num > 15) + port_num = 15; + route |= port_num << (hub->hub_depth * 4); + dev = dev_get_parent_priv(dev->dev); + port_num = dev->portnr; + dev = dev_get_parent_priv(dev->dev->parent); + } + + debug("route string %x\n", route); +#endif + slot_ctx->dev_info |= route;
switch (speed) { case USB_SPEED_SUPER:

A high speed hub has a special responsibility to handle full speed/ low speed devices connected on downstream ports. In this case, the hub must isolate the high speed signaling environment from the full speed/low speed signaling environment with the help of Transaction Translator (TT). TT details are provided by hub descriptors and we parse and save it to hub uclass_priv for later use.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org ---
Changes in v3: None Changes in v2: None
common/usb_hub.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ include/usb.h | 16 ++++++++++++++++ include/usb_defs.h | 12 ++++++++++++ 3 files changed, 78 insertions(+)
diff --git a/common/usb_hub.c b/common/usb_hub.c index 18c366a..bbb1155 100644 --- a/common/usb_hub.c +++ b/common/usb_hub.c @@ -700,6 +700,56 @@ static int usb_hub_configure(struct usb_device *dev) break; }
+ switch (dev->descriptor.bDeviceProtocol) { + case USB_HUB_PR_FS: + break; + case USB_HUB_PR_HS_SINGLE_TT: + debug("Single TT\n"); + break; + case USB_HUB_PR_HS_MULTI_TT: + ret = usb_set_interface(dev, 0, 1); + if (ret == 0) { + debug("TT per port\n"); + hub->tt.multi = true; + } else { + debug("Using single TT (err %d)\n", ret); + } + break; + case USB_HUB_PR_SS: + /* USB 3.0 hubs don't have a TT */ + break; + default: + debug("Unrecognized hub protocol %d\n", + dev->descriptor.bDeviceProtocol); + break; + } + + /* Note 8 FS bit times == (8 bits / 12000000 bps) ~= 666ns */ + switch (hubCharacteristics & HUB_CHAR_TTTT) { + case HUB_TTTT_8_BITS: + if (dev->descriptor.bDeviceProtocol != 0) { + hub->tt.think_time = 666; + debug("TT requires at most %d FS bit times (%d ns)\n", + 8, hub->tt.think_time); + } + break; + case HUB_TTTT_16_BITS: + hub->tt.think_time = 666 * 2; + debug("TT requires at most %d FS bit times (%d ns)\n", + 16, hub->tt.think_time); + break; + case HUB_TTTT_24_BITS: + hub->tt.think_time = 666 * 3; + debug("TT requires at most %d FS bit times (%d ns)\n", + 24, hub->tt.think_time); + break; + case HUB_TTTT_32_BITS: + hub->tt.think_time = 666 * 4; + debug("TT requires at most %d FS bit times (%d ns)\n", + 32, hub->tt.think_time); + break; + } + debug("power on to power good time: %dms\n", descriptor->bPwrOn2PwrGood * 2); debug("hub controller current requirement: %dmA\n", diff --git a/include/usb.h b/include/usb.h index f71da52..58b4549 100644 --- a/include/usb.h +++ b/include/usb.h @@ -537,6 +537,21 @@ struct usb_hub_status { unsigned short wHubChange; } __attribute__ ((packed));
+/* + * Hub Device descriptor + * USB Hub class device protocols + */ +#define USB_HUB_PR_FS 0 /* Full speed hub */ +#define USB_HUB_PR_HS_NO_TT 0 /* Hi-speed hub without TT */ +#define USB_HUB_PR_HS_SINGLE_TT 1 /* Hi-speed hub with single TT */ +#define USB_HUB_PR_HS_MULTI_TT 2 /* Hi-speed hub with multiple TT */ +#define USB_HUB_PR_SS 3 /* Super speed hub */ + +/* Transaction Translator Think Times, in bits */ +#define HUB_TTTT_8_BITS 0x00 +#define HUB_TTTT_16_BITS 0x20 +#define HUB_TTTT_24_BITS 0x40 +#define HUB_TTTT_32_BITS 0x60
/* Hub descriptor */ struct usb_hub_descriptor { @@ -571,6 +586,7 @@ struct usb_hub_device { ulong query_delay; /* Device query delay in ms */ int overcurrent_count[USB_MAXCHILDREN]; /* Over-current counter */ int hub_depth; /* USB 3.0 hub depth */ + struct usb_tt tt; /* Transaction Translator */ };
#ifdef CONFIG_DM_USB diff --git a/include/usb_defs.h b/include/usb_defs.h index 273337f..b7f2ead 100644 --- a/include/usb_defs.h +++ b/include/usb_defs.h @@ -293,6 +293,7 @@ #define HUB_CHAR_LPSM 0x0003 #define HUB_CHAR_COMPOUND 0x0004 #define HUB_CHAR_OCPM 0x0018 +#define HUB_CHAR_TTTT 0x0060 /* TT Think Time mask */
/* * Hub Status & Hub Change bit masks @@ -310,6 +311,17 @@ #define USB_REQ_SET_HUB_DEPTH 0x0c
/* + * As of USB 2.0, full/low speed devices are segregated into trees. + * One type grows from USB 1.1 host controllers (OHCI, UHCI etc). + * The other type grows from high speed hubs when they connect to + * full/low speed devices using "Transaction Translators" (TTs). + */ +struct usb_tt { + bool multi; /* true means one TT per port */ + unsigned think_time; /* think time in ns */ +}; + +/* * CBI style */

For USB host controllers like xHC, its internal representation of hub needs to be updated after the hub descriptor is fetched. This adds a new op that does this.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org ---
Changes in v3: None Changes in v2: None
drivers/usb/host/usb-uclass.c | 11 +++++++++++ include/usb.h | 21 ++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/host/usb-uclass.c b/drivers/usb/host/usb-uclass.c index d8d74bd..0b8a501 100644 --- a/drivers/usb/host/usb-uclass.c +++ b/drivers/usb/host/usb-uclass.c @@ -139,6 +139,17 @@ int usb_reset_root_port(struct usb_device *udev) return ops->reset_root_port(bus, udev); }
+int usb_update_hub_device(struct usb_device *udev) +{ + struct udevice *bus = udev->controller_dev; + struct dm_usb_ops *ops = usb_get_ops(bus); + + if (!ops->update_hub_device) + return -ENOSYS; + + return ops->update_hub_device(bus, udev); +} + int usb_stop(void) { struct udevice *bus; diff --git a/include/usb.h b/include/usb.h index 58b4549..fad0401 100644 --- a/include/usb.h +++ b/include/usb.h @@ -758,6 +758,14 @@ struct dm_usb_ops { * reset_root_port() - Reset usb root port */ int (*reset_root_port)(struct udevice *bus, struct usb_device *udev); + + /** + * update_hub_device() - Update HCD's internal representation of hub + * + * After a hub descriptor is fetched, notify HCD so that its internal + * representation of this hub can be updated (xHCI) + */ + int (*update_hub_device)(struct udevice *bus, struct usb_device *udev); };
#define usb_get_ops(dev) ((struct dm_usb_ops *)(dev)->driver->ops) @@ -931,6 +939,17 @@ int usb_new_device(struct usb_device *dev); int usb_alloc_device(struct usb_device *dev);
/** + * update_hub_device() - Update HCD's internal representation of hub + * + * After a hub descriptor is fetched, notify HCD so that its internal + * representation of this hub can be updated. + * + * @dev: Hub device + * @return 0 if OK, -ve on error + */ +int usb_update_hub_device(struct usb_device *dev); + +/** * usb_emul_setup_device() - Set up a new USB device emulation * * This is normally called when a new emulation device is bound. It tells @@ -943,7 +962,7 @@ int usb_alloc_device(struct usb_device *dev); * @desc_list: List of points or USB descriptors, terminated by NULL. * The first entry must be struct usb_device_descriptor, * and others follow on after that. - * @return 0 if OK, -ve on error + * @return 0 if OK, -ENOSYS if not implemented, other -ve on error */ int usb_emul_setup_device(struct udevice *dev, int maxpacketsize, struct usb_string *strings, void **desc_list);

After fetching hub descriptor, we need to call USB uclass operation update_hub_device() to notify HCD to do some preparation work.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org ---
Changes in v3: None Changes in v2: None
common/usb_hub.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/common/usb_hub.c b/common/usb_hub.c index bbb1155..70bc6e2 100644 --- a/common/usb_hub.c +++ b/common/usb_hub.c @@ -789,6 +789,17 @@ static int usb_hub_configure(struct usb_device *dev)
#ifdef CONFIG_DM_USB /* + * Update USB host controller's internal representation of this hub + * after the hub descriptor is fetched. + */ + ret = usb_update_hub_device(dev); + if (ret < 0 && ret != -ENOSYS) { + debug("%s: failed to update hub device for HCD (%x)\n", + __func__, ret); + return ret; + } + + /* * A maximum of seven tiers are allowed in a USB topology, and the * root hub occupies the first tier. The last tier ends with a normal * USB device. USB 3.0 hubs use a 20-bit field called 'route string'

There is no way to know whether the attached device is a hub or not in advance before the device's descriptor is fetched. But once we know it's a high speed hub, per the xHCI spec, we need to tell xHC it's a hub device by initializing hub-related fields in the input slot context.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org
---
Changes in v3: None Changes in v2: - fix several nits in the commit message - add a comment for 666 in TT think time
drivers/usb/host/xhci.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+)
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 336613f..9b82ee5 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1170,6 +1170,64 @@ static int xhci_alloc_device(struct udevice *dev, struct usb_device *udev) return _xhci_alloc_device(udev); }
+static int xhci_update_hub_device(struct udevice *dev, struct usb_device *udev) +{ + struct xhci_ctrl *ctrl = dev_get_priv(dev); + struct usb_hub_device *hub = dev_get_uclass_priv(udev->dev); + struct xhci_virt_device *virt_dev; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_container_ctx *out_ctx; + struct xhci_container_ctx *in_ctx; + struct xhci_slot_ctx *slot_ctx; + int slot_id = udev->slot_id; + unsigned think_time; + + debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev); + + /* Ignore root hubs */ + if (usb_hub_is_root_hub(udev->dev)) + return 0; + + virt_dev = ctrl->devs[slot_id]; + BUG_ON(!virt_dev); + + out_ctx = virt_dev->out_ctx; + in_ctx = virt_dev->in_ctx; + + ctrl_ctx = xhci_get_input_control_ctx(in_ctx); + /* Initialize the input context control */ + ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); + ctrl_ctx->drop_flags = 0; + + xhci_inval_cache((uintptr_t)out_ctx->bytes, out_ctx->size); + + /* slot context */ + xhci_slot_copy(ctrl, in_ctx, out_ctx); + slot_ctx = xhci_get_slot_ctx(ctrl, in_ctx); + + /* Update hub related fields */ + slot_ctx->dev_info |= cpu_to_le32(DEV_HUB); + if (hub->tt.multi && udev->speed == USB_SPEED_HIGH) + slot_ctx->dev_info |= cpu_to_le32(DEV_MTT); + slot_ctx->dev_info2 |= cpu_to_le32(XHCI_MAX_PORTS(udev->maxchild)); + /* + * Set TT think time - convert from ns to FS bit times. + * Note 8 FS bit times == (8 bits / 12000000 bps) ~= 666ns + * + * 0 = 8 FS bit times, 1 = 16 FS bit times, + * 2 = 24 FS bit times, 3 = 32 FS bit times. + * + * This field shall be 0 if the device is not a high-spped hub. + */ + think_time = hub->tt.think_time; + if (think_time != 0) + think_time = (think_time / 666) - 1; + if (udev->speed == USB_SPEED_HIGH) + slot_ctx->tt_info |= cpu_to_le32(TT_THINK_TIME(think_time)); + + return xhci_configure_endpoints(udev, false); +} + int xhci_register(struct udevice *dev, struct xhci_hccr *hccr, struct xhci_hcor *hcor) { @@ -1222,6 +1280,7 @@ struct dm_usb_ops xhci_usb_ops = { .bulk = xhci_submit_bulk_msg, .interrupt = xhci_submit_int_msg, .alloc_device = xhci_alloc_device, + .update_hub_device = xhci_update_hub_device, };
#endif

These two macros really need a parameter to make them useful.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org ---
Changes in v3: None Changes in v2: None
drivers/usb/host/xhci.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index cdce67c..a497d9d 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -548,12 +548,12 @@ struct xhci_slot_ctx { * The Slot ID of the hub that isolates the high speed signaling from * this low or full-speed device. '0' if attached to root hub port. */ -#define TT_SLOT (0xff) +#define TT_SLOT(p) (((p) & 0xff) << 0) /* * The number of the downstream facing port of the high-speed hub * '0' if the device is not low or full speed. */ -#define TT_PORT (0xff << 8) +#define TT_PORT(p) (((p) & 0xff) << 8) #define TT_THINK_TIME(p) (((p) & 0x3) << 16)
/* dev_state bitmasks */

So far LS/FS devices directly attached to xHC root port can be successfully enumerated by xHCI driver, but if they are connected behind a hub, the enumeration process fails to address the device.
It turns out xHCI driver still misses a part that in the device's input slot context, all Transaction Translator (TT) related fields are not programmed. The xHCI spec defines how to enable TT.
Now LS/FS devices like USB keyboard/mouse can be enumerated behind a high speed hub.
Signed-off-by: Bin Meng bmeng.cn@gmail.com Reviewed-by: Simon Glass sjg@chromium.org ---
Changes in v3: None Changes in v2: None
drivers/usb/host/xhci-mem.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index c02059e..d5eab3a 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -783,6 +783,20 @@ void xhci_setup_addressable_virt_dev(struct xhci_ctrl *ctrl, BUG(); }
+#ifdef CONFIG_DM_USB + /* Set up TT fields to support FS/LS devices */ + if (speed == USB_SPEED_LOW || speed == USB_SPEED_FULL) { + dev = dev_get_parent_priv(udev->dev); + if (dev->speed == USB_SPEED_HIGH) { + hub = dev_get_uclass_priv(udev->dev); + if (hub->tt.multi) + slot_ctx->dev_info |= cpu_to_le32(DEV_MTT); + slot_ctx->tt_info |= cpu_to_le32(TT_PORT(udev->portnr)); + slot_ctx->tt_info |= cpu_to_le32(TT_SLOT(dev->slot_id)); + } + } +#endif + port_num = hop_portnr; debug("port_num = %d\n", port_num);

On 07/19/2017 03:51 PM, Bin Meng wrote:
This series is a follow-up series to previous series [1] that adds support for USB 3.0 hubs.
USB 3.0 hubs have slightly different hub descriptor format, as well as different port status bit positions. These needs to be properly handled by U-Boot USB core stack codes. xHCI driver has also been updated to correctly set up context structures that are required for devices behind a 3.0 hub to work.
Besides USB 3.0 hubs support, this series also adds support for low speed/full speed devices connected behind a high speed hub with the xHC controller. It turns out the 'Transaction Translator' part in the xHC data structure is missing.
Note there is one error during testing USB keyboard when CONFIG_USB_KEYBOARD is on.
"Failed to get keyboard state from device 413c:2105"
The enumeration process did pass and the error was thrown in the USB keyboard driver. This was due to the interrupt transfer is still not supported by xHCI driver, as of today.
This series is available at u-boot-x86/xhci-working2 for testing.
[1] https://lists.denx.de/pipermail/u-boot/2017-June/296166.html
Applied to u-boot-usb/master, thanks.
participants (3)
-
Bin Meng
-
Marek Vasut
-
Simon Glass