Bug 25555 - USB CDROM access crashes IBM Bladecenter diff -purNX /suse/olh/kernel/kernel_exclude.txt linux-2.4.22/drivers/usb/host/usb-ohci.c linux-2.4.23-pre9/drivers/usb/host/usb-ohci.c --- linux-2.4.22/drivers/usb/host/usb-ohci.c 2003-08-25 13:44:42.000000000 +0200 +++ linux-2.4.23-pre9/drivers/usb/host/usb-ohci.c 2003-10-30 20:31:26.000000000 +0100 @@ -104,10 +104,6 @@ #define OHCI_UNLINK_TIMEOUT (HZ / 10) -static LIST_HEAD (ohci_hcd_list); -static spinlock_t usb_ed_lock = SPIN_LOCK_UNLOCKED; - - /*-------------------------------------------------------------------------*/ /* AMD-756 (D2 rev) reports corrupt register contents in some cases. @@ -135,6 +131,57 @@ static u32 roothub_portstatus (struct oh * URB support functions *-------------------------------------------------------------------------*/ +static void ohci_complete_add(struct ohci *ohci, struct urb *urb) +{ + + if (urb->hcpriv != NULL) { + printk("completing with non-null priv!\n"); + return; + } + + if (ohci->complete_tail == NULL) { + ohci->complete_head = urb; + ohci->complete_tail = urb; + } else { + ohci->complete_head->hcpriv = urb; + ohci->complete_tail = urb; + } +} + +static inline struct urb *ohci_complete_get(struct ohci *ohci) +{ + struct urb *urb; + + if ((urb = ohci->complete_head) == NULL) + return NULL; + if (urb == ohci->complete_tail) { + ohci->complete_tail = NULL; + ohci->complete_head = NULL; + } else { + ohci->complete_head = urb->hcpriv; + } + urb->hcpriv = NULL; + return urb; +} + +static inline void ohci_complete(struct ohci *ohci) +{ + struct urb *urb; + + spin_lock(&ohci->ohci_lock); + while ((urb = ohci_complete_get(ohci)) != NULL) { + spin_unlock(&ohci->ohci_lock); + if (urb->dev) { + usb_dec_dev_use (urb->dev); + urb->dev = NULL; + } + if (urb->complete) + (*urb->complete)(urb); + spin_lock(&ohci->ohci_lock); + } + spin_unlock(&ohci->ohci_lock); +} + /* free HCD-private data associated with this URB */ static void urb_free_priv (struct ohci *hc, urb_priv_t * urb_priv) @@ -209,18 +256,12 @@ static void urb_rm_priv_locked (struct u } urb_free_priv ((struct ohci *)urb->dev->bus->hcpriv, urb_priv); - usb_dec_dev_use (urb->dev); - urb->dev = NULL; + } else { + if (urb->dev != NULL) { + err ("Non-null dev at rm_priv time"); + // urb->dev = NULL; } } - -static void urb_rm_priv (struct urb * urb) -{ - unsigned long flags; - - spin_lock_irqsave (&usb_ed_lock, flags); - urb_rm_priv_locked (urb); - spin_unlock_irqrestore (&usb_ed_lock, flags); } /*-------------------------------------------------------------------------*/ @@ -466,7 +507,6 @@ static int sohci_return_urb (struct ohci { urb_priv_t * urb_priv = urb->hcpriv; struct urb * urbt; - unsigned long flags; int i; if (!urb_priv) @@ -474,7 +514,8 @@ static int sohci_return_urb (struct ohci /* just to be sure */ if (!urb->complete) { - urb_rm_priv (urb); + urb_rm_priv_locked (urb); + ohci_complete_add(hc, urb); /* Just usb_dec_dev_use */ return -1; } @@ -499,7 +540,7 @@ static int sohci_return_urb (struct ohci urb->status = -EINPROGRESS; td_submit_urb (urb); } else { - urb_rm_priv(urb); + urb_rm_priv_locked (urb); urb->complete (urb); } break; @@ -515,7 +556,6 @@ static int sohci_return_urb (struct ohci ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); urb->complete (urb); - spin_lock_irqsave (&usb_ed_lock, flags); urb->actual_length = 0; urb->status = USB_ST_URB_PENDING; urb->start_frame = urb_priv->ed->last_iso + 1; @@ -526,18 +566,17 @@ static int sohci_return_urb (struct ohci } td_submit_urb (urb); } - spin_unlock_irqrestore (&usb_ed_lock, flags); } else { /* unlink URB, call complete */ - urb_rm_priv (urb); - urb->complete (urb); + urb_rm_priv_locked (urb); + ohci_complete_add(hc, urb); } break; case PIPE_BULK: case PIPE_CONTROL: /* unlink URB, call complete */ - urb_rm_priv (urb); - urb->complete (urb); + urb_rm_priv_locked (urb); + ohci_complete_add(hc, urb); break; } return 0; @@ -579,15 +618,19 @@ static int sohci_submit_urb (struct urb if (usb_pipedevice (pipe) == ohci->rh.devnum) return rh_submit_urb (urb); + spin_lock_irqsave(&ohci->ohci_lock, flags); + /* when controller's hung, permit only roothub cleanup attempts * such as powering down ports */ if (ohci->disabled) { + spin_unlock_irqrestore(&ohci->ohci_lock, flags); usb_dec_dev_use (urb->dev); return -ESHUTDOWN; } /* every endpoint has a ed, locate and fill it */ if (!(ed = ep_add_ed (urb->dev, pipe, urb->interval, 1, mem_flags))) { + spin_unlock_irqrestore(&ohci->ohci_lock, flags); usb_dec_dev_use (urb->dev); return -ENOMEM; } @@ -609,6 +652,7 @@ static int sohci_submit_urb (struct urb case PIPE_ISOCHRONOUS: /* number of packets from URB */ size = urb->number_of_packets; if (size <= 0) { + spin_unlock_irqrestore(&ohci->ohci_lock, flags); usb_dec_dev_use (urb->dev); return -EINVAL; } @@ -628,8 +672,9 @@ static int sohci_submit_urb (struct urb /* allocate the private part of the URB */ urb_priv = kmalloc (sizeof (urb_priv_t) + size * sizeof (td_t *), - in_interrupt() ? GFP_ATOMIC : GFP_KERNEL); + GFP_ATOMIC); if (!urb_priv) { + spin_unlock_irqrestore(&ohci->ohci_lock, flags); usb_dec_dev_use (urb->dev); return -ENOMEM; } @@ -640,13 +685,12 @@ static int sohci_submit_urb (struct urb urb_priv->ed = ed; /* allocate the TDs (updating hash chains) */ - spin_lock_irqsave (&usb_ed_lock, flags); for (i = 0; i < size; i++) { urb_priv->td[i] = td_alloc (ohci, SLAB_ATOMIC); if (!urb_priv->td[i]) { urb_priv->length = i; urb_free_priv (ohci, urb_priv); - spin_unlock_irqrestore (&usb_ed_lock, flags); + spin_unlock_irqrestore(&ohci->ohci_lock, flags); usb_dec_dev_use (urb->dev); return -ENOMEM; } @@ -654,7 +698,7 @@ static int sohci_submit_urb (struct urb if (ed->state == ED_NEW || (ed->state & ED_DEL)) { urb_free_priv (ohci, urb_priv); - spin_unlock_irqrestore (&usb_ed_lock, flags); + spin_unlock_irqrestore(&ohci->ohci_lock, flags); usb_dec_dev_use (urb->dev); return -EINVAL; } @@ -676,7 +720,7 @@ static int sohci_submit_urb (struct urb } if (bustime < 0) { urb_free_priv (ohci, urb_priv); - spin_unlock_irqrestore (&usb_ed_lock, flags); + spin_unlock_irqrestore(&ohci->ohci_lock, flags); usb_dec_dev_use (urb->dev); return bustime; } @@ -719,7 +763,7 @@ static int sohci_submit_urb (struct urb } #endif - spin_unlock_irqrestore (&usb_ed_lock, flags); + spin_unlock_irqrestore(&ohci->ohci_lock, flags); return 0; } @@ -750,6 +794,7 @@ static int sohci_unlink_urb (struct urb if (usb_pipedevice (urb->pipe) == ohci->rh.devnum) return rh_unlink_urb (urb); + spin_lock_irqsave(&ohci->ohci_lock, flags); if (urb->hcpriv && (urb->status == USB_ST_URB_PENDING)) { if (!ohci->disabled) { urb_priv_t * urb_priv; @@ -759,6 +804,7 @@ static int sohci_unlink_urb (struct urb */ if (!(urb->transfer_flags & USB_ASYNC_UNLINK) && in_interrupt ()) { + spin_unlock_irqrestore(&ohci->ohci_lock, flags); err ("bug in call from %p; use async!", __builtin_return_address(0)); return -EWOULDBLOCK; @@ -767,11 +813,10 @@ static int sohci_unlink_urb (struct urb /* flag the urb and its TDs for deletion in some * upcoming SF interrupt delete list processing */ - spin_lock_irqsave (&usb_ed_lock, flags); urb_priv = urb->hcpriv; if (!urb_priv || (urb_priv->state == URB_DEL)) { - spin_unlock_irqrestore (&usb_ed_lock, flags); + spin_unlock_irqrestore(&ohci->ohci_lock, flags); return 0; } @@ -786,7 +831,7 @@ static int sohci_unlink_urb (struct urb add_wait_queue (&unlink_wakeup, &wait); urb_priv->wait = &unlink_wakeup; - spin_unlock_irqrestore (&usb_ed_lock, flags); + spin_unlock_irqrestore(&ohci->ohci_lock, flags); /* wait until all TDs are deleted */ set_current_state(TASK_UNINTERRUPTIBLE); @@ -800,14 +845,22 @@ static int sohci_unlink_urb (struct urb err ("unlink URB timeout"); return -ETIMEDOUT; } + + usb_dec_dev_use (urb->dev); + urb->dev = NULL; + if (urb->complete) + urb->complete (urb); } else { /* usb_dec_dev_use done in dl_del_list() */ urb->status = -EINPROGRESS; - spin_unlock_irqrestore (&usb_ed_lock, flags); + spin_unlock_irqrestore(&ohci->ohci_lock, flags); return -EINPROGRESS; } } else { - urb_rm_priv (urb); + urb_rm_priv_locked (urb); + spin_unlock_irqrestore(&ohci->ohci_lock, flags); + usb_dec_dev_use (urb->dev); + urb->dev = NULL; if (urb->transfer_flags & USB_ASYNC_UNLINK) { urb->status = -ECONNRESET; if (urb->complete) @@ -815,6 +868,8 @@ static int sohci_unlink_urb (struct urb } else urb->status = -ENOENT; } + } else { + spin_unlock_irqrestore(&ohci->ohci_lock, flags); } return 0; } @@ -857,7 +912,7 @@ static int sohci_free_dev (struct usb_de * (freeing all the TDs, unlinking EDs) but we need * to defend against bugs that prevent that. */ - spin_lock_irqsave (&usb_ed_lock, flags); + spin_lock_irqsave(&ohci->ohci_lock, flags); for(i = 0; i < NUM_EDS; i++) { ed = &(dev->ed[i]); if (ed->state != ED_NEW) { @@ -872,7 +927,7 @@ static int sohci_free_dev (struct usb_de cnt++; } } - spin_unlock_irqrestore (&usb_ed_lock, flags); + spin_unlock_irqrestore(&ohci->ohci_lock, flags); /* if the controller is running, tds for those unlinked * urbs get freed by dl_del_list at the next SF interrupt @@ -1206,17 +1261,12 @@ static ed_t * ep_add_ed ( td_t * td; ed_t * ed_ret; volatile ed_t * ed; - unsigned long flags; - - - spin_lock_irqsave (&usb_ed_lock, flags); ed = ed_ret = &(usb_to_ohci (usb_dev)->ed[(usb_pipeendpoint (pipe) << 1) | (usb_pipecontrol (pipe)? 0: usb_pipeout (pipe))]); if ((ed->state & ED_DEL) || (ed->state & ED_URB_DEL)) { /* pending delete request */ - spin_unlock_irqrestore (&usb_ed_lock, flags); return NULL; } @@ -1229,7 +1279,6 @@ static ed_t * ep_add_ed ( /* out of memory */ if (td) td_free(ohci, td); - spin_unlock_irqrestore (&usb_ed_lock, flags); return NULL; } ed->hwTailP = cpu_to_le32 (td->td_dma); @@ -1253,7 +1302,6 @@ static ed_t * ep_add_ed ( ed->int_load = load; } - spin_unlock_irqrestore (&usb_ed_lock, flags); return ed_ret; } @@ -1512,7 +1560,7 @@ static void dl_transfer_length(td_t * td /* handle an urb that is being unlinked */ -static void dl_del_urb (struct urb * urb) +static void dl_del_urb (ohci_t *ohci, struct urb * urb) { wait_queue_head_t * wait_head = ((urb_priv_t *)(urb->hcpriv))->wait; @@ -1520,12 +1568,9 @@ static void dl_del_urb (struct urb * urb if (urb->transfer_flags & USB_ASYNC_UNLINK) { urb->status = -ECONNRESET; - if (urb->complete) - urb->complete (urb); + ohci_complete_add(ohci, urb); } else { urb->status = -ENOENT; - if (urb->complete) - urb->complete (urb); /* unblock sohci_unlink_urb */ if (wait_head) @@ -1544,9 +1589,6 @@ static td_t * dl_reverse_done_list (ohci td_t * td_rev = NULL; td_t * td_list = NULL; urb_priv_t * urb_priv = NULL; - unsigned long flags; - - spin_lock_irqsave (&usb_ed_lock, flags); td_list_hc = le32_to_cpup (&ohci->hcca->done_head) & 0xfffffff0; ohci->hcca->done_head = 0; @@ -1573,7 +1615,6 @@ static td_t * dl_reverse_done_list (ohci td_rev = td_list; td_list_hc = le32_to_cpup (&td_list->hwNextTD) & 0xfffffff0; } - spin_unlock_irqrestore (&usb_ed_lock, flags); return td_list; } @@ -1585,7 +1626,6 @@ static td_t * dl_reverse_done_list (ohci static void dl_del_list (ohci_t * ohci, unsigned int frame) { - unsigned long flags; ed_t * ed; __u32 edINFO; __u32 tdINFO; @@ -1593,8 +1633,6 @@ static void dl_del_list (ohci_t * ohci, __u32 * td_p; int ctrl = 0, bulk = 0; - spin_lock_irqsave (&usb_ed_lock, flags); - for (ed = ohci->ed_rm_list[frame]; ed != NULL; ed = ed->ed_rm_list) { tdTailP = dma_to_td (ohci, le32_to_cpup (&ed->hwTailP) & 0xfffffff0); @@ -1615,7 +1653,7 @@ static void dl_del_list (ohci_t * ohci, /* URB is done; clean up */ if (++(urb_priv->td_cnt) == urb_priv->length) - dl_del_urb (urb); + dl_del_urb (ohci, urb); } else { td_p = &td->hwNextTD; } @@ -1672,7 +1710,6 @@ static void dl_del_list (ohci_t * ohci, } ohci->ed_rm_list[frame] = NULL; - spin_unlock_irqrestore (&usb_ed_lock, flags); } @@ -1690,8 +1727,6 @@ static void dl_done_list (ohci_t * ohci, urb_priv_t * urb_priv; __u32 tdINFO, edHeadP, edTailP; - unsigned long flags; - while (td_list) { td_list_next = td_list->next_dl_td; @@ -1720,13 +1755,10 @@ static void dl_done_list (ohci_t * ohci, urb->status = cc_to_error[cc]; sohci_return_urb (ohci, urb); } else { - spin_lock_irqsave (&usb_ed_lock, flags); - dl_del_urb (urb); - spin_unlock_irqrestore (&usb_ed_lock, flags); + dl_del_urb (ohci, urb); } } - spin_lock_irqsave (&usb_ed_lock, flags); if (ed->state != ED_NEW) { edHeadP = le32_to_cpup (&ed->hwHeadP) & 0xfffffff0; edTailP = le32_to_cpup (&ed->hwTailP); @@ -1735,7 +1767,6 @@ static void dl_done_list (ohci_t * ohci, if ((edHeadP == edTailP) && (ed->state == ED_OPER)) ep_unlink (ohci, ed); } - spin_unlock_irqrestore (&usb_ed_lock, flags); td_list = td_list_next; } @@ -1926,6 +1957,7 @@ static int rh_submit_urb (struct urb * u int leni = urb->transfer_buffer_length; int len = 0; int status = TD_CC_NOERROR; + unsigned long flags; __u32 datab[4]; __u8 * data_buf = (__u8 *) datab; @@ -1935,6 +1967,8 @@ static int rh_submit_urb (struct urb * u __u16 wIndex; __u16 wLength; + spin_lock_irqsave(&ohci->ohci_lock, flags); + if (usb_pipeint(pipe)) { ohci->rh.urb = urb; ohci->rh.send = 1; @@ -1942,6 +1976,7 @@ static int rh_submit_urb (struct urb * u rh_init_int_timer(urb); urb->status = cc_to_error [TD_CC_NOERROR]; + spin_unlock_irqrestore(&ohci->ohci_lock, flags); return 0; } @@ -2113,6 +2148,7 @@ static int rh_submit_urb (struct urb * u #endif urb->hcpriv = NULL; + spin_unlock_irqrestore(&ohci->ohci_lock, flags); usb_dec_dev_use (usb_dev); urb->dev = NULL; if (urb->complete) @@ -2125,13 +2161,16 @@ static int rh_submit_urb (struct urb * u static int rh_unlink_urb (struct urb * urb) { ohci_t * ohci = urb->dev->bus->hcpriv; + unsigned int flags; + spin_lock_irqsave(&ohci->ohci_lock, flags); if (ohci->rh.urb == urb) { ohci->rh.send = 0; del_timer (&ohci->rh.rh_int_timer); ohci->rh.urb = NULL; urb->hcpriv = NULL; + spin_unlock_irqrestore(&ohci->ohci_lock, flags); usb_dec_dev_use(urb->dev); urb->dev = NULL; if (urb->transfer_flags & USB_ASYNC_UNLINK) { @@ -2140,6 +2179,8 @@ static int rh_unlink_urb (struct urb * u urb->complete (urb); } else urb->status = -ENOENT; + } else { + spin_unlock_irqrestore(&ohci->ohci_lock, flags); } return 0; } @@ -2283,7 +2324,7 @@ static int hc_start (ohci_t * ohci) static void check_timeouts (struct ohci *ohci) { - spin_lock (&usb_ed_lock); + spin_lock (&ohci->ohci_lock); while (!list_empty (&ohci->timeout_list)) { struct urb *urb; @@ -2296,15 +2337,15 @@ static void check_timeouts (struct ohci continue; urb->transfer_flags |= USB_TIMEOUT_KILLED | USB_ASYNC_UNLINK; - spin_unlock (&usb_ed_lock); + spin_unlock (&ohci->ohci_lock); // outside the interrupt handler (in a timer...) // this reference would race interrupts sohci_unlink_urb (urb); - spin_lock (&usb_ed_lock); + spin_lock (&ohci->ohci_lock); } - spin_unlock (&usb_ed_lock); + spin_unlock (&ohci->ohci_lock); } @@ -2318,6 +2359,8 @@ static void hc_interrupt (int irq, void struct ohci_regs * regs = ohci->regs; int ints; + spin_lock (&ohci->ohci_lock); + /* avoid (slow) readl if only WDH happened */ if ((ohci->hcca->done_head != 0) && !(le32_to_cpup (&ohci->hcca->done_head) & 0x01)) { @@ -2326,11 +2369,13 @@ static void hc_interrupt (int irq, void /* cardbus/... hardware gone before remove() */ } else if ((ints = readl (®s->intrstatus)) == ~(u32)0) { ohci->disabled++; + spin_unlock (&ohci->ohci_lock); err ("%s device removed!", ohci->ohci_dev->slot_name); return; /* interrupt for some other device? */ } else if ((ints &= readl (®s->intrenable)) == 0) { + spin_unlock (&ohci->ohci_lock); return; } @@ -2381,6 +2426,13 @@ static void hc_interrupt (int irq, void } } + /* + * Finally, we are done with trashing about our hardware lists + * and other CPUs are allowed in. The festive flipping of the lock + * ensues as we struggle with the check_timeouts disaster. + */ + spin_unlock (&ohci->ohci_lock); + if (!list_empty (&ohci->timeout_list)) { check_timeouts (ohci); // FIXME: enable SF as needed in a timer; @@ -2393,6 +2445,8 @@ static void hc_interrupt (int irq, void writel (ints, ®s->intrstatus); writel (OHCI_INTR_MIE, ®s->intrenable); (void)readl (®s->intrdisable); /* PCI posting flush */ + + ohci_complete(ohci); } /*-------------------------------------------------------------------------*/ @@ -2425,10 +2479,8 @@ static ohci_t * __devinit hc_alloc_ohci ohci->ohci_dev = dev; pci_set_drvdata(dev, ohci); - INIT_LIST_HEAD (&ohci->ohci_hcd_list); - list_add (&ohci->ohci_hcd_list, &ohci_hcd_list); - INIT_LIST_HEAD (&ohci->timeout_list); + spin_lock_init(&ohci->ohci_lock); ohci->bus = usb_alloc_bus (&sohci_device_operations); if (!ohci->bus) { @@ -2472,9 +2524,6 @@ static void hc_release_ohci (ohci_t * oh usb_free_bus (ohci->bus); } - list_del (&ohci->ohci_hcd_list); - INIT_LIST_HEAD (&ohci->ohci_hcd_list); - ohci_mem_cleanup (ohci); /* unmap the IO address space */ @@ -2720,12 +2769,12 @@ ohci_pci_suspend (struct pci_dev *dev, u ohci->sleeping = 1; /* First stop processing */ - spin_lock_irqsave (&usb_ed_lock, flags); + spin_lock_irqsave (&ohci->ohci_lock, flags); ohci->hc_control &= ~(OHCI_CTRL_PLE|OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_IE); writel (ohci->hc_control, &ohci->regs->control); writel (OHCI_INTR_SF, &ohci->regs->intrstatus); (void) readl (&ohci->regs->intrstatus); - spin_unlock_irqrestore (&usb_ed_lock, flags); + spin_unlock_irqrestore (&ohci->ohci_lock, flags); /* Wait a frame or two */ mdelay(1); @@ -2851,7 +2900,7 @@ ohci_pci_resume (struct pci_dev *dev) mdelay (3); /* Then re-enable operations */ - spin_lock_irqsave (&usb_ed_lock, flags); + spin_lock_irqsave (&ohci->ohci_lock, flags); ohci->disabled = 0; ohci->sleeping = 0; ohci->hc_control = OHCI_CONTROL_INIT | OHCI_USB_OPER; @@ -2867,7 +2916,6 @@ ohci_pci_resume (struct pci_dev *dev) /* Check for a pending done list */ writel (OHCI_INTR_WDH, &ohci->regs->intrdisable); (void) readl (&ohci->regs->intrdisable); - spin_unlock_irqrestore (&usb_ed_lock, flags); #ifdef CONFIG_PMAC_PBOOK if (_machine == _MACH_Pmac) enable_irq (ohci->irq); @@ -2877,6 +2925,7 @@ ohci_pci_resume (struct pci_dev *dev) writel (OHCI_INTR_WDH, &ohci->regs->intrenable); writel (OHCI_BLF, &ohci->regs->cmdstatus); /* start bulk list */ writel (OHCI_CLF, &ohci->regs->cmdstatus); /* start Control list */ + spin_unlock_irqrestore (&ohci->ohci_lock, flags); break; default: diff -purNX /suse/olh/kernel/kernel_exclude.txt linux-2.4.22/drivers/usb/host/usb-ohci.h linux-2.4.23-pre9/drivers/usb/host/usb-ohci.h --- linux-2.4.22/drivers/usb/host/usb-ohci.h 2003-06-13 16:51:36.000000000 +0200 +++ linux-2.4.23-pre9/drivers/usb/host/usb-ohci.h 2003-10-30 20:27:59.000000000 +0100 @@ -384,11 +384,11 @@ typedef struct ohci { #define OHCI_QUIRK_SUCKYIO 0x02 /* NSC superio */ struct ohci_regs * regs; /* OHCI controller's memory */ - struct list_head ohci_hcd_list; /* list of all ohci_hcd */ struct list_head timeout_list; // struct list_head urb_list; // list of all pending urbs - // spinlock_t urb_list_lock; // lock to keep consistency + spinlock_t ohci_lock; /* Covers all fields up & down */ + struct urb *complete_head, *complete_tail; int ohci_int_load[32]; /* load of the 32 Interrupt Chains (for load balancing)*/ ed_t * ed_rm_list[2]; /* lists of all endpoints to be removed */