[zircon] Fix XHCI, PHY tuning, and CDC Ethernet issues

This patch fixes various XHCI issues that caused problems
with CDC-Ethernet on certain boards with high-latency interrupts.

The following changes have been made in this patch:

* Add a direct mode for USB URBs to bypass re-assembly
and buffering throughout the pipeline and take the
most "direct" path from an interrupt handler to driver code.

* Enable direct mode for CDC Ethernet TX and RX paths.
* Increase the buffer size used for CDC Ethernet.
* Implement multi-page TRB support in XHCI.
* Increase the size of the transfer and event ring buffers.
* Revert workaround in pack-images.py

CONN-50
ZX-3957

Test: Validate no software-related packet loss in XHCI and
CDC Ethernet drivers on affected boards. Ran additional tests described
in CONN-50.

Change-Id: Ice7429ee63109db67b254488d7127a8bd95394ab
diff --git a/zircon/system/banjo/ddk.protocol.usb.request/usb-request.banjo b/zircon/system/banjo/ddk.protocol.usb.request/usb-request.banjo
index 5e1172d..b0d918c 100644
--- a/zircon/system/banjo/ddk.protocol.usb.request/usb-request.banjo
+++ b/zircon/system/banjo/ddk.protocol.usb.request/usb-request.banjo
@@ -77,6 +77,15 @@
     /// |silent_completions_count| to know how many consecutive requests prior to this one
     /// (in relation to queue order for the endpoint) have completed successfully.
     bool cb_on_error_only;
+    /// Direct mode -- if set to true, this packet is handled with high priority directly
+    /// in interrupt context. It will NOT be safe to block in any callbacks, and all layers
+    /// should take the most direct path to route the packet to the requesting driver from
+    /// hardware.
+    bool direct;
+    /// If true, resets an endpoint and does not transfer any data.
+    bool reset;
+    /// The address of the endpoint to reset.
+    uint8 reset_address;
 };
 
 [Layout="ddk-callback"]
diff --git a/zircon/system/core/netsvc/BUILD.gn b/zircon/system/core/netsvc/BUILD.gn
index c47a522..28dfe83 100644
--- a/zircon/system/core/netsvc/BUILD.gn
+++ b/zircon/system/core/netsvc/BUILD.gn
@@ -11,17 +11,14 @@
     "netsvc.cpp",
   ]
   if (enable_netsvc_debugging_features) {
-    sources += [
-      "debug-command-enabled.cpp",
-    ]
+    sources += [ "debug-command-enabled.cpp" ]
   } else {
-    sources += [
-      "debug-command-disabled.cpp",
-    ]
+    sources += [ "debug-command-disabled.cpp" ]
   }
   deps = [
     ":netsvc_common",
     "$zx/system/fidl/fuchsia-hardware-ethernet:c",
+    "$zx/system/ulib/fbl",
     "$zx/system/ulib/fdio",
     "$zx/system/ulib/inet6",
     "$zx/system/ulib/sync",
@@ -39,8 +36,8 @@
   sources = [
     "board-name.cpp",
     "file-api.cpp",
-    "netcp.cpp",
     "netboot.cpp",
+    "netcp.cpp",
     "paver.cpp",
     "payload-streamer.cpp",
     "tftp.cpp",
@@ -51,8 +48,8 @@
     "$zx/system/fidl/fuchsia-device-manager:c",
     "$zx/system/fidl/fuchsia-hardware-block:c",
     "$zx/system/fidl/fuchsia-sysinfo:c",
-    "$zx/system/ulib/chromeos-disk-setup",
     "$zx/system/ulib/async-loop:async-loop-cpp",
+    "$zx/system/ulib/chromeos-disk-setup",
     "$zx/system/ulib/fdio",
     "$zx/system/ulib/gpt",
     "$zx/system/ulib/libzbi",
diff --git a/zircon/system/dev/board/astro/astro-usb.c b/zircon/system/dev/board/astro/astro-usb.c
index 7864656..0721150 100644
--- a/zircon/system/dev/board/astro/astro-usb.c
+++ b/zircon/system/dev/board/astro/astro-usb.c
@@ -63,22 +63,6 @@
         return status;
     }
 
-    volatile void* base = buf.vaddr;
-
-    if (default_val) {
-        writel(0, base + 0x38);
-        writel(PLL_SETTING_5, base + 0x34);
-    } else {
-        writel(PLL_SETTING_3, base + 0x50);
-        writel(PLL_SETTING_4, base + 0x10);
-        if (host) {
-            writel(PLL_SETTING_6, base + 0x38);
-        } else {
-            writel(PLL_SETTING_7, base + 0x38);
-        }
-        writel(PLL_SETTING_5, base + 0x34);
-    }
-
     mmio_buffer_release(&buf);
     return ZX_OK;
 }
diff --git a/zircon/system/dev/ethernet/ethernet/ethernet.cpp b/zircon/system/dev/ethernet/ethernet/ethernet.cpp
index 144ef3a..c75411a 100644
--- a/zircon/system/dev/ethernet/ethernet/ethernet.cpp
+++ b/zircon/system/dev/ethernet/ethernet/ethernet.cpp
@@ -143,10 +143,13 @@
         if (status != ZX_OK) {
             if (status == ZX_ERR_SHOULD_WAIT) {
                 fail_receive_read_ += 1;
-                if (fail_receive_read_ == 1 ||
-                    (fail_receive_read_ % kFailureReportRate) == 0) {
-                    zxlogf(WARN, "eth [%s]: warning: no rx buffers available, frame dropped "
-                                 "(%u time%s)\n",
+                if (fail_receive_read_ == 1 || (fail_receive_read_ % kFailureReportRate) == 0) {
+                    // TODO(bbosak): Printing this warning
+                    // can result in more dropped packets.
+                    // Find a better way to log this.
+                    zxlogf(WARN,
+                           "eth [%s]: warning: no rx buffers available, frame dropped "
+                           "(%u time%s)\n",
                            name_, fail_receive_read_, fail_receive_read_ > 1 ? "s" : "");
                 }
             } else {
diff --git a/zircon/system/dev/ethernet/usb-cdc-ecm/usb-cdc-ecm.c b/zircon/system/dev/ethernet/usb-cdc-ecm/usb-cdc-ecm.c
index 013115b..589c9cd 100644
--- a/zircon/system/dev/ethernet/usb-cdc-ecm/usb-cdc-ecm.c
+++ b/zircon/system/dev/ethernet/usb-cdc-ecm/usb-cdc-ecm.c
@@ -24,7 +24,7 @@
 
 // The maximum amount of memory we are willing to allocate to transaction buffers
 #define MAX_TX_BUF_SZ 32768
-#define MAX_RX_BUF_SZ 32768
+#define MAX_RX_BUF_SZ 1500 * 2048
 
 #define ETHMAC_MAX_TRANSMIT_DELAY 100
 #define ETHMAC_MAX_RECV_DELAY 100
@@ -249,34 +249,61 @@
     return ZX_OK;
 }
 
-static void usb_write_complete(void* cookie, usb_request_t* request) {
+// Write completion callback. Normally -- this will simply acquire the TX lock, release it,
+// and re-queue the USB request.
+// The error case is a bit more complicated. We set the reset bit on the request, and queue
+// a packet that triggers a reset (asynchronously). We then immediately return to the interrupt
+// thread with the lock held to allow for interrupt processing to take place. Once the reset
+// completes, this function is called again with the lock still held, and request processing
+// continues normally. It is necessary to keep the lock held after returning in the error case
+// because we do not want other packets to get queued out-of-order while the asynchronous operation
+// is in progress.
+static void usb_write_complete(void* cookie,
+                               usb_request_t* request) __TA_NO_THREAD_SAFETY_ANALYSIS {
     ecm_ctx_t* ctx = cookie;
 
     if (request->response.status == ZX_ERR_IO_NOT_PRESENT) {
         usb_request_release(request);
         return;
     }
-
-    mtx_lock(&ctx->tx_mutex);
-
-    // Return transmission buffer to pool
-    zx_status_t status = usb_req_list_add_tail(&ctx->tx_txn_bufs, request, ctx->parent_req_size);
-    ZX_DEBUG_ASSERT(status == ZX_OK);
-
-    if (request->response.status == ZX_ERR_IO_REFUSED) {
-        zxlogf(TRACE, "%s: resetting transmit endpoint\n", module_name);
-        usb_reset_endpoint(&ctx->usb, ctx->tx_endpoint.addr);
-    }
-
-    if (request->response.status == ZX_ERR_IO_INVALID) {
-        zxlogf(TRACE, "%s: slowing down the requests by %d usec."
-                     "Resetting the transmit endpoint\n",
-               module_name, ETHMAC_TRANSMIT_DELAY);
-        if (ctx->tx_endpoint_delay < ETHMAC_MAX_TRANSMIT_DELAY) {
-            ctx->tx_endpoint_delay += ETHMAC_TRANSMIT_DELAY;
+    // If reset, we still hold the TX mutex.
+    if (!request->reset) {
+        mtx_lock(&ctx->tx_mutex);
+        // Return transmission buffer to pool
+        zx_status_t status =
+            usb_req_list_add_tail(&ctx->tx_txn_bufs, request, ctx->parent_req_size);
+        ZX_DEBUG_ASSERT(status == ZX_OK);
+        if (request->response.status == ZX_ERR_IO_REFUSED) {
+            zxlogf(TRACE, "%s: resetting transmit endpoint\n", module_name);
+            request->reset = true;
+            request->reset_address = ctx->tx_endpoint.addr;
+            usb_request_complete_t complete = {
+                .callback = usb_write_complete,
+                .ctx = ctx,
+            };
+            usb_request_queue(&ctx->usb, request, &complete);
+            return;
         }
-        usb_reset_endpoint(&ctx->usb, ctx->tx_endpoint.addr);
+
+        if (request->response.status == ZX_ERR_IO_INVALID) {
+            zxlogf(TRACE,
+                   "%s: slowing down the requests by %d usec."
+                   "Resetting the transmit endpoint\n",
+                   module_name, ETHMAC_TRANSMIT_DELAY);
+            if (ctx->tx_endpoint_delay < ETHMAC_MAX_TRANSMIT_DELAY) {
+                ctx->tx_endpoint_delay += ETHMAC_TRANSMIT_DELAY;
+            }
+            request->reset = true;
+            request->reset_address = ctx->tx_endpoint.addr;
+            usb_request_complete_t complete = {
+                .callback = usb_write_complete,
+                .ctx = ctx,
+            };
+            usb_request_queue(&ctx->usb, request, &complete);
+            return;
+        }
     }
+    request->reset = false;
 
     bool additional_tx_queued = false;
     txn_info_t* txn;
@@ -321,12 +348,11 @@
     mtx_unlock(&ctx->ethmac_mutex);
 }
 
-static void usb_read_complete(void* cookie, usb_request_t* request) {
+static void usb_read_complete(void* cookie, usb_request_t* request) __TA_NO_THREAD_SAFETY_ANALYSIS {
     ecm_ctx_t* ctx = cookie;
-
     if (request->response.status != ZX_OK) {
-        zxlogf(TRACE, "%s: usb_read_complete called with status %d\n",
-                module_name, (int)request->response.status);
+        zxlogf(TRACE, "%s: usb_read_complete called with status %d\n", module_name,
+               (int)request->response.status);
     }
 
     if (request->response.status == ZX_ERR_IO_NOT_PRESENT) {
@@ -336,20 +362,37 @@
 
     if (request->response.status == ZX_ERR_IO_REFUSED) {
         zxlogf(TRACE, "%s: resetting receive endpoint\n", module_name);
-        usb_reset_endpoint(&ctx->usb, ctx->rx_endpoint.addr);
+        request->reset = true;
+        request->reset_address = ctx->rx_endpoint.addr;
+        usb_request_complete_t complete = {
+            .callback = usb_read_complete,
+            .ctx = ctx,
+        };
+        usb_request_queue(&ctx->usb, request, &complete);
+        return;
     } else if (request->response.status == ZX_ERR_IO_INVALID) {
         if (ctx->rx_endpoint_delay < ETHMAC_MAX_RECV_DELAY) {
             ctx->rx_endpoint_delay += ETHMAC_RECV_DELAY;
         }
-        zxlogf(TRACE, "%s: slowing down the requests by %d usec."
-                     "Resetting the recv endpoint\n",
+        zxlogf(TRACE,
+               "%s: slowing down the requests by %d usec."
+               "Resetting the recv endpoint\n",
                module_name, ETHMAC_RECV_DELAY);
-        usb_reset_endpoint(&ctx->usb, ctx->rx_endpoint.addr);
-    } else if (request->response.status == ZX_OK) {
+        request->reset = true;
+        request->reset_address = ctx->rx_endpoint.addr;
+        usb_request_complete_t complete = {
+            .callback = usb_read_complete,
+            .ctx = ctx,
+        };
+        usb_request_queue(&ctx->usb, request, &complete);
+        return;
+    } else if (request->response.status == ZX_OK && !request->reset) {
         usb_recv(ctx, request);
     }
-
-    zx_nanosleep(zx_deadline_after(ZX_USEC(ctx->rx_endpoint_delay)));
+    if (ctx->rx_endpoint_delay) {
+        zx_nanosleep(zx_deadline_after(ZX_USEC(ctx->rx_endpoint_delay)));
+    }
+    request->reset = false;
     usb_request_complete_t complete = {
         .callback = usb_read_complete,
         .ctx = ctx,
@@ -722,8 +765,9 @@
     size_t tx_buf_remain = MAX_TX_BUF_SZ;
     while (tx_buf_remain >= tx_buf_sz) {
         usb_request_t* tx_buf;
-        zx_status_t alloc_result = usb_request_alloc(&tx_buf, tx_buf_sz,
-                                                     ecm_ctx->tx_endpoint.addr, req_size);
+        zx_status_t alloc_result =
+            usb_request_alloc(&tx_buf, tx_buf_sz, ecm_ctx->tx_endpoint.addr, req_size);
+        tx_buf->direct = true;
         if (alloc_result != ZX_OK) {
             result = alloc_result;
             goto fail;
@@ -741,7 +785,7 @@
     }
 
     // Allocate rx transaction buffers
-    uint16_t rx_buf_sz = ecm_ctx->mtu;
+    uint32_t rx_buf_sz = ecm_ctx->mtu;
 #if MAX_TX_BUF_SZ < UINT16_MAX
     if (rx_buf_sz > MAX_RX_BUF_SZ) {
         zxlogf(ERROR, "%s: insufficient space for even a single rx buffer\n", module_name);
@@ -763,7 +807,7 @@
             result = alloc_result;
             goto fail;
         }
-
+        rx_buf->direct = true;
         usb_request_queue(&ecm_ctx->usb, rx_buf, &complete);
         rx_buf_remain -= rx_buf_sz;
     }
diff --git a/zircon/system/dev/lib/amlogic/aml-usb-phy-v2.c b/zircon/system/dev/lib/amlogic/aml-usb-phy-v2.c
index 8e02226..3356d889 100644
--- a/zircon/system/dev/lib/amlogic/aml-usb-phy-v2.c
+++ b/zircon/system/dev/lib/amlogic/aml-usb-phy-v2.c
@@ -10,9 +10,16 @@
 #include <soc/aml-s905d2/s905d2-hw.h>
 
 // from mesong12a.dtsi
-#define PLL_SETTING_0   0x09400414
-#define PLL_SETTING_1   0x927E0000
-#define PLL_SETTING_2   0xac5f49e5
+#define PLL_SETTING_0 0x09400414
+#define PLL_SETTING_1 0x927E0000
+#define PLL_SETTING_2 0xac5f49e5
+#define PLL_SETTING_3 0xfe18
+#define PLL_SETTING_4 0xfff
+#define PLL_SETTING_5 0x78000
+#define PLL_SETTING_6 0xe0004
+#define PLL_SETTING_7 0xe000c
+
+// Order PLL, tuning
 
 // set_usb_pll() in phy_aml_new_usb2_v2.c
 static zx_status_t set_usb_pll(zx_paddr_t reg_base) {
@@ -33,6 +40,24 @@
     zx_nanosleep(zx_deadline_after(ZX_USEC(100)));
     writel((0x10000000 | PLL_SETTING_0), reg + 0x40);
 
+    // PLL
+
+    zx_nanosleep(zx_deadline_after(ZX_USEC(100)));
+    writel((PLL_SETTING_3), reg + 0x50);
+    writel((PLL_SETTING_4), reg + 0x10);
+    // Recovery state
+    writel(0, reg + 0x38);
+    writel((PLL_SETTING_5), reg + 0x34);
+    // Disconnect threshold
+    writel(0x3c, reg + 0xc);
+    // Tuning
+
+    zx_nanosleep(zx_deadline_after(ZX_USEC(100)));
+    writel(PLL_SETTING_6, reg + 0x38);
+    writel(PLL_SETTING_5, reg + 0x34);
+
+    zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
+
     mmio_buffer_release(&buf);
     return ZX_OK;
 }
diff --git a/zircon/system/dev/usb/usb-bus/usb-device.cpp b/zircon/system/dev/usb/usb-bus/usb-device.cpp
index 442f902..59b9b3f 100644
--- a/zircon/system/dev/usb/usb-bus/usb-device.cpp
+++ b/zircon/system/dev/usb/usb-bus/usb-device.cpp
@@ -62,6 +62,11 @@
 
         // Call completion callbacks outside of the lock.
         for (auto req = temp_queue.pop(); req; req = temp_queue.pop()) {
+            if (req->operation()->reset) {
+                req->Complete(hci_.ResetEndpoint(device_id_, req->operation()->reset_address), 0,
+                              req->private_storage()->silent_completions_count);
+                continue;
+            }
             const auto& response = req->request()->response;
             req->Complete(response.status, response.actual,
                           req->private_storage()->silent_completions_count);
@@ -163,6 +168,10 @@
 
 // usb request completion for the requests passed down to the HCI driver
 void UsbDevice::RequestComplete(usb_request_t* req) {
+    if (req->reset) {
+        QueueCallback(req);
+        return;
+    }
     auto* ep = GetEndpoint(req->header.ep_address);
     if (!ep) {
         zxlogf(ERROR, "could not find endpoint with address 0x%x\n", req->header.ep_address);
@@ -352,6 +361,19 @@
 
 void UsbDevice::UsbRequestQueue(usb_request_t* req, const usb_request_complete_t* complete_cb) {
     req->header.device_id = device_id_;
+    if (req->reset) {
+        // Save client's callback in private storage.
+        UnownedRequest request(req, *complete_cb, parent_req_size_);
+        *request.private_storage() = {.ready_for_client = false,
+                                      .require_callback = !req->cb_on_error_only,
+                                      .silent_completions_count = 0};
+        RequestComplete(request.take());
+        return;
+    }
+    if (req->direct) {
+        hci_.RequestQueue(req, complete_cb);
+        return;
+    }
 
     // Queue to HCI driver with our own completion callback so we can call client's completion
     // on our own thread, to avoid drivers from deadlocking the HCI driver's interrupt thread.
diff --git a/zircon/system/dev/usb/xhci/trb-sizes.h b/zircon/system/dev/usb/xhci/trb-sizes.h
new file mode 100644
index 0000000..accff8bf
--- /dev/null
+++ b/zircon/system/dev/usb/xhci/trb-sizes.h
@@ -0,0 +1,9 @@
+// Copyright 2019 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.
+
+// choose ring sizes to allow each ring to fit in a single page
+#define COMMAND_RING_SIZE (PAGE_SIZE / sizeof(xhci_trb_t))
+#define TRANSFER_RING_SIZE ((PAGE_SIZE * 16) / sizeof(xhci_trb_t))
+#define EVENT_RING_SIZE ((PAGE_SIZE * 16) / sizeof(xhci_trb_t))
+#define ERST_ARRAY_SIZE (static_cast<uint32_t>((EVENT_RING_SIZE * sizeof(xhci_trb_t)) / PAGE_SIZE))
diff --git a/zircon/system/dev/usb/xhci/xdc-transfer.cpp b/zircon/system/dev/usb/xhci/xdc-transfer.cpp
index f833b31b..981975e 100644
--- a/zircon/system/dev/usb/xhci/xdc-transfer.cpp
+++ b/zircon/system/dev/usb/xhci/xdc-transfer.cpp
@@ -57,7 +57,7 @@
     // If we get here, then we are ready to ring the doorbell.
     // Save the ring position so we can update the ring dequeue ptr once the transfer completes.
     xdc_req_internal_t* req_int = USB_REQ_TO_XDC_INTERNAL(req, sizeof(usb_request_t));
-    req_int->context = ring->current;
+    req_int->context = ring->current_trb;
     xdc_ring_doorbell(xdc, ep);
 
     return ZX_OK;
@@ -174,7 +174,7 @@
     // Convert all pending TRBs on the transfer ring into NO-OPs TRBs.
     // ring->current is just after our last queued TRB.
     xhci_trb_t* last_trb = nullptr;
-    while (trb != ring->current) {
+    while (trb != ring->current_trb) {
         xhci_set_transfer_noop_trb(trb);
         last_trb = trb;
         trb = xhci_get_next_trb(ring, trb);
diff --git a/zircon/system/dev/usb/xhci/xdc.cpp b/zircon/system/dev/usb/xhci/xdc.cpp
index b71dfd1..a79bf3b 100644
--- a/zircon/system/dev/usb/xhci/xdc.cpp
+++ b/zircon/system/dev/usb/xhci/xdc.cpp
@@ -2,20 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <assert.h>
 #include <ddk/debug.h>
 #include <fuchsia/usb/debug/c/fidl.h>
-#include <xdc-server-utils/msg.h>
-#include <xdc-server-utils/stream.h>
-#include <zircon/hw/usb.h>
-#include <assert.h>
 #include <string.h>
 #include <threads.h>
 #include <unistd.h>
+#include <xdc-server-utils/msg.h>
+#include <xdc-server-utils/stream.h>
+#include <zircon/hw/usb.h>
 
-#include "xdc.h"
+#include "trb-sizes.h"
 #include "xdc-transfer.h"
+#include "xdc.h"
 #include "xhci-hw.h"
 #include "xhci-util.h"
+#include <fbl/alloc_checker.h>
 
 #ifndef MIN
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
@@ -29,11 +31,9 @@
 #define XDC_SERIAL_NUMBER      u""
 #define XDC_VENDOR_ID          0x18D1
 #define XDC_PRODUCT_ID         0xA0DC
-#define XDC_REVISION           0x1000
+#define XDC_REVISION 0x1000
 
 // Multi-segment event rings are not currently supported.
-#define ERST_ARRAY_SIZE        1
-#define EVENT_RING_SIZE        (PAGE_SIZE / sizeof(xhci_trb_t))
 
 // The maximum duration to transition from connected to configured state.
 #define TRANSITION_CONFIGURED_THRESHOLD ZX_SEC(5)
@@ -183,10 +183,10 @@
     if (status != ZX_OK) {
         return status;
     }
-    zx_paddr_t tr_dequeue = xhci_transfer_ring_start_phys(&ep->transfer_ring);
+    zx_paddr_t tr_dequeue = ep->transfer_ring.buffers.front()->phys_list()[0];
 
-    uint32_t max_burst = XHCI_GET_BITS32(&xdc->debug_cap_regs->dcctrl,
-                                         DCCTRL_MAX_BURST_START, DCCTRL_MAX_BURST_BITS);
+    uint32_t max_burst = XHCI_GET_BITS32(&xdc->debug_cap_regs->dcctrl, DCCTRL_MAX_BURST_START,
+                                         DCCTRL_MAX_BURST_BITS);
     int avg_trb_length = EP_CTX_MAX_PACKET_SIZE * (max_burst + 1);
 
 
@@ -690,7 +690,7 @@
     while ((req = xdc_req_list_remove_tail(&xdc->free_read_reqs, usb_req_size)) != nullptr) {
         usb_request_release(req);
     }
-    free(xdc);
+    delete xdc;
 }
 
 static zx_status_t xdc_suspend(void* ctx, uint32_t flags) {
@@ -1286,8 +1286,9 @@
 }
 
 zx_status_t xdc_bind(zx_device_t* parent, zx_handle_t bti_handle, void* mmio) {
-    auto* xdc = static_cast<xdc_t*>(calloc(1, sizeof(xdc_t)));
-    if (!xdc) {
+    fbl::AllocChecker ac;
+    auto* xdc = new (&ac) xdc_t();
+    if (!ac.check()) {
         return ZX_ERR_NO_MEMORY;
     }
     xdc->bti_handle = bti_handle;
@@ -1332,7 +1333,7 @@
 
 error_return:
     zxlogf(ERROR, "xdc_bind failed: %d\n", status);
-    xdc_free(xdc);
+    delete xdc;
     return status;
 }
 
diff --git a/zircon/system/dev/usb/xhci/xdc.h b/zircon/system/dev/usb/xhci/xdc.h
index 84d4099..b39c149 100644
--- a/zircon/system/dev/usb/xhci/xdc.h
+++ b/zircon/system/dev/usb/xhci/xdc.h
@@ -17,10 +17,10 @@
 
 namespace usb_xhci {
 
-#define TRANSFER_RING_SIZE     (PAGE_SIZE / sizeof(xhci_trb_t))
+#define TRANSFER_RING_SIZE ((PAGE_SIZE * 16) / sizeof(xhci_trb_t))
 
 // The type and length fields for a string descriptor are one byte each.
-#define STR_DESC_METADATA_LEN  2
+#define STR_DESC_METADATA_LEN 2
 #define MAX_STR_LEN            64
 
 // There are only two endpoints, one for bulk OUT and one for bulk IN.
@@ -88,49 +88,49 @@
 } xdc_poll_state_t;
 
 typedef struct {
-    zx_device_t* zxdev;
+    zx_device_t* zxdev = nullptr;
 
     // Shared from XHCI.
-    zx_handle_t bti_handle;
-    void* mmio;
+    zx_handle_t bti_handle = ZX_HANDLE_INVALID;
+    void* mmio = nullptr;
 
-    xdc_debug_cap_regs_t* debug_cap_regs;
+    xdc_debug_cap_regs_t* debug_cap_regs = nullptr;
 
     // Underlying buffer for the event ring segment table
-    io_buffer_t erst_buffer;
-    erst_entry_t* erst_array;
+    io_buffer_t erst_buffer = {};
+    erst_entry_t* erst_array = nullptr;
 
     xhci_event_ring_t event_ring;
 
     // Underlying buffer for the context data and string descriptors.
-    io_buffer_t context_str_descs_buffer;
-    xdc_context_data_t* context_data;
-    xdc_str_descs_t* str_descs;
+    io_buffer_t context_str_descs_buffer = {};
+    xdc_context_data_t* context_data = nullptr;
+    xdc_str_descs_t* str_descs = nullptr;
 
-    thrd_t start_thread;
+    thrd_t start_thread = 0;
 
     // Whether to suspend all activity.
     std::atomic<bool> suspended;
 
     xdc_endpoint_t eps[NUM_EPS];
     // Whether the Debug Device is in the Configured state.
-    bool configured;
+    bool configured = false;
     // Needs to be acquired before accessing the eps and configured members.
     // TODO(jocelyndang): make these separate locks?
     mtx_t lock;
 
-    bool writable;
-    usb_request_pool_t free_write_reqs;
+    bool writable = false;
+    usb_request_pool_t free_write_reqs = {};
     mtx_t write_lock;
 
-    list_node_t free_read_reqs;
-    xdc_packet_state_t cur_read_packet;
+    list_node_t free_read_reqs = {};
+    xdc_packet_state_t cur_read_packet = {};
     mtx_t read_lock;
 
-    list_node_t instance_list;
+    list_node_t instance_list = {};
     // Streams registered by the host.
-    list_node_t host_streams;
-    mtx_t instance_list_lock;
+    list_node_t host_streams = {};
+    mtx_t instance_list_lock = {};
 
     // At least one xdc instance has been opened.
     sync_completion_t has_instance_completion;
diff --git a/zircon/system/dev/usb/xhci/xhci-device-manager.cpp b/zircon/system/dev/usb/xhci/xhci-device-manager.cpp
index 5cea6b7..6be14d33 100644
--- a/zircon/system/dev/usb/xhci/xhci-device-manager.cpp
+++ b/zircon/system/dev/usb/xhci/xhci-device-manager.cpp
@@ -93,10 +93,10 @@
             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.get(),
-        TRANSFER_RING_SIZE);
-        if (status < 0) return status;
+        status =
+            xhci_transfer_ring_init(&ep->transfer_ring, xhci->bti_handle.get(), TRANSFER_RING_SIZE);
+        if (status < 0)
+            return status;
 
         fbl::AllocChecker ac;
         ep->transfer_state = new (&ac) xhci_transfer_state_t;
@@ -791,7 +791,7 @@
         return status;
     }
 
-    zx_paddr_t tr_dequeue = xhci_transfer_ring_start_phys(&slot->eps[index].transfer_ring);
+    zx_paddr_t tr_dequeue = slot->eps[index].transfer_ring.buffers.front()->phys_list()[0];
 
     XHCI_SET_BITS32(&epc->epc0, EP_CTX_INTERVAL_START, EP_CTX_INTERVAL_BITS,
                     compute_interval(ep_desc, speed));
diff --git a/zircon/system/dev/usb/xhci/xhci-transfer-common.cpp b/zircon/system/dev/usb/xhci/xhci-transfer-common.cpp
index 3708e67..0d7ed65 100644
--- a/zircon/system/dev/usb/xhci/xhci-transfer-common.cpp
+++ b/zircon/system/dev/usb/xhci/xhci-transfer-common.cpp
@@ -12,10 +12,14 @@
 
 void xhci_print_trb(xhci_transfer_ring_t* ring, xhci_trb_t* trb) {
     size_t index = trb - ring->start;
-    uint32_t* ptr = (uint32_t *)trb;
-    uint64_t paddr = ring->buffer.phys() + index * sizeof(xhci_trb_t);
+    uint32_t* ptr = (uint32_t*)trb;
+    auto physmap = ring->virt_to_phys_map.get(VirtualAddress(reinterpret_cast<size_t>(trb)));
 
-    zxlogf(LSPEW, "trb[%03zu] %p: %08X %08X %08X %08X\n", index, (void *)paddr, ptr[0], ptr[1], ptr[2], ptr[3]);
+    uint64_t paddr =
+        physmap->second + (reinterpret_cast<size_t>(ring->current_trb) - physmap->first.virt_start);
+
+    zxlogf(LSPEW, "trb[%03zu] %p: %08X %08X %08X %08X\n", index, (void*)paddr, ptr[0], ptr[1],
+           ptr[2], ptr[3]);
 }
 
 void xhci_transfer_state_init(xhci_transfer_state_t* state, usb_request_t* req,
@@ -59,9 +63,10 @@
     zx_paddr_t paddr;
     size_t transfer_size = 0;
     bool first_packet = (state->phys_iter.total_iterated == 0);
-    while (free_trbs > 0 && (((transfer_size = usb_request_phys_iter_next(&state->phys_iter, &paddr)) > 0) ||
-                             state->needs_transfer_trb || state->needs_zlp)) {
-        xhci_trb_t* trb = ring->current;
+    while (free_trbs > 0 &&
+           (((transfer_size = usb_request_phys_iter_next(&state->phys_iter, &paddr)) > 0) ||
+            state->needs_transfer_trb || state->needs_zlp)) {
+        xhci_trb_t* trb = ring->current_trb;
         xhci_clear_trb(trb);
         XHCI_WRITE64(&trb->ptr, paddr);
         XHCI_SET_BITS32(&trb->status, XFER_TRB_XFER_LENGTH_START, XFER_TRB_XFER_LENGTH_BITS,
@@ -121,7 +126,7 @@
         }
 
         // Queue event data TRB
-        xhci_trb_t* trb = ring->current;
+        xhci_trb_t* trb = ring->current_trb;
         xhci_clear_trb(trb);
         trb->ptr = reinterpret_cast<uint64_t>(req);
         XHCI_SET_BITS32(&trb->status, XFER_TRB_INTR_TARGET_START, XFER_TRB_INTR_TARGET_BITS,
diff --git a/zircon/system/dev/usb/xhci/xhci-transfer.cpp b/zircon/system/dev/usb/xhci/xhci-transfer.cpp
index b7eb5f3..06ef856 100644
--- a/zircon/system/dev/usb/xhci/xhci-transfer.cpp
+++ b/zircon/system/dev/usb/xhci/xhci-transfer.cpp
@@ -13,6 +13,7 @@
 #include <threads.h>
 
 #include "xhci-transfer.h"
+#include "xhci-trb.h"
 #include "xhci-util.h"
 
 namespace usb_xhci {
@@ -45,7 +46,7 @@
         zxlogf(ERROR, "TRB_CMD_SET_TR_DEQUEUE failed cc: %d\n", cc);
         return ZX_ERR_INTERNAL;
     }
-    xhci_set_dequeue_ptr(transfer_ring, transfer_ring->current);
+    xhci_set_dequeue_ptr(transfer_ring, transfer_ring->current_trb);
 
     return ZX_OK;
 }
@@ -207,7 +208,7 @@
     usb_setup_t* setup = (req->header.ep_address == 0 ? &req->setup : nullptr);
     if (setup) {
         // Setup Stage
-        xhci_trb_t* trb = ring->current;
+        xhci_trb_t* trb = ring->current_trb;
         xhci_clear_trb(trb);
 
         XHCI_SET_BITS32(&trb->ptr_low, SETUP_TRB_REQ_TYPE_START, SETUP_TRB_REQ_TYPE_BITS,
@@ -293,7 +294,7 @@
         }
 
         // Status Stage
-        xhci_trb_t* trb = ring->current;
+        xhci_trb_t* trb = ring->current_trb;
         xhci_clear_trb(trb);
         XHCI_SET_BITS32(&trb->status, XFER_TRB_INTR_TARGET_START, XFER_TRB_INTR_TARGET_BITS,
                         interrupter_target);
@@ -312,7 +313,7 @@
     xhci_usb_request_internal_t* req_int = USB_REQ_TO_XHCI_INTERNAL(req);
     // if we get here, then we are ready to ring the doorbell
     // update dequeue_ptr to TRB following this transaction
-    req_int->context = ring->current;
+    req_int->context = ring->current_trb;
 
     XHCI_WRITE32(&xhci->doorbells[header->device_id], ep_index + 1);
     // it seems we need to ring the doorbell a second time when transitioning from STOPPED
@@ -643,99 +644,98 @@
     list_node_t completed_reqs = LIST_INITIAL_VALUE(completed_reqs);
     {
         fbl::AutoLock al(&ep->lock);
-
         zx_status_t result;
         switch (cc) {
-            case TRB_CC_SUCCESS:
-            case TRB_CC_SHORT_PACKET:
-                result = length;
-                break;
-            case TRB_CC_BABBLE_DETECTED_ERROR:
-                zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_BABBLE_DETECTED_ERROR\n");
-                result = ZX_ERR_IO_OVERRUN;
-                break;
-            case TRB_CC_TRB_ERROR:
-                zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_TRB_ERROR\n");
-                int ep_ctx_state;
-                ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
-                /*
-                 * For usb-c ethernet adapters on Intel xhci controller, we receive this error
-                 * when a packet fails with NRDY token on the bus.see NET:97 for more details.
-                 * Slow down the requests in the client when this error is received.
-                 */
-                if (ep_ctx_state == EP_CTX_STATE_ERROR) {
-                    result = ZX_ERR_IO_INVALID;
-                } else {
-                    result = ZX_ERR_IO;
-                }
-                break;
-            case TRB_CC_USB_TRANSACTION_ERROR:
-            case TRB_CC_STALL_ERROR: {
-                int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
-                zxlogf(TRACE, "xhci_handle_transfer_event: cc %d ep_ctx_state %d\n", cc,
-                       ep_ctx_state);
-                if (ep_ctx_state == EP_CTX_STATE_HALTED) {
-                    result = ZX_ERR_IO_REFUSED;
-                } else {
-                    result = ZX_ERR_IO;
-                }
-                break;
+        case TRB_CC_SUCCESS:
+        case TRB_CC_SHORT_PACKET:
+            result = length;
+            break;
+        case TRB_CC_BABBLE_DETECTED_ERROR:
+            zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_BABBLE_DETECTED_ERROR\n");
+            result = ZX_ERR_IO_OVERRUN;
+            break;
+        case TRB_CC_TRB_ERROR:
+            zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_TRB_ERROR\n");
+            int ep_ctx_state;
+            ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
+            /*
+             * For usb-c ethernet adapters on Intel xhci controller, we receive this error
+             * when a packet fails with NRDY token on the bus.see NET:97 for more details.
+             * Slow down the requests in the client when this error is received.
+             */
+            if (ep_ctx_state == EP_CTX_STATE_ERROR) {
+                result = ZX_ERR_IO_INVALID;
+            } else {
+                result = ZX_ERR_IO;
             }
-            case TRB_CC_RING_UNDERRUN:
-                // non-fatal error that happens when no transfers are available for isochronous
-                // endpoint
-                zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_RING_UNDERRUN\n");
-                return;
-            case TRB_CC_RING_OVERRUN:
-                // non-fatal error that happens when no transfers are available for isochronous
-                // endpoint
-                zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_RING_OVERRUN\n");
-                return;
-           case TRB_CC_MISSED_SERVICE_ERROR:
-                zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_MISSED_SERVICE_ERROR\n");
-                result = ZX_ERR_IO_MISSED_DEADLINE;
-                break;
-            case TRB_CC_STOPPED:
-            case TRB_CC_STOPPED_LENGTH_INVALID:
-            case TRB_CC_STOPPED_SHORT_PACKET:
-            case TRB_CC_ENDPOINT_NOT_ENABLED_ERROR:
-                switch (ep->state) {
-                case EP_STATE_PAUSED:
-                    result = ZX_ERR_CANCELED;
-                    break;
-                case EP_STATE_DISABLED:
-                    result = ZX_ERR_BAD_STATE;
-                    break;
-                case EP_STATE_DEAD:
-                    result = ZX_ERR_IO_NOT_PRESENT;
-                    break;
-                default:
-                    zxlogf(ERROR, "xhci_handle_transfer_event: bad state for stopped req: %d\n",
-                           ep->state);
-                    result = ZX_ERR_INTERNAL;
-                }
-                break;
-            default: {
-                int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
-                zxlogf(ERROR, "xhci_handle_transfer_event: unhandled transfer event condition"
-                       " code %d  ep_ctx_state %d:  %08X %08X %08X %08X\n", cc, ep_ctx_state,
-                        ((uint32_t*)trb)[0], ((uint32_t*)trb)[1], ((uint32_t*)trb)[2],
-                        ((uint32_t*)trb)[3]);
-                if (ep_ctx_state == EP_CTX_STATE_HALTED) {
-                    result = ZX_ERR_IO_REFUSED;
-                } else if (ep_ctx_state == EP_CTX_STATE_ERROR) {
-                    result = ZX_ERR_IO_INVALID;
-                } else {
-                    result = ZX_ERR_IO;
-                }
-                break;
+            break;
+        case TRB_CC_USB_TRANSACTION_ERROR:
+        case TRB_CC_STALL_ERROR: {
+            int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
+            zxlogf(TRACE, "xhci_handle_transfer_event: cc %d ep_ctx_state %d\n", cc, ep_ctx_state);
+            if (ep_ctx_state == EP_CTX_STATE_HALTED) {
+                result = ZX_ERR_IO_REFUSED;
+            } else {
+                result = ZX_ERR_IO;
             }
+            break;
+        }
+        case TRB_CC_RING_UNDERRUN:
+            // non-fatal error that happens when no transfers are available for isochronous
+            // endpoint
+            zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_RING_UNDERRUN\n");
+            return;
+        case TRB_CC_RING_OVERRUN:
+            // non-fatal error that happens when no transfers are available for isochronous
+            // endpoint
+            zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_RING_OVERRUN\n");
+            return;
+        case TRB_CC_MISSED_SERVICE_ERROR:
+            zxlogf(TRACE, "xhci_handle_transfer_event: TRB_CC_MISSED_SERVICE_ERROR\n");
+            result = ZX_ERR_IO_MISSED_DEADLINE;
+            break;
+        case TRB_CC_STOPPED:
+        case TRB_CC_STOPPED_LENGTH_INVALID:
+        case TRB_CC_STOPPED_SHORT_PACKET:
+        case TRB_CC_ENDPOINT_NOT_ENABLED_ERROR:
+            switch (ep->state) {
+            case EP_STATE_PAUSED:
+                result = ZX_ERR_CANCELED;
+                break;
+            case EP_STATE_DISABLED:
+                result = ZX_ERR_BAD_STATE;
+                break;
+            case EP_STATE_DEAD:
+                result = ZX_ERR_IO_NOT_PRESENT;
+                break;
+            default:
+                zxlogf(ERROR, "xhci_handle_transfer_event: bad state for stopped req: %d\n",
+                       ep->state);
+                result = ZX_ERR_INTERNAL;
+            }
+            break;
+        default: {
+            int ep_ctx_state = xhci_get_ep_ctx_state(slot, ep);
+            zxlogf(ERROR,
+                   "xhci_handle_transfer_event: unhandled transfer event condition"
+                   " code %d  ep_ctx_state %d:  %08X %08X %08X %08X\n",
+                   cc, ep_ctx_state, ((uint32_t*)trb)[0], ((uint32_t*)trb)[1], ((uint32_t*)trb)[2],
+                   ((uint32_t*)trb)[3]);
+            if (ep_ctx_state == EP_CTX_STATE_HALTED) {
+                result = ZX_ERR_IO_REFUSED;
+            } else if (ep_ctx_state == EP_CTX_STATE_ERROR) {
+                result = ZX_ERR_IO_INVALID;
+            } else {
+                result = ZX_ERR_IO;
+            }
+            break;
+        }
         }
 
         bool req_status_set = false;
 
-        if (trb->ptr && !list_is_empty(&ep->pending_reqs)
-                && ep->state != EP_STATE_DISABLED && ep->state != EP_STATE_DEAD) {
+        if (trb->ptr && !list_is_empty(&ep->pending_reqs) && ep->state != EP_STATE_DISABLED &&
+            ep->state != EP_STATE_DEAD) {
             if (control & EVT_TRB_ED) {
                 req = reinterpret_cast<usb_request_t*>(trb->ptr);
                 if (ep_index == 0) {
@@ -808,7 +808,7 @@
         }
         if (!found_req) {
             zxlogf(TRACE, "xhci_handle_transfer_event: ignoring transfer event for completed "
-                   "transfer\n");
+                          "transfer\n");
             return;
         }
 
diff --git a/zircon/system/dev/usb/xhci/xhci-trb.cpp b/zircon/system/dev/usb/xhci/xhci-trb.cpp
index e9db845..8c53050 100644
--- a/zircon/system/dev/usb/xhci/xhci-trb.cpp
+++ b/zircon/system/dev/usb/xhci/xhci-trb.cpp
@@ -3,38 +3,66 @@
 // found in the LICENSE file.
 
 #include <assert.h>
+#include <fbl/auto_lock.h>
 #include <hw/arch_ops.h>
 
+#include <bits/limits.h>
 #include "xhci.h"
 
 namespace usb_xhci {
 
 zx_status_t xhci_transfer_ring_init(xhci_transfer_ring_t* ring, zx_handle_t bti_handle, int count) {
-    zx_status_t status = ring->buffer.Init(bti_handle, count * sizeof(xhci_trb_t),
-                                           IO_BUFFER_RW | IO_BUFFER_CONTIG |
-                                           XHCI_IO_BUFFER_UNCACHED);
-    if (status != ZX_OK) return status;
-
-    ring->start = static_cast<xhci_trb_t*>(ring->buffer.virt());
-    ring->current = ring->start;
+    fbl::AllocChecker ac;
+    ddk::IoBuffer buffer;
+    zx_status_t status =
+        buffer.Init(bti_handle, count * sizeof(xhci_trb_t), IO_BUFFER_RW | XHCI_IO_BUFFER_UNCACHED);
+    if (status != ZX_OK)
+        return status;
+    status = buffer.PhysMap();
+    if (status != ZX_OK)
+        return status;
+    std::unique_ptr<IoBufferContainer> container(new (&ac) IoBufferContainer(std::move(buffer)));
+    if (!ac.check()) {
+        return ZX_ERR_NO_MEMORY;
+    }
+    size_t phys_count = (*container)->phys_count();
+    const zx_paddr_t* sg_list = (*container)->phys_list();
+    ring->start = static_cast<xhci_trb_t*>((*container)->virt());
+    ring->current_trb = ring->start;
     ring->dequeue_ptr = ring->start;
     ring->full = false;
-    ring->size = count - 1;    // subtract 1 for LINK TRB at the end
+    ring->size = count - phys_count; // subtract 1 for LINK TRB at the end
     ring->pcs = TRB_C;
-
     // set link TRB at end to point back to the beginning
-    ring->start[count - 1].ptr = ring->buffer.phys();
+    ring->start[count - 1].ptr = sg_list[0];
     trb_set_control(&ring->start[count - 1], TRB_LINK, TRB_TC);
+    for (size_t i = 0; i < phys_count; i++) {
+        if (i + 1 < phys_count) {
+            xhci_trb_t* trb =
+                reinterpret_cast<xhci_trb_t*>(reinterpret_cast<size_t>(ring->start) +
+                                              (i * PAGE_SIZE) + (PAGE_SIZE - sizeof(xhci_trb_t)));
+            XHCI_WRITE64(&trb->ptr, sg_list[i + 1]);
+            XHCI_WRITE32(&trb->control, TRB_LINK << TRB_TYPE_START);
+        }
+        VirtualAddress address_mapping(reinterpret_cast<size_t>((*container)->virt()) +
+                                       (PAGE_SIZE * i));
+        address_mapping.phys_start = sg_list[i];
+        ring->virt_to_phys_map[address_mapping] = address_mapping.phys_start;
+        ring->phys_to_virt_map[address_mapping.phys_start / PAGE_SIZE] = address_mapping.virt_start;
+    }
+    ring->buffers.push_back(std::move(container));
     return ZX_OK;
 }
 
 void xhci_transfer_ring_free(xhci_transfer_ring_t* ring) {
-    ring->buffer.release();
+    ring->buffers.clear();
+    ring->virt_to_phys_map.clear();
+    ring->phys_to_virt_map.clear();
 }
 
 // return the number of free TRBs in the ring
 size_t xhci_transfer_ring_free_trbs(xhci_transfer_ring_t* ring) {
-    xhci_trb_t* current = ring->current;
+    xhci_trb_t* current = ring->current_trb;
     xhci_trb_t* dequeue_ptr = ring->dequeue_ptr;
 
     if (ring->full) {
@@ -55,23 +83,41 @@
 zx_status_t xhci_event_ring_init(xhci_event_ring_t* ring, zx_handle_t bti_handle,
                                  erst_entry_t* erst_array, int count) {
     // allocate a read-only buffer for TRBs
-    zx_status_t status = ring->buffer.Init(bti_handle, count * sizeof(xhci_trb_t),
-                                           IO_BUFFER_RO | IO_BUFFER_CONTIG |
-                                           XHCI_IO_BUFFER_UNCACHED);
-    if (status != ZX_OK) return status;
-
-    ring->start = static_cast<xhci_trb_t*>(ring->buffer.virt());
-    XHCI_WRITE64(&erst_array[0].ptr, ring->buffer.phys());
-    XHCI_WRITE32(&erst_array[0].size, count);
-
+    fbl::AllocChecker ac;
+    ddk::IoBuffer buffer;
+    zx_status_t status =
+        buffer.Init(bti_handle, count * sizeof(xhci_trb_t), IO_BUFFER_RW | XHCI_IO_BUFFER_UNCACHED);
+    if (status != ZX_OK)
+        return status;
+    status = buffer.PhysMap();
+    if (status != ZX_OK)
+        return status;
+    size_t phys_count = buffer.phys_count();
+    const zx_paddr_t* sg_list = buffer.phys_list();
+    std::unique_ptr<IoBufferContainer> container(new (&ac) IoBufferContainer(std::move(buffer)));
+    if (!ac.check()) {
+        return ZX_ERR_NO_MEMORY;
+    }
+    ring->start = static_cast<xhci_trb_t*>((*container)->virt());
     ring->current = ring->start;
     ring->end = ring->start + count;
     ring->ccs = TRB_C;
+    for (size_t i = 0; i < phys_count; i++) {
+        VirtualAddress address_mapping(reinterpret_cast<size_t>((*container)->virt()) +
+                                       (PAGE_SIZE * i));
+        address_mapping.phys_start = sg_list[i];
+        ring->virt_to_phys_map[address_mapping] = address_mapping.phys_start;
+        ring->phys_to_virt_map[address_mapping.phys_start / PAGE_SIZE] = address_mapping.virt_start;
+        XHCI_WRITE64(&erst_array[i].ptr, sg_list[i]);
+        XHCI_WRITE32(&erst_array[i].size, PAGE_SIZE / sizeof(xhci_trb_t));
+    }
+    ring->buffers.push_back(std::move(container));
     return ZX_OK;
 }
 
 void xhci_event_ring_free(xhci_event_ring_t* ring) {
-    ring->buffer.release();
+    ring->buffers.clear();
+    ring->virt_to_phys_map.clear();
 }
 
 void xhci_clear_trb(xhci_trb_t* trb) {
@@ -95,10 +141,12 @@
 xhci_trb_t* xhci_read_trb_ptr(xhci_transfer_ring_t* ring, xhci_trb_t* trb) {
     // convert physical address to virtual
     uintptr_t ptr = trb->ptr;
-    ptr += (reinterpret_cast<uintptr_t>(ring->buffer.virt()) - ring->buffer.phys());
-    return reinterpret_cast<xhci_trb_t*>(ptr);
+    return reinterpret_cast<xhci_trb_t*>(ring->phys_to_virt_map[ptr / PAGE_SIZE] +
+                                         (ptr % PAGE_SIZE));
 }
-
+xhci_trb_t* xhci_next_evt(xhci_event_ring_t* ring, xhci_trb_t* trb) {
+    return trb + 1;
+}
 xhci_trb_t* xhci_get_next_trb(xhci_transfer_ring_t* ring, xhci_trb_t* trb) {
     trb++;
     uint32_t control = XHCI_READ32(&trb->control);
@@ -109,14 +157,13 @@
 }
 
 void xhci_increment_ring(xhci_transfer_ring_t* ring) {
-    xhci_trb_t* trb = ring->current;
+    xhci_trb_t* trb = ring->current_trb;
     uint32_t control = XHCI_READ32(&trb->control);
     uint32_t chain = control & TRB_CHAIN;
     if (ring->pcs) {
         XHCI_WRITE32(&trb->control, control | ring->pcs);
     }
-    trb = ++ring->current;
-
+    trb = ++ring->current_trb;
     // check for LINK TRB
     control = XHCI_READ32(&trb->control);
     if ((control & TRB_TYPE_MASK) == (TRB_LINK << TRB_TYPE_START)) {
@@ -127,10 +174,10 @@
         if (control & TRB_TC) {
             ring->pcs ^= TRB_C;
         }
-        ring->current = xhci_read_trb_ptr(ring, trb);
+        ring->current_trb = xhci_read_trb_ptr(ring, trb);
     }
 
-    if (ring->current == ring->dequeue_ptr) {
+    if (ring->current_trb == ring->dequeue_ptr) {
         // We've just enqueued something, so if the pointers are equal,
         // the ring must be full.
         ring->full = true;
@@ -143,15 +190,8 @@
 }
 
 xhci_trb_t* xhci_transfer_ring_phys_to_trb(xhci_transfer_ring_t* ring, zx_paddr_t phys) {
-    zx_paddr_t first_trb_phys = xhci_transfer_ring_start_phys(ring);
-    // Get the physical address of the start of the last trb,
-    // ring->size does not include the LINK TRB at the end of the ring.
-    zx_paddr_t last_trb_phys = first_trb_phys + (ring->size * sizeof(xhci_trb_t));
-
-    if (phys < first_trb_phys || phys > last_trb_phys) {
-        return nullptr;
-    }
-   return ring->start + ((phys - first_trb_phys) / sizeof(xhci_trb_t));
+    return reinterpret_cast<xhci_trb_t*>(ring->phys_to_virt_map[phys / PAGE_SIZE] +
+                                         (phys % PAGE_SIZE));
 }
 
 } // namespace usb_xhci
diff --git a/zircon/system/dev/usb/xhci/xhci-trb.h b/zircon/system/dev/usb/xhci/xhci-trb.h
index e009067..f407837 100644
--- a/zircon/system/dev/usb/xhci/xhci-trb.h
+++ b/zircon/system/dev/usb/xhci/xhci-trb.h
@@ -4,18 +4,100 @@
 
 #pragma once
 
-#include <ddk/io-buffer.h>
-#include <zircon/listnode.h>
-
+#include <bits/limits.h>
+#include "trb-sizes.h"
 #include "xhci-hw.h"
+#include <ddk/io-buffer.h>
+#include <fbl/intrusive_double_list.h>
+#include <fbl/mutex.h>
+#include <memory>
+#include <zircon/listnode.h>
 
 namespace usb_xhci {
 
+class IoBufferContainer;
+// IO Buffer Container to allow nesting an IoBuffer
+// in an intrusive doubly-linked list.
+class IoBufferContainer : public fbl::DoublyLinkedListable<std::unique_ptr<IoBufferContainer>> {
+public:
+    IoBufferContainer(ddk::IoBuffer buffer) : buffer_(std::move(buffer)) {}
+    const ddk::IoBuffer* operator->() const { return &buffer_; }
+    const ddk::IoBuffer& operator*() { return buffer_; }
+
+private:
+    ddk::IoBuffer buffer_;
+};
+
+// Represents a virtual memory address mapping.
+// Contains information about the virtual range,
+// and physical starting address for contiguous mappings.
+struct VirtualAddress {
+    VirtualAddress() {}
+    VirtualAddress(zx_vaddr_t virt_start, size_t virt_end)
+        : virt_start(virt_start), virt_end(virt_end) {}
+    VirtualAddress(size_t virt_start)
+        : virt_start(virt_start), virt_end((virt_start + PAGE_SIZE) - 1) {}
+    bool operator<(const VirtualAddress& other) const {
+        return (virt_start / PAGE_SIZE) < (other.virt_start / PAGE_SIZE);
+    }
+    bool operator==(const VirtualAddress& other) const {
+        return (virt_start / PAGE_SIZE) == (other.virt_start / PAGE_SIZE);
+    }
+    size_t GetKey() const { return virt_start / PAGE_SIZE; }
+    zx_vaddr_t virt_start = 0;
+    zx_vaddr_t virt_end = 0;
+    size_t phys_start = 0;
+};
+
+// Constant-size map implementation with O(n) lookup time,
+// and O(1) insertion time. Maintains a mapping of Keys to Values.
+template <typename Key, typename Value, size_t Count> class XhciMap {
+public:
+    XhciMap() {}
+    // Retrieves a given key, or creates one if it doesn't already exist
+    // Asserts if the number of keys exceeds the statically-allocated buffer
+    // size.
+    Value& operator[](const Key& key) {
+        for (size_t i = 0; i < len_; i++) {
+            if (data_[i].first == key) {
+                return data_[i].second;
+            }
+        }
+        assert(len_ < Count);
+        data_[len_].first = key;
+        return data_[len_++].second;
+    }
+    // Retrieves the std::pair<Key, Value> for a given Key.
+    // Returns nullptr if the key is not found in the map.
+    std::pair<Key, Value>* get(const Key& key) {
+        for (size_t i = 0; i < len_; i++) {
+            if (data_[i].first == key) {
+                return data_ + i;
+            }
+        }
+        return nullptr;
+    }
+    // Removes all entries from this map
+    void clear() {
+        for (size_t i = 0; i < len_; i++) {
+            data_[i] = {};
+        }
+        len_ = 0;
+    }
+
+private:
+    size_t len_ = 0;
+    std::pair<Key, Value> data_[Count];
+};
+
 // used for both command ring and transfer rings
 struct xhci_transfer_ring_t {
-    ddk::IoBuffer buffer;
+    fbl::DoublyLinkedList<std::unique_ptr<IoBufferContainer>> buffers;
+    XhciMap<VirtualAddress, uint64_t, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> virt_to_phys_map;
+    // Map of physical page indices to virtual addresses
+    XhciMap<zx_paddr_t, zx_vaddr_t, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> phys_to_virt_map;
     xhci_trb_t* start;
-    xhci_trb_t* current;        // next to be filled by producer
+    xhci_trb_t* current_trb;    // next to be filled by producer
     uint8_t pcs;                // producer cycle status
     xhci_trb_t* dequeue_ptr;    // next to be processed by consumer
                                 // (not used for command ring)
@@ -23,14 +105,25 @@
     bool full;                  // true if there are no available TRBs,
                                 // this is needed to differentiate between
                                 // an empty and full ring state
+    fbl::Mutex xfer_lock;
+};
+
+struct EventMapping {
+    uint64_t phys = 0;
+    xhci_trb_t* next = nullptr;
+    EventMapping() {}
+    EventMapping(uint64_t phys) : phys(phys) {}
 };
 
 struct xhci_event_ring_t {
-    ddk::IoBuffer buffer;
+    fbl::DoublyLinkedList<std::unique_ptr<IoBufferContainer>> buffers;
+    XhciMap<VirtualAddress, EventMapping, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> virt_to_phys_map;
+    XhciMap<zx_paddr_t, zx_vaddr_t, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> phys_to_virt_map;
     xhci_trb_t* start;
     xhci_trb_t* current;
     xhci_trb_t* end;
     uint8_t ccs; // consumer cycle status
+    fbl::Mutex xfer_lock;
 };
 
 zx_status_t xhci_transfer_ring_init(xhci_transfer_ring_t* tr, zx_handle_t bti_handle, int count);
@@ -43,27 +136,37 @@
 // Converts a transfer trb into a NO-OP transfer TRB, does nothing if it is the LINK TRB.
 void xhci_set_transfer_noop_trb(xhci_trb_t* trb);
 xhci_trb_t* xhci_read_trb_ptr(xhci_transfer_ring_t* ring, xhci_trb_t* trb);
+xhci_trb_t* xhci_next_evt(xhci_event_ring_t* ring, xhci_trb_t* trb);
 xhci_trb_t* xhci_get_next_trb(xhci_transfer_ring_t* ring, xhci_trb_t* trb);
 void xhci_increment_ring(xhci_transfer_ring_t* ring);
 void xhci_set_dequeue_ptr(xhci_transfer_ring_t* ring, xhci_trb_t* new_ptr);
 
-// Returns the TRB corresponding to the given physical address, or nullptr if the address is invalid.
+// Returns the TRB corresponding to the given physical address, or nullptr if the address is
+// invalid.
 xhci_trb_t* xhci_transfer_ring_phys_to_trb(xhci_transfer_ring_t* ring, zx_paddr_t phys);
 
-static inline zx_paddr_t xhci_transfer_ring_start_phys(xhci_transfer_ring_t* ring) {
-    return ring->buffer.phys();
-}
-
 static inline zx_paddr_t xhci_transfer_ring_current_phys(xhci_transfer_ring_t* ring) {
-    return ring->buffer.phys() + ((ring->current - ring->start) * sizeof(xhci_trb_t));
-}
-
-static inline zx_paddr_t xhci_event_ring_start_phys(xhci_event_ring_t* ring) {
-    return ring->buffer.phys();
+    auto physmap =
+        ring->virt_to_phys_map.get(VirtualAddress(reinterpret_cast<size_t>(ring->current_trb)));
+    if (physmap == nullptr) {
+        abort();
+    }
+    return physmap->second +
+           (reinterpret_cast<size_t>(ring->current_trb) - physmap->first.virt_start);
 }
 
 static inline zx_paddr_t xhci_event_ring_current_phys(xhci_event_ring_t* ring) {
-    return ring->buffer.phys() + ((ring->current - ring->start) * sizeof(xhci_trb_t));
+    auto physmap =
+        ring->virt_to_phys_map.get(VirtualAddress(reinterpret_cast<size_t>(ring->current)));
+    if (physmap == nullptr) {
+        abort();
+    }
+    return physmap->second.phys +
+           (reinterpret_cast<size_t>(ring->current) - physmap->first.virt_start);
 }
+struct xhci_t;
+// Enlarges the XHCI rings. The caller must ensure exclusive ownership of the rings
+// before invoking this function. Refer to XHCI 4.9.2.3
+zx_status_t xhci_enlarge_ring(xhci_t* xhci, xhci_transfer_ring_t* transfer);
 
 } // namespace usb_xhci
diff --git a/zircon/system/dev/usb/xhci/xhci.cpp b/zircon/system/dev/usb/xhci/xhci.cpp
index b3e859c..afaf33d 100644
--- a/zircon/system/dev/usb/xhci/xhci.cpp
+++ b/zircon/system/dev/usb/xhci/xhci.cpp
@@ -5,25 +5,28 @@
 #include "xhci.h"
 
 #include <ddk/debug.h>
-#include <hw/arch_ops.h>
 #include <fbl/alloc_checker.h>
 #include <fbl/auto_lock.h>
+#include <hw/arch_ops.h>
 #include <hw/reg.h>
-#include <zircon/types.h>
-#include <zircon/syscalls.h>
-#include <zircon/process.h>
-#include <usb/usb-request.h>
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <threads.h>
 #include <unistd.h>
+#include <usb/usb-request.h>
+#include <zircon/process.h>
+#include <zircon/syscalls.h>
+#include <zircon/types.h>
 
+#include <bits/limits.h>
+#include <ddk/io-buffer.h>
 #include "xhci-device-manager.h"
 #include "xhci-root-hub.h"
 #include "xhci-transfer.h"
 #include "xhci-util.h"
+#include <zircon/errors.h>
 
 namespace usb_xhci {
 
@@ -179,8 +182,15 @@
     uint32_t scratch_pad_bufs = XHCI_GET_BITS32(hcsparams2, HCSPARAMS2_MAX_SBBUF_HI_START,
                                                 HCSPARAMS2_MAX_SBBUF_HI_BITS);
     scratch_pad_bufs <<= HCSPARAMS2_MAX_SBBUF_LO_BITS;
-    scratch_pad_bufs |= XHCI_GET_BITS32(hcsparams2, HCSPARAMS2_MAX_SBBUF_LO_START,
-                                        HCSPARAMS2_MAX_SBBUF_LO_BITS);
+    scratch_pad_bufs |=
+        XHCI_GET_BITS32(hcsparams2, HCSPARAMS2_MAX_SBBUF_LO_START, HCSPARAMS2_MAX_SBBUF_LO_BITS);
+    uint32_t max_erst_count =
+        1 << XHCI_GET_BITS32(hcsparams2, HCSPARAMS2_ERST_MAX_START, HCSPARAMS2_ERST_MAX_BITS);
+    if (max_erst_count * sizeof(erst_entry_t) > PAGE_SIZE) {
+        max_erst_count = PAGE_SIZE / sizeof(erst_entry_t);
+        // TODO(bbosak): Implement a mechanism to allocate many physically contiguous pages
+        // reliably.
+    }
     xhci->page_size = XHCI_READ32(&xhci->op_regs->pagesize) << 12;
 
     fbl::AllocChecker ac;
@@ -265,28 +275,21 @@
     xhci->input_context = static_cast<uint8_t*>(xhci->input_context_buffer.virt());
     xhci->input_context_phys = xhci->input_context_buffer.phys();
 
-    // DCBAA can only be 256 * sizeof(uint64_t) = 2048 bytes, so we have room for ERST array after DCBAA
-    zx_off_t erst_offset;
-    erst_offset = 256 * sizeof(uint64_t);
-
-    size_t array_bytes;
-    array_bytes = ERST_ARRAY_SIZE * sizeof(erst_entry_t);
-    // MSI only supports up to 32 interrupts, so the required ERST arrays will fit
-    // within the page. Potentially more pages will need to be allocated for MSI-X.
+    // DCBAA can only be 256 * sizeof(uint64_t) = 2048 bytes, so we have room for ERST array after
+    // DCBAA MSI only supports up to 32 interrupts, so the required ERST arrays will fit within the
+    // page. Potentially more pages will need to be allocated for MSI-X. Max number of contiguous
+    // physical pages per interrupt: 8
     for (uint32_t i = 0; i < xhci->num_interrupts; i++) {
-        // Ran out of space in page.
-        if (erst_offset + array_bytes > PAGE_SIZE) {
-            zxlogf(ERROR, "only have space for %u ERST arrays, want %u\n", i,
-                    xhci->num_interrupts);
+        result = xhci->erst_buffers[i].Init(
+            xhci->bti_handle.get(), PAGE_ROUNDUP(max_erst_count * sizeof(erst_entry_t)),
+            IO_BUFFER_RW | IO_BUFFER_CONTIG | XHCI_IO_BUFFER_UNCACHED);
+        if (result != ZX_OK) {
+            zxlogf(ERROR, "io_buffer_init failed for xhci->erst_buffer\n");
             goto fail;
         }
-        xhci->erst_arrays[i] = reinterpret_cast<erst_entry_t*>(
-                                    reinterpret_cast<uintptr_t>(xhci->dcbaa) + erst_offset);
-        xhci->erst_arrays_phys[i] = xhci->dcbaa_phys + erst_offset;
-        // ERST arrays must be 64 byte aligned - see Table 54 in XHCI spec.
-        // dcbaa_phys is already page (and hence 64 byte) aligned, so only
-        // need to round the offset.
-        erst_offset = ROUNDUP_TO(erst_offset + array_bytes, 64);
+        xhci->erst_sizes[i] = max_erst_count;
+        xhci->erst_arrays[i] = reinterpret_cast<erst_entry_t*>(xhci->erst_buffers[i].virt());
+        xhci->erst_arrays_phys[i] = xhci->erst_buffers[i].phys();
     }
 
     if (scratch_pad_bufs > 0) {
@@ -339,7 +342,6 @@
             ep->current_req = nullptr;
         }
     }
-
     // initialize virtual root hub devices
     for (int i = 0; i < XHCI_RH_COUNT; i++) {
         result = xhci_root_hub_init(xhci, i);
@@ -439,7 +441,7 @@
     // setup operational registers
     xhci_op_regs_t* op_regs = xhci->op_regs;
     // initialize command ring
-    uint64_t crcr = xhci_transfer_ring_start_phys(&xhci->command_ring);
+    uint64_t crcr = xhci->command_ring.buffers.front()->phys_list()[0];
     if (xhci->command_ring.pcs) {
         crcr |= CRCR_RCS;
     }
@@ -533,7 +535,7 @@
         return ZX_ERR_NO_RESOURCES;
     }
 
-    xhci_trb_t* trb = cr->current;
+    xhci_trb_t* trb = cr->current_trb;
     auto index = trb - cr->start;
     xhci->command_contexts[index] = context;
 
@@ -542,7 +544,7 @@
     trb_set_control(trb, command, control_bits);
 
     xhci_increment_ring(cr);
-    context->next_trb = cr->current;
+    context->next_trb = cr->current_trb;
 
     hw_mb();
     XHCI_WRITE32(&xhci->doorbells[0], 0);
@@ -600,7 +602,6 @@
 
 static void xhci_handle_events(xhci_t* xhci, int interrupter) {
     xhci_event_ring_t* er = &xhci->event_rings[interrupter];
-
     // process all TRBs with cycle bit matching our CCS
     while ((XHCI_READ32(&er->current->control) & TRB_C) == er->ccs) {
         uint32_t type = trb_get_type(er->current);
@@ -621,12 +622,17 @@
             zxlogf(ERROR, "xhci_handle_events: unhandled event type %d\n", type);
             break;
         }
-
-        er->current++;
-        if (er->current == er->end) {
+        fbl::AutoLock l(&er->xfer_lock);
+        if (er->current + 1 == er->end) {
             er->current = er->start;
             er->ccs ^= TRB_C;
+        } else {
+            er->current = xhci_next_evt(er, er->current);
         }
+        xhci_intr_regs_t* intr_regs = &xhci->runtime_regs->intr_regs[interrupter];
+        // Update the dequeue pointer to allow other events to come in
+        uint64_t erdp = xhci_event_ring_current_phys(er);
+        XHCI_WRITE64(&intr_regs->erdp, erdp);
     }
 
     // update event ring dequeue pointer and clear event handler busy flag
@@ -637,7 +643,6 @@
     // clear the interrupt pending flag
     xhci_intr_regs_t* intr_regs = &xhci->runtime_regs->intr_regs[interrupter];
     XHCI_WRITE32(&intr_regs->iman, IMAN_IE | IMAN_IP);
-
     xhci_handle_events(xhci, interrupter);
 }
 
diff --git a/zircon/system/dev/usb/xhci/xhci.h b/zircon/system/dev/usb/xhci/xhci.h
index 18c1154..4d25580 100644
--- a/zircon/system/dev/usb/xhci/xhci.h
+++ b/zircon/system/dev/usb/xhci/xhci.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <ddk/device.h>
+#include <ddk/io-buffer.h>
 #include <ddk/protocol/pci.h>
 #include <ddk/protocol/usb/bus.h>
 #include <ddk/protocol/usb/request.h>
@@ -24,6 +25,7 @@
 #include <limits.h>
 #include <threads.h>
 
+#include "trb-sizes.h"
 #include "xhci-hw.h"
 #include "xhci-root-hub.h"
 #include "xhci-transfer-common.h"
@@ -31,12 +33,6 @@
 
 namespace usb_xhci {
 
-// choose ring sizes to allow each ring to fit in a single page
-#define COMMAND_RING_SIZE (PAGE_SIZE / sizeof(xhci_trb_t))
-#define TRANSFER_RING_SIZE (PAGE_SIZE / sizeof(xhci_trb_t))
-#define EVENT_RING_SIZE (PAGE_SIZE / sizeof(xhci_trb_t))
-#define ERST_ARRAY_SIZE 1
-
 #define XHCI_RH_USB_2 0 // index of USB 2.0 virtual root hub device
 #define XHCI_RH_USB_3 1 // index of USB 2.0 virtual root hub device
 #define XHCI_RH_COUNT 2 // number of virtual root hub devices
@@ -154,7 +150,7 @@
 
     // Each interrupter has an event ring.
     // Only indices up to num_interrupts will be populated.
-    xhci_event_ring_t event_rings[INTERRUPTER_COUNT] = {};
+    xhci_event_ring_t event_rings[INTERRUPTER_COUNT];
     erst_entry_t* erst_arrays[INTERRUPTER_COUNT] = {};
     zx_paddr_t erst_arrays_phys[INTERRUPTER_COUNT] = {};
 
@@ -203,6 +199,8 @@
 
     // VMO buffer for DCBAA and ERST array
     ddk::IoBuffer dcbaa_erst_buffer;
+    ddk::IoBuffer erst_buffers[INTERRUPTER_COUNT];
+    size_t erst_sizes[INTERRUPTER_COUNT];
     // VMO buffer for input context
     ddk::IoBuffer input_context_buffer;
     // VMO buffer for scratch pad pages
diff --git a/zircon/system/ulib/ddk/include/ddk/io-buffer.h b/zircon/system/ulib/ddk/include/ddk/io-buffer.h
index 9eeacf4..51f5287 100644
--- a/zircon/system/ulib/ddk/include/ddk/io-buffer.h
+++ b/zircon/system/ulib/ddk/include/ddk/io-buffer.h
@@ -122,14 +122,18 @@
 class IoBuffer {
 public:
     IoBuffer() {}
-
-    ~IoBuffer() {
-        io_buffer_release(&io_buffer_);
+    IoBuffer(IoBuffer&& other) {
+        io_buffer_ = other.io_buffer_;
+        other.io_buffer_ = {};
     }
-
-    inline void release() {
-        io_buffer_release(&io_buffer_);
+    IoBuffer& operator=(IoBuffer&& other) {
+        io_buffer_ = other.io_buffer_;
+        other.io_buffer_ = {};
+        return *this;
     }
+    ~IoBuffer() { io_buffer_release(&io_buffer_); }
+
+    inline void release() { io_buffer_release(&io_buffer_); }
 
     inline zx_status_t Init(zx_handle_t bti, size_t size, uint32_t flags) {
         return io_buffer_init(&io_buffer_, bti, size, flags);