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 +#include +#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 + +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