Commit 914b7012 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: EHCI: use the new clear_tt_buffer interface

This patch (as1256) changes ehci-hcd and all the other drivers in the
EHCI family to make use of the new clear_tt_buffer callbacks.  When a
Clear-TT-Buffer request is in progress for a QH, the QH is not allowed
to be linked into the async schedule until the request is finished.
At that time, if there are any URBs queued for the QH, it is linked
into the async schedule.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Cc: stable <stable@kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent cb88a1b8
...@@ -113,6 +113,8 @@ static const struct hc_driver ehci_au1xxx_hc_driver = { ...@@ -113,6 +113,8 @@ static const struct hc_driver ehci_au1xxx_hc_driver = {
.bus_resume = ehci_bus_resume, .bus_resume = ehci_bus_resume,
.relinquish_port = ehci_relinquish_port, .relinquish_port = ehci_relinquish_port,
.port_handed_over = ehci_port_handed_over, .port_handed_over = ehci_port_handed_over,
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
}; };
static int ehci_hcd_au1xxx_drv_probe(struct platform_device *pdev) static int ehci_hcd_au1xxx_drv_probe(struct platform_device *pdev)
......
...@@ -325,6 +325,8 @@ static const struct hc_driver ehci_fsl_hc_driver = { ...@@ -325,6 +325,8 @@ static const struct hc_driver ehci_fsl_hc_driver = {
.bus_resume = ehci_bus_resume, .bus_resume = ehci_bus_resume,
.relinquish_port = ehci_relinquish_port, .relinquish_port = ehci_relinquish_port,
.port_handed_over = ehci_port_handed_over, .port_handed_over = ehci_port_handed_over,
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
}; };
static int ehci_fsl_drv_probe(struct platform_device *pdev) static int ehci_fsl_drv_probe(struct platform_device *pdev)
......
...@@ -1003,6 +1003,8 @@ idle_timeout: ...@@ -1003,6 +1003,8 @@ idle_timeout:
schedule_timeout_uninterruptible(1); schedule_timeout_uninterruptible(1);
goto rescan; goto rescan;
case QH_STATE_IDLE: /* fully unlinked */ case QH_STATE_IDLE: /* fully unlinked */
if (qh->clearing_tt)
goto idle_timeout;
if (list_empty (&qh->qtd_list)) { if (list_empty (&qh->qtd_list)) {
qh_put (qh); qh_put (qh);
break; break;
......
...@@ -61,6 +61,8 @@ static const struct hc_driver ixp4xx_ehci_hc_driver = { ...@@ -61,6 +61,8 @@ static const struct hc_driver ixp4xx_ehci_hc_driver = {
#endif #endif
.relinquish_port = ehci_relinquish_port, .relinquish_port = ehci_relinquish_port,
.port_handed_over = ehci_port_handed_over, .port_handed_over = ehci_port_handed_over,
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
}; };
static int ixp4xx_ehci_probe(struct platform_device *pdev) static int ixp4xx_ehci_probe(struct platform_device *pdev)
......
...@@ -165,6 +165,8 @@ static const struct hc_driver ehci_orion_hc_driver = { ...@@ -165,6 +165,8 @@ static const struct hc_driver ehci_orion_hc_driver = {
.bus_resume = ehci_bus_resume, .bus_resume = ehci_bus_resume,
.relinquish_port = ehci_relinquish_port, .relinquish_port = ehci_relinquish_port,
.port_handed_over = ehci_port_handed_over, .port_handed_over = ehci_port_handed_over,
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
}; };
static void __init static void __init
......
...@@ -404,6 +404,8 @@ static const struct hc_driver ehci_pci_hc_driver = { ...@@ -404,6 +404,8 @@ static const struct hc_driver ehci_pci_hc_driver = {
.bus_resume = ehci_bus_resume, .bus_resume = ehci_bus_resume,
.relinquish_port = ehci_relinquish_port, .relinquish_port = ehci_relinquish_port,
.port_handed_over = ehci_port_handed_over, .port_handed_over = ehci_port_handed_over,
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
...@@ -79,6 +79,8 @@ static const struct hc_driver ehci_ppc_of_hc_driver = { ...@@ -79,6 +79,8 @@ static const struct hc_driver ehci_ppc_of_hc_driver = {
#endif #endif
.relinquish_port = ehci_relinquish_port, .relinquish_port = ehci_relinquish_port,
.port_handed_over = ehci_port_handed_over, .port_handed_over = ehci_port_handed_over,
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
}; };
......
...@@ -75,6 +75,8 @@ static const struct hc_driver ps3_ehci_hc_driver = { ...@@ -75,6 +75,8 @@ static const struct hc_driver ps3_ehci_hc_driver = {
#endif #endif
.relinquish_port = ehci_relinquish_port, .relinquish_port = ehci_relinquish_port,
.port_handed_over = ehci_port_handed_over, .port_handed_over = ehci_port_handed_over,
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
}; };
static int __devinit ps3_ehci_probe(struct ps3_system_bus_device *dev) static int __devinit ps3_ehci_probe(struct ps3_system_bus_device *dev)
......
...@@ -139,6 +139,55 @@ qh_refresh (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -139,6 +139,55 @@ qh_refresh (struct ehci_hcd *ehci, struct ehci_qh *qh)
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static void qh_link_async(struct ehci_hcd *ehci, struct ehci_qh *qh);
static void ehci_clear_tt_buffer_complete(struct usb_hcd *hcd,
struct usb_host_endpoint *ep)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct ehci_qh *qh = ep->hcpriv;
unsigned long flags;
spin_lock_irqsave(&ehci->lock, flags);
qh->clearing_tt = 0;
if (qh->qh_state == QH_STATE_IDLE && !list_empty(&qh->qtd_list)
&& HC_IS_RUNNING(hcd->state))
qh_link_async(ehci, qh);
spin_unlock_irqrestore(&ehci->lock, flags);
}
static void ehci_clear_tt_buffer(struct ehci_hcd *ehci, struct ehci_qh *qh,
struct urb *urb, u32 token)
{
/* If an async split transaction gets an error or is unlinked,
* the TT buffer may be left in an indeterminate state. We
* have to clear the TT buffer.
*
* Note: this routine is never called for Isochronous transfers.
*/
if (urb->dev->tt && !usb_pipeint(urb->pipe) && !qh->clearing_tt) {
#ifdef DEBUG
struct usb_device *tt = urb->dev->tt->hub;
dev_dbg(&tt->dev,
"clear tt buffer port %d, a%d ep%d t%08x\n",
urb->dev->ttport, urb->dev->devnum,
usb_pipeendpoint(urb->pipe), token);
#endif /* DEBUG */
if (!ehci_is_TDI(ehci)
|| urb->dev->tt->hub !=
ehci_to_hcd(ehci)->self.root_hub) {
if (usb_hub_clear_tt_buffer(urb) == 0)
qh->clearing_tt = 1;
} else {
/* REVISIT ARC-derived cores don't clear the root
* hub TT buffer in this way...
*/
}
}
}
static int qtd_copy_status ( static int qtd_copy_status (
struct ehci_hcd *ehci, struct ehci_hcd *ehci,
struct urb *urb, struct urb *urb,
...@@ -195,28 +244,6 @@ static int qtd_copy_status ( ...@@ -195,28 +244,6 @@ static int qtd_copy_status (
usb_pipeendpoint (urb->pipe), usb_pipeendpoint (urb->pipe),
usb_pipein (urb->pipe) ? "in" : "out", usb_pipein (urb->pipe) ? "in" : "out",
token, status); token, status);
/* if async CSPLIT failed, try cleaning out the TT buffer */
if (status != -EPIPE
&& urb->dev->tt
&& !usb_pipeint(urb->pipe)
&& ((token & QTD_STS_MMF) != 0
|| QTD_CERR(token) == 0)
&& (!ehci_is_TDI(ehci)
|| urb->dev->tt->hub !=
ehci_to_hcd(ehci)->self.root_hub)) {
#ifdef DEBUG
struct usb_device *tt = urb->dev->tt->hub;
dev_dbg (&tt->dev,
"clear tt buffer port %d, a%d ep%d t%08x\n",
urb->dev->ttport, urb->dev->devnum,
usb_pipeendpoint (urb->pipe), token);
#endif /* DEBUG */
/* REVISIT ARC-derived cores don't clear the root
* hub TT buffer in this way...
*/
usb_hub_clear_tt_buffer(urb);
}
} }
return status; return status;
...@@ -407,9 +434,16 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -407,9 +434,16 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
/* qh unlinked; token in overlay may be most current */ /* qh unlinked; token in overlay may be most current */
if (state == QH_STATE_IDLE if (state == QH_STATE_IDLE
&& cpu_to_hc32(ehci, qtd->qtd_dma) && cpu_to_hc32(ehci, qtd->qtd_dma)
== qh->hw_current) == qh->hw_current) {
token = hc32_to_cpu(ehci, qh->hw_token); token = hc32_to_cpu(ehci, qh->hw_token);
/* An unlink may leave an incomplete
* async transaction in the TT buffer.
* We have to clear it.
*/
ehci_clear_tt_buffer(ehci, qh, urb, token);
}
/* force halt for unlinked or blocked qh, so we'll /* force halt for unlinked or blocked qh, so we'll
* patch the qh later and so that completions can't * patch the qh later and so that completions can't
* activate it while we "know" it's stopped. * activate it while we "know" it's stopped.
...@@ -435,6 +469,13 @@ halt: ...@@ -435,6 +469,13 @@ halt:
&& (qtd->hw_alt_next && (qtd->hw_alt_next
& EHCI_LIST_END(ehci))) & EHCI_LIST_END(ehci)))
last_status = -EINPROGRESS; last_status = -EINPROGRESS;
/* As part of low/full-speed endpoint-halt processing
* we must clear the TT buffer (11.17.5).
*/
if (unlikely(last_status != -EINPROGRESS &&
last_status != -EREMOTEIO))
ehci_clear_tt_buffer(ehci, qh, urb, token);
} }
/* if we're removing something not at the queue head, /* if we're removing something not at the queue head,
...@@ -864,6 +905,10 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) ...@@ -864,6 +905,10 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
__hc32 dma = QH_NEXT(ehci, qh->qh_dma); __hc32 dma = QH_NEXT(ehci, qh->qh_dma);
struct ehci_qh *head; struct ehci_qh *head;
/* Don't link a QH if there's a Clear-TT-Buffer pending */
if (unlikely(qh->clearing_tt))
return;
/* (re)start the async schedule? */ /* (re)start the async schedule? */
head = ehci->async; head = ehci->async;
timer_action_done (ehci, TIMER_ASYNC_OFF); timer_action_done (ehci, TIMER_ASYNC_OFF);
......
...@@ -354,7 +354,9 @@ struct ehci_qh { ...@@ -354,7 +354,9 @@ struct ehci_qh {
unsigned short period; /* polling interval */ unsigned short period; /* polling interval */
unsigned short start; /* where polling starts */ unsigned short start; /* where polling starts */
#define NO_FRAME ((unsigned short)~0) /* pick new start */ #define NO_FRAME ((unsigned short)~0) /* pick new start */
struct usb_device *dev; /* access to TT */ struct usb_device *dev; /* access to TT */
unsigned clearing_tt:1; /* Clear-TT-Buf in progress */
} __attribute__ ((aligned (32))); } __attribute__ ((aligned (32)));
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
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