diff --git a/system/banjo/ddk-protocol-usb-bus/usb-bus.banjo b/system/banjo/ddk-protocol-usb-bus/usb-bus.banjo
index c52d8ea8cc267a231db47cc4c3a4e5ba9bbb2fe5..f43812b1528b3429f7f984ead4be5ccaa48f20bb 100644
--- a/system/banjo/ddk-protocol-usb-bus/usb-bus.banjo
+++ b/system/banjo/ddk-protocol-usb-bus/usb-bus.banjo
@@ -22,6 +22,8 @@ interface UsbBus {
     DeviceAdded(ddk.driver.ZxDevice? hub_device, uint32 port, UsbSpeed speed) -> (zx.status s);
     /// Informs the USB bus that a device has been removed.
     DeviceRemoved(ddk.driver.ZxDevice? hub_device, uint32 port) -> (zx.status s);
+    /// Informs the USB bus that a device has been reset.
+    DeviceReset(ddk.driver.ZxDevice? hub_device, uint32 port) -> (zx.status s);
     /// Used by USB hub driver to register its USB hub protocol with the USB bus driver.
     SetHubInterface(ddk.driver.ZxDevice? usb_device, ddk.protocol.usb.hub.UsbHubInterface hub)
 -> (zx.status s);
@@ -36,4 +38,6 @@ interface UsbBusInterface {
     RemoveDevice(uint32 device_id) -> (zx.status s);
     /// Used by the HCI controller to reset a port on a USB hub.
     ResetPort(uint32 hub_id, uint32 port) -> (zx.status s);
+    /// Used by the HCI controller to reinitialize a device after it has been reset.
+    ReinitializeDevice(uint32 device_id) -> (zx.status s);
 };
diff --git a/system/banjo/ddk-protocol-usb-hci/usb-hci.banjo b/system/banjo/ddk-protocol-usb-hci/usb-hci.banjo
index b93a02b54d96c2ac32df41c2e72d30d9b0a40ac8..3e1829fa7f3e5b414d0ec405fe43d63158b88c3b 100644
--- a/system/banjo/ddk-protocol-usb-hci/usb-hci.banjo
+++ b/system/banjo/ddk-protocol-usb-hci/usb-hci.banjo
@@ -31,8 +31,12 @@ interface UsbHci {
     HubDeviceAdded(uint32 device_id, uint32 port, zircon.hw.usb.UsbSpeed speed) -> (zx.status s);
     /// Used by the USB hub driver to notify the HCI driver when a device has been removed.
     HubDeviceRemoved(uint32 device_id, uint32 port) -> (zx.status s);
+    /// Used by the USB hub driver to notify the HCI driver when a device has been reset.
+    HubDeviceReset(uint32 device_id, uint32 port) -> (zx.status s);
     /// Resets an endpoint on the specified device.
     ResetEndpoint(uint32 device_id, uint8 ep_address) -> (zx.status s);
+    /// Resets the specified device.
+    ResetDevice(uint32 hub_address, uint32 device_id) -> (zx.status s);
     /// Returns the maximum size of a packet that can be queued on the specified endpoint.
     GetMaxTransferSize(uint32 device_id, uint8 ep_address) -> (usize size);
     /// Cancels all transactions currently queued on the specified endpoint.
diff --git a/system/banjo/ddk-protocol-usb/usb.banjo b/system/banjo/ddk-protocol-usb/usb.banjo
index 1c2999dda3ea0793f18848623a191446096d5f2a..6e222c1b524d679586801f9d9b6a4dcd3cf2a4fe 100644
--- a/system/banjo/ddk-protocol-usb/usb.banjo
+++ b/system/banjo/ddk-protocol-usb/usb.banjo
@@ -58,6 +58,10 @@ interface Usb {
     /// usb_reset_endpoint() the endpoint to normal running state.
     ResetEndpoint(uint8 ep_address) -> (zx.status s);
 
+    /// Resets the device and restores the prior configuration.
+    /// Returns ZX_ERR_BAD_STATE if the device is already being reset.
+    ResetDevice() -> (zx.status s);
+
     /// Returns the maximum amount of data that can be transferred on an endpoint in a single
     /// transaction.
     GetMaxTransferSize(uint8 ep_address) -> (usize s);
diff --git a/system/dev/usb/usb-bus/usb-bus.c b/system/dev/usb/usb-bus/usb-bus.c
index 1d794ed9fb6607c70e82f85e690751f4d46c4ee9..18f2427085d3dfa39fd4b63a78620a5f23fb433a 100644
--- a/system/dev/usb/usb-bus/usb-bus.c
+++ b/system/dev/usb/usb-bus/usb-bus.c
@@ -11,6 +11,7 @@
 
 #include "usb-bus.h"
 #include "usb-device.h"
+#include "util.h"
 
 static zx_status_t bus_add_device(void* ctx, uint32_t device_id, uint32_t hub_id,
                                       usb_speed_t speed) {
@@ -58,10 +59,46 @@ static zx_status_t bus_reset_hub_port(void* ctx, uint32_t hub_id, uint32_t port)
     return usb_hub_interface_reset_port(&device->hub_intf, port);
 }
 
+static zx_status_t bus_reinitialize_device(void* ctx, uint32_t device_id) {
+    usb_bus_t* bus = ctx;
+    usb_device_t* device = bus->devices[device_id];
+    if (!device) {
+        zxlogf(ERROR, "could not find device %u\n", device_id);
+        return ZX_ERR_INTERNAL;
+    }
+    // Check if the USB device descriptor changed, in which case we need to force the device to
+    // re-enumerate so we can load the uploaded device driver.
+    // This can happen during a Device Firmware Upgrade.
+    usb_device_descriptor_t updated_desc;
+    size_t actual;
+    zx_status_t status = usb_util_get_descriptor(device, USB_DT_DEVICE, 0, 0, &updated_desc,
+                                                 sizeof(updated_desc), &actual);
+    if (status != ZX_OK || actual != sizeof(updated_desc)) {
+        zxlogf(ERROR, "could not get updated descriptor: %d got len %lu\n", status, actual);
+        // We should try reinitializing the device anyway.
+        goto done;
+    }
+    // TODO(jocelyndang): we may want to check other descriptors as well.
+    bool descriptors_changed = memcmp(&device->device_desc, &updated_desc,
+                                      sizeof(usb_device_descriptor_t)) != 0;
+    if (descriptors_changed) {
+        zxlogf(INFO, "device updated from VID 0x%x PID 0x%x to VID 0x%x PID 0x%x\n",
+               device->device_desc.idVendor, device->device_desc.idProduct,
+               updated_desc.idVendor, updated_desc.idProduct);
+        // TODO(jocelyndang): handle this.
+    }
+
+done:
+    return usb_device_reinitialize(device);
+
+    // TODO(jocelyndang): should we notify the interfaces that the device has been reset?
+}
+
 static usb_bus_interface_ops_t _bus_interface = {
     .add_device = bus_add_device,
     .remove_device = bus_remove_device,
     .reset_port = bus_reset_hub_port,
+    .reinitialize_device = bus_reinitialize_device,
 };
 
 static zx_status_t bus_get_device_id(zx_device_t* device, uint32_t* out) {
@@ -102,6 +139,19 @@ static zx_status_t bus_device_removed(void* ctx, zx_device_t* hub_device, uint32
     return usb_hci_hub_device_removed(&bus->hci, hub_id, port);
 }
 
+static zx_status_t bus_device_reset(void* ctx, zx_device_t* hub_device, uint32_t port) {
+    usb_bus_t* bus = ctx;
+    uint32_t hub_id;
+    if (bus_get_device_id(hub_device, &hub_id) != ZX_OK) {
+        return ZX_ERR_INTERNAL;
+    }
+    zx_status_t status = usb_hci_hub_device_reset(&bus->hci, hub_id, port);
+    if (status != ZX_OK) {
+        return status;
+    }
+    return ZX_OK;
+}
+
 static zx_status_t bus_set_hub_interface(void* ctx, zx_device_t* usb_device,
                                          const usb_hub_interface_t* hub) {
     usb_bus_t* bus = ctx;
@@ -116,13 +166,14 @@ static zx_status_t bus_set_hub_interface(void* ctx, zx_device_t* usb_device,
     }
 
     zxlogf(ERROR, "bus_set_hub_interface: no device for usb_device_id %u\n", usb_device_id);
-    return ZX_ERR_INTERNAL;   
+    return ZX_ERR_INTERNAL;
 }
 
 static usb_bus_protocol_ops_t _bus_protocol = {
     .configure_hub = bus_configure_hub,
     .device_added = bus_device_added,
     .device_removed = bus_device_removed,
+    .device_reset = bus_device_reset,
     .set_hub_interface = bus_set_hub_interface,
 };
 
diff --git a/system/dev/usb/usb-bus/usb-device.c b/system/dev/usb/usb-bus/usb-device.c
index 13370bd1d4428b0fd4beaa9731b95ed6c315254a..2696a47db825018f47f29597b3545f035f35e279 100644
--- a/system/dev/usb/usb-bus/usb-device.c
+++ b/system/dev/usb/usb-bus/usb-device.c
@@ -5,6 +5,7 @@
 #include <ddk/debug.h>
 #include <ddk/metadata.h>
 #include <ddk/protocol/usb.h>
+#include <ddk/protocol/usb/bus.h>
 #include <zircon/usb/device/c/fidl.h>
 #include <stdint.h>
 #include <stdlib.h>
@@ -357,6 +358,32 @@ static zx_status_t usb_device_reset_endpoint(void* ctx, uint8_t ep_address) {
     return usb_hci_reset_endpoint(&dev->hci, dev->device_id, ep_address);
 }
 
+static zx_status_t usb_device_update_state(usb_device_t* dev, bool resetting) {
+    mtx_lock(&dev->state_lock);
+    if (dev->resetting == resetting) {
+        zxlogf(ERROR, "usb_device_update_state: already had resetting = %d\n", resetting);
+        mtx_unlock(&dev->state_lock);
+        return ZX_ERR_BAD_STATE;
+    }
+    dev->resetting = resetting;
+    mtx_unlock(&dev->state_lock);
+    return ZX_OK;
+}
+
+static zx_status_t usb_device_reset_device(void* ctx) {
+    usb_device_t* dev = ctx;
+
+    zx_status_t status = usb_device_update_state(dev, /* resetting */ true);
+    if (status != ZX_OK) {
+        return status;
+    }
+    status = usb_hci_reset_device(&dev->hci, dev->hub_id, dev->device_id);
+    if (status != ZX_OK) {
+        return status;
+    }
+    return status;
+}
+
 static size_t usb_device_get_max_transfer_size(void* ctx, uint8_t ep_address) {
     usb_device_t* dev = ctx;
     return usb_hci_get_max_transfer_size(&dev->hci, dev->device_id, ep_address);
@@ -459,6 +486,7 @@ static usb_protocol_ops_t _usb_protocol = {
     .set_configuration = usb_device_set_configuration,
     .enable_endpoint = usb_device_enable_endpoint,
     .reset_endpoint = usb_device_reset_endpoint,
+    .reset_device = usb_device_reset_device,
     .get_max_transfer_size = usb_device_get_max_transfer_size,
     .get_device_id = _usb_device_get_device_id,
     .get_device_descriptor = usb_device_get_device_descriptor,
@@ -719,3 +747,19 @@ error_exit:
     free(dev);
     return status;
 }
+
+zx_status_t usb_device_reinitialize(usb_device_t* dev) {
+    zx_status_t status = usb_device_update_state(dev, /* resetting */ false);
+    if (status != ZX_OK) {
+        return status;
+    }
+    uint8_t config = dev->config_descs[dev->current_config_index]->bConfigurationValue;
+    status = usb_util_control(dev, USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
+                              USB_REQ_SET_CONFIGURATION, config, 0, NULL, 0, NULL);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "could not restore configuration to %u, got err: %d\n", config, status);
+        return status;
+    }
+    // TODO(jocelyndang): we should re-enable endpoints and restore alternate settings.
+    return ZX_OK;
+}
diff --git a/system/dev/usb/usb-bus/usb-device.h b/system/dev/usb/usb-bus/usb-device.h
index 4cb7fa979480c5010f5cc435cca88600f3af5954..b1e3ce1e0cd1fa06750dc8b9fe88e6750c600c62 100644
--- a/system/dev/usb/usb-bus/usb-device.h
+++ b/system/dev/usb/usb-bus/usb-device.h
@@ -41,6 +41,10 @@ typedef struct usb_device {
     atomic_bool langids_fetched;
     atomic_uintptr_t lang_ids;
 
+    bool resetting;
+    // mutex that protects the resetting state member
+    mtx_t state_lock;
+
     // thread for calling client's usb request complete callback
     thrd_t callback_thread;
     bool callback_thread_stop;
@@ -62,6 +66,8 @@ void usb_device_set_hub_interface(usb_device_t* dev, const usb_hub_interface_t*
 zx_status_t usb_device_add(usb_bus_t* bus, uint32_t device_id, uint32_t hub_id,
                            usb_speed_t speed, usb_device_t** out_device);
 
+zx_status_t usb_device_reinitialize(usb_device_t* dev);
+
 #define USB_REQ_TO_DEV_INTERNAL(req, size) \
     ((usb_device_req_internal_t *)((uintptr_t)(req) + (size)))
 #define DEV_INTERNAL_TO_USB_REQ(ctx, size) ((usb_request_t *)((uintptr_t)(ctx) - (size)))
diff --git a/system/dev/usb/usb-composite/usb-interface.c b/system/dev/usb/usb-composite/usb-interface.c
index 4ae95efe54584ae2c42ad0adfefddeaa7d23273d..5ec0b1b8e8c285f8298054417647df9c39e4501c 100644
--- a/system/dev/usb/usb-composite/usb-interface.c
+++ b/system/dev/usb/usb-composite/usb-interface.c
@@ -184,6 +184,11 @@ static zx_status_t usb_interface_reset_endpoint(void* ctx, uint8_t ep_address) {
     return usb_reset_endpoint(&intf->comp->usb, ep_address);
 }
 
+static zx_status_t usb_interface_reset_device(void* ctx) {
+    usb_interface_t* intf = ctx;
+    return usb_reset_device(&intf->comp->usb);
+}
+
 static size_t usb_interface_get_max_transfer_size(void* ctx, uint8_t ep_address) {
     usb_interface_t* intf = ctx;
     return usb_get_max_transfer_size(&intf->comp->usb, ep_address);
@@ -356,6 +361,7 @@ usb_protocol_ops_t usb_device_protocol = {
     .set_configuration = usb_interface_set_configuration,
     .enable_endpoint = usb_interface_enable_endpoint,
     .reset_endpoint = usb_interface_reset_endpoint,
+    .reset_device = usb_interface_reset_device,
     .get_max_transfer_size = usb_interface_get_max_transfer_size,
     .get_device_id = usb_interface_get_device_id,
     .get_device_descriptor = usb_interface_get_device_descriptor,
diff --git a/system/dev/usb/usb-dfu/usb-dfu.cpp b/system/dev/usb/usb-dfu/usb-dfu.cpp
index a106be6249a4cddf87e2b9889df85e5ca144ba7f..0a171a00afc43e23b53031ff85931d0ed731a9ca 100644
--- a/system/dev/usb/usb-dfu/usb-dfu.cpp
+++ b/system/dev/usb/usb-dfu/usb-dfu.cpp
@@ -199,9 +199,8 @@ zx_status_t Dfu::LoadFirmware(zx::vmo fw_vmo, size_t fw_size) {
         block_num++;
         vmo_offset += len_to_write;
     } while (len_to_write != 0);  // The device expects a zero length transfer to signify the end.
-    // TODO(jocelyndang): issue a USB Reset to enter Application Mode.
 
-    return ZX_OK;
+    return usb_reset_device(&usb_);
 }
 
 zx_status_t Dfu::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
diff --git a/system/dev/usb/usb-hub/usb-hub.c b/system/dev/usb/usb-hub/usb-hub.c
index fd4461cedef43a2e75888435b9462bd14d82a6a1..e80428507f33d0eb5a9181055148483f6e5f13dd 100644
--- a/system/dev/usb/usb-hub/usb-hub.c
+++ b/system/dev/usb/usb-hub/usb-hub.c
@@ -227,6 +227,11 @@ static zx_status_t usb_hub_port_reset(void* ctx, uint32_t port) {
     if (status != ZX_OK) {
         zxlogf(ERROR, "usb_hub_wait_for_port USB_PORT_RESET failed for USB hub, port %d\n", port);
     }
+    status = usb_bus_device_reset(&hub->bus, hub->usb_device, port);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "usb_bus_device_reset failed, err: %d\n", status);
+        return status;
+    }
     return status;
 }
 
diff --git a/system/dev/usb/xhci/usb-xhci.cpp b/system/dev/usb/xhci/usb-xhci.cpp
index 6c5fe8ef1c17130afab18c8948eb26048474879c..f6b3814442b2c9c75b0e2f3490ee04f575a9ada8 100644
--- a/system/dev/usb/xhci/usb-xhci.cpp
+++ b/system/dev/usb/xhci/usb-xhci.cpp
@@ -110,11 +110,31 @@ static zx_status_t xhci_hub_device_removed(void* ctx, uint32_t hub_address, uint
     return ZX_OK;
 }
 
+static zx_status_t xhci_hub_device_reset(void* ctx, uint32_t hub_address, uint32_t port) {
+    auto* xhci = static_cast<xhci_t*>(ctx);
+    return xhci_device_reset(xhci, hub_address, port);
+}
+
 static zx_status_t xhci_reset_ep(void* ctx, uint32_t device_id, uint8_t ep_address) {
     auto* xhci = static_cast<xhci_t*>(ctx);
     return xhci_reset_endpoint(xhci, device_id, ep_address);
 }
 
+static zx_status_t xhci_reset_device(void* ctx, uint32_t hub_address, uint32_t device_id) {
+    auto* xhci = static_cast<xhci_t*>(ctx);
+
+    auto* slot = &xhci->slots[device_id];
+    uint32_t port = slot->port;
+    if (slot->hub_address == 0) {
+        // Convert real port number to virtual root hub number.
+        port = xhci->rh_port_map[port - 1] + 1;
+    }
+    zxlogf(TRACE, "xhci_reset_device slot_id: %u port: %u hub_address: %u\n",
+           device_id, port, hub_address);
+
+    return usb_bus_interface_reset_port(&xhci->bus, hub_address, port);
+}
+
 static size_t xhci_get_max_transfer_size(void* ctx, uint32_t device_id, uint8_t ep_address) {
     if (ep_address == 0) {
         // control requests have uint16 length field so we need to support UINT16_MAX
@@ -147,7 +167,9 @@ usb_hci_protocol_ops_t xhci_hci_protocol = {
     .configure_hub = xhci_config_hub,
     .hub_device_added = xhci_hub_device_added,
     .hub_device_removed = xhci_hub_device_removed,
+    .hub_device_reset = xhci_hub_device_reset,
     .reset_endpoint = xhci_reset_ep,
+    .reset_device = xhci_reset_device,
     .get_max_transfer_size = xhci_get_max_transfer_size,
     .cancel_all = xhci_cancel_all,
     .get_request_size = xhci_get_request_size,
diff --git a/system/dev/usb/xhci/xhci-device-manager.cpp b/system/dev/usb/xhci/xhci-device-manager.cpp
index c01acabef0c6bb913a93420332879a4a87de07e7..cb86d75363f74ec629cd31b47e17de7f9dffb9a4 100644
--- a/system/dev/usb/xhci/xhci-device-manager.cpp
+++ b/system/dev/usb/xhci/xhci-device-manager.cpp
@@ -18,6 +18,7 @@
 typedef enum {
     ENUMERATE_DEVICE,
     DISCONNECT_DEVICE,
+    RESET_DEVICE,
     START_ROOT_HUBS,
     STOP_THREAD,
 } xhci_command_t;
@@ -66,32 +67,40 @@ static zx_status_t xhci_address_device(xhci_t* xhci, uint32_t slot_id, uint32_t
     }
 
     xhci_slot_t* slot = &xhci->slots[slot_id];
-    if (slot->sc)
-        return ZX_ERR_BAD_STATE;
     slot->hub_address = hub_address;
     slot->port = port;
     slot->rh_port = (hub_address == 0 ? port : xhci->slots[hub_address].rh_port);
     slot->speed = speed;
 
-    // allocate a read-only DMA buffer for device context
-    size_t dc_length = xhci->context_size * XHCI_NUM_EPS;
-    zx_status_t status = io_buffer_init(&slot->buffer, xhci->bti_handle, dc_length,
-                                        IO_BUFFER_RO | IO_BUFFER_CONTIG | XHCI_IO_BUFFER_UNCACHED);
-    if (status != ZX_OK) {
-        zxlogf(ERROR, "xhci_address_device: failed to allocate io_buffer for slot\n");
-        return status;
-    }
-    auto* device_context = static_cast<uint8_t*>(io_buffer_virt(&slot->buffer));
     xhci_endpoint_t* ep = &slot->eps[0];
-    status = xhci_transfer_ring_init(&ep->transfer_ring, xhci->bti_handle, TRANSFER_RING_SIZE);
-    if (status < 0) return status;
-    ep->transfer_state = static_cast<xhci_transfer_state_t*>(
-                                                calloc(1, sizeof(xhci_transfer_state_t)));
-    if (!ep->transfer_state) {
-        return ZX_ERR_NO_MEMORY;
+
+    bool device_resetting = slot->sc != nullptr;
+    zx_status_t status;
+
+    // Allocate the buffers if we haven't already. They will already exist in the case of a
+    // device reset.
+    if (!device_resetting) {
+        // allocate a read-only DMA buffer for device context
+        size_t dc_length = xhci->context_size * XHCI_NUM_EPS;
+        status = io_buffer_init(&slot->buffer, xhci->bti_handle, dc_length,
+                                IO_BUFFER_RO | IO_BUFFER_CONTIG | XHCI_IO_BUFFER_UNCACHED);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "xhci_address_device: failed to allocate io_buffer for slot\n");
+            return status;
+        }
+
+        status = xhci_transfer_ring_init(&ep->transfer_ring, xhci->bti_handle, TRANSFER_RING_SIZE);
+        if (status < 0) return status;
+        ep->transfer_state = static_cast<xhci_transfer_state_t*>(
+                                               calloc(1, sizeof(xhci_transfer_state_t)));
+        if (!ep->transfer_state) {
+            return ZX_ERR_NO_MEMORY;
+        }
+        ep->ep_type = USB_ENDPOINT_CONTROL;
     }
+
+    auto* device_context = static_cast<uint8_t*>(io_buffer_virt(&slot->buffer));
     xhci_transfer_ring_t* transfer_ring = &ep->transfer_ring;
-    ep->ep_type = USB_ENDPOINT_CONTROL;
 
     mtx_lock(&xhci->input_context_lock);
     auto* icc = reinterpret_cast<xhci_input_control_context_t*>(xhci->input_context);
@@ -147,7 +156,8 @@ static zx_status_t xhci_address_device(xhci_t* xhci, uint32_t slot_id, uint32_t
                     tt_port_number);
 
     // Setup endpoint context for ep0
-    zx_paddr_t tr_dequeue = xhci_transfer_ring_start_phys(transfer_ring);
+    // If this is following a device reset, the dequeue pointer may not be the start of the ring.
+    zx_paddr_t tr_dequeue = xhci_transfer_ring_current_phys(transfer_ring);
 
     // start off with reasonable default max packet size for ep0 based on speed
     int mps;
@@ -182,7 +192,10 @@ static zx_status_t xhci_address_device(xhci_t* xhci, uint32_t slot_id, uint32_t
         if (status == ZX_OK) {
             break;
         } else if (status != ZX_ERR_TIMED_OUT) {
-            usb_bus_interface_reset_port(&xhci->bus, hub_address, port);
+            // Don't want to get into a reset loop.
+            if (!device_resetting) {
+                usb_bus_interface_reset_port(&xhci->bus, hub_address, port);
+            }
             status = xhci_send_command(xhci, TRB_CMD_ADDRESS_DEVICE, icc_phys,
                                       ((slot_id << TRB_SLOT_ID_START) | TRB_ADDRESS_DEVICE_BSR));
             if (status != ZX_OK) {
@@ -294,27 +307,13 @@ static void xhci_disable_slot(xhci_t* xhci, uint32_t slot_id) {
     slot->port = USB_SPEED_UNDEFINED;
 }
 
-static zx_status_t xhci_handle_enumerate_device(xhci_t* xhci, uint32_t hub_address, uint32_t port,
-                                                usb_speed_t speed) {
-    zxlogf(TRACE, "xhci_handle_enumerate_device hub_address:%d port %d\n", hub_address, port);
-    zx_status_t result = ZX_OK;
-    uint32_t slot_id = 0;
-
-    xhci_sync_command_t command;
-    xhci_sync_command_init(&command);
-    xhci_post_command(xhci, TRB_CMD_ENABLE_SLOT, 0, 0, &command.context);
-    int cc = xhci_sync_command_wait(&command);
-    if (cc == TRB_CC_SUCCESS) {
-        slot_id = xhci_sync_command_slot_id(&command);
-    } else {
-        zxlogf(ERROR, "xhci_handle_enumerate_device: unable to get a slot\n");
-        return ZX_ERR_NO_RESOURCES;
-    }
-
-    result = xhci_address_device(xhci, slot_id, hub_address, port, speed);
+static zx_status_t xhci_setup_slot(xhci_t* xhci, uint32_t slot_id, uint32_t hub_address,
+                                   uint32_t port, usb_speed_t speed) {
+    zx_status_t result = xhci_address_device(xhci, slot_id, hub_address, port, speed);
     if (result != ZX_OK) {
-        goto disable_slot_exit;
+        return result;
     }
+
     // Let SET_ADDRESS settle down
     zx_nanosleep(zx_deadline_after(ZX_MSEC(10)));
     // read first 8 bytes of device descriptor to fetch ep0 max packet size
@@ -325,13 +324,16 @@ static zx_status_t xhci_handle_enumerate_device(xhci_t* xhci, uint32_t hub_addre
                                      &device_descriptor, 8, &actual);
         if (result == ZX_ERR_IO_REFUSED || result == ZX_ERR_IO_INVALID) {
             xhci_reset_endpoint(xhci, slot_id, 0);
+        } else if  (result != ZX_OK) {
+            // Try again. The device may be flaky or slow recovering.
+            continue;
         } else {
             break;
         }
     }
     if (actual != 8) {
-        zxlogf(ERROR, "xhci_handle_enumerate_device: xhci_get_descriptor failed: %d\n", result);
-        goto disable_slot_exit;
+        zxlogf(ERROR, "xhci_handle_enable_device: xhci_get_descriptor failed: %d\n", result);
+        return ZX_ERR_BAD_STATE;
     }
 
     int mps;
@@ -375,7 +377,30 @@ static zx_status_t xhci_handle_enumerate_device(xhci_t* xhci, uint32_t hub_addre
                                (slot_id << TRB_SLOT_ID_START));
     mtx_unlock(&xhci->input_context_lock);
     if (result != ZX_OK) {
-        zxlogf(ERROR, "xhci_handle_enumerate_device: TRB_CMD_EVAL_CONTEXT failed\n");
+        zxlogf(ERROR, "xhci_handle_enable_device: TRB_CMD_EVAL_CONTEXT failed\n");
+    }
+    return result;
+}
+
+static zx_status_t xhci_handle_enumerate_device(xhci_t* xhci, uint32_t hub_address, uint32_t port,
+                                                usb_speed_t speed) {
+    zxlogf(TRACE, "xhci_handle_enumerate_device hub_address:%d port %d\n", hub_address, port);
+    zx_status_t result = ZX_OK;
+    uint32_t slot_id = 0;
+
+    xhci_sync_command_t command;
+    xhci_sync_command_init(&command);
+    xhci_post_command(xhci, TRB_CMD_ENABLE_SLOT, 0, 0, &command.context);
+    int cc = xhci_sync_command_wait(&command);
+    if (cc == TRB_CC_SUCCESS) {
+        slot_id = xhci_sync_command_slot_id(&command);
+    } else {
+        zxlogf(ERROR, "xhci_handle_enumerate_device: unable to get a slot\n");
+        return ZX_ERR_NO_RESOURCES;
+    }
+
+    result = xhci_setup_slot(xhci, slot_id, hub_address, port, speed);
+    if (result != ZX_OK) {
         goto disable_slot_exit;
     }
 
@@ -388,11 +413,42 @@ disable_slot_exit:
     return result;
 }
 
+static zx_status_t xhci_free_endpoint_state(xhci_t* xhci, xhci_slot_t* slot, xhci_endpoint_t* ep,
+                                            zx_status_t complete_status) {
+    xhci_transfer_ring_t* transfer_ring = &ep->transfer_ring;
+
+    mtx_lock(&ep->lock);
+    if (ep->state != EP_STATE_DISABLED && ep->state != EP_STATE_DEAD) {
+        mtx_unlock(&ep->lock);
+        return ZX_ERR_BAD_STATE;
+    }
+    mtx_unlock(&ep->lock);
+
+    free(ep->transfer_state);
+    ep->transfer_state = nullptr;
+    xhci_transfer_ring_free(transfer_ring);
+
+    // complete any remaining requests
+    usb_request_t* req = nullptr;
+    xhci_usb_request_internal_t* req_int = nullptr;
+    while ((req_int = list_remove_head_type(&ep->pending_reqs,
+                                            xhci_usb_request_internal_t, node)) != nullptr) {
+        req = XHCI_INTERNAL_TO_USB_REQ(req_int);
+        usb_request_complete(req, complete_status, 0, &req_int->complete_cb);
+    }
+    while ((req_int = list_remove_head_type(&ep->queued_reqs,
+                                            xhci_usb_request_internal_t, node)) != nullptr) {
+        req = XHCI_INTERNAL_TO_USB_REQ(req_int);
+        usb_request_complete(req, complete_status, 0, &req_int->complete_cb);
+    }
+
+    return ZX_OK;
+}
+
 static zx_status_t xhci_stop_endpoint(xhci_t* xhci, uint32_t slot_id, int ep_index,
                                       xhci_ep_state_t new_state, zx_status_t complete_status) {
     xhci_slot_t* slot = &xhci->slots[slot_id];
     xhci_endpoint_t* ep =  &slot->eps[ep_index];
-    xhci_transfer_ring_t* transfer_ring = &ep->transfer_ring;
 
     if (new_state != EP_STATE_DISABLED && new_state != EP_STATE_DEAD) {
         ZX_DEBUG_ASSERT_MSG(false, "xhci_stop_endpoint: bad state argument %d\n", new_state);
@@ -420,29 +476,13 @@ static zx_status_t xhci_stop_endpoint(xhci_t* xhci, uint32_t slot_id, int ep_ind
         return ZX_ERR_INTERNAL;
     }
 
-    free(ep->transfer_state);
-    ep->transfer_state = nullptr;
-    xhci_transfer_ring_free(transfer_ring);
-
-    // complete any remaining requests
-    usb_request_t* req = nullptr;
-    xhci_usb_request_internal_t* req_int = nullptr;
-    while ((req_int = list_remove_head_type(&ep->pending_reqs,
-                                            xhci_usb_request_internal_t, node)) != nullptr) {
-        req = XHCI_INTERNAL_TO_USB_REQ(req_int);
-        usb_request_complete(req, complete_status, 0, &req_int->complete_cb);
-    }
-    while ((req_int = list_remove_head_type(&ep->queued_reqs,
-                                            xhci_usb_request_internal_t, node)) != nullptr) {
-        req = XHCI_INTERNAL_TO_USB_REQ(req_int);
-        usb_request_complete(req, complete_status, 0, &req_int->complete_cb);
-    }
-
-    return ZX_OK;
+    return xhci_free_endpoint_state(xhci, slot, ep, complete_status);
 }
 
-static zx_status_t xhci_handle_disconnect_device(xhci_t* xhci, uint32_t hub_address, uint32_t port) {
-    zxlogf(TRACE, "xhci_handle_disconnect_device\n");
+// Returns the slot for the given |hub_address| and |port|, or nullptr if no such slot exists.
+// The slot id will be stored in |out_slot_id|.
+static xhci_slot_t* xhci_get_slot(xhci_t* xhci, uint32_t hub_address, uint32_t port,
+                                  uint32_t* out_slot_id) {
     xhci_slot_t* slot = nullptr;
     uint32_t slot_id;
 
@@ -461,6 +501,16 @@ static zx_status_t xhci_handle_disconnect_device(xhci_t* xhci, uint32_t hub_addr
             break;
         }
     }
+    if (slot) {
+        *out_slot_id = slot_id;
+    }
+    return slot;
+}
+
+static zx_status_t xhci_handle_disconnect_device(xhci_t* xhci, uint32_t hub_address, uint32_t port) {
+    zxlogf(TRACE, "xhci_handle_disconnect_device\n");
+    uint32_t slot_id;
+    xhci_slot_t* slot = xhci_get_slot(xhci, hub_address, port, &slot_id);
     if (!slot) {
         zxlogf(ERROR, "slot not found in xhci_handle_disconnect_device\n");
         return ZX_ERR_NOT_FOUND;
@@ -468,7 +518,7 @@ static zx_status_t xhci_handle_disconnect_device(xhci_t* xhci, uint32_t hub_addr
 
     uint32_t drop_flags = 0;
     for (int i = 0; i < XHCI_NUM_EPS; i++) {
-        if (slot->eps[i].state != EP_STATE_DEAD) {
+        if (slot->eps[i].state != EP_STATE_DEAD && slot->eps[i].state != EP_STATE_DISABLED) {
             zx_status_t status = xhci_stop_endpoint(xhci, slot_id, i, EP_STATE_DEAD,
                                                     ZX_ERR_IO_NOT_PRESENT);
             if (status != ZX_OK) {
@@ -499,6 +549,50 @@ static zx_status_t xhci_handle_disconnect_device(xhci_t* xhci, uint32_t hub_addr
     return ZX_OK;
 }
 
+static zx_status_t xhci_handle_reset_device(xhci_t* xhci, uint32_t hub_address, uint32_t port) {
+    zxlogf(TRACE, "xhci_handle_reset_device %u %u\n", hub_address, port);
+    zx_status_t result;
+    uint32_t slot_id;
+    xhci_slot_t* slot = xhci_get_slot(xhci, hub_address, port, &slot_id);
+    if (!slot) {
+        zxlogf(ERROR, "slot not found in xhci_handle_reset_device\n");
+        result = ZX_ERR_NOT_FOUND;
+        goto done;
+    }
+    result = xhci_send_command(xhci, TRB_CMD_RESET_DEVICE, 0, (slot_id << TRB_SLOT_ID_START));
+    if (result != ZX_OK) {
+        zxlogf(ERROR, "xhci_handle_reset_device: TRB_CMD_RESET_DEVICE failed\n");
+        goto done;
+    }
+
+    // TRB_CMD_RESET_DEVICE disables all endpoints except the control endpoint.
+    for (int i = 1; i < XHCI_NUM_EPS; i++) {
+        xhci_endpoint_t* ep =  &slot->eps[i];
+
+        mtx_lock(&ep->lock);
+        ep->state = EP_STATE_DISABLED;
+        mtx_unlock(&ep->lock);
+
+        result = xhci_free_endpoint_state(xhci, slot, ep, ZX_ERR_IO_NOT_PRESENT);
+        if (result != ZX_OK) {
+            zxlogf(ERROR, "xhci_free_endpoint_state failed slot %u ep %u, err: %d\n",
+                   result, slot_id, i);
+        }
+    }
+    // The slot is now in the Default state and we need to address it again.
+    result = xhci_setup_slot(xhci, slot_id, hub_address, port, slot->speed);
+    if (result != ZX_OK) {
+        zxlogf(ERROR, "xhci_handle_reset_device: xhci_setup_slot failed: %d\n", result);
+        goto done;
+    }
+    zxlogf(TRACE, "xhci_handle_reset_device %u %u successful\n", hub_address, port);
+
+done:
+    // We should always call this so we can update the device state.
+    usb_bus_interface_reinitialize_device(&xhci->bus, slot_id);
+    return result;
+}
+
 static int xhci_device_thread(void* arg) {
     auto* xhci = static_cast<xhci_t*>(arg);
 
@@ -530,6 +624,9 @@ static int xhci_device_thread(void* arg) {
         case DISCONNECT_DEVICE:
             xhci_handle_disconnect_device(xhci, command->hub_address, command->port);
             break;
+        case RESET_DEVICE:
+            xhci_handle_reset_device(xhci, command->hub_address, command->port);
+            break;
         case START_ROOT_HUBS:
             xhci_start_root_hubs(xhci);
             break;
@@ -594,6 +691,10 @@ zx_status_t xhci_device_disconnected(xhci_t* xhci, uint32_t hub_address, uint32_
     return xhci_queue_command(xhci, DISCONNECT_DEVICE, hub_address, port, USB_SPEED_UNDEFINED);
 }
 
+zx_status_t xhci_device_reset(xhci_t* xhci, uint32_t hub_address, uint32_t port) {
+    return xhci_queue_command(xhci, RESET_DEVICE, hub_address, port, USB_SPEED_UNDEFINED);
+}
+
 zx_status_t xhci_queue_start_root_hubs(xhci_t* xhci) {
     return xhci_queue_command(xhci, START_ROOT_HUBS, 0, 0, USB_SPEED_UNDEFINED);
 }
diff --git a/system/dev/usb/xhci/xhci-device-manager.h b/system/dev/usb/xhci/xhci-device-manager.h
index 512427fa1b704451dac8ec104787c67ca7eac5f8..42744b4fdec5bbc3a96f291c3ed0c72074faafe9 100644
--- a/system/dev/usb/xhci/xhci-device-manager.h
+++ b/system/dev/usb/xhci/xhci-device-manager.h
@@ -13,6 +13,7 @@ typedef struct xhci xhci_t;
 zx_status_t xhci_enumerate_device(xhci_t* xhci, uint32_t hub_address, uint32_t port,
                                   usb_speed_t speed);
 zx_status_t xhci_device_disconnected(xhci_t* xhci, uint32_t hub_address, uint32_t port);
+zx_status_t xhci_device_reset(xhci_t* xhci, uint32_t hub_address, uint32_t port);
 void xhci_start_device_thread(xhci_t* xhci);
 void xhci_stop_device_thread(xhci_t* xhci);
 zx_status_t xhci_queue_start_root_hubs(xhci_t* xhci);