Commit 74ad6029 authored by Arvid Brodin's avatar Arvid Brodin Committed by Greg Kroah-Hartman

usb/isp1760: Clear TT buffer on interrupted low & full speed transfers

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: default avatarArvid Brodin <arvid.brodin@enea.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent d80cba6c
...@@ -114,6 +114,7 @@ struct isp1760_qh { ...@@ -114,6 +114,7 @@ struct isp1760_qh {
u32 toggle; u32 toggle;
u32 ping; u32 ping;
int slot; int slot;
int tt_buffer_dirty; /* See USB2.0 spec section 11.17.5 */
}; };
struct urb_listitem { struct urb_listitem {
...@@ -845,6 +846,10 @@ static void enqueue_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh) ...@@ -845,6 +846,10 @@ static void enqueue_qtds(struct usb_hcd *hcd, struct isp1760_qh *qh)
return; 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, if (usb_pipeint(list_entry(qh->qtd_list.next, struct isp1760_qtd,
qtd_list)->urb->pipe)) { qtd_list)->urb->pipe)) {
ptd_offset = INT_PTD_OFFSET; ptd_offset = INT_PTD_OFFSET;
...@@ -1182,6 +1187,15 @@ static void handle_done_ptds(struct usb_hcd *hcd) ...@@ -1182,6 +1187,15 @@ static void handle_done_ptds(struct usb_hcd *hcd)
case PTD_STATE_URB_RETIRE: case PTD_STATE_URB_RETIRE:
qtd->status = QTD_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; qtd = NULL;
qh->toggle = 0; qh->toggle = 0;
qh->ping = 0; qh->ping = 0;
...@@ -1611,6 +1625,41 @@ static void kill_transfer(struct usb_hcd *hcd, struct urb *urb, ...@@ -1611,6 +1625,41 @@ static void kill_transfer(struct usb_hcd *hcd, struct urb *urb,
qh->slot = -1; qh->slot = -1;
} }
/*
* 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, static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
int status) int status)
{ {
...@@ -1633,9 +1682,8 @@ static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, ...@@ -1633,9 +1682,8 @@ static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
list_for_each_entry(qtd, &qh->qtd_list, qtd_list) list_for_each_entry(qtd, &qh->qtd_list, qtd_list)
if (qtd->urb == urb) { if (qtd->urb == urb) {
if (qtd->status == QTD_XFER_STARTED) dequeue_urb_from_qtd(hcd, qh, qtd);
kill_transfer(hcd, urb, qh); break;
qtd->status = QTD_RETIRE;
} }
urb->status = status; urb->status = status;
...@@ -1660,10 +1708,9 @@ static void isp1760_endpoint_disable(struct usb_hcd *hcd, ...@@ -1660,10 +1708,9 @@ static void isp1760_endpoint_disable(struct usb_hcd *hcd,
if (!qh) if (!qh)
goto out; goto out;
list_for_each_entry(qtd, &qh->qtd_list, qtd_list) { list_for_each_entry(qtd, &qh->qtd_list, qtd_list)
if (qtd->status == QTD_XFER_STARTED) if (qtd->status != QTD_RETIRE) {
kill_transfer(hcd, qtd->urb, qh); dequeue_urb_from_qtd(hcd, qh, qtd);
qtd->status = QTD_RETIRE;
qtd->urb->status = -ECONNRESET; qtd->urb->status = -ECONNRESET;
} }
...@@ -2088,6 +2135,23 @@ static void isp1760_shutdown(struct usb_hcd *hcd) ...@@ -2088,6 +2135,23 @@ static void isp1760_shutdown(struct usb_hcd *hcd)
reg_write32(hcd->regs, HC_USBCMD, command); 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 = { static const struct hc_driver isp1760_hc_driver = {
.description = "isp1760-hcd", .description = "isp1760-hcd",
.product_desc = "NXP ISP1760 USB Host Controller", .product_desc = "NXP ISP1760 USB Host Controller",
...@@ -2104,6 +2168,7 @@ static const struct hc_driver isp1760_hc_driver = { ...@@ -2104,6 +2168,7 @@ static const struct hc_driver isp1760_hc_driver = {
.get_frame_number = isp1760_get_frame, .get_frame_number = isp1760_get_frame,
.hub_status_data = isp1760_hub_status_data, .hub_status_data = isp1760_hub_status_data,
.hub_control = isp1760_hub_control, .hub_control = isp1760_hub_control,
.clear_tt_buffer_complete = isp1760_clear_tt_buffer_complete,
}; };
int __init init_kmem_once(void) int __init init_kmem_once(void)
......
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