Commit e8ee53e8 authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

USB ohci driver fixes

  
   - An oopsable bug affecting unlink of interrupt
     transfers. Fix mirrors one done ages ago for ISO.
     (Original patch by Matt Hughes)
   - Better cleanup on init failure (Matthew Frederickson)
   - fixes the problem Stuart reported, where interrupt urbs
     couldn't be unlinked from their completion handlers, and it
     also makes OHCI return the correct status code for async
     unlink requests (-EINPROGRESS not zero).
parent 791d5027
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* URB OHCI HCD (Host Controller Driver) for USB. * URB OHCI HCD (Host Controller Driver) for USB.
* *
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
* (C) Copyright 2000-2001 David Brownell <dbrownell@users.sourceforge.net> * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
* *
* [ Initialisation is based on Linus' ] * [ Initialisation is based on Linus' ]
* [ uhci code and gregs ohci fragments ] * [ uhci code and gregs ohci fragments ]
...@@ -12,6 +12,11 @@ ...@@ -12,6 +12,11 @@
* *
* History: * History:
* *
* 2002/03/08 interrupt unlink fix (Matt Hughes), better cleanup on
* load failure (Matthew Frederickson)
* 2002/01/20 async unlink fixes: return -EINPROGRESS (per spec) and
* make interrupt unlink-in-completion work (db)
*
* 2001/09/19 USB_ZERO_PACKET support (Jean Tourrilhes) * 2001/09/19 USB_ZERO_PACKET support (Jean Tourrilhes)
* 2001/07/17 power management and pmac cleanup (Benjamin Herrenschmidt) * 2001/07/17 power management and pmac cleanup (Benjamin Herrenschmidt)
* 2001/03/24 td/ed hashing to remove bus_to_virt (Steve Longerbeam); * 2001/03/24 td/ed hashing to remove bus_to_virt (Steve Longerbeam);
...@@ -489,7 +494,6 @@ static int sohci_return_urb (struct ohci *hc, struct urb * urb) ...@@ -489,7 +494,6 @@ static int sohci_return_urb (struct ohci *hc, struct urb * urb)
/* implicitly requeued */ /* implicitly requeued */
urb->actual_length = 0; urb->actual_length = 0;
urb->status = -EINPROGRESS; urb->status = -EINPROGRESS;
if (urb_priv->state != URB_DEL)
td_submit_urb (urb); td_submit_urb (urb);
break; break;
...@@ -800,6 +804,7 @@ static int sohci_unlink_urb (struct urb * urb) ...@@ -800,6 +804,7 @@ static int sohci_unlink_urb (struct urb * urb)
/* usb_dec_dev_use done in dl_del_list() */ /* usb_dec_dev_use done in dl_del_list() */
urb->status = -EINPROGRESS; urb->status = -EINPROGRESS;
spin_unlock_irqrestore (&usb_ed_lock, flags); spin_unlock_irqrestore (&usb_ed_lock, flags);
return -EINPROGRESS;
} }
} else { } else {
urb_rm_priv (urb); urb_rm_priv (urb);
...@@ -1083,6 +1088,28 @@ static int ep_link (ohci_t * ohci, ed_t * edi) ...@@ -1083,6 +1088,28 @@ static int ep_link (ohci_t * ohci, ed_t * edi)
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/* scan the periodic table to find and unlink this ED */
static void periodic_unlink (
struct ohci *ohci,
struct ed *ed,
unsigned index,
unsigned period
) {
for (; index < NUM_INTS; index += period) {
__u32 *ed_p = &ohci->hcca->int_table [index];
/* ED might have been unlinked through another path */
while (*ed_p != 0) {
if ((dma_to_ed (ohci, le32_to_cpup (ed_p))) == ed) {
*ed_p = ed->hwNextED;
break;
}
ed_p = & ((dma_to_ed (ohci,
le32_to_cpup (ed_p)))->hwNextED);
}
}
}
/* unlink an ed from one of the HC chains. /* unlink an ed from one of the HC chains.
* just the link to the ed is unlinked. * just the link to the ed is unlinked.
* the link from the ed still points to another operational ed or 0 * the link from the ed still points to another operational ed or 0
...@@ -1090,11 +1117,7 @@ static int ep_link (ohci_t * ohci, ed_t * edi) ...@@ -1090,11 +1117,7 @@ static int ep_link (ohci_t * ohci, ed_t * edi)
static int ep_unlink (ohci_t * ohci, ed_t * ed) static int ep_unlink (ohci_t * ohci, ed_t * ed)
{ {
int int_branch;
int i; int i;
int inter;
int interval;
__u32 * ed_p;
ed->hwINFO |= cpu_to_le32 (OHCI_ED_SKIP); ed->hwINFO |= cpu_to_le32 (OHCI_ED_SKIP);
...@@ -1134,21 +1157,8 @@ static int ep_unlink (ohci_t * ohci, ed_t * ed) ...@@ -1134,21 +1157,8 @@ static int ep_unlink (ohci_t * ohci, ed_t * ed)
break; break;
case PIPE_INTERRUPT: case PIPE_INTERRUPT:
int_branch = ed->int_branch; periodic_unlink (ohci, ed, 0, 1);
interval = ed->int_interval; for (i = ed->int_branch; i < 32; i += ed->int_interval)
for (i = 0; i < ep_rev (6, interval); i += inter) {
for (ed_p = &(ohci->hcca->int_table[ep_rev (5, i) + int_branch]), inter = 1;
(*ed_p != 0) && (*ed_p != ed->hwNextED);
ed_p = &((dma_to_ed (ohci, le32_to_cpup (ed_p)))->hwNextED),
inter = ep_rev (6, (dma_to_ed (ohci, le32_to_cpup (ed_p)))->int_interval)) {
if((dma_to_ed (ohci, le32_to_cpup (ed_p))) == ed) {
*ed_p = ed->hwNextED;
break;
}
}
}
for (i = int_branch; i < 32; i += interval)
ohci->ohci_int_load[i] -= ed->int_load; ohci->ohci_int_load[i] -= ed->int_load;
#ifdef DEBUG #ifdef DEBUG
ep_print_int_eds (ohci, "UNLINK_INT"); ep_print_int_eds (ohci, "UNLINK_INT");
...@@ -1159,23 +1169,13 @@ static int ep_unlink (ohci_t * ohci, ed_t * ed) ...@@ -1159,23 +1169,13 @@ static int ep_unlink (ohci_t * ohci, ed_t * ed)
if (ohci->ed_isotail == ed) if (ohci->ed_isotail == ed)
ohci->ed_isotail = ed->ed_prev; ohci->ed_isotail = ed->ed_prev;
if (ed->hwNextED != 0) if (ed->hwNextED != 0)
(dma_to_ed (ohci, le32_to_cpup (&ed->hwNextED)))->ed_prev = ed->ed_prev; (dma_to_ed (ohci, le32_to_cpup (&ed->hwNextED)))
->ed_prev = ed->ed_prev;
if (ed->ed_prev != NULL) { if (ed->ed_prev != NULL)
ed->ed_prev->hwNextED = ed->hwNextED; ed->ed_prev->hwNextED = ed->hwNextED;
} else { else
for (i = 0; i < 32; i++) { periodic_unlink (ohci, ed, 0, 1);
for (ed_p = &(ohci->hcca->int_table[ep_rev (5, i)]);
*ed_p != 0;
ed_p = &((dma_to_ed (ohci, le32_to_cpup (ed_p)))->hwNextED)) {
// inter = ep_rev (6, (dma_to_ed (ohci, le32_to_cpup (ed_p)))->int_interval);
if((dma_to_ed (ohci, le32_to_cpup (ed_p))) == ed) {
*ed_p = ed->hwNextED;
break;
}
}
}
}
#ifdef DEBUG #ifdef DEBUG
ep_print_int_eds (ohci, "UNLINK_ISO"); ep_print_int_eds (ohci, "UNLINK_ISO");
#endif #endif
...@@ -2363,7 +2363,6 @@ static void hc_interrupt (int irq, void * __ohci, struct pt_regs * r) ...@@ -2363,7 +2363,6 @@ static void hc_interrupt (int irq, void * __ohci, struct pt_regs * r)
static ohci_t * __devinit hc_alloc_ohci (struct pci_dev *dev, void * mem_base) static ohci_t * __devinit hc_alloc_ohci (struct pci_dev *dev, void * mem_base)
{ {
ohci_t * ohci; ohci_t * ohci;
struct usb_bus * bus;
ohci = (ohci_t *) kmalloc (sizeof *ohci, GFP_KERNEL); ohci = (ohci_t *) kmalloc (sizeof *ohci, GFP_KERNEL);
if (!ohci) if (!ohci)
...@@ -2392,14 +2391,15 @@ static ohci_t * __devinit hc_alloc_ohci (struct pci_dev *dev, void * mem_base) ...@@ -2392,14 +2391,15 @@ static ohci_t * __devinit hc_alloc_ohci (struct pci_dev *dev, void * mem_base)
INIT_LIST_HEAD (&ohci->timeout_list); INIT_LIST_HEAD (&ohci->timeout_list);
bus = usb_alloc_bus (&sohci_device_operations); ohci->bus = usb_alloc_bus (&sohci_device_operations);
if (!bus) { if (!ohci->bus) {
pci_set_drvdata (dev, NULL);
pci_free_consistent (ohci->ohci_dev, sizeof *ohci->hcca,
ohci->hcca, ohci->hcca_dma);
kfree (ohci); kfree (ohci);
return NULL; return NULL;
} }
ohci->bus->hcpriv = (void *) ohci;
ohci->bus = bus;
bus->hcpriv = (void *) ohci;
return ohci; return ohci;
} }
...@@ -2425,9 +2425,11 @@ static void hc_release_ohci (ohci_t * ohci) ...@@ -2425,9 +2425,11 @@ static void hc_release_ohci (ohci_t * ohci)
ohci->irq = -1; ohci->irq = -1;
} }
pci_set_drvdata(ohci->ohci_dev, NULL); pci_set_drvdata(ohci->ohci_dev, NULL);
if (ohci->bus) {
if (ohci->bus->busnum)
usb_deregister_bus (ohci->bus); usb_deregister_bus (ohci->bus);
usb_free_bus (ohci->bus); usb_free_bus (ohci->bus);
}
list_del (&ohci->ohci_hcd_list); list_del (&ohci->ohci_hcd_list);
INIT_LIST_HEAD (&ohci->ohci_hcd_list); INIT_LIST_HEAD (&ohci->ohci_hcd_list);
...@@ -2575,12 +2577,14 @@ ohci_pci_probe (struct pci_dev *dev, const struct pci_device_id *id) ...@@ -2575,12 +2577,14 @@ ohci_pci_probe (struct pci_dev *dev, const struct pci_device_id *id)
{ {
unsigned long mem_resource, mem_len; unsigned long mem_resource, mem_len;
void *mem_base; void *mem_base;
int status;
if (pci_enable_device(dev) < 0) if (pci_enable_device(dev) < 0)
return -ENODEV; return -ENODEV;
if (!dev->irq) { if (!dev->irq) {
err("found OHCI device with no IRQ assigned. check BIOS settings!"); err("found OHCI device with no IRQ assigned. check BIOS settings!");
pci_disable_device (dev);
return -ENODEV; return -ENODEV;
} }
...@@ -2589,19 +2593,28 @@ ohci_pci_probe (struct pci_dev *dev, const struct pci_device_id *id) ...@@ -2589,19 +2593,28 @@ ohci_pci_probe (struct pci_dev *dev, const struct pci_device_id *id)
mem_len = pci_resource_len(dev, 0); mem_len = pci_resource_len(dev, 0);
if (!request_mem_region (mem_resource, mem_len, ohci_pci_driver.name)) { if (!request_mem_region (mem_resource, mem_len, ohci_pci_driver.name)) {
dbg ("controller already in use"); dbg ("controller already in use");
pci_disable_device (dev);
return -EBUSY; return -EBUSY;
} }
mem_base = ioremap_nocache (mem_resource, mem_len); mem_base = ioremap_nocache (mem_resource, mem_len);
if (!mem_base) { if (!mem_base) {
err("Error mapping OHCI memory"); err("Error mapping OHCI memory");
release_mem_region (mem_resource, mem_len);
pci_disable_device (dev);
return -EFAULT; return -EFAULT;
} }
/* controller writes into our memory */ /* controller writes into our memory */
pci_set_master (dev); pci_set_master (dev);
return hc_found_ohci (dev, dev->irq, mem_base, id); status = hc_found_ohci (dev, dev->irq, mem_base, id);
if (status < 0) {
iounmap (mem_base);
release_mem_region (mem_resource, mem_len);
pci_disable_device (dev);
}
return status;
} }
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
...@@ -2639,6 +2652,7 @@ ohci_pci_remove (struct pci_dev *dev) ...@@ -2639,6 +2652,7 @@ ohci_pci_remove (struct pci_dev *dev)
hc_release_ohci (ohci); hc_release_ohci (ohci);
release_mem_region (pci_resource_start (dev, 0), pci_resource_len (dev, 0)); release_mem_region (pci_resource_start (dev, 0), pci_resource_len (dev, 0));
pci_disable_device (dev);
} }
......
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