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