summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--payloads/libpayload/Config.in4
-rw-r--r--payloads/libpayload/drivers/Makefile.inc1
-rw-r--r--payloads/libpayload/drivers/usb/generic_hub.c297
-rw-r--r--payloads/libpayload/drivers/usb/generic_hub.h83
4 files changed, 385 insertions, 0 deletions
diff --git a/payloads/libpayload/Config.in b/payloads/libpayload/Config.in
index 9d083ea239..66d478dc28 100644
--- a/payloads/libpayload/Config.in
+++ b/payloads/libpayload/Config.in
@@ -378,6 +378,10 @@ config USB_MSC
storage devices (USB memory sticks, hard drives, CDROM/DVD drives)
Say Y here unless you know exactly what you are doing.
+config USB_GEN_HUB
+ bool
+ default n
+
endmenu
menu "Debugging"
diff --git a/payloads/libpayload/drivers/Makefile.inc b/payloads/libpayload/drivers/Makefile.inc
index 60e0fab369..0f014d647d 100644
--- a/payloads/libpayload/drivers/Makefile.inc
+++ b/payloads/libpayload/drivers/Makefile.inc
@@ -64,6 +64,7 @@ libc-$(CONFIG_USB) += usb/usbinit.c
libc-$(CONFIG_USB) += usb/usb.c
libc-$(CONFIG_USB) += usb/usb_dev.c
libc-$(CONFIG_USB) += usb/quirks.c
+libc-$(CONFIG_USB_GEN_HUB) += usb/generic_hub.c
libc-$(CONFIG_USB_HUB) += usb/usbhub.c
libc-$(CONFIG_USB_UHCI) += usb/uhci.c
libc-$(CONFIG_USB_UHCI) += usb/uhci_rh.c
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;
+}
diff --git a/payloads/libpayload/drivers/usb/generic_hub.h b/payloads/libpayload/drivers/usb/generic_hub.h
new file mode 100644
index 0000000000..7be20e8a9e
--- /dev/null
+++ b/payloads/libpayload/drivers/usb/generic_hub.h
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+#ifndef __USB_HUB_H
+#define __USB_HUB_H
+
+#include <usb/usb.h>
+
+typedef struct generic_hub_ops {
+ /* negative results denote an error */
+
+ /* returns 1 if the hub's status changed since the last call (optional) */
+ int (*hub_status_changed)(usbdev_t *);
+ /* returns 1 if the port's status changed since the last call */
+ int (*port_status_changed)(usbdev_t *, int port);
+ /* returns 1 if something is connected to the port */
+ int (*port_connected)(usbdev_t *, int port);
+ /* returns 1 if port is currently resetting */
+ int (*port_in_reset)(usbdev_t *, int port);
+ /* returns 1 if the port is enabled */
+ int (*port_enabled)(usbdev_t *, int port);
+ /* returns speed if port is enabled, negative value if not */
+ int (*port_speed)(usbdev_t *, int port);
+
+ /* enables (powers up) a port (optional) */
+ int (*enable_port)(usbdev_t *, int port);
+ /* disables (powers down) a port (optional) */
+ int (*disable_port)(usbdev_t *, int port);
+ /* starts a port reset (required if reset_port is set to a generic one from below) */
+ int (*start_port_reset)(usbdev_t *, int port);
+
+ /* performs a port reset (optional, generic implementations below) */
+ int (*reset_port)(usbdev_t *, int port);
+} generic_hub_ops_t;
+
+typedef struct generic_hub {
+ int num_ports;
+ /* port numbers are always 1 based,
+ so we waste one int for convenience */
+ int *ports; /* allocated to sizeof(*ports)*(num_ports+1) */
+#define NO_DEV -1
+
+ const generic_hub_ops_t *ops;
+
+ void *data;
+} generic_hub_t;
+
+void generic_hub_destroy(usbdev_t *);
+int generic_hub_resetport(usbdev_t *, int port);
+int generic_hub_rh_resetport(usbdev_t *, int port); /* root hubs have different timing requirements */
+int generic_hub_scanport(usbdev_t *, int port);
+/* the provided generic_hub_ops struct has to be static */
+int generic_hub_init(usbdev_t *, int num_ports, const generic_hub_ops_t *);
+
+#define GEN_HUB(usbdev) ((generic_hub_t *)(usbdev)->data)
+
+#endif