Commit 48df4a6f authored by Sarah Sharp's avatar Sarah Sharp

xhci: Handle zero-length isochronous packets.

For a long time, the xHCI driver has had this note:
	/* FIXME: Ignoring zero-length packets, can those happen? */

It turns out that, yes, there are drivers that need to queue zero-length
transfers for isochronous OUT transfers.  Without this patch, users will
see kernel hang messages when a driver attempts to enqueue an isochronous
URB with a zero length transfer (because count_isoc_trbs_needed will return
zero for that TD, xhci_td->last_trb will never be set, and updating the
dequeue pointer will cause an infinite loop).

Matěj ran into this issue when using an NI Audio4DJ USB soundcard
with the snd-usb-caiaq driver.  See
	https://bugzilla.kernel.org/show_bug.cgi?id=40702

Fix count_isoc_trbs_needed() to return 1 for zero-length transfers (thanks
Alan on the math help).  Update the various TRB field calculations to deal
with zero-length transfers.  We're still transferring one packet with a
zero-length data payload, so the total_packet_count should be 1. The
Transfer Burst Count (TBC) and Transfer Last Burst Packet Count (TLBPC)
fields should be set to zero.

This patch should be backported to kernels as old as 2.6.36.
Signed-off-by: default avatarSarah Sharp <sarah.a.sharp@linux.intel.com>
Tested-by: default avatarMatěj Laitl <matej@laitl.cz>
Cc: Daniel Mack <zonque@gmail.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: stable@kernel.org
parent 8a9af4fd
...@@ -2684,6 +2684,10 @@ static u32 xhci_v1_0_td_remainder(int running_total, int trb_buff_len, ...@@ -2684,6 +2684,10 @@ static u32 xhci_v1_0_td_remainder(int running_total, int trb_buff_len,
{ {
int packets_transferred; int packets_transferred;
/* One TRB with a zero-length data packet. */
if (running_total == 0 && trb_buff_len == 0)
return 0;
/* All the TRB queueing functions don't count the current TRB in /* All the TRB queueing functions don't count the current TRB in
* running_total. * running_total.
*/ */
...@@ -3125,21 +3129,16 @@ static int count_isoc_trbs_needed(struct xhci_hcd *xhci, ...@@ -3125,21 +3129,16 @@ static int count_isoc_trbs_needed(struct xhci_hcd *xhci,
struct urb *urb, int i) struct urb *urb, int i)
{ {
int num_trbs = 0; int num_trbs = 0;
u64 addr, td_len, running_total; u64 addr, td_len;
addr = (u64) (urb->transfer_dma + urb->iso_frame_desc[i].offset); addr = (u64) (urb->transfer_dma + urb->iso_frame_desc[i].offset);
td_len = urb->iso_frame_desc[i].length; td_len = urb->iso_frame_desc[i].length;
running_total = TRB_MAX_BUFF_SIZE - (addr & (TRB_MAX_BUFF_SIZE - 1)); num_trbs = DIV_ROUND_UP(td_len + (addr & (TRB_MAX_BUFF_SIZE - 1)),
running_total &= TRB_MAX_BUFF_SIZE - 1; TRB_MAX_BUFF_SIZE);
if (running_total != 0) if (num_trbs == 0)
num_trbs++; num_trbs++;
while (running_total < td_len) {
num_trbs++;
running_total += TRB_MAX_BUFF_SIZE;
}
return num_trbs; return num_trbs;
} }
...@@ -3250,9 +3249,11 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, ...@@ -3250,9 +3249,11 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
addr = start_addr + urb->iso_frame_desc[i].offset; addr = start_addr + urb->iso_frame_desc[i].offset;
td_len = urb->iso_frame_desc[i].length; td_len = urb->iso_frame_desc[i].length;
td_remain_len = td_len; td_remain_len = td_len;
/* FIXME: Ignoring zero-length packets, can those happen? */
total_packet_count = roundup(td_len, total_packet_count = roundup(td_len,
le16_to_cpu(urb->ep->desc.wMaxPacketSize)); le16_to_cpu(urb->ep->desc.wMaxPacketSize));
/* A zero-length transfer still involves at least one packet. */
if (total_packet_count == 0)
total_packet_count++;
burst_count = xhci_get_burst_count(xhci, urb->dev, urb, burst_count = xhci_get_burst_count(xhci, urb->dev, urb,
total_packet_count); total_packet_count);
residue = xhci_get_last_burst_packet_count(xhci, residue = xhci_get_last_burst_packet_count(xhci,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment