summaryrefslogtreecommitdiffstats
path: root/drivers/usb/host/xhci-mem.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/xhci-mem.c')
-rw-r--r--drivers/usb/host/xhci-mem.c93
1 files changed, 84 insertions, 9 deletions
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index a9534396e85..a003e79aacd 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -814,14 +814,64 @@ void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci,
ep0_ctx->deq |= ep_ring->cycle_state;
}
+/*
+ * The xHCI roothub may have ports of differing speeds in any order in the port
+ * status registers. xhci->port_array provides an array of the port speed for
+ * each offset into the port status registers.
+ *
+ * The xHCI hardware wants to know the roothub port number that the USB device
+ * is attached to (or the roothub port its ancestor hub is attached to). All we
+ * know is the index of that port under either the USB 2.0 or the USB 3.0
+ * roothub, but that doesn't give us the real index into the HW port status
+ * registers. Scan through the xHCI roothub port array, looking for the Nth
+ * entry of the correct port speed. Return the port number of that entry.
+ */
+static u32 xhci_find_real_port_number(struct xhci_hcd *xhci,
+ struct usb_device *udev)
+{
+ struct usb_device *top_dev;
+ unsigned int num_similar_speed_ports;
+ unsigned int faked_port_num;
+ int i;
+
+ for (top_dev = udev; top_dev->parent && top_dev->parent->parent;
+ top_dev = top_dev->parent)
+ /* Found device below root hub */;
+ faked_port_num = top_dev->portnum;
+ for (i = 0, num_similar_speed_ports = 0;
+ i < HCS_MAX_PORTS(xhci->hcs_params1); i++) {
+ u8 port_speed = xhci->port_array[i];
+
+ /*
+ * Skip ports that don't have known speeds, or have duplicate
+ * Extended Capabilities port speed entries.
+ */
+ if (port_speed == 0 || port_speed == -1)
+ continue;
+
+ /*
+ * USB 3.0 ports are always under a USB 3.0 hub. USB 2.0 and
+ * 1.1 ports are under the USB 2.0 hub. If the port speed
+ * matches the device speed, it's a similar speed port.
+ */
+ if ((port_speed == 0x03) == (udev->speed == USB_SPEED_SUPER))
+ num_similar_speed_ports++;
+ if (num_similar_speed_ports == faked_port_num)
+ /* Roothub ports are numbered from 1 to N */
+ return i+1;
+ }
+ return 0;
+}
+
/* Setup an xHCI virtual device for a Set Address command */
int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *udev)
{
struct xhci_virt_device *dev;
struct xhci_ep_ctx *ep0_ctx;
- struct usb_device *top_dev;
struct xhci_slot_ctx *slot_ctx;
struct xhci_input_control_ctx *ctrl_ctx;
+ u32 port_num;
+ struct usb_device *top_dev;
dev = xhci->devs[udev->slot_id];
/* Slot ID 0 is reserved */
@@ -863,16 +913,20 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud
BUG();
}
/* Find the root hub port this device is under */
+ port_num = xhci_find_real_port_number(xhci, udev);
+ if (!port_num)
+ return -EINVAL;
+ slot_ctx->dev_info2 |= (u32) ROOT_HUB_PORT(port_num);
+ /* Set the port number in the virtual_device to the faked port number */
for (top_dev = udev; top_dev->parent && top_dev->parent->parent;
top_dev = top_dev->parent)
/* Found device below root hub */;
- slot_ctx->dev_info2 |= (u32) ROOT_HUB_PORT(top_dev->portnum);
dev->port = top_dev->portnum;
- xhci_dbg(xhci, "Set root hub portnum to %d\n", top_dev->portnum);
+ xhci_dbg(xhci, "Set root hub portnum to %d\n", port_num);
+ xhci_dbg(xhci, "Set fake root hub portnum to %d\n", dev->port);
- /* Is this a LS/FS device under a HS hub? */
- if ((udev->speed == USB_SPEED_LOW || udev->speed == USB_SPEED_FULL) &&
- udev->tt) {
+ /* Is this a LS/FS device under an external HS hub? */
+ if (udev->tt && udev->tt->hub->parent) {
slot_ctx->tt_info = udev->tt->hub->slot_id;
slot_ctx->tt_info |= udev->ttport << 8;
if (udev->tt->multi)
@@ -1452,7 +1506,8 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
xhci->page_size = 0;
xhci->page_shift = 0;
- xhci->bus_suspended = 0;
+ xhci->bus_state[0].bus_suspended = 0;
+ xhci->bus_state[1].bus_suspended = 0;
}
static int xhci_test_trb_in_td(struct xhci_hcd *xhci,
@@ -1748,6 +1803,20 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags)
}
xhci_dbg(xhci, "Found %u USB 2.0 ports and %u USB 3.0 ports.\n",
xhci->num_usb2_ports, xhci->num_usb3_ports);
+
+ /* Place limits on the number of roothub ports so that the hub
+ * descriptors aren't longer than the USB core will allocate.
+ */
+ if (xhci->num_usb3_ports > 15) {
+ xhci_dbg(xhci, "Limiting USB 3.0 roothub ports to 15.\n");
+ xhci->num_usb3_ports = 15;
+ }
+ if (xhci->num_usb2_ports > USB_MAXCHILDREN) {
+ xhci_dbg(xhci, "Limiting USB 2.0 roothub ports to %u.\n",
+ USB_MAXCHILDREN);
+ xhci->num_usb2_ports = USB_MAXCHILDREN;
+ }
+
/*
* Note we could have all USB 3.0 ports, or all USB 2.0 ports.
* Not sure how the USB core will handle a hub with no ports...
@@ -1772,6 +1841,8 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags)
"addr = %p\n", i,
xhci->usb2_ports[port_index]);
port_index++;
+ if (port_index == xhci->num_usb2_ports)
+ break;
}
}
if (xhci->num_usb3_ports) {
@@ -1790,6 +1861,8 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags)
"addr = %p\n", i,
xhci->usb3_ports[port_index]);
port_index++;
+ if (port_index == xhci->num_usb3_ports)
+ break;
}
}
return 0;
@@ -1971,8 +2044,10 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
init_completion(&xhci->addr_dev);
for (i = 0; i < MAX_HC_SLOTS; ++i)
xhci->devs[i] = NULL;
- for (i = 0; i < MAX_HC_PORTS; ++i)
- xhci->resume_done[i] = 0;
+ for (i = 0; i < USB_MAXCHILDREN; ++i) {
+ xhci->bus_state[0].resume_done[i] = 0;
+ xhci->bus_state[1].resume_done[i] = 0;
+ }
if (scratchpad_alloc(xhci, flags))
goto fail;