[PATCH 2/6] usb/isp1760: Clear TT buffer on interrupted low & full speed transfers

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

 



When a low or full speed urb in progress is unlinked (or some other error
occurs), the buffer in the transaction translator (part of the hub) might end
up in an inconsistent state. This can make all further low and full speed
transactions fail, unless the buffer is cleared.

The bug can be seen when running the usbtest unlink tests as "set altsetting
to 0 failed, -110", and gets fixed by this patch.

Signed-off-by: Arvid Brodin <arvid.brodin@xxxxxxxx>
---
 drivers/usb/host/isp1760-hcd.c |   83 +++++++++++++++++++++++++++++++++++----
 1 files changed, 74 insertions(+), 9 deletions(-)

diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c
index 840beda..2ab8fb2 100644
--- a/drivers/usb/host/isp1760-hcd.c
+++ b/drivers/usb/host/isp1760-hcd.c
@@ -114,6 +114,7 @@ struct isp1760_qh {
 	u32 toggle;
 	u32 ping;
 	int slot;
+	int tt_buffer_dirty;	/* See USB2.0 spec section 11.17.5 */
 };
 
 struct urb_listitem {
@@ -928,6 +929,10 @@ static void enqueue_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh)
 		return;
 	}
 
+	/* Make sure this endpoint's TT buffer is clean before queueing ptds */
+	if (qh->tt_buffer_dirty)
+		return;
+
 	if (usb_pipeint(list_entry(qh->qtd_list.next, struct isp1760_qtd,
 							qtd_list)->urb->pipe)) {
 		ptd_offset = INT_PTD_OFFSET;
@@ -1281,6 +1286,15 @@ static irqreturn_t isp1760_irq(struct usb_hcd *hcd)
 
 		case PTD_STATE_URB_RETIRE:
 			qtd->status = QTD_RETIRE;
+			if ((qtd->urb->dev->speed != USB_SPEED_HIGH) &&
+					(qtd->urb->status != -EPIPE) &&
+					(qtd->urb->status != -EREMOTEIO)) {
+				qh->tt_buffer_dirty = 1;
+				if (usb_hub_clear_tt_buffer(qtd->urb))
+					/* Clear failed; let's hope things work
+					   anyway */
+					qh->tt_buffer_dirty = 0;
+			}
 			qtd = NULL;
 			qh->toggle = 0;
 			qh->ping = 0;
@@ -1573,6 +1587,41 @@ static void kill_transfer(struct usb_hcd *hcd, struct urb *urb,
 	priv->active_ptds--;
 }
 
+/*
+ * Retire the qtds beginning at 'qtd' and belonging all to the same urb, killing
+ * any active transfer belonging to the urb in the process.
+ */
+static void dequeue_urb_from_qtd(struct usb_hcd *hcd, struct isp1760_qh *qh,
+						struct isp1760_qtd *qtd)
+{
+	struct urb *urb;
+	int urb_was_running;
+
+	urb = qtd->urb;
+	urb_was_running = 0;
+	list_for_each_entry_from(qtd, &qh->qtd_list, qtd_list) {
+		if (qtd->urb != urb)
+			break;
+
+		if (qtd->status >= QTD_XFER_STARTED)
+			urb_was_running = 1;
+		if (last_qtd_of_urb(qtd, qh) &&
+					(qtd->status >= QTD_XFER_COMPLETE))
+			urb_was_running = 0;
+
+		if (qtd->status == QTD_XFER_STARTED)
+			kill_transfer(hcd, urb, qh);
+		qtd->status = QTD_RETIRE;
+	}
+
+	if ((urb->dev->speed != USB_SPEED_HIGH) && urb_was_running) {
+		qh->tt_buffer_dirty = 1;
+		if (usb_hub_clear_tt_buffer(urb))
+			/* Clear failed; let's hope things work anyway */
+			qh->tt_buffer_dirty = 0;
+	}
+}
+
 static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
 		int status)
 {
@@ -1595,9 +1644,8 @@ static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
 
 	list_for_each_entry(qtd, &qh->qtd_list, qtd_list)
 		if (qtd->urb == urb) {
-			if (qtd->status == QTD_XFER_STARTED)
-				kill_transfer(hcd, urb, qh);
-			qtd->status = QTD_RETIRE;
+			dequeue_urb_from_qtd(hcd, qh, qtd);
+			break;
 		}
 
 	urb->status = status;
@@ -1622,12 +1670,11 @@ static void isp1760_endpoint_disable(struct usb_hcd *hcd,
 	if (!qh)
 		goto out;
 
-	list_for_each_entry(qtd, &qh->qtd_list, qtd_list) {
-		if (qtd->status == QTD_XFER_STARTED)
-			kill_transfer(hcd, qtd->urb, qh);
-		qtd->status = QTD_RETIRE;
-		qtd->urb->status = -ECONNRESET;
-	}
+	list_for_each_entry(qtd, &qh->qtd_list, qtd_list)
+		if (qtd->status != QTD_RETIRE) {
+			dequeue_urb_from_qtd(hcd, qh, qtd);
+			qtd->urb->status = -ECONNRESET;
+		}
 
 	ep->hcpriv = NULL;
 	/* Cannot free qh here since it will be parsed by schedule_ptds() */
@@ -2048,6 +2095,23 @@ static void isp1760_shutdown(struct usb_hcd *hcd)
 	reg_write32(hcd->regs, HC_USBCMD, command);
 }
 
+static void isp1760_clear_tt_buffer_complete(struct usb_hcd *hcd,
+						struct usb_host_endpoint *ep)
+{
+	struct isp1760_hcd *priv = hcd_to_priv(hcd);
+	struct isp1760_qh *qh = ep->hcpriv;
+	unsigned long spinflags;
+
+	if (!qh)
+		return;
+
+	spin_lock_irqsave(&priv->lock, spinflags);
+	qh->tt_buffer_dirty = 0;
+	schedule_ptds(hcd);
+	spin_unlock_irqrestore(&priv->lock, spinflags);
+}
+
+
 static const struct hc_driver isp1760_hc_driver = {
 	.description		= "isp1760-hcd",
 	.product_desc		= "NXP ISP1760 USB Host Controller",
@@ -2064,6 +2128,7 @@ static const struct hc_driver isp1760_hc_driver = {
 	.get_frame_number	= isp1760_get_frame,
 	.hub_status_data	= isp1760_hub_status_data,
 	.hub_control		= isp1760_hub_control,
+	.clear_tt_buffer_complete	= isp1760_clear_tt_buffer_complete,
 };
 
 int __init init_kmem_once(void)
-- 
1.6.3.3

-- 
Arvid Brodin
Enea Services Stockholm AB

--
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