Re: Bugs in xhci-hcd isochronous support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, 22 Apr 2014, Russel Hughes wrote:

> > More importantly, the routine sets urb->start_frame to the current
> > value of the frame counter.  This is completely wrong; urb->start_frame
> > is supposed to be the (micro-)frame number for when the transfer
> > begins, not when the transfer was submitted.
> >
> > As far as I can tell, the only way to do this correctly is to set the
> > Frame ID field (with SIA = 0) in the first TD of an isochronous stream,
> > and then set SIA = 1 in all the following TDs (see 4.11.2.5).  That
> > way, xhci-hcd will know exactly when the stream begins, so it can keep
> > track of the frame in which each URB starts.  Dealing with underruns is
> > left as an exercise for the implementer...
> >
> 
> Let me know if you want any changes tested using my DAC that reliably
> shows the problem.

Russel, here's a patch you can test.  It's only a partial fix for the 
problem, because it doesn't handle over/underruns.  Still, it would be 
nice to see if the patch makes any difference in normal operation.

Even if it doesn't fix the problem, please post a short stretch (a few 
hundred lines) from a usbmon trace with the patch installed.

Alan Stern



Index: usb-3.15/drivers/usb/host/xhci-ring.c
===================================================================
--- usb-3.15.orig/drivers/usb/host/xhci-ring.c
+++ usb-3.15/drivers/usb/host/xhci-ring.c
@@ -3153,7 +3153,7 @@ static void check_trb_math(struct urb *u
 
 static void giveback_first_trb(struct xhci_hcd *xhci, int slot_id,
 		unsigned int ep_index, unsigned int stream_id, int start_cycle,
-		struct xhci_generic_trb *start_trb)
+		struct xhci_generic_trb *start_trb, bool ring_doorbell)
 {
 	/*
 	 * Pass all the TRBs to the hardware at once and make sure this write
@@ -3164,7 +3164,8 @@ static void giveback_first_trb(struct xh
 		start_trb->field[3] |= cpu_to_le32(start_cycle);
 	else
 		start_trb->field[3] &= cpu_to_le32(~TRB_CYCLE);
-	xhci_ring_ep_doorbell(xhci, slot_id, ep_index, stream_id);
+	if (ring_doorbell)
+		xhci_ring_ep_doorbell(xhci, slot_id, ep_index, stream_id);
 }
 
 /*
@@ -3406,7 +3407,7 @@ static int queue_bulk_sg_tx(struct xhci_
 
 	check_trb_math(urb, num_trbs, running_total);
 	giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id,
-			start_cycle, start_trb);
+			start_cycle, start_trb, true);
 	return 0;
 }
 
@@ -3545,7 +3546,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *
 
 	check_trb_math(urb, num_trbs, running_total);
 	giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id,
-			start_cycle, start_trb);
+			start_cycle, start_trb, true);
 	return 0;
 }
 
@@ -3662,7 +3663,7 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *
 			field | TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state);
 
 	giveback_first_trb(xhci, slot_id, ep_index, 0,
-			start_cycle, start_trb);
+			start_cycle, start_trb, true);
 	return 0;
 }
 
@@ -3742,8 +3743,10 @@ static unsigned int xhci_get_last_burst_
 
 /* This is for isoc transfer */
 static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
-		struct urb *urb, int slot_id, unsigned int ep_index)
+		struct urb *urb, int slot_id, unsigned int ep_index,
+		unsigned esit)
 {
+	struct xhci_virt_ep *ep;
 	struct xhci_ring *ep_ring;
 	struct urb_priv *urb_priv;
 	struct xhci_td *td;
@@ -3756,8 +3759,11 @@ static int xhci_queue_isoc_tx(struct xhc
 	u64 start_addr, addr;
 	int i, j;
 	bool more_trbs_coming;
+	bool new_isoc_stream;
+	unsigned start_uframe;
 
-	ep_ring = xhci->devs[slot_id]->eps[ep_index].ring;
+	ep = &xhci->devs[slot_id]->eps[ep_index];
+	ep_ring = ep->ring;
 
 	num_tds = urb->number_of_packets;
 	if (num_tds < 1) {
@@ -3765,6 +3771,8 @@ static int xhci_queue_isoc_tx(struct xhc
 		return -EINVAL;
 	}
 
+	new_isoc_stream = list_empty(&urb->ep->urb_list);
+
 	start_addr = (u64) urb->transfer_dma;
 	start_trb = &ep_ring->enqueue->generic;
 	start_cycle = ep_ring->cycle_state;
@@ -3826,10 +3834,6 @@ static int xhci_queue_isoc_tx(struct xhc
 				field |= ep_ring->cycle_state;
 			}
 
-			/* Only set interrupt on short packet for IN EPs */
-			if (usb_urb_dir_in(urb))
-				field |= TRB_ISP;
-
 			/* Chain all the TRBs together; clear the chain bit in
 			 * the last TRB to indicate it's the last TRB in the
 			 * chain.
@@ -3895,8 +3899,52 @@ static int xhci_queue_isoc_tx(struct xhc
 	}
 	xhci_to_hcd(xhci)->self.bandwidth_isoc_reqs++;
 
+	/*
+	 * For new isochronous streams, schedule the transfer to start
+	 * in the first slot beyond the Isochronous Scheduling Threshold
+	 * (4.14.2.1.4).  Otherwise, leave the SIA (Schedule Isochronous ASAP)
+	 * bit set in the first TRB.  Over/underruns are handled when the
+	 * events are received.
+	 */
+	if (new_isoc_stream) {
+		unsigned ist = HCS_IST(xhci->hcs_params2);
+		unsigned boundary;
+
+		start_uframe = readl(&xhci->run_regs->microframe_index);
+
+		/* IST is given in frames if bit 3 is set, else in uframes */
+		start_uframe += (ist & 0x8) ? (ist & 0x7) << 3 : ist & 0x7;
+
+		/* Round up to the following frame or ESIT boundary */
+		boundary = max(esit, 8u);
+		start_uframe = (start_uframe + boundary) & (- boundary);
+		start_uframe &= MFINDEX_MASK;
+
+		/* Store the starting frame number in the first TRB */
+		start_trb->field[3] &= cpu_to_le32(~TRB_SIA);
+		start_trb->field[3] |= cpu_to_le32(
+				TRB_FRAME_ID(start_uframe >> 3));
+	} else {
+		start_uframe = ep->next_uframe;
+	}
+	ep->next_uframe = (start_uframe + esit * num_tds) & MFINDEX_MASK;
+
+	urb->start_frame = start_uframe;
+	if (urb->dev->speed == USB_SPEED_FULL)
+		urb->start_frame >>= 3;
+
+	/*
+	 * Ring the endpoint doorbell only for the first TRB of a new
+	 * isochronous stream.  Following TRBs should be queued sufficiently
+	 * far in advance (beyond the IST) that no doorbell ring is needed.
+	 *
+	 * Indeed, if we did ring the doorbell again and an over/underrun
+	 * occurred at the same time, we wouldn't know whether or not the
+	 * endpoint had been removed from the periodic schedule, because
+	 * ringing the doorbell reactivates endpoints (4.14.2.1).
+	 */
 	giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id,
-			start_cycle, start_trb);
+			start_cycle, start_trb, new_isoc_stream);
 	return 0;
 cleanup:
 	/* Clean up a partially enqueued isoc transfer. */
@@ -3935,7 +3983,6 @@ int xhci_queue_isoc_tx_prepare(struct xh
 	struct xhci_virt_device *xdev;
 	struct xhci_ring *ep_ring;
 	struct xhci_ep_ctx *ep_ctx;
-	int start_frame;
 	int xhci_interval;
 	int ep_interval;
 	int num_tds, num_trbs, i;
@@ -3958,19 +4005,10 @@ int xhci_queue_isoc_tx_prepare(struct xh
 	if (ret)
 		return ret;
 
-	start_frame = readl(&xhci->run_regs->microframe_index);
-	start_frame &= 0x3fff;
-
-	urb->start_frame = start_frame;
-	if (urb->dev->speed == USB_SPEED_LOW ||
-			urb->dev->speed == USB_SPEED_FULL)
-		urb->start_frame >>= 3;
-
 	xhci_interval = EP_INTERVAL_TO_UFRAMES(le32_to_cpu(ep_ctx->ep_info));
 	ep_interval = urb->interval;
 	/* Convert to microframes */
-	if (urb->dev->speed == USB_SPEED_LOW ||
-			urb->dev->speed == USB_SPEED_FULL)
+	if (urb->dev->speed == USB_SPEED_FULL)
 		ep_interval *= 8;
 	/* FIXME change this to a warning and a suggestion to use the new API
 	 * to set the polling interval (once the API is added).
@@ -3982,13 +4020,13 @@ int xhci_queue_isoc_tx_prepare(struct xh
 				xhci_interval, xhci_interval == 1 ? "" : "s");
 		urb->interval = xhci_interval;
 		/* Convert back to frames for LS/FS devices */
-		if (urb->dev->speed == USB_SPEED_LOW ||
-				urb->dev->speed == USB_SPEED_FULL)
+		if (urb->dev->speed == USB_SPEED_FULL)
 			urb->interval /= 8;
 	}
 	ep_ring->num_trbs_free_temp = ep_ring->num_trbs_free;
 
-	return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index);
+	return xhci_queue_isoc_tx(xhci, mem_flags, urb, slot_id, ep_index,
+			xhci_interval);
 }
 
 /****		Command Ring Operations		****/
Index: usb-3.15/drivers/usb/host/xhci.h
===================================================================
--- usb-3.15.orig/drivers/usb/host/xhci.h
+++ usb-3.15/drivers/usb/host/xhci.h
@@ -481,6 +481,8 @@ struct xhci_run_regs {
 	struct xhci_intr_reg	ir_set[128];
 };
 
+#define MFINDEX_MASK	0x3fff
+
 /**
  * struct doorbell_array
  *
@@ -887,6 +889,8 @@ struct xhci_virt_ep {
 	 * process the missed tds on the endpoint ring.
 	 */
 	bool			skip;
+	unsigned		next_uframe;	/* isochronous scheduling */
+
 	/* Bandwidth checking storage */
 	struct xhci_bw_info	bw_info;
 	struct list_head	bw_endpoint_list;
@@ -1167,6 +1171,7 @@ enum xhci_setup_dev {
 
 /* Isochronous TRB specific fields */
 #define TRB_SIA			(1<<31)
+#define TRB_FRAME_ID(p)		((p) << 20)
 
 struct xhci_generic_trb {
 	__le32 field[4];


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux