| /* |
| * 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 usb_speed 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; |
| } |