mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-05 18:39:59 +00:00
![Mike Nuss](/assets/img/avatar_default.png)
The ZF Micro OHCI controller exhibits unexpected behavior that seems to be related to high load. Under certain conditions, the controller will complete a TD, remove it from the endpoint's queue, and fail to add it to the donelist. This causes the endpoint to appear to stop responding. Worse, if the device is removed while in that state, OHCI will hang while waiting for the orphaned TD to complete. The situation is not recoverable without rebooting. This fix enhances the scope of the existing OHCI_QUIRK_ZFMICRO flag: 1. A watchdog routine periodically scans the OHCI structures to check for orphaned TDs. In these cases the TD is taken back from the controller and completed normally. 2. If a device is removed while the endpoint is hung but before the watchdog catches the situation, any outstanding TDs are taken back from the controller in the 'sanitize' phase. The ohci-hcd driver used to print "INTR_SF lossage" in this situation; this changes it to the universally accurate "ED unlink timeout". Other instances of this message presumably have different root causes. Both this Compaq quirk and a NEC quirk are now properly compiled out for non-PCI builds of this driver. Signed-off-by: Mike Nuss <mike@terascala.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
138 lines
3.3 KiB
C
138 lines
3.3 KiB
C
/*
|
|
* OHCI HCD (Host Controller Driver) for USB.
|
|
*
|
|
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
|
|
* (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
|
|
*
|
|
* This file is licenced under the GPL.
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
* OHCI deals with three types of memory:
|
|
* - data used only by the HCD ... kmalloc is fine
|
|
* - async and periodic schedules, shared by HC and HCD ... these
|
|
* need to use dma_pool or dma_alloc_coherent
|
|
* - driver buffers, read/written by HC ... the hcd glue or the
|
|
* device driver provides us with dma addresses
|
|
*
|
|
* There's also "register" data, which is memory mapped.
|
|
* No memory seen by this driver (or any HCD) may be paged out.
|
|
*/
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void ohci_hcd_init (struct ohci_hcd *ohci)
|
|
{
|
|
ohci->next_statechange = jiffies;
|
|
spin_lock_init (&ohci->lock);
|
|
INIT_LIST_HEAD (&ohci->pending);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static int ohci_mem_init (struct ohci_hcd *ohci)
|
|
{
|
|
ohci->td_cache = dma_pool_create ("ohci_td",
|
|
ohci_to_hcd(ohci)->self.controller,
|
|
sizeof (struct td),
|
|
32 /* byte alignment */,
|
|
0 /* no page-crossing issues */);
|
|
if (!ohci->td_cache)
|
|
return -ENOMEM;
|
|
ohci->ed_cache = dma_pool_create ("ohci_ed",
|
|
ohci_to_hcd(ohci)->self.controller,
|
|
sizeof (struct ed),
|
|
16 /* byte alignment */,
|
|
0 /* no page-crossing issues */);
|
|
if (!ohci->ed_cache) {
|
|
dma_pool_destroy (ohci->td_cache);
|
|
return -ENOMEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ohci_mem_cleanup (struct ohci_hcd *ohci)
|
|
{
|
|
if (ohci->td_cache) {
|
|
dma_pool_destroy (ohci->td_cache);
|
|
ohci->td_cache = NULL;
|
|
}
|
|
if (ohci->ed_cache) {
|
|
dma_pool_destroy (ohci->ed_cache);
|
|
ohci->ed_cache = NULL;
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* ohci "done list" processing needs this mapping */
|
|
static inline struct td *
|
|
dma_to_td (struct ohci_hcd *hc, dma_addr_t td_dma)
|
|
{
|
|
struct td *td;
|
|
|
|
td_dma &= TD_MASK;
|
|
td = hc->td_hash [TD_HASH_FUNC(td_dma)];
|
|
while (td && td->td_dma != td_dma)
|
|
td = td->td_hash;
|
|
return td;
|
|
}
|
|
|
|
/* TDs ... */
|
|
static struct td *
|
|
td_alloc (struct ohci_hcd *hc, gfp_t mem_flags)
|
|
{
|
|
dma_addr_t dma;
|
|
struct td *td;
|
|
|
|
td = dma_pool_alloc (hc->td_cache, mem_flags, &dma);
|
|
if (td) {
|
|
/* in case hc fetches it, make it look dead */
|
|
memset (td, 0, sizeof *td);
|
|
td->hwNextTD = cpu_to_hc32 (hc, dma);
|
|
td->td_dma = dma;
|
|
/* hashed in td_fill */
|
|
}
|
|
return td;
|
|
}
|
|
|
|
static void
|
|
td_free (struct ohci_hcd *hc, struct td *td)
|
|
{
|
|
struct td **prev = &hc->td_hash [TD_HASH_FUNC (td->td_dma)];
|
|
|
|
while (*prev && *prev != td)
|
|
prev = &(*prev)->td_hash;
|
|
if (*prev)
|
|
*prev = td->td_hash;
|
|
else if ((td->hwINFO & cpu_to_hc32(hc, TD_DONE)) != 0)
|
|
ohci_dbg (hc, "no hash for td %p\n", td);
|
|
dma_pool_free (hc->td_cache, td, td->td_dma);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
/* EDs ... */
|
|
static struct ed *
|
|
ed_alloc (struct ohci_hcd *hc, gfp_t mem_flags)
|
|
{
|
|
dma_addr_t dma;
|
|
struct ed *ed;
|
|
|
|
ed = dma_pool_alloc (hc->ed_cache, mem_flags, &dma);
|
|
if (ed) {
|
|
memset (ed, 0, sizeof (*ed));
|
|
INIT_LIST_HEAD (&ed->td_list);
|
|
ed->dma = dma;
|
|
}
|
|
return ed;
|
|
}
|
|
|
|
static void
|
|
ed_free (struct ohci_hcd *hc, struct ed *ed)
|
|
{
|
|
dma_pool_free (hc->ed_cache, ed, ed->dma);
|
|
}
|
|
|