patch-2.4.0-test7 linux/drivers/usb/usb-ohci.c

Next file: linux/drivers/usb/usb-ohci.h
Previous file: linux/drivers/usb/usb-core.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.0-test6/linux/drivers/usb/usb-ohci.c linux/drivers/usb/usb-ohci.c
@@ -76,7 +76,6 @@
 	(OHCI_CTRL_CBSR & 0x3) \
 	| OHCI_CTRL_BLE | OHCI_CTRL_CLE | OHCI_CTRL_IE | OHCI_CTRL_PLE
 
-static DECLARE_WAIT_QUEUE_HEAD (op_wakeup); 
 static LIST_HEAD (ohci_hcd_list);
 static spinlock_t usb_ed_lock = SPIN_LOCK_UNLOCKED;
 
@@ -534,7 +533,6 @@
 {
 	unsigned long flags;
 	ohci_t * ohci;
-	DECLARE_WAITQUEUE (wait, current);
 	
 	if (!urb) /* just to be sure */ 
 		return -EINVAL;
@@ -580,14 +578,18 @@
 			spin_unlock_irqrestore (&usb_ed_lock, flags);
 
 			if (!(urb->transfer_flags & USB_ASYNC_UNLINK)) {
+				DECLARE_WAIT_QUEUE_HEAD (unlink_wakeup); 
+				DECLARE_WAITQUEUE (wait, current);
+
 				usb_dec_dev_use (urb->dev);	
-				add_wait_queue (&op_wakeup, &wait);
-				current->state = TASK_UNINTERRUPTIBLE;
 				/* wait until all TDs are deleted */
-				if (!schedule_timeout (HZ / 10))
-					err("unlink URB timeout!");
-				remove_wait_queue (&op_wakeup, &wait); 
+				add_wait_queue (&unlink_wakeup, &wait);
+				urb_priv->wait = &unlink_wakeup;
+				current->state = TASK_UNINTERRUPTIBLE;
+				schedule ();
+				remove_wait_queue (&unlink_wakeup, &wait); 
 				urb->status = -ENOENT;
+				urb_priv->wait = 0;
 			} else {
 				/* usb_dec_dev_use done in dl_del_list() */
 				urb->status = -EINPROGRESS;
@@ -634,7 +636,6 @@
 	unsigned long flags;
 	int i, cnt = 0;
 	ed_t * ed;
-	DECLARE_WAITQUEUE (wait, current);
 	struct ohci_device * dev = usb_to_ohci (usb_dev);
 	ohci_t * ohci = usb_dev->bus->hcpriv;
 	
@@ -643,13 +644,20 @@
 	
 	if (usb_dev->devnum >= 0) {
 	
-		/* delete all TDs of all EDs */
+		/* driver disconnects should have unlinked all urbs
+		 * (freeing all the TDs, unlinking EDs) but we need
+		 * to defend against bugs that prevent that.
+		 */
 		spin_lock_irqsave (&usb_ed_lock, flags);	
 		for(i = 0; i < NUM_EDS; i++) {
   			ed = &(dev->ed[i]);
   			if (ed->state != ED_NEW) {
-  				if (ed->state == ED_OPER)
+  				if (ed->state == ED_OPER) {
+					/* driver on that interface didn't unlink an urb */
+					dbg ("driver usb-%s dev %d ed 0x%x unfreed URB",
+						ohci->ohci_dev->slot_name, usb_dev->devnum, i);
 					ep_unlink (ohci, ed);
+				}
   				ep_rm_ed (usb_dev, ed);
   				ed->state = ED_DEL;
   				cnt++;
@@ -657,6 +665,9 @@
   		}
   		spin_unlock_irqrestore (&usb_ed_lock, flags);
   		
+		/* if the controller is running, tds for those unlinked
+		 * urbs get freed by dl_del_list at the next SF interrupt
+		 */
 		if (cnt > 0) {
 
 			if (ohci->disabled) {
@@ -670,15 +681,21 @@
 				warn ("TD leak, %d", cnt);
 
 			} else if (!in_interrupt ()) {
+				DECLARE_WAIT_QUEUE_HEAD (freedev_wakeup); 
+				DECLARE_WAITQUEUE (wait, current);
+
 				/* SF interrupt handler calls dl_del_list */
-				add_wait_queue (&op_wakeup, &wait);
+				add_wait_queue (&freedev_wakeup, &wait);
+				dev->wait = &freedev_wakeup;
 				current->state = TASK_UNINTERRUPTIBLE;
-				schedule_timeout (HZ / 10);
-				remove_wait_queue (&op_wakeup, &wait);
+				schedule ();
+				remove_wait_queue (&freedev_wakeup, &wait);
 
 			} else {
-				/* drivers mustn't expect this to work */
-				err ("can't free tds, interrupt context");
+				/* likely some interface's driver has a refcount bug */
+				err ("bus %s devnum %d deletion in interrupt",
+					ohci->ohci_dev->slot_name, usb_dev->devnum);
+				BUG ();
 			}
 		}
 	}
@@ -907,35 +924,34 @@
 #endif
 		break;
 		
-    case ISO:
-    	if (ohci->ed_isotail == ed)
-				ohci->ed_isotail = ed->ed_prev;
+	case ISO:
+		if (ohci->ed_isotail == ed)
+			ohci->ed_isotail = ed->ed_prev;
 		if (ed->hwNextED != 0) 
-				((ed_t *) bus_to_virt (le32_to_cpup (&ed->hwNextED)))->ed_prev = ed->ed_prev;
-				
+		    ((ed_t *) bus_to_virt (le32_to_cpup (&ed->hwNextED)))->ed_prev = ed->ed_prev;
+				    
 		if (ed->ed_prev != NULL) {
 			ed->ed_prev->hwNextED = ed->hwNextED;
 		} else {
-			for (i = 0; i < 32; i += inter) {
-				inter = 1;
+			for (i = 0; i < 32; i++) {
 				for (ed_p = &(ohci->hcca.int_table[ep_rev (5, i)]); 
-					*ed_p != 0; 
-					ed_p = &(((ed_t *) bus_to_virt (le32_to_cpup (ed_p)))->hwNextED)) {
-						inter = ep_rev (6, ((ed_t *) bus_to_virt (le32_to_cpup (ed_p)))->int_interval);
-						if(((ed_t *) bus_to_virt (le32_to_cpup (ed_p))) == ed) {
-			  				*ed_p = ed->hwNextED;		
-			  				break;
-			  			}
-			  	}
+						*ed_p != 0; 
+						ed_p = &(((ed_t *) bus_to_virt (le32_to_cpup (ed_p)))->hwNextED)) {
+					// inter = ep_rev (6, ((ed_t *) bus_to_virt (le32_to_cpup (ed_p)))->int_interval);
+					if(((ed_t *) bus_to_virt (le32_to_cpup (ed_p))) == ed) {
+						*ed_p = ed->hwNextED;		
+						break;
+					}
+				}
 			}	
 		}	
 #ifdef DEBUG
 		ep_print_int_eds (ohci, "UNLINK_ISO");
 #endif
 		break;
-    }
-    ed->state = ED_UNLINK;
-    return 0;
+	}
+	ed->state = ED_UNLINK;
+	return 0;
 }
 
 
@@ -1276,28 +1292,33 @@
 				tdINFO = le32_to_cpup (&td->hwINFO);
 				if (TD_CC_GET (tdINFO) < 0xE) dl_transfer_length (td);
 				*td_p = td->hwNextTD | (*td_p & cpu_to_le32 (0x3));
-				if(++ (urb_priv->td_cnt) == urb_priv->length) 
+				/* URB is done; clean up */
+				if (++(urb_priv->td_cnt) == urb_priv->length) {
+					void	*condition = urb_priv->wait; 
+
 					urb_rm_priv (urb);
 					if (urb->transfer_flags & USB_ASYNC_UNLINK) {
 						usb_dec_dev_use (urb->dev);
 						urb->status = -ECONNRESET;
 						urb->complete (urb); 
-					} else {	
-						wake_up (&op_wakeup);
+					} else if (condition) {	
+						/* unblock sohci_unlink_urb */
+						wake_up (condition);
 					}
+				}
 			} else {
 				td_p = &td->hwNextTD;
 			}
-			
 		}
+
 		if (ed->state & ED_DEL) { /* set by sohci_free_dev */
 			struct ohci_device * dev = usb_to_ohci (ohci->dev[edINFO & 0x7F]);
-   	 		OHCI_FREE (tdTailP); /* free dummy td */
+			OHCI_FREE (tdTailP); /* free dummy td */
    	 		ed->hwINFO = cpu_to_le32 (OHCI_ED_SKIP); 
    	 		ed->state = ED_NEW; 
    	 		/* if all eds are removed wake up sohci_free_dev */
-   	 		if (!--dev->ed_cnt)
-				wake_up (&op_wakeup);
+   	 		if (!--dev->ed_cnt && dev->wait)
+				wake_up (dev->wait);
    	 	}
    	 	else {
    	 		ed->state &= ~ED_URB_DEL;
@@ -1839,6 +1860,7 @@
 
  	/* start controller operations */
  	ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER;
+	ohci->disabled = 0;
  	writel (ohci->hc_control, &ohci->regs->control);
  
 	/* Choose the interrupts we care about now, others later on demand */
@@ -1850,25 +1872,28 @@
 	writel ((readl(&ohci->regs->roothub.a) | RH_A_NPS) & ~RH_A_PSM,
 		&ohci->regs->roothub.a);
 	writel (RH_HS_LPSC, &ohci->regs->roothub.status);
+#endif	/* OHCI_USE_NPS */
+
 	// POTPGT delay is bits 24-31, in 2 ms units.
 	mdelay ((readl(&ohci->regs->roothub.a) >> 23) & 0x1fe);
-#endif	/* OHCI_USE_NPS */
  
 	/* connect the virtual root hub */
 	ohci->rh.devnum = 0;
 	usb_dev = usb_alloc_dev (NULL, ohci->bus);
-	if (!usb_dev)
+	if (!usb_dev) {
+	    ohci->disabled = 1;
 	    return -ENOMEM;
+	}
 
 	dev = usb_to_ohci (usb_dev);
 	ohci->bus->root_hub = usb_dev;
 	usb_connect (usb_dev);
 	if (usb_new_device (usb_dev) != 0) {
 		usb_free_dev (usb_dev); 
+		ohci->disabled = 1;
 		return -ENODEV;
 	}
 	
-	ohci->disabled = 0;
 	return 0;
 }
 
@@ -1936,7 +1961,7 @@
 
 /* allocate OHCI */
 
-static ohci_t * __devinit hc_alloc_ohci (void * mem_base)
+static ohci_t * __devinit hc_alloc_ohci (struct pci_dev *dev, void * mem_base)
 {
 	ohci_t * ohci;
 	struct usb_bus * bus;
@@ -1947,9 +1972,16 @@
 		
 	memset (ohci, 0, sizeof (ohci_t));
 	
+	ohci->disabled = 1;
 	ohci->irq = -1;
 	ohci->regs = mem_base;   
 
+	ohci->ohci_dev = dev;
+	dev->driver_data = ohci;
+ 
+	INIT_LIST_HEAD (&ohci->ohci_hcd_list);
+	list_add (&ohci->ohci_hcd_list, &ohci_hcd_list);
+
 	bus = usb_alloc_bus (&sohci_device_operations);
 	if (!bus) {
 		kfree (ohci);
@@ -1958,8 +1990,7 @@
 
 	ohci->bus = bus;
 	bus->hcpriv = (void *) ohci;
-	ohci->disabled = 1;
- 
+
 	return ohci;
 } 
 
@@ -2008,6 +2039,7 @@
 hc_found_ohci (struct pci_dev *dev, int irq, void * mem_base)
 {
 	ohci_t * ohci;
+	u8 latency, limit;
 	char buf[8], *bufp = buf;
 
 #ifndef __sparc__
@@ -2019,15 +2051,24 @@
 		(unsigned long)	mem_base, bufp);
 	printk(KERN_INFO __FILE__ ": usb-%s, %s\n", dev->slot_name, dev->name);
     
-	ohci = hc_alloc_ohci (mem_base);
+	ohci = hc_alloc_ohci (dev, mem_base);
 	if (!ohci) {
 		return -ENOMEM;
 	}
 
-	ohci->ohci_dev = dev;
-	dev->driver_data = ohci;
-	INIT_LIST_HEAD (&ohci->ohci_hcd_list);
-	list_add (&ohci->ohci_hcd_list, &ohci_hcd_list);
+	/* bad pci latencies can contribute to overruns */ 
+	pci_read_config_byte (dev, PCI_LATENCY_TIMER, &latency);
+	if (latency) {
+		pci_read_config_byte (dev, PCI_MAX_LAT, &limit);
+		if (limit && limit < latency) {
+			dbg ("PCI latency reduced to max %d", limit);
+			pci_write_config_byte (dev, PCI_LATENCY_TIMER, limit);
+			ohci->pci_latency = limit;
+		} else {
+			/* it might already have been reduced */
+			ohci->pci_latency = latency;
+		}
+	}
 
 	if (hc_reset (ohci) < 0) {
 		hc_release_ohci (ohci);
@@ -2042,7 +2083,7 @@
 	
 	if (request_irq (irq, hc_interrupt, SA_SHIRQ,
 			ohci_pci_driver.name, ohci) != 0) {
-		err ("request interrupt %d failed", irq);
+		err ("request interrupt %s failed", bufp);
 		hc_release_ohci (ohci);
 		return -EBUSY;
 	}
@@ -2072,6 +2113,9 @@
 	int temp;
 	int i;
 
+	if (ohci->pci_latency)
+		pci_write_config_byte (ohci->ohci_dev, PCI_LATENCY_TIMER, ohci->pci_latency);
+
 	ohci->disabled = 1;
 	if (ohci->bus->root_hub)
 		usb_disconnect (&ohci->bus->root_hub);
@@ -2106,7 +2150,6 @@
 ohci_pci_probe (struct pci_dev *dev, const struct pci_device_id *id)
 {
 	unsigned long mem_resource, mem_len;
-	u8 latency, limit;
 	void *mem_base;
 
 	if (pci_enable_device(dev) < 0)
@@ -2128,14 +2171,6 @@
 
 	/* controller writes into our memory */
 	pci_set_master (dev);
-	pci_read_config_byte (dev, PCI_LATENCY_TIMER, &latency);
-	if (latency) {
-		pci_read_config_byte (dev, PCI_MAX_LAT, &limit);
-		if (limit && limit < latency) {
-			dbg ("PCI latency reduced to max %d", limit);
-			pci_write_config_byte (dev, PCI_LATENCY_TIMER, limit);
-		}
-	}
 
 	return hc_found_ohci (dev, dev->irq, mem_base);
 } 
@@ -2157,6 +2192,9 @@
 		ohci->disabled ? " (disabled)" : "",
 		in_interrupt () ? " in interrupt" : ""
 		);
+#ifdef	DEBUG
+	ohci_dump (ohci, 1);
+#endif
 
 	/* don't wake up sleeping controllers, or block in interrupt context */
 	if ((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER || in_interrupt ()) {
@@ -2209,6 +2247,14 @@
 	ohci_t		*ohci = (ohci_t *) dev->driver_data;
 	int		temp;
 
+	/* guard against multiple resumes */
+	atomic_inc (&ohci->resume_count);
+	if (atomic_read (&ohci->resume_count) != 1) {
+		err ("concurrent PCI resumes for usb-%s", dev->slot_name);
+		atomic_dec (&ohci->resume_count);
+		return;
+	}
+
 	/* did we suspend, or were we powered off? */
 	ohci->hc_control = readl (&ohci->regs->control);
 	temp = ohci->hc_control & OHCI_CTRL_HCFS;
@@ -2253,6 +2299,9 @@
 	default:
 		warn ("odd PCI resume for usb-%s", dev->slot_name);
 	}
+
+	/* controller is operational, extra resumes are harmless */
+	atomic_dec (&ohci->resume_count);
 }
 
 #endif	/* CONFIG_PM */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)