Add USB EHCI controller support.
[seabios.git] / src / usb-hub.c
index ce9326b2a8e7ef74ef4baf742df86c185fafdd2b..9effbc3328f8859fdbc416f37c1a4df94baf4cc1 100644 (file)
@@ -10,7 +10,7 @@
 #include "usb.h" // struct usb_s
 
 static int
-get_hub_desc(struct usb_hub_descriptor *desc, u32 endp)
+get_hub_desc(struct usb_pipe *pipe, struct usb_hub_descriptor *desc)
 {
     struct usb_ctrlrequest req;
     req.bRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE;
@@ -18,11 +18,11 @@ get_hub_desc(struct usb_hub_descriptor *desc, u32 endp)
     req.wValue = USB_DT_HUB<<8;
     req.wIndex = 0;
     req.wLength = sizeof(*desc);
-    return send_default_control(endp, &req, desc);
+    return send_default_control(pipe, &req, desc);
 }
 
 static int
-set_port_feature(int port, int feature, u32 endp)
+set_port_feature(struct usbhub_s *hub, int port, int feature)
 {
     struct usb_ctrlrequest req;
     req.bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER;
@@ -30,11 +30,14 @@ set_port_feature(int port, int feature, u32 endp)
     req.wValue = feature;
     req.wIndex = port;
     req.wLength = 0;
-    return send_default_control(endp, &req, NULL);
+    mutex_lock(&hub->lock);
+    int ret = send_default_control(hub->pipe, &req, NULL);
+    mutex_unlock(&hub->lock);
+    return ret;
 }
 
 static int
-clear_port_feature(int port, int feature, u32 endp)
+clear_port_feature(struct usbhub_s *hub, int port, int feature)
 {
     struct usb_ctrlrequest req;
     req.bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER;
@@ -42,11 +45,14 @@ clear_port_feature(int port, int feature, u32 endp)
     req.wValue = feature;
     req.wIndex = port;
     req.wLength = 0;
-    return send_default_control(endp, &req, NULL);
+    mutex_lock(&hub->lock);
+    int ret = send_default_control(hub->pipe, &req, NULL);
+    mutex_unlock(&hub->lock);
+    return ret;
 }
 
 static int
-get_port_status(int port, struct usb_port_status *sts, u32 endp)
+get_port_status(struct usbhub_s *hub, int port, struct usb_port_status *sts)
 {
     struct usb_ctrlrequest req;
     req.bRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_OTHER;
@@ -54,85 +60,132 @@ get_port_status(int port, struct usb_port_status *sts, u32 endp)
     req.wValue = 0;
     req.wIndex = port;
     req.wLength = sizeof(*sts);
-    return send_default_control(endp, &req, sts);
+    mutex_lock(&hub->lock);
+    int ret = send_default_control(hub->pipe, &req, sts);
+    mutex_unlock(&hub->lock);
+    return ret;
 }
 
-// Configure a usb hub and then find devices connected to it.
-int
-usb_hub_init(u32 endp)
+static void
+init_hub_port(void *data)
 {
-    if (!CONFIG_USB_HUB)
-        return 0;
+    struct usbhub_s *hub = data;
+    u32 port = hub->port; // XXX - find better way to pass port
 
-    struct usb_hub_descriptor desc;
-    int ret = get_hub_desc(&desc, endp);
+    // Turn on power to port.
+    int ret = set_port_feature(hub, port, USB_PORT_FEAT_POWER);
     if (ret)
-        return ret;
+        goto fail;
 
-    // Turn on power to all ports.
-    int i;
-    for (i=1; i<=desc.bNbrPorts; i++) {
-        ret = set_port_feature(i, USB_PORT_FEAT_POWER, endp);
+    // Wait for port power to stabilize.
+    msleep(hub->powerwait);
+
+    // Check periodically for a device connect.
+    struct usb_port_status sts;
+    u64 end = calc_future_tsc(USB_TIME_SIGATT);
+    for (;;) {
+        ret = get_port_status(hub, port, &sts);
         if (ret)
             goto fail;
+        if (sts.wPortStatus & USB_PORT_STAT_CONNECTION)
+            // Device connected.
+            break;
+        if (check_time(end))
+            // No device found.
+            goto done;
+        msleep(5);
     }
 
-    // Wait for port detection.
-    msleep(desc.bPwrOn2PwrGood * 2 + USB_TIME_SIGATT);
-    // XXX - should poll for ports becoming active sooner and then
-    // possibly wait USB_TIME_ATTDB.
+    // XXX - wait USB_TIME_ATTDB time?
 
-    // Detect down stream devices.
-    struct usb_s *cntl = endp2cntl(endp);
-    int totalcount = 0;
-    for (i=1; i<=desc.bNbrPorts; i++) {
-        struct usb_port_status sts;
-        ret = get_port_status(i, &sts, endp);
+    // Reset port.
+    mutex_lock(&hub->cntl->resetlock);
+    ret = set_port_feature(hub, port, USB_PORT_FEAT_RESET);
+    if (ret)
+        goto resetfail;
+
+    // Wait for reset to complete.
+    end = calc_future_tsc(USB_TIME_DRST * 2);
+    for (;;) {
+        ret = get_port_status(hub, port, &sts);
         if (ret)
-            goto fail;
-        if (!(sts.wPortStatus & USB_PORT_STAT_CONNECTION))
-            // XXX - power down port?
-            continue;
+            goto resetfail;
+        if (!(sts.wPortStatus & USB_PORT_STAT_RESET))
+            break;
+        if (check_time(end)) {
+            warn_timeout();
+            goto resetfail;
+        }
+        msleep(5);
+    }
 
-        // Reset port.
-        ret = set_port_feature(i, USB_PORT_FEAT_RESET, endp);
+    // Reset complete.
+    if (!(sts.wPortStatus & USB_PORT_STAT_CONNECTION))
+        // Device no longer present
+        goto resetfail;
+
+    // Set address of port
+    struct usb_pipe *pipe = usb_set_address(
+        hub, port, ((sts.wPortStatus & USB_PORT_STAT_SPEED_MASK)
+                    >> USB_PORT_STAT_SPEED_SHIFT));
+    if (!pipe)
+        goto resetfail;
+    mutex_unlock(&hub->cntl->resetlock);
+
+    // Configure the device
+    int count = configure_usb_device(pipe);
+    free_pipe(pipe);
+    if (!count) {
+        ret = clear_port_feature(hub, port, USB_PORT_FEAT_ENABLE);
         if (ret)
             goto fail;
+    }
+    hub->devcount += count;
+done:
+    hub->threads--;
+    return;
 
-        // Wait for reset to complete.
-        u64 end = calc_future_tsc(USB_TIME_DRST * 2);
-        for (;;) {
-            ret = get_port_status(i, &sts, endp);
-            if (ret)
-                goto fail;
-            if (!(sts.wPortStatus & USB_PORT_STAT_RESET))
-                break;
-            if (check_time(end)) {
-                // Timeout.
-                warn_timeout();
-                goto fail;
-            }
-            yield();
-        }
-        if (!(sts.wPortStatus & USB_PORT_STAT_CONNECTION))
-            // Device no longer present.  XXX - power down port?
-            continue;
-
-        // XXX - should try to parallelize configuration.
-        int count = configure_usb_device(
-            cntl, !!(sts.wPortStatus & USB_PORT_STAT_LOW_SPEED));
-        if (! count) {
-            // Shutdown port
-            ret = clear_port_feature(i, USB_PORT_FEAT_ENABLE, endp);
-            if (ret)
-                goto fail;
-        }
-        totalcount += count;
+resetfail:
+    clear_port_feature(hub, port, USB_PORT_FEAT_ENABLE);
+    mutex_unlock(&hub->cntl->resetlock);
+fail:
+    dprintf(1, "Failure on hub port %d setup\n", port);
+    goto done;
+}
+
+// Configure a usb hub and then find devices connected to it.
+int
+usb_hub_init(struct usb_pipe *pipe)
+{
+    ASSERT32FLAT();
+    if (!CONFIG_USB_HUB)
+        return -1;
+
+    struct usb_hub_descriptor desc;
+    int ret = get_hub_desc(pipe, &desc);
+    if (ret)
+        return ret;
+
+    struct usbhub_s hub;
+    memset(&hub, 0, sizeof(hub));
+    hub.pipe = pipe;
+    hub.cntl = pipe->cntl;
+    hub.powerwait = desc.bPwrOn2PwrGood * 2;
+
+    // Launch a thread for every port.
+    int i;
+    for (i=1; i<=desc.bNbrPorts; i++) {
+        hub.port = i;
+        hub.threads++;
+        run_thread(init_hub_port, &hub);
     }
 
-    return totalcount;
+    // Wait for threads to complete.
+    while (hub.threads)
+        yield();
 
-fail:
-    dprintf(1, "Failure on hub setup\n");
-    return 0;
+    dprintf(1, "Initialized USB HUB (%d ports used)\n", hub.devcount);
+    if (hub.devcount)
+        return 0;
+    return -1;
 }