提交 15341303 编写于 作者: G Gerd Hoffmann 提交者: Sarah Sharp

xhci: fix usb3 streams

xhci maintains a radix tree for each stream endpoint because it must
be able to map a trb address to the stream ring.  Each ring segment
must be added to the ring for this to work.  Currently xhci sticks
only the first segment of each stream ring into the radix tree.

Result is that things work initially, but as soon as the first segment
is full xhci can't map the trb address from the completion event to the
stream ring any more -> BOOM.  You'll find this message in the logs:

  ERROR Transfer event for disabled endpoint or incorrect stream ring

This patch adds a helper function to update the radix tree, and a
function to remove ring segments from the tree.  Both functions loop
over the segment list and handles all segments instead of just the
first.

[Note: Sarah changed this patch to add radix_tree_maybe_preload() and
radix_tree_preload_end() calls around the radix tree insert, since we
can now insert entries in interrupt context.  There are now two helper
functions to make the code cleaner, and those functions are moved to
make them static.]
Signed-off-by: NGerd Hoffmann <kraxel@redhat.com>
Signed-off-by: NHans de Goede <hdegoede@redhat.com>
Signed-off-by: NSarah Sharp <sarah.a.sharp@linux.intel.com>
上级 e587b8b2
...@@ -149,14 +149,95 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring, ...@@ -149,14 +149,95 @@ static void xhci_link_rings(struct xhci_hcd *xhci, struct xhci_ring *ring,
} }
} }
/*
* We need a radix tree for mapping physical addresses of TRBs to which stream
* ID they belong to. We need to do this because the host controller won't tell
* us which stream ring the TRB came from. We could store the stream ID in an
* event data TRB, but that doesn't help us for the cancellation case, since the
* endpoint may stop before it reaches that event data TRB.
*
* The radix tree maps the upper portion of the TRB DMA address to a ring
* segment that has the same upper portion of DMA addresses. For example, say I
* have segments of size 1KB, that are always 64-byte aligned. A segment may
* start at 0x10c91000 and end at 0x10c913f0. If I use the upper 10 bits, the
* key to the stream ID is 0x43244. I can use the DMA address of the TRB to
* pass the radix tree a key to get the right stream ID:
*
* 0x10c90fff >> 10 = 0x43243
* 0x10c912c0 >> 10 = 0x43244
* 0x10c91400 >> 10 = 0x43245
*
* Obviously, only those TRBs with DMA addresses that are within the segment
* will make the radix tree return the stream ID for that ring.
*
* Caveats for the radix tree:
*
* The radix tree uses an unsigned long as a key pair. On 32-bit systems, an
* unsigned long will be 32-bits; on a 64-bit system an unsigned long will be
* 64-bits. Since we only request 32-bit DMA addresses, we can use that as the
* key on 32-bit or 64-bit systems (it would also be fine if we asked for 64-bit
* PCI DMA addresses on a 64-bit system). There might be a problem on 32-bit
* extended systems (where the DMA address can be bigger than 32-bits),
* if we allow the PCI dma mask to be bigger than 32-bits. So don't do that.
*/
static int xhci_update_stream_mapping(struct xhci_ring *ring, gfp_t mem_flags)
{
struct xhci_segment *seg;
unsigned long key;
int ret;
if (WARN_ON_ONCE(ring->trb_address_map == NULL))
return 0;
seg = ring->first_seg;
do {
key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT);
/* Skip any segments that were already added. */
if (radix_tree_lookup(ring->trb_address_map, key))
continue;
ret = radix_tree_maybe_preload(mem_flags);
if (ret)
return ret;
ret = radix_tree_insert(ring->trb_address_map,
key, ring);
radix_tree_preload_end();
if (ret)
return ret;
seg = seg->next;
} while (seg != ring->first_seg);
return 0;
}
static void xhci_remove_stream_mapping(struct xhci_ring *ring)
{
struct xhci_segment *seg;
unsigned long key;
if (WARN_ON_ONCE(ring->trb_address_map == NULL))
return;
seg = ring->first_seg;
do {
key = (unsigned long)(seg->dma >> TRB_SEGMENT_SHIFT);
if (radix_tree_lookup(ring->trb_address_map, key))
radix_tree_delete(ring->trb_address_map, key);
seg = seg->next;
} while (seg != ring->first_seg);
}
/* XXX: Do we need the hcd structure in all these functions? */ /* XXX: Do we need the hcd structure in all these functions? */
void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring) void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring)
{ {
if (!ring) if (!ring)
return; return;
if (ring->first_seg) if (ring->first_seg) {
if (ring->type == TYPE_STREAM)
xhci_remove_stream_mapping(ring);
xhci_free_segments_for_ring(xhci, ring->first_seg); xhci_free_segments_for_ring(xhci, ring->first_seg);
}
kfree(ring); kfree(ring);
} }
...@@ -354,6 +435,11 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, ...@@ -354,6 +435,11 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
"ring expansion succeed, now has %d segments", "ring expansion succeed, now has %d segments",
ring->num_segs); ring->num_segs);
if (ring->type == TYPE_STREAM) {
ret = xhci_update_stream_mapping(ring, flags);
WARN_ON(ret); /* FIXME */
}
return 0; return 0;
} }
...@@ -510,36 +596,6 @@ struct xhci_ring *xhci_stream_id_to_ring( ...@@ -510,36 +596,6 @@ struct xhci_ring *xhci_stream_id_to_ring(
* The number of stream contexts in the stream context array may be bigger than * The number of stream contexts in the stream context array may be bigger than
* the number of streams the driver wants to use. This is because the number of * the number of streams the driver wants to use. This is because the number of
* stream context array entries must be a power of two. * stream context array entries must be a power of two.
*
* We need a radix tree for mapping physical addresses of TRBs to which stream
* ID they belong to. We need to do this because the host controller won't tell
* us which stream ring the TRB came from. We could store the stream ID in an
* event data TRB, but that doesn't help us for the cancellation case, since the
* endpoint may stop before it reaches that event data TRB.
*
* The radix tree maps the upper portion of the TRB DMA address to a ring
* segment that has the same upper portion of DMA addresses. For example, say I
* have segments of size 1KB, that are always 64-byte aligned. A segment may
* start at 0x10c91000 and end at 0x10c913f0. If I use the upper 10 bits, the
* key to the stream ID is 0x43244. I can use the DMA address of the TRB to
* pass the radix tree a key to get the right stream ID:
*
* 0x10c90fff >> 10 = 0x43243
* 0x10c912c0 >> 10 = 0x43244
* 0x10c91400 >> 10 = 0x43245
*
* Obviously, only those TRBs with DMA addresses that are within the segment
* will make the radix tree return the stream ID for that ring.
*
* Caveats for the radix tree:
*
* The radix tree uses an unsigned long as a key pair. On 32-bit systems, an
* unsigned long will be 32-bits; on a 64-bit system an unsigned long will be
* 64-bits. Since we only request 32-bit DMA addresses, we can use that as the
* key on 32-bit or 64-bit systems (it would also be fine if we asked for 64-bit
* PCI DMA addresses on a 64-bit system). There might be a problem on 32-bit
* extended systems (where the DMA address can be bigger than 32-bits),
* if we allow the PCI dma mask to be bigger than 32-bits. So don't do that.
*/ */
struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci,
unsigned int num_stream_ctxs, unsigned int num_stream_ctxs,
...@@ -548,7 +604,6 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, ...@@ -548,7 +604,6 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci,
struct xhci_stream_info *stream_info; struct xhci_stream_info *stream_info;
u32 cur_stream; u32 cur_stream;
struct xhci_ring *cur_ring; struct xhci_ring *cur_ring;
unsigned long key;
u64 addr; u64 addr;
int ret; int ret;
...@@ -603,6 +658,7 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, ...@@ -603,6 +658,7 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci,
if (!cur_ring) if (!cur_ring)
goto cleanup_rings; goto cleanup_rings;
cur_ring->stream_id = cur_stream; cur_ring->stream_id = cur_stream;
cur_ring->trb_address_map = &stream_info->trb_address_map;
/* Set deq ptr, cycle bit, and stream context type */ /* Set deq ptr, cycle bit, and stream context type */
addr = cur_ring->first_seg->dma | addr = cur_ring->first_seg->dma |
SCT_FOR_CTX(SCT_PRI_TR) | SCT_FOR_CTX(SCT_PRI_TR) |
...@@ -612,10 +668,7 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, ...@@ -612,10 +668,7 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci,
xhci_dbg(xhci, "Setting stream %d ring ptr to 0x%08llx\n", xhci_dbg(xhci, "Setting stream %d ring ptr to 0x%08llx\n",
cur_stream, (unsigned long long) addr); cur_stream, (unsigned long long) addr);
key = (unsigned long) ret = xhci_update_stream_mapping(cur_ring, mem_flags);
(cur_ring->first_seg->dma >> TRB_SEGMENT_SHIFT);
ret = radix_tree_insert(&stream_info->trb_address_map,
key, cur_ring);
if (ret) { if (ret) {
xhci_ring_free(xhci, cur_ring); xhci_ring_free(xhci, cur_ring);
stream_info->stream_rings[cur_stream] = NULL; stream_info->stream_rings[cur_stream] = NULL;
...@@ -635,9 +688,6 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, ...@@ -635,9 +688,6 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci,
for (cur_stream = 1; cur_stream < num_streams; cur_stream++) { for (cur_stream = 1; cur_stream < num_streams; cur_stream++) {
cur_ring = stream_info->stream_rings[cur_stream]; cur_ring = stream_info->stream_rings[cur_stream];
if (cur_ring) { if (cur_ring) {
addr = cur_ring->first_seg->dma;
radix_tree_delete(&stream_info->trb_address_map,
addr >> TRB_SEGMENT_SHIFT);
xhci_ring_free(xhci, cur_ring); xhci_ring_free(xhci, cur_ring);
stream_info->stream_rings[cur_stream] = NULL; stream_info->stream_rings[cur_stream] = NULL;
} }
...@@ -698,7 +748,6 @@ void xhci_free_stream_info(struct xhci_hcd *xhci, ...@@ -698,7 +748,6 @@ void xhci_free_stream_info(struct xhci_hcd *xhci,
{ {
int cur_stream; int cur_stream;
struct xhci_ring *cur_ring; struct xhci_ring *cur_ring;
dma_addr_t addr;
if (!stream_info) if (!stream_info)
return; return;
...@@ -707,9 +756,6 @@ void xhci_free_stream_info(struct xhci_hcd *xhci, ...@@ -707,9 +756,6 @@ void xhci_free_stream_info(struct xhci_hcd *xhci,
cur_stream++) { cur_stream++) {
cur_ring = stream_info->stream_rings[cur_stream]; cur_ring = stream_info->stream_rings[cur_stream];
if (cur_ring) { if (cur_ring) {
addr = cur_ring->first_seg->dma;
radix_tree_delete(&stream_info->trb_address_map,
addr >> TRB_SEGMENT_SHIFT);
xhci_ring_free(xhci, cur_ring); xhci_ring_free(xhci, cur_ring);
stream_info->stream_rings[cur_stream] = NULL; stream_info->stream_rings[cur_stream] = NULL;
} }
......
...@@ -1341,6 +1341,7 @@ struct xhci_ring { ...@@ -1341,6 +1341,7 @@ struct xhci_ring {
unsigned int num_trbs_free_temp; unsigned int num_trbs_free_temp;
enum xhci_ring_type type; enum xhci_ring_type type;
bool last_td_was_short; bool last_td_was_short;
struct radix_tree_root *trb_address_map;
}; };
struct xhci_erst_entry { struct xhci_erst_entry {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册