C++ hub wip
Change-Id: I5c7a32d7f19b1b7119aff7a4da634cd07ebb3b9b
diff --git a/system/dev/usb/usb-hub/binding.c b/system/dev/usb/usb-hub/binding.c
new file mode 100644
index 0000000..e076677
--- /dev/null
+++ b/system/dev/usb/usb-hub/binding.c
@@ -0,0 +1,21 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddk/device.h>
+#include <ddk/driver.h>
+#include <ddk/binding.h>
+
+#include <zircon/hw/usb.h>
+
+extern zx_status_t usb_hub_bind(void* ctx, zx_device_t* device);
+
+static zx_driver_ops_t usb_hub_driver_ops = {
+ .version = DRIVER_OPS_VERSION,
+ .bind = usb_hub_bind,
+};
+
+ZIRCON_DRIVER_BEGIN(usb_hub, usb_hub_driver_ops, "zircon", "0.1", 2)
+ BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB),
+ BI_MATCH_IF(EQ, BIND_USB_CLASS, USB_CLASS_HUB),
+ZIRCON_DRIVER_END(usb_hub)
diff --git a/system/dev/usb/usb-hub/rules.mk b/system/dev/usb/usb-hub/rules.mk
index f3731c3..fbc399b 100644
--- a/system/dev/usb/usb-hub/rules.mk
+++ b/system/dev/usb/usb-hub/rules.mk
@@ -8,10 +8,22 @@
MODULE_TYPE := driver
-MODULE_SRCS := $(LOCAL_DIR)/usb-hub.c
+MODULE_SRCS := \
+ $(LOCAL_DIR)/binding.c \
+ $(LOCAL_DIR)/usb-hub.cpp \
+
-MODULE_STATIC_LIBS := system/ulib/ddk system/ulib/sync
+MODULE_STATIC_LIBS := \
+ system/ulib/ddk \
+ system/ulib/ddktl \
+ system/ulib/zx \
+ system/ulib/zxcpp \
+ system/ulib/fbl \
+ system/ulib/sync \
-MODULE_LIBS := system/ulib/driver system/ulib/zircon system/ulib/c
+MODULE_LIBS := \
+ system/ulib/driver \
+ system/ulib/zircon \
+ system/ulib/c \
include make/module.mk
diff --git a/system/dev/usb/usb-hub/usb-hub.c b/system/dev/usb/usb-hub/usb-hub.c
deleted file mode 100644
index 5d2ea6b..0000000
--- a/system/dev/usb/usb-hub/usb-hub.c
+++ /dev/null
@@ -1,480 +0,0 @@
-// Copyright 2016 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <ddk/binding.h>
-#include <ddk/debug.h>
-#include <ddk/device.h>
-#include <ddk/driver.h>
-#include <ddk/protocol/usb.h>
-#include <ddk/protocol/usb-bus.h>
-#include <ddk/usb-request.h>
-#include <driver/usb.h>
-#include <zircon/hw/usb-hub.h>
-#include <sync/completion.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <zircon/listnode.h>
-#include <threads.h>
-#include <unistd.h>
-
-// usb_port_status_t.wPortStatus
-typedef uint16_t port_status_t;
-
-typedef struct usb_hub {
- // the device we are publishing
- zx_device_t* zxdev;
-
- // Underlying USB device
- zx_device_t* usb_device;
- usb_protocol_t usb;
-
- zx_device_t* bus_device;
- usb_bus_protocol_t bus;
-
- usb_speed_t hub_speed;
- int num_ports;
- // delay after port power in microseconds
- zx_time_t power_on_delay;
-
- usb_request_t* status_request;
- completion_t completion;
-
- thrd_t thread;
- bool thread_done;
-
- // port status values for our ports
- // length is num_ports
- port_status_t* port_status;
-
- // bit field indicating which ports have devices attached
- uint8_t attached_ports[128 / 8];
-} usb_hub_t;
-
-inline bool usb_hub_is_port_attached(usb_hub_t* hub, int port) {
- return (hub->attached_ports[port / 8] & (1 << (port % 8))) != 0;
-}
-
-inline void usb_hub_set_port_attached(usb_hub_t* hub, int port, bool enabled) {
- if (enabled) {
- hub->attached_ports[port / 8] |= (1 << (port % 8));
- } else {
- hub->attached_ports[port / 8] &= ~(1 << (port % 8));
- }
-}
-
-static zx_status_t usb_hub_get_port_status(usb_hub_t* hub, int port, port_status_t* out_status) {
- usb_port_status_t status;
-
- size_t out_length;
- zx_status_t result = usb_get_status(&hub->usb, USB_RECIP_PORT, port, &status, sizeof(status),
- ZX_TIME_INFINITE, &out_length);
- if (result != ZX_OK) {
- return result;
- }
- if (out_length != sizeof(status)) {
- return ZX_ERR_BAD_STATE;
- }
-
- zxlogf(TRACE, "usb_hub_get_port_status port %d ", port);
-
- uint16_t port_change = status.wPortChange;
- if (port_change & USB_C_PORT_CONNECTION) {
- zxlogf(TRACE, "USB_C_PORT_CONNECTION ");
- usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_CONNECTION, port,
- ZX_TIME_INFINITE);
- }
- if (port_change & USB_C_PORT_ENABLE) {
- zxlogf(TRACE, "USB_C_PORT_ENABLE ");
- usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_ENABLE, port,
- ZX_TIME_INFINITE);
- }
- if (port_change & USB_C_PORT_SUSPEND) {
- zxlogf(TRACE, "USB_C_PORT_SUSPEND ");
- usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_SUSPEND, port,
- ZX_TIME_INFINITE);
- }
- if (port_change & USB_C_PORT_OVER_CURRENT) {
- zxlogf(TRACE, "USB_C_PORT_OVER_CURRENT ");
- usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_OVER_CURRENT, port,
- ZX_TIME_INFINITE);
- }
- if (port_change & USB_C_PORT_RESET) {
- zxlogf(TRACE, "USB_C_PORT_RESET");
- usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_RESET, port,
- ZX_TIME_INFINITE);
- }
- if (port_change & USB_C_BH_PORT_RESET) {
- zxlogf(TRACE, "USB_C_BH_PORT_RESET");
- usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_BH_PORT_RESET, port,
- ZX_TIME_INFINITE);
- }
- if (port_change & USB_C_PORT_LINK_STATE) {
- zxlogf(TRACE, "USB_C_PORT_LINK_STATE");
- usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_LINK_STATE, port,
- ZX_TIME_INFINITE);
- }
- if (port_change & USB_C_PORT_CONFIG_ERROR) {
- zxlogf(TRACE, "USB_C_PORT_CONFIG_ERROR");
- usb_clear_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_C_PORT_CONFIG_ERROR, port,
- ZX_TIME_INFINITE);
- }
- zxlogf(TRACE, "\n");
-
- *out_status = status.wPortStatus;
- return ZX_OK;
-}
-
-static zx_status_t usb_hub_wait_for_port(usb_hub_t* hub, int port, port_status_t* out_status,
- port_status_t status_bits, port_status_t status_mask,
- zx_time_t stable_time) {
- const zx_time_t timeout = ZX_SEC(2); // 2 second total timeout
- const zx_time_t poll_delay = ZX_MSEC(25); // poll every 25 milliseconds
- zx_time_t total = 0;
- zx_time_t stable = 0;
-
- while (total < timeout) {
- zx_nanosleep(zx_deadline_after(poll_delay));
- total += poll_delay;
-
- zx_status_t result = usb_hub_get_port_status(hub, port, out_status);
- if (result != ZX_OK) {
- return result;
- }
- hub->port_status[port] = *out_status;
-
- if ((*out_status & status_mask) == status_bits) {
- stable += poll_delay;
- if (stable >= stable_time) {
- return ZX_OK;
- }
- } else {
- stable = 0;
- }
- }
-
- return ZX_ERR_TIMED_OUT;
-}
-
-static void usb_hub_interrupt_complete(usb_request_t* req, void* cookie) {
- zxlogf(TRACE, "usb_hub_interrupt_complete got %d %" PRIu64 "\n", req->response.status, req->response.actual);
- usb_hub_t* hub = (usb_hub_t*)cookie;
- completion_signal(&hub->completion);
-}
-
-static void usb_hub_power_on_port(usb_hub_t* hub, int port) {
- usb_set_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_PORT_POWER, port, ZX_TIME_INFINITE);
- usleep(hub->power_on_delay);
-}
-
-static void usb_hub_port_enabled(usb_hub_t* hub, int port) {
- port_status_t status;
-
- zxlogf(TRACE, "port %d usb_hub_port_enabled\n", port);
-
- // USB 2.0 spec section 9.1.2 recommends 100ms delay before enumerating
- // wait for USB_PORT_ENABLE == 1 and USB_PORT_RESET == 0
- if (usb_hub_wait_for_port(hub, port, &status, USB_PORT_ENABLE, USB_PORT_ENABLE | USB_PORT_RESET,
- ZX_MSEC(100)) != ZX_OK) {
- zxlogf(ERROR, "usb_hub_wait_for_port USB_PORT_RESET failed for USB hub, port %d\n", port);
- return;
- }
-
- usb_speed_t speed;
- if (hub->hub_speed == USB_SPEED_SUPER) {
- speed = USB_SPEED_SUPER;
- } else if (status & USB_PORT_LOW_SPEED) {
- speed = USB_SPEED_LOW;
- } else if (status & USB_PORT_HIGH_SPEED) {
- speed = USB_SPEED_HIGH;
- } else {
- speed = USB_SPEED_FULL;
- }
-
- zxlogf(TRACE, "call hub_device_added for port %d\n", port);
- usb_bus_hub_device_added(&hub->bus, hub->usb_device, port, speed);
- usb_hub_set_port_attached(hub, port, true);
-}
-
-static void usb_hub_port_connected(usb_hub_t* hub, int port) {
- port_status_t status;
-
- zxlogf(TRACE, "port %d usb_hub_port_connected\n", port);
-
- // USB 2.0 spec section 7.1.7.3 recommends 100ms between connect and reset
- if (usb_hub_wait_for_port(hub, port, &status, USB_PORT_CONNECTION, USB_PORT_CONNECTION,
- ZX_MSEC(100)) != ZX_OK) {
- zxlogf(ERROR, "usb_hub_wait_for_port USB_PORT_CONNECTION failed for USB hub, port %d\n", port);
- return;
- }
-
- usb_set_feature(&hub->usb, USB_RECIP_PORT, USB_FEATURE_PORT_RESET, port, ZX_TIME_INFINITE);
- usb_hub_port_enabled(hub, port);
-}
-
-static void usb_hub_port_disconnected(usb_hub_t* hub, int port) {
- zxlogf(TRACE, "port %d usb_hub_port_disconnected\n", port);
- usb_bus_hub_device_removed(&hub->bus, hub->usb_device, port);
- usb_hub_set_port_attached(hub, port, false);
-}
-
-static void usb_hub_handle_port_status(usb_hub_t* hub, int port, port_status_t status) {
- port_status_t old_status = hub->port_status[port];
-
- zxlogf(TRACE, "usb_hub_handle_port_status port: %d status: %04X old_status: %04X\n", port, status,
- old_status);
-
- hub->port_status[port] = status;
-
- if ((status & USB_PORT_CONNECTION) && !(status & USB_PORT_ENABLE)) {
- // Handle race condition where device is quickly disconnected and reconnected.
- // This happens when Android devices switch USB configurations.
- // In this case, any change to the connect state should trigger a disconnect
- // before handling a connect event.
- if (usb_hub_is_port_attached(hub, port)) {
- usb_hub_port_disconnected(hub, port);
- old_status &= ~USB_PORT_CONNECTION;
- }
- }
- if ((status & USB_PORT_CONNECTION) && !(old_status & USB_PORT_CONNECTION)) {
- usb_hub_port_connected(hub, port);
- } else if (!(status & USB_PORT_CONNECTION) && (old_status & USB_PORT_CONNECTION)) {
- usb_hub_port_disconnected(hub, port);
- } else if ((status & USB_PORT_ENABLE) && !(old_status & USB_PORT_ENABLE)) {
- usb_hub_port_enabled(hub, port);
- }
-}
-
-static void usb_hub_unbind(void* ctx) {
- usb_hub_t* hub = ctx;
- for (int port = 1; port <= hub->num_ports; port++) {
- if (usb_hub_is_port_attached(hub, port)) {
- usb_hub_port_disconnected(hub, port);
- }
- }
- device_remove(hub->zxdev);
-}
-
-static zx_status_t usb_hub_free(usb_hub_t* hub) {
- usb_request_release(hub->status_request);
- free(hub->port_status);
- free(hub);
- return ZX_OK;
-}
-
-static void usb_hub_release(void* ctx) {
- usb_hub_t* hub = ctx;
-
- hub->thread_done = true;
- completion_signal(&hub->completion);
- thrd_join(hub->thread, NULL);
- usb_hub_free(hub);
-}
-
-static zx_protocol_device_t usb_hub_device_proto = {
- .version = DEVICE_OPS_VERSION,
- .unbind = usb_hub_unbind,
- .release = usb_hub_release,
-};
-
-static int usb_hub_thread(void* arg) {
- usb_hub_t* hub = (usb_hub_t*)arg;
- usb_request_t* req = hub->status_request;
-
- usb_hub_descriptor_t desc;
- size_t out_length;
- int desc_type = (hub->hub_speed == USB_SPEED_SUPER ? USB_HUB_DESC_TYPE_SS : USB_HUB_DESC_TYPE);
- zx_status_t result = usb_get_descriptor(&hub->usb, USB_TYPE_CLASS | USB_RECIP_DEVICE,
- desc_type, 0, &desc, sizeof(desc), ZX_TIME_INFINITE,
- &out_length);
- if (result < 0) {
- zxlogf(ERROR, "get hub descriptor failed: %d\n", result);
- goto fail;
- }
- // The length of the descriptor varies depending on whether it is USB 2.0 or 3.0,
- // and how many ports it has.
- size_t min_length = 7;
- size_t max_length = sizeof(desc);
- if (out_length < min_length || out_length > max_length) {
- zxlogf(ERROR, "get hub descriptor got length %lu, want length between %lu and %lu\n",
- out_length, min_length, max_length);
- result = ZX_ERR_BAD_STATE;
- goto fail;
- }
-
- result = usb_bus_configure_hub(&hub->bus, hub->usb_device, hub->hub_speed, &desc);
- if (result < 0) {
- zxlogf(ERROR, "configure_hub failed: %d\n", result);
- goto fail;
- }
-
- int num_ports = desc.bNbrPorts;
- hub->num_ports = num_ports;
- hub->port_status = calloc(num_ports + 1, sizeof(port_status_t));
- if (!hub->port_status) {
- result = ZX_ERR_NO_MEMORY;
- goto fail;
- }
-
- // power on delay in microseconds
- hub->power_on_delay = desc.bPowerOn2PwrGood * 2 * 1000;
- if (hub->power_on_delay < 100 * 1000) {
- // USB 2.0 spec section 9.1.2 recommends atleast 100ms delay after power on
- hub->power_on_delay = 100 * 1000;
- }
-
- for (int i = 1; i <= num_ports; i++) {
- usb_hub_power_on_port(hub, i);
- }
-
- device_make_visible(hub->zxdev);
-
- // bit field for port status bits
- uint8_t status_buf[128 / 8];
- memset(status_buf, 0, sizeof(status_buf));
-
- // This loop handles events from our interrupt endpoint
- while (1) {
- completion_reset(&hub->completion);
- usb_request_queue(&hub->usb, req);
- completion_wait(&hub->completion, ZX_TIME_INFINITE);
- if (req->response.status != ZX_OK || hub->thread_done) {
- break;
- }
-
- usb_request_copyfrom(req, status_buf, req->response.actual, 0);
- uint8_t* bitmap = status_buf;
- uint8_t* bitmap_end = bitmap + req->response.actual;
-
- // bit zero is hub status
- if (bitmap[0] & 1) {
- // what to do here?
- zxlogf(ERROR, "usb_hub_interrupt_complete hub status changed\n");
- }
-
- int port = 1;
- int bit = 1;
- while (bitmap < bitmap_end && port <= num_ports) {
- if (*bitmap & (1 << bit)) {
- port_status_t status;
- zx_status_t result = usb_hub_get_port_status(hub, port, &status);
- if (result == ZX_OK) {
- usb_hub_handle_port_status(hub, port, status);
- }
- }
- port++;
- if (++bit == 8) {
- bitmap++;
- bit = 0;
- }
- }
- }
-
- return ZX_OK;
-
-fail:
- device_remove(hub->zxdev);
- return result;
-}
-
-static zx_status_t usb_hub_bind(void* ctx, zx_device_t* device) {
- usb_protocol_t usb;
- zx_status_t status = device_get_protocol(device, ZX_PROTOCOL_USB, &usb);
- if (status != ZX_OK) {
- return status;
- }
-
- // search for the bus device
- zx_device_t* bus_device = device_get_parent(device);
- usb_bus_protocol_t bus = { NULL, NULL };
- while (bus_device != NULL && bus.ops == NULL) {
- if (device_get_protocol(bus_device, ZX_PROTOCOL_USB_BUS, &bus) == ZX_OK) {
- break;
- }
- bus_device = device_get_parent(bus_device);
- }
- if (!bus_device || !bus.ops) {
- zxlogf(ERROR, "usb_hub_bind could not find bus device\n");
- return ZX_ERR_NOT_SUPPORTED;
- }
-
- // find our interrupt endpoint
- usb_desc_iter_t iter;
- status = usb_desc_iter_init(&usb, &iter);
- if (status < 0) return status;
-
- usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, true);
- if (!intf || intf->bNumEndpoints != 1) {
- usb_desc_iter_release(&iter);
- return ZX_ERR_NOT_SUPPORTED;
- }
-
- uint8_t ep_addr = 0;
- uint16_t max_packet_size = 0;
- usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter);
- if (endp && usb_ep_type(endp) == USB_ENDPOINT_INTERRUPT) {
- ep_addr = endp->bEndpointAddress;
- max_packet_size = usb_ep_max_packet(endp);
- }
- usb_desc_iter_release(&iter);
-
- if (!ep_addr) {
- return ZX_ERR_NOT_SUPPORTED;
- }
-
- usb_hub_t* hub = calloc(1, sizeof(usb_hub_t));
- if (!hub) {
- zxlogf(ERROR, "Not enough memory for usb_hub_t.\n");
- return ZX_ERR_NO_MEMORY;
- }
-
- hub->usb_device = device;
- hub->hub_speed = usb_get_speed(&usb);
- hub->bus_device = bus_device;
- memcpy(&hub->usb, &usb, sizeof(usb_protocol_t));
- memcpy(&hub->bus, &bus, sizeof(usb_bus_protocol_t));
-
- usb_request_t* req;
- status = usb_request_alloc(&req, max_packet_size, ep_addr);
- if (status != ZX_OK) {
- usb_hub_free(hub);
- return status;
- }
- req->complete_cb = usb_hub_interrupt_complete;
- req->cookie = hub;
- hub->status_request = req;
-
- device_add_args_t args = {
- .version = DEVICE_ADD_ARGS_VERSION,
- .name = "usb-hub",
- .ctx = hub,
- .ops = &usb_hub_device_proto,
- .flags = DEVICE_ADD_NON_BINDABLE | DEVICE_ADD_INVISIBLE,
- };
-
- status = device_add(hub->usb_device, &args, &hub->zxdev);
- if (status != ZX_OK) {
- usb_hub_free(hub);
- return status;
- }
-
- int ret = thrd_create_with_name(&hub->thread, usb_hub_thread, hub, "usb_hub_thread");
- if (ret != thrd_success) {
- device_remove(hub->zxdev);
- return ZX_ERR_NO_MEMORY;
- }
-
- return ZX_OK;
-}
-
-static zx_driver_ops_t usb_hub_driver_ops = {
- .version = DRIVER_OPS_VERSION,
- .bind = usb_hub_bind,
-};
-
-ZIRCON_DRIVER_BEGIN(usb_hub, usb_hub_driver_ops, "zircon", "0.1", 2)
- BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB),
- BI_MATCH_IF(EQ, BIND_USB_CLASS, USB_CLASS_HUB),
-ZIRCON_DRIVER_END(usb_hub)
diff --git a/system/dev/usb/usb-hub/usb-hub.cpp b/system/dev/usb/usb-hub/usb-hub.cpp
new file mode 100644
index 0000000..e1bebfc
--- /dev/null
+++ b/system/dev/usb/usb-hub/usb-hub.cpp
@@ -0,0 +1,433 @@
+// Copyright 2016 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/driver.h>
+#include <ddk/protocol/usb.h>
+#include <ddk/protocol/usb-bus.h>
+#include <ddk/usb-request.h>
+#include <driver/usb.h>
+#include <zircon/hw/usb-hub.h>
+#include <zx/time.h>
+
+#include <sync/completion.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "usb-hub.h"
+
+namespace usb {
+namespace hub {
+
+UsbHub::UsbHub(zx_device_t* device) : ddk::Device<UsbHub, ddk::Unbindable>(device) {
+}
+
+UsbHub::~UsbHub() {
+ if (status_request_) {
+ usb_request_release(status_request_);
+ }
+ free(port_status_);
+}
+
+zx_status_t UsbHub::Init() {
+ zx_status_t status = device_get_protocol(parent_, ZX_PROTOCOL_USB,
+ reinterpret_cast<void*>(&usb_));
+ if (status != ZX_OK) {
+ return status;
+ }
+
+ // search for the bus device
+ zx_device_t* bus_device = device_get_parent(parent_);
+ while (bus_device && !bus_.ops) {
+ if (device_get_protocol(bus_device, ZX_PROTOCOL_USB_BUS, &bus_) == ZX_OK) {
+ break;
+ }
+ bus_device = device_get_parent(bus_device);
+ }
+ if (!bus_device || !bus_.ops) {
+ zxlogf(ERROR, "usb_hub_bind could not find bus device\n");
+ return ZX_ERR_NOT_SUPPORTED;
+ }
+
+ // find our interrupt endpoint
+ usb_desc_iter_t iter;
+ status = usb_desc_iter_init(&usb_, &iter);
+ if (status < 0) return status;
+
+ usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, true);
+ if (!intf || intf->bNumEndpoints != 1) {
+ usb_desc_iter_release(&iter);
+ return ZX_ERR_NOT_SUPPORTED;
+ }
+
+ uint8_t ep_addr = 0;
+ uint16_t max_packet_size = 0;
+ usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter);
+ if (endp && usb_ep_type(endp) == USB_ENDPOINT_INTERRUPT) {
+ ep_addr = endp->bEndpointAddress;
+ max_packet_size = usb_ep_max_packet(endp);
+ }
+ usb_desc_iter_release(&iter);
+
+ if (!ep_addr) {
+ return ZX_ERR_NOT_SUPPORTED;
+ }
+
+ hub_speed_ = usb_get_speed(&usb_);
+ bus_device_ = bus_device;
+
+ usb_request_t* req;
+ status = usb_request_alloc(&req, max_packet_size, ep_addr);
+ if (status != ZX_OK) {
+ return status;
+ }
+ req->complete_cb = InterruptComplete;
+ req->cookie = this;
+ status_request_ = req;
+
+ return ZX_OK;
+}
+
+zx_status_t UsbHub::StartThread() {
+ int ret = thrd_create_with_name(&thread_, ThreadEntry, reinterpret_cast<void*>(this),
+ "usb_hub_thread");
+ if (ret != thrd_success) {
+ return ZX_ERR_NO_MEMORY;
+ }
+ return ZX_OK;
+}
+
+int UsbHub::ThreadEntry(void* arg) {
+ UsbHub* hub = reinterpret_cast<UsbHub*>(arg);
+ hub->Thread();
+ return 0;
+}
+
+void UsbHub::Thread() {
+ usb_request_t* req = status_request_;
+ usb_hub_descriptor_t desc;
+ size_t out_length;
+ size_t min_length;
+ size_t max_length;
+
+ uint16_t desc_type = (hub_speed_ == USB_SPEED_SUPER ? USB_HUB_DESC_TYPE_SS : USB_HUB_DESC_TYPE);
+ zx_status_t result = usb_get_descriptor(&usb_, USB_TYPE_CLASS | USB_RECIP_DEVICE,
+ desc_type, 0, &desc, sizeof(desc), ZX_TIME_INFINITE,
+ &out_length);
+ if (result < 0) {
+ zxlogf(ERROR, "get hub descriptor failed: %d\n", result);
+ goto fail;
+ }
+ // The length of the descriptor varies depending on whether it is USB 2.0 or 3.0,
+ // and how many ports it has.
+ min_length = 7;
+ max_length = sizeof(desc);
+ if (out_length < min_length || out_length > max_length) {
+ zxlogf(ERROR, "get hub descriptor got length %lu, want length between %lu and %lu\n",
+ out_length, min_length, max_length);
+ result = ZX_ERR_BAD_STATE;
+ goto fail;
+ }
+
+ result = usb_bus_configure_hub(&bus_, parent_, hub_speed_, &desc);
+ if (result < 0) {
+ zxlogf(ERROR, "configure_hub failed: %d\n", result);
+ goto fail;
+ }
+
+ num_ports_ = desc.bNbrPorts;
+ port_status_ = (port_status_t *)calloc(num_ports_ + 1, sizeof(port_status_t));
+ if (!port_status_) {
+ result = ZX_ERR_NO_MEMORY;
+ goto fail;
+ }
+
+ // power on delay in microseconds
+ power_on_delay_ = desc.bPowerOn2PwrGood * 2 * 1000;
+ if (power_on_delay_ < 100 * 1000) {
+ // USB 2.0 spec section 9.1.2 recommends atleast 100ms delay after power on
+ power_on_delay_ = 100 * 1000;
+ }
+
+ for (port_t i = 1; i <= num_ports_; i++) {
+ PowerOnPort(i);
+ }
+
+ DdkMakeVisible();
+
+ // bit field for port status bits
+ uint8_t status_buf[128 / 8];
+ memset(status_buf, 0, sizeof(status_buf));
+
+ // This loop handles events from our interrupt endpoint
+ while (1) {
+ completion_reset(&completion_);
+ usb_request_queue(&usb_, req);
+ completion_wait(&completion_, ZX_TIME_INFINITE);
+ if (req->response.status != ZX_OK || thread_done_) {
+ break;
+ }
+
+ usb_request_copyfrom(req, status_buf, req->response.actual, 0);
+ uint8_t* bitmap = status_buf;
+ uint8_t* bitmap_end = bitmap + req->response.actual;
+
+ // bit zero is hub status
+ if (bitmap[0] & 1) {
+ // what to do here?
+ zxlogf(ERROR, "usb_hub_interrupt_complete hub status changed\n");
+ }
+
+ port_t port = 1;
+ int bit = 1;
+ while (bitmap < bitmap_end && port <= num_ports_) {
+ if (*bitmap & (1 << bit)) {
+ port_status_t status;
+ zx_status_t result = GetPortStatus(port, &status);
+ if (result == ZX_OK) {
+ HandlePortStatus(port, status);
+ }
+ }
+ port++;
+ if (++bit == 8) {
+ bitmap++;
+ bit = 0;
+ }
+ }
+ }
+
+ return;
+
+fail:
+ DdkRemove();
+}
+
+zx_status_t UsbHub::GetPortStatus(port_t port, port_status_t* out_status) {
+ usb_port_status_t status;
+
+ size_t out_length;
+ zx_status_t result = usb_get_status(&usb_, USB_RECIP_PORT, port, &status, sizeof(status),
+ ZX_TIME_INFINITE, &out_length);
+ if (result != ZX_OK) {
+ return result;
+ }
+ if (out_length != sizeof(status)) {
+ return ZX_ERR_BAD_STATE;
+ }
+
+ zxlogf(TRACE, "UsbHub::GetPortStatus port %d ", port);
+
+ uint16_t port_change = status.wPortChange;
+ if (port_change & USB_C_PORT_CONNECTION) {
+ zxlogf(TRACE, "USB_C_PORT_CONNECTION ");
+ usb_clear_feature(&usb_, USB_RECIP_PORT, USB_FEATURE_C_PORT_CONNECTION, port,
+ ZX_TIME_INFINITE);
+ }
+ if (port_change & USB_C_PORT_ENABLE) {
+ zxlogf(TRACE, "USB_C_PORT_ENABLE ");
+ usb_clear_feature(&usb_, USB_RECIP_PORT, USB_FEATURE_C_PORT_ENABLE, port,
+ ZX_TIME_INFINITE);
+ }
+ if (port_change & USB_C_PORT_SUSPEND) {
+ zxlogf(TRACE, "USB_C_PORT_SUSPEND ");
+ usb_clear_feature(&usb_, USB_RECIP_PORT, USB_FEATURE_C_PORT_SUSPEND, port,
+ ZX_TIME_INFINITE);
+ }
+ if (port_change & USB_C_PORT_OVER_CURRENT) {
+ zxlogf(TRACE, "USB_C_PORT_OVER_CURRENT ");
+ usb_clear_feature(&usb_, USB_RECIP_PORT, USB_FEATURE_C_PORT_OVER_CURRENT, port,
+ ZX_TIME_INFINITE);
+ }
+ if (port_change & USB_C_PORT_RESET) {
+ zxlogf(TRACE, "USB_C_PORT_RESET");
+ usb_clear_feature(&usb_, USB_RECIP_PORT, USB_FEATURE_C_PORT_RESET, port,
+ ZX_TIME_INFINITE);
+ }
+ if (port_change & USB_C_BH_PORT_RESET) {
+ zxlogf(TRACE, "USB_C_BH_PORT_RESET");
+ usb_clear_feature(&usb_, USB_RECIP_PORT, USB_FEATURE_C_BH_PORT_RESET, port,
+ ZX_TIME_INFINITE);
+ }
+ if (port_change & USB_C_PORT_LINK_STATE) {
+ zxlogf(TRACE, "USB_C_PORT_LINK_STATE");
+ usb_clear_feature(&usb_, USB_RECIP_PORT, USB_FEATURE_C_PORT_LINK_STATE, port,
+ ZX_TIME_INFINITE);
+ }
+ if (port_change & USB_C_PORT_CONFIG_ERROR) {
+ zxlogf(TRACE, "USB_C_PORT_CONFIG_ERROR");
+ usb_clear_feature(&usb_, USB_RECIP_PORT, USB_FEATURE_C_PORT_CONFIG_ERROR, port,
+ ZX_TIME_INFINITE);
+ }
+ zxlogf(TRACE, "\n");
+
+ *out_status = status.wPortStatus;
+ return ZX_OK;
+}
+
+zx_status_t UsbHub::WaitForPort(port_t port, port_status_t* out_status, port_status_t status_bits,
+ port_status_t status_mask, zx_time_t stable_time) {
+ const zx_time_t timeout = ZX_SEC(2); // 2 second total timeout
+ const zx_time_t poll_delay = ZX_MSEC(25); // poll every 25 milliseconds
+ zx_time_t total = 0;
+ zx_time_t stable = 0;
+
+ while (total < timeout) {
+ zx_nanosleep(zx_deadline_after(poll_delay));
+ total += poll_delay;
+
+ zx_status_t result = GetPortStatus(port, out_status);
+ if (result != ZX_OK) {
+ return result;
+ }
+ port_status_[port] = *out_status;
+
+ if ((*out_status & status_mask) == status_bits) {
+ stable += poll_delay;
+ if (stable >= stable_time) {
+ return ZX_OK;
+ }
+ } else {
+ stable = 0;
+ }
+ }
+
+ return ZX_ERR_TIMED_OUT;
+}
+
+void UsbHub::InterruptComplete(usb_request_t* request, void* cookie) {
+ zxlogf(TRACE, "UsbHub::InterruptComplete got %d %" PRIu64 "\n", request->response.status,
+ request->response.actual);
+ auto hub = static_cast<UsbHub*>(cookie);
+ completion_signal(&hub->completion_);
+}
+
+void UsbHub::PowerOnPort(port_t port) {
+ usb_set_feature(&usb_, USB_RECIP_PORT, USB_FEATURE_PORT_POWER, port, ZX_TIME_INFINITE);
+
+ zx::nanosleep(zx::deadline_after(ZX_USEC(power_on_delay_)));
+}
+
+void UsbHub::PortEnabled(port_t port) {
+ port_status_t status;
+
+ zxlogf(TRACE, "UsbHub::PortEnabled: port %u\n", port);
+
+ // USB 2.0 spec section 9.1.2 recommends 100ms delay before enumerating
+ // wait for USB_PORT_ENABLE == 1 and USB_PORT_RESET == 0
+ if (WaitForPort(port, &status, USB_PORT_ENABLE, USB_PORT_ENABLE | USB_PORT_RESET,
+ ZX_MSEC(100)) != ZX_OK) {
+ zxlogf(ERROR, "UsbHub::WaitForPort USB_PORT_RESET failed for USB hub, port %d\n", port);
+ return;
+ }
+
+ usb_speed_t speed;
+ if (hub_speed_ == USB_SPEED_SUPER) {
+ speed = USB_SPEED_SUPER;
+ } else if (status & USB_PORT_LOW_SPEED) {
+ speed = USB_SPEED_LOW;
+ } else if (status & USB_PORT_HIGH_SPEED) {
+ speed = USB_SPEED_HIGH;
+ } else {
+ speed = USB_SPEED_FULL;
+ }
+
+ zxlogf(TRACE, "call hub_device_added for port %d\n", port);
+ usb_bus_hub_device_added(&bus_, parent_, port, speed);
+ SetPortAttached(port, true);
+}
+
+void UsbHub::PortConnected(port_t port) {
+ port_status_t status;
+
+ zxlogf(TRACE, "port %d UsbHub::PortConnected\n", port);
+
+ // USB 2.0 spec section 7.1.7.3 recommends 100ms between connect and reset
+ if (WaitForPort(port, &status, USB_PORT_CONNECTION, USB_PORT_CONNECTION,
+ ZX_MSEC(100)) != ZX_OK) {
+ zxlogf(ERROR, "usb_hub_wait_for_port USB_PORT_CONNECTION failed for USB hub, port %d\n", port);
+ return;
+ }
+
+ usb_set_feature(&usb_, USB_RECIP_PORT, USB_FEATURE_PORT_RESET, port, ZX_TIME_INFINITE);
+ PortEnabled(port);
+}
+
+void UsbHub::PortDisconnected(port_t port) {
+ zxlogf(TRACE, "port %d UsbHub::PortDisconnected\n", port);
+ usb_bus_hub_device_removed(&bus_, parent_, port);
+ SetPortAttached(port, false);
+}
+
+void UsbHub::HandlePortStatus(port_t port, port_status_t status) {
+ port_status_t old_status = port_status_[port];
+
+ zxlogf(TRACE, "usb_hub_handle_port_status port: %d status: %04X old_status: %04X\n", port, status,
+ old_status);
+
+ port_status_[port] = status;
+
+ if ((status & USB_PORT_CONNECTION) && !(status & USB_PORT_ENABLE)) {
+ // Handle race condition where device is quickly disconnected and reconnected.
+ // This happens when Android devices switch USB configurations.
+ // In this case, any change to the connect state should trigger a disconnect
+ // before handling a connect event.
+ if (IsPortAttached(port)) {
+ PortDisconnected(port);
+ old_status &= (port_status_t)~USB_PORT_CONNECTION;
+ }
+ }
+ if ((status & USB_PORT_CONNECTION) && !(old_status & USB_PORT_CONNECTION)) {
+ PortConnected(port);
+ } else if (!(status & USB_PORT_CONNECTION) && (old_status & USB_PORT_CONNECTION)) {
+ PortDisconnected(port);
+ } else if ((status & USB_PORT_ENABLE) && !(old_status & USB_PORT_ENABLE)) {
+ PortEnabled(port);
+ }
+}
+
+void UsbHub::DdkUnbind() {
+ for (port_t port = 1; port <= num_ports_; port++) {
+ if (IsPortAttached(port)) {
+ PortDisconnected(port);
+ }
+ }
+ DdkRemove();
+}
+
+void UsbHub::DdkRelease() {
+ thread_done_ = true;
+ completion_signal(&completion_);
+ thrd_join(thread_, nullptr);
+ delete this;
+}
+
+} // namespace hub
+} // namespace usb
+
+extern "C" zx_status_t usb_hub_bind(void* ctx, zx_device_t* device) {
+ auto dev = fbl::unique_ptr<usb::hub::UsbHub>(new usb::hub::UsbHub(device));
+ zx_status_t status = dev->Init();
+ if (status != ZX_OK) {
+ return status;
+ }
+
+ status = dev->DdkAdd("usb-hub", DEVICE_ADD_NON_BINDABLE | DEVICE_ADD_INVISIBLE);
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "%s: could not add device: %d\n", __func__, status);
+ return status;
+ }
+
+ status = dev->StartThread();
+ if (status != ZX_OK) {
+ dev->DdkRemove();
+ }
+
+ // devmgr owns the memory now
+ dev.release();
+ return status;
+}
diff --git a/system/dev/usb/usb-hub/usb-hub.h b/system/dev/usb/usb-hub/usb-hub.h
new file mode 100644
index 0000000..bef364d
--- /dev/null
+++ b/system/dev/usb/usb-hub/usb-hub.h
@@ -0,0 +1,87 @@
+// Copyright 2017 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <ddk/device.h>
+#include <ddktl/device.h>
+#include <ddktl/protocol/hidbus.h>
+#include <fbl/mutex.h>
+#include <fbl/unique_ptr.h>
+#include <threads.h>
+#include <zircon/compiler.h>
+#include <zircon/device/hidctl.h>
+#include <zircon/types.h>
+#include <zx/socket.h>
+
+namespace usb {
+namespace hub {
+
+class UsbHub : public ddk::Device<UsbHub, ddk::Unbindable> {
+ public:
+ UsbHub(zx_device_t* device);
+ virtual ~UsbHub();
+ zx_status_t Init();
+ zx_status_t StartThread();
+
+ void DdkRelease();
+ void DdkUnbind();
+
+ private:
+ typedef uint16_t port_t;
+ typedef uint16_t port_status_t;
+ static constexpr port_t kMaxPort = 128;
+
+ static int ThreadEntry(void* arg);
+ void Thread();
+
+ static void InterruptComplete(usb_request_t* request, void* cookie);
+
+ zx_status_t GetPortStatus(port_t port, port_status_t* out_status);
+ zx_status_t WaitForPort(port_t port, port_status_t* out_status, port_status_t status_bits,
+ port_status_t status_mask, zx_time_t stable_time);
+ void PowerOnPort(port_t port);
+ void PortEnabled(port_t port);
+ void PortConnected(port_t port);
+ void PortDisconnected(port_t port);
+ void HandlePortStatus(port_t port, port_status_t status);
+
+ inline bool IsPortAttached(int port) {
+ return (attached_ports_[port / 8] & (1 << (port % 8))) != 0;
+ }
+
+ inline void SetPortAttached(int port, bool attached) {
+ if (attached) {
+ attached_ports_[port / 8] |= (uint8_t)(1 << (port % 8));
+ } else {
+ attached_ports_[port / 8] &= (uint8_t)(~(1 << (port % 8)));
+ }
+ }
+
+ usb_protocol_t usb_;
+
+ zx_device_t* bus_device_;
+ usb_bus_protocol_t bus_;
+
+ usb_speed_t hub_speed_;
+ port_t num_ports_;
+ // delay after port power in microseconds
+ zx_time_t power_on_delay_;
+
+ usb_request_t* status_request_;
+ completion_t completion_;
+
+ thrd_t thread_;
+ bool thread_done_;
+
+ // port status values for our ports
+ // length is num_ports
+ port_status_t* port_status_;
+
+ // bit field indicating which ports have devices attached
+ uint8_t attached_ports_[kMaxPort / 8];
+};
+
+} // namespace hub
+} // namespace usb