Skip to content

Commit

Permalink
xHCI: dynamic ring expansion
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Andiry Xu authored and Sarah Sharp committed Mar 13, 2012
1 parent 186a7ef commit 8dfec61
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 17 deletions.
75 changes: 63 additions & 12 deletions drivers/usb/host/xhci-mem.c
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
33 changes: 28 additions & 5 deletions drivers/usb/host/xhci-ring.c
Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions drivers/usb/host/xhci.h
Expand Up @@ -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);
Expand Down

0 comments on commit 8dfec61

Please sign in to comment.