xHCI: dynamic ring expansion
authorAndiry Xu <andiry.xu@amd.com>
Mon, 5 Mar 2012 09:49:37 +0000 (17:49 +0800)
committerSarah Sharp <sarah.a.sharp@linux.intel.com>
Tue, 13 Mar 2012 16:30:24 +0000 (09:30 -0700)
If room_on_ring() check fails, try to expand the ring and check again.

When expand a ring, use a cached ring or allocate new segments, link
the original ring and the new ring or segments, update the original ring's
segment numbers and the last segment pointer.

Signed-off-by: Andiry Xu <andiry.xu@amd.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Tested-by: Paul Zimmerman <Paul.Zimmerman@synopsys.com>
drivers/usb/host/xhci-mem.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.h

index c1800c7582b79208e24b94d2c16ad638466a7607..c37aa1ba9126561770890648036bc8e1d679e804 100644 (file)
@@ -119,6 +119,34 @@ static void xhci_link_segments(struct xhci_hcd *xhci, struct xhci_segment *prev,
        }
 }
 
+/*
+ * Link the ring to the new segments.
+ * Set Toggle Cycle for the new ring if needed.
+ */
+static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring,
+               struct xhci_segment *first, struct xhci_segment *last,
+               unsigned int num_segs)
+{
+       struct xhci_segment *next;
+
+       if (!ring || !first || !last)
+               return;
+
+       next = ring->enq_seg->next;
+       xhci_link_segments(xhci, ring->enq_seg, first, ring->type);
+       xhci_link_segments(xhci, last, next, ring->type);
+       ring->num_segs += num_segs;
+       ring->num_trbs_free += (TRBS_PER_SEGMENT - 1) * num_segs;
+
+       if (ring->type != TYPE_EVENT && ring->enq_seg == ring->last_seg) {
+               ring->last_seg->trbs[TRBS_PER_SEGMENT-1].link.control
+                       &= ~cpu_to_le32(LINK_TOGGLE);
+               last->trbs[TRBS_PER_SEGMENT-1].link.control
+                       |= cpu_to_le32(LINK_TOGGLE);
+               ring->last_seg = last;
+       }
+}
+
 /* XXX: Do we need the hcd structure in all these functions? */
 void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring)
 {
@@ -287,6 +315,39 @@ static void xhci_reinit_cached_ring(struct xhci_hcd *xhci,
        INIT_LIST_HEAD(&ring->td_list);
 }
 
+/*
+ * Expand an existing ring.
+ * Look for a cached ring or allocate a new ring which has same segment numbers
+ * and link the two rings.
+ */
+int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
+                               unsigned int num_trbs, gfp_t flags)
+{
+       struct xhci_segment     *first;
+       struct xhci_segment     *last;
+       unsigned int            num_segs;
+       unsigned int            num_segs_needed;
+       int                     ret;
+
+       num_segs_needed = (num_trbs + (TRBS_PER_SEGMENT - 1) - 1) /
+                               (TRBS_PER_SEGMENT - 1);
+
+       /* Allocate number of segments we needed, or double the ring size */
+       num_segs = ring->num_segs > num_segs_needed ?
+                       ring->num_segs : num_segs_needed;
+
+       ret = xhci_alloc_segments_for_ring(xhci, &first, &last,
+                       num_segs, ring->cycle_state, ring->type, flags);
+       if (ret)
+               return -ENOMEM;
+
+       xhci_link_rings(xhci, ring, first, last, num_segs);
+       xhci_dbg(xhci, "ring expansion succeed, now has %d segments\n",
+                       ring->num_segs);
+
+       return 0;
+}
+
 #define CTX_SIZE(_hcc) (HCC_64BYTE_CONTEXT(_hcc) ? 64 : 32)
 
 static struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci,
@@ -1361,18 +1422,8 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
 
        type = usb_endpoint_type(&ep->desc);
        /* Set up the endpoint ring */
-       /*
-        * Isochronous endpoint ring needs bigger size because one isoc URB
-        * carries multiple packets and it will insert multiple tds to the
-        * ring.
-        * This should be replaced with dynamic ring resizing in the future.
-        */
-       if (usb_endpoint_xfer_isoc(&ep->desc))
-               virt_dev->eps[ep_index].new_ring =
-                       xhci_ring_alloc(xhci, 8, 1, type, mem_flags);
-       else
-               virt_dev->eps[ep_index].new_ring =
-                       xhci_ring_alloc(xhci, 1, 1, type, mem_flags);
+       virt_dev->eps[ep_index].new_ring =
+               xhci_ring_alloc(xhci, 1, 1, type, mem_flags);
        if (!virt_dev->eps[ep_index].new_ring) {
                /* Attempt to use the ring cache */
                if (virt_dev->num_rings_cached == 0)
index f9b6fa364f22ecd0572231171722b0176966e2dc..4194f348e3a7095d63695c88ade4fd123a0460c6 100644 (file)
@@ -2490,6 +2490,8 @@ static void queue_trb(struct xhci_hcd *xhci, struct xhci_ring *ring,
 static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring,
                u32 ep_state, unsigned int num_trbs, gfp_t mem_flags)
 {
+       unsigned int num_trbs_needed;
+
        /* Make sure the endpoint has been added to xHC schedule */
        switch (ep_state) {
        case EP_STATE_DISABLED:
@@ -2517,11 +2519,32 @@ static int prepare_ring(struct xhci_hcd *xhci, struct xhci_ring *ep_ring,
                 */
                return -EINVAL;
        }
-       if (!room_on_ring(xhci, ep_ring, num_trbs)) {
-               /* FIXME allocate more room */
-               xhci_err(xhci, "ERROR no room on ep ring\n");
-               return -ENOMEM;
-       }
+
+       while (1) {
+               if (room_on_ring(xhci, ep_ring, num_trbs))
+                       break;
+
+               if (ep_ring == xhci->cmd_ring) {
+                       xhci_err(xhci, "Do not support expand command ring\n");
+                       return -ENOMEM;
+               }
+
+               if (ep_ring->enq_seg == ep_ring->deq_seg &&
+                               ep_ring->dequeue > ep_ring->enqueue) {
+                       xhci_err(xhci, "Can not expand the ring while dequeue "
+                               "pointer has not passed the link TRB\n");
+                       return -ENOMEM;
+               }
+
+               xhci_dbg(xhci, "ERROR no room on ep ring, "
+                                       "try ring expansion\n");
+               num_trbs_needed = num_trbs - ep_ring->num_trbs_free;
+               if (xhci_ring_expansion(xhci, ep_ring, num_trbs_needed,
+                                       mem_flags)) {
+                       xhci_err(xhci, "Ring expansion failed\n");
+                       return -ENOMEM;
+               }
+       };
 
        if (enqueue_is_link_trb(ep_ring)) {
                struct xhci_ring *ring = ep_ring;
index ea8fc237d15849eea852e6922611f69ed5644fab..57cd632064b4088bd17fd6c75fc177611b8ff6df 100644 (file)
@@ -1622,6 +1622,8 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev,
                struct usb_device *udev, struct usb_host_endpoint *ep,
                gfp_t mem_flags);
 void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring);
+int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
+                               unsigned int num_trbs, gfp_t flags);
 void xhci_free_or_cache_endpoint_ring(struct xhci_hcd *xhci,
                struct xhci_virt_device *virt_dev,
                unsigned int ep_index);