aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/host/xhci-mem.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host/xhci-mem.c')
-rw-r--r--drivers/usb/host/xhci-mem.c76
1 files changed, 63 insertions, 13 deletions
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 9034721106d7..bffcef7a5545 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -125,6 +125,23 @@ void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring)
kfree(ring);
}
+static void xhci_initialize_ring_info(struct xhci_ring *ring)
+{
+ /* The ring is empty, so the enqueue pointer == dequeue pointer */
+ ring->enqueue = ring->first_seg->trbs;
+ ring->enq_seg = ring->first_seg;
+ ring->dequeue = ring->enqueue;
+ ring->deq_seg = ring->first_seg;
+ /* The ring is initialized to 0. The producer must write 1 to the cycle
+ * bit to handover ownership of the TRB, so PCS = 1. The consumer must
+ * compare CCS to the cycle bit to check ownership, so CCS = 1.
+ */
+ ring->cycle_state = 1;
+ /* Not necessary for new rings, but needed for re-initialized rings */
+ ring->enq_updates = 0;
+ ring->deq_updates = 0;
+}
+
/**
* Create a new ring with zero or more segments.
*
@@ -173,17 +190,7 @@ static struct xhci_ring *xhci_ring_alloc(struct xhci_hcd *xhci,
" segment %p (virtual), 0x%llx (DMA)\n",
prev, (unsigned long long)prev->dma);
}
- /* The ring is empty, so the enqueue pointer == dequeue pointer */
- ring->enqueue = ring->first_seg->trbs;
- ring->enq_seg = ring->first_seg;
- ring->dequeue = ring->enqueue;
- ring->deq_seg = ring->first_seg;
- /* The ring is initialized to 0. The producer must write 1 to the cycle
- * bit to handover ownership of the TRB, so PCS = 1. The consumer must
- * compare CCS to the cycle bit to check ownership, so CCS = 1.
- */
- ring->cycle_state = 1;
-
+ xhci_initialize_ring_info(ring);
return ring;
fail:
@@ -191,6 +198,27 @@ fail:
return 0;
}
+/* Zero an endpoint ring (except for link TRBs) and move the enqueue and dequeue
+ * pointers to the beginning of the ring.
+ */
+static void xhci_reinit_cached_ring(struct xhci_hcd *xhci,
+ struct xhci_ring *ring)
+{
+ struct xhci_segment *seg = ring->first_seg;
+ do {
+ memset(seg->trbs, 0,
+ sizeof(union xhci_trb)*TRBS_PER_SEGMENT);
+ /* All endpoint rings have link TRBs */
+ xhci_link_segments(xhci, seg, seg->next, 1);
+ seg = seg->next;
+ } while (seg != ring->first_seg);
+ xhci_initialize_ring_info(ring);
+ /* td list should be empty since all URBs have been cancelled,
+ * but just in case...
+ */
+ INIT_LIST_HEAD(&ring->td_list);
+}
+
#define CTX_SIZE(_hcc) (HCC_64BYTE_CONTEXT(_hcc) ? 64 : 32)
struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci,
@@ -276,6 +304,12 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id)
if (dev->eps[i].ring)
xhci_ring_free(xhci, dev->eps[i].ring);
+ if (dev->ring_cache) {
+ for (i = 0; i < dev->num_rings_cached; i++)
+ xhci_ring_free(xhci, dev->ring_cache[i]);
+ kfree(dev->ring_cache);
+ }
+
if (dev->in_ctx)
xhci_free_container_ctx(xhci, dev->in_ctx);
if (dev->out_ctx)
@@ -329,6 +363,14 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id,
if (!dev->eps[0].ring)
goto fail;
+ /* Allocate pointers to the ring cache */
+ dev->ring_cache = kzalloc(
+ sizeof(struct xhci_ring *)*XHCI_MAX_RINGS_CACHED,
+ flags);
+ if (!dev->ring_cache)
+ goto fail;
+ dev->num_rings_cached = 0;
+
init_completion(&dev->cmd_completion);
INIT_LIST_HEAD(&dev->cmd_list);
@@ -555,8 +597,16 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
/* Set up the endpoint ring */
virt_dev->eps[ep_index].new_ring =
xhci_ring_alloc(xhci, 1, true, mem_flags);
- if (!virt_dev->eps[ep_index].new_ring)
- return -ENOMEM;
+ if (!virt_dev->eps[ep_index].new_ring) {
+ /* Attempt to use the ring cache */
+ if (virt_dev->num_rings_cached == 0)
+ return -ENOMEM;
+ virt_dev->eps[ep_index].new_ring =
+ virt_dev->ring_cache[virt_dev->num_rings_cached];
+ virt_dev->ring_cache[virt_dev->num_rings_cached] = NULL;
+ virt_dev->num_rings_cached--;
+ xhci_reinit_cached_ring(xhci, virt_dev->eps[ep_index].new_ring);
+ }
ep_ring = virt_dev->eps[ep_index].new_ring;
ep_ctx->deq = ep_ring->first_seg->dma | ep_ring->cycle_state;