aboutsummaryrefslogtreecommitdiff
path: root/payloads/libpayload/drivers/usb/generic_hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'payloads/libpayload/drivers/usb/generic_hub.c')
-rw-r--r--payloads/libpayload/drivers/usb/generic_hub.c297
1 files changed, 297 insertions, 0 deletions
diff --git a/payloads/libpayload/drivers/usb/generic_hub.c b/payloads/libpayload/drivers/usb/generic_hub.c
new file mode 100644
index 0000000000..d568d4b8a4
--- /dev/null
+++ b/payloads/libpayload/drivers/usb/generic_hub.c
@@ -0,0 +1,297 @@
+/*
+ * This file is part of the libpayload project.
+ *
+ * Copyright (C) 2013 secunet Security Networks AG
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+//#define USB_DEBUG
+
+#include <stdlib.h>
+#include <usb/usb.h>
+#include "generic_hub.h"
+
+void
+generic_hub_destroy(usbdev_t *const dev)
+{
+ generic_hub_t *const hub = GEN_HUB(dev);
+ if (!hub)
+ return;
+
+ /* First, detach all devices behind this hub */
+ int port;
+ for (port = 1; port <= hub->num_ports; ++port) {
+ if (hub->ports[port] >= 0) {
+ usb_debug("generic_hub: Detachment at port %d\n", port);
+ usb_detach_device(dev->controller, hub->ports[port]);
+ hub->ports[port] = NO_DEV;
+ }
+ }
+
+ /* Disable all ports */
+ if (hub->ops->disable_port) {
+ for (port = 1; port <= hub->num_ports; ++port)
+ hub->ops->disable_port(dev, port);
+ }
+
+ free(hub->ports);
+ free(hub);
+}
+
+static int
+generic_hub_debounce(usbdev_t *const dev, const int port)
+{
+ generic_hub_t *const hub = GEN_HUB(dev);
+
+ const int step_ms = 1; /* linux uses 25ms, we're busy anyway */
+ const int at_least_ms = 100; /* 100ms as in usb20 spec 9.1.2 */
+ const int timeout_ms = 1500; /* linux uses this value */
+
+ int total_ms = 0;
+ int stable_ms = 0;
+ while (stable_ms < at_least_ms && total_ms < timeout_ms) {
+ mdelay(step_ms);
+
+ const int changed = hub->ops->port_status_changed(dev, port);
+ const int connected = hub->ops->port_connected(dev, port);
+ if (changed < 0 || connected < 0)
+ return -1;
+
+ if (!changed && connected) {
+ stable_ms += step_ms;
+ } else {
+ usb_debug("generic_hub: Unstable connection at %d\n",
+ port);
+ stable_ms = 0;
+ }
+ total_ms += step_ms;
+ }
+ if (total_ms >= timeout_ms)
+ usb_debug("generic_hub: Debouncing timed out at %d\n", port);
+ return 0; /* ignore timeouts, try to always go on */
+}
+
+static int
+generic_hub_wait_for_port(usbdev_t *const dev, const int port,
+ const int wait_for,
+ int (*const port_op)(usbdev_t *, int),
+ int timeout_steps, const int step_us)
+{
+ int state;
+ do {
+ state = port_op(dev, port);
+ if (state < 0)
+ return -1;
+ else if (!!state == wait_for)
+ return timeout_steps;
+ udelay(step_us);
+ --timeout_steps;
+ } while (timeout_steps);
+ return 0;
+}
+
+int
+generic_hub_resetport(usbdev_t *const dev, const int port)
+{
+ generic_hub_t *const hub = GEN_HUB(dev);
+
+ if (hub->ops->start_port_reset(dev, port) < 0)
+ return -1;
+
+ /* wait for 10ms (usb20 spec 11.5.1.5: reset should take 10 to 20ms) */
+ mdelay(10);
+
+ /* now wait 12ms for the hub to finish the reset */
+ const int ret = generic_hub_wait_for_port(
+ /* time out after 120 * 100us = 12ms */
+ dev, port, 0, hub->ops->port_in_reset, 120, 100);
+ if (ret < 0)
+ return -1;
+ else if (!ret)
+ usb_debug("generic_hub: Reset timed out at port %d\n", port);
+
+ return 0; /* ignore timeouts, try to always go on */
+}
+
+int
+generic_hub_rh_resetport(usbdev_t *const dev, const int port)
+{
+ generic_hub_t *const hub = GEN_HUB(dev);
+
+ /*
+ * Resetting a root hub port should hold 50ms with pulses of at
+ * least 10ms and gaps of at most 3ms (usb20 spec 7.1.7.5).
+ * After reset, the port will be enabled automatically.
+ */
+ int total = 500; /* 500 * 100us = 50ms */
+ while (total > 0) {
+ if (hub->ops->start_port_reset(dev, port) < 0)
+ return -1;
+
+ /* wait 100ms for the hub to finish the reset pulse */
+ const int timeout = generic_hub_wait_for_port(
+ /* time out after 1000 * 100us = 100ms */
+ dev, port, 0, hub->ops->port_in_reset, 1000, 100);
+ const int reset_time = 1000 - timeout;
+ if (timeout < 0)
+ return -1;
+ else if (!timeout)
+ usb_debug("generic_hub: Reset timed out at port %d\n",
+ port);
+ else if (reset_time < 100) /* i.e. < 100 * 100us */
+ usb_debug("generic_hub: Port reset too short\n");
+ total -= reset_time;
+ }
+ return 0; /* ignore timeouts, try to always go on */
+}
+
+static int
+generic_hub_detach_dev(usbdev_t *const dev, const int port)
+{
+ generic_hub_t *const hub = GEN_HUB(dev);
+
+ usb_detach_device(dev->controller, hub->ports[port]);
+ hub->ports[port] = NO_DEV;
+
+ return 0;
+}
+
+static int
+generic_hub_attach_dev(usbdev_t *const dev, const int port)
+{
+ generic_hub_t *const hub = GEN_HUB(dev);
+
+ if (generic_hub_debounce(dev, port) < 0)
+ return -1;
+
+ if (hub->ops->reset_port) {
+ if (hub->ops->reset_port(dev, port) < 0)
+ return -1;
+ /* after reset the port will be enabled automatically */
+ const int ret = generic_hub_wait_for_port(
+ /* time out after 1,000 * 10us = 10ms */
+ dev, port, 1, hub->ops->port_enabled, 1000, 10);
+ if (ret < 0)
+ return -1;
+ else if (!ret)
+ usb_debug("generic_hub: Port %d still "
+ "disabled after 10ms\n", port);
+ }
+
+ const int speed = hub->ops->port_speed(dev, port);
+ if (speed >= 0) {
+ usb_debug("generic_hub: Success at port %d\n", port);
+ if (hub->ops->reset_port)
+ mdelay(10); /* Reset recovery time
+ (usb20 spec 7.1.7.5) */
+ hub->ports[port] = usb_attach_device(
+ dev->controller, dev->address, port, speed);
+ }
+ return 0;
+}
+
+int
+generic_hub_scanport(usbdev_t *const dev, const int port)
+{
+ generic_hub_t *const hub = GEN_HUB(dev);
+
+ if (hub->ports[port] >= 0) {
+ usb_debug("generic_hub: Detachment at port %d\n", port);
+
+ const int ret = generic_hub_detach_dev(dev, port);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (hub->ops->port_connected(dev, port)) {
+ usb_debug("generic_hub: Attachment at port %d\n", port);
+
+ return generic_hub_attach_dev(dev, port);
+ }
+
+ return 0;
+}
+
+static void
+generic_hub_poll(usbdev_t *const dev)
+{
+ generic_hub_t *const hub = GEN_HUB(dev);
+ if (!hub)
+ return;
+
+ if (hub->ops->hub_status_changed &&
+ hub->ops->hub_status_changed(dev) != 1)
+ return;
+
+ int port;
+ for (port = 1; port <= hub->num_ports; ++port) {
+ const int ret = hub->ops->port_status_changed(dev, port);
+ if (ret < 0) {
+ return;
+ } else if (ret == 1) {
+ usb_debug("generic_hub: Port change at %d\n", port);
+ if (generic_hub_scanport(dev, port) < 0)
+ return;
+ }
+ }
+}
+
+int
+generic_hub_init(usbdev_t *const dev, const int num_ports,
+ const generic_hub_ops_t *const ops)
+{
+ int port;
+
+ dev->destroy = generic_hub_destroy;
+ dev->poll = generic_hub_poll;
+ dev->data = malloc(sizeof(generic_hub_t));
+ if (!dev->data) {
+ usb_debug("generic_hub: ERROR: Out of memory\n");
+ return -1;
+ }
+
+ generic_hub_t *const hub = GEN_HUB(dev);
+ hub->num_ports = num_ports;
+ hub->ports = malloc(sizeof(*hub->ports) * (num_ports + 1));
+ hub->ops = ops;
+ if (!hub->ports) {
+ usb_debug("generic_hub: ERROR: Out of memory\n");
+ free(dev->data);
+ dev->data = NULL;
+ return -1;
+ }
+ for (port = 1; port <= num_ports; ++port)
+ hub->ports[port] = NO_DEV;
+
+ /* Enable all ports */
+ if (ops->enable_port) {
+ for (port = 1; port <= num_ports; ++port)
+ ops->enable_port(dev, port);
+ /* wait once for all ports */
+ mdelay(20);
+ }
+
+ return 0;
+}