Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

USB: Fix NEC OHCI chip silicon bug

This patch fixes a silicon bug in some NEC OHCI chips. The bug appears
at random times and is very, very difficult to reproduce. Without the
following patch, Linux would shut the chip and its associated devices
down. In Apple PowerBooks this leads to an unusable keyboard and mouse
(SSH still working). The idea of restarting the chip is taken from
public Darwin code.

Signed-off-by: Michael Hanselmann <linux-kernel@hansmi.ch>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

authored by

Michael Hanselmann and committed by
Greg Kroah-Hartman
d576bb9f 02036338

+61 -14
+39 -12
drivers/usb/host/ohci-hcd.c
··· 35 35 #include <linux/dma-mapping.h> 36 36 #include <linux/dmapool.h> 37 37 #include <linux/reboot.h> 38 + #include <linux/workqueue.h> 38 39 39 40 #include <asm/io.h> 40 41 #include <asm/irq.h> ··· 83 82 static void ohci_dump (struct ohci_hcd *ohci, int verbose); 84 83 static int ohci_init (struct ohci_hcd *ohci); 85 84 static void ohci_stop (struct usb_hcd *hcd); 85 + static int ohci_restart (struct ohci_hcd *ohci); 86 + static void ohci_quirk_nec_worker (struct work_struct *work); 86 87 87 88 #include "ohci-hub.c" 88 89 #include "ohci-dbg.c" ··· 654 651 } 655 652 656 653 if (ints & OHCI_INTR_UE) { 657 - disable (ohci); 658 - ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n"); 659 654 // e.g. due to PCI Master/Target Abort 655 + if (ohci->flags & OHCI_QUIRK_NEC) { 656 + /* Workaround for a silicon bug in some NEC chips used 657 + * in Apple's PowerBooks. Adapted from Darwin code. 658 + */ 659 + ohci_err (ohci, "OHCI Unrecoverable Error, scheduling NEC chip restart\n"); 660 + 661 + ohci_writel (ohci, OHCI_INTR_UE, &regs->intrdisable); 662 + 663 + schedule_work (&ohci->nec_work); 664 + } else { 665 + disable (ohci); 666 + ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n"); 667 + } 660 668 661 669 ohci_dump (ohci, 1); 662 670 ohci_usb_reset (ohci); ··· 769 755 /*-------------------------------------------------------------------------*/ 770 756 771 757 /* must not be called from interrupt context */ 772 - 773 - #ifdef CONFIG_PM 774 - 775 758 static int ohci_restart (struct ohci_hcd *ohci) 776 759 { 777 760 int temp; 778 761 int i; 779 762 struct urb_priv *priv; 780 763 781 - /* mark any devices gone, so they do nothing till khubd disconnects. 782 - * recycle any "live" eds/tds (and urbs) right away. 783 - * later, khubd disconnect processing will recycle the other state, 784 - * (either as disconnect/reconnect, or maybe someday as a reset). 785 - */ 786 764 spin_lock_irq(&ohci->lock); 787 765 disable (ohci); 788 - usb_root_hub_lost_power(ohci_to_hcd(ohci)->self.root_hub); 766 + 767 + /* Recycle any "live" eds/tds (and urbs). */ 789 768 if (!list_empty (&ohci->pending)) 790 769 ohci_dbg(ohci, "abort schedule...\n"); 791 770 list_for_each_entry (priv, &ohci->pending, pending) { ··· 829 822 ohci_dbg(ohci, "restart complete\n"); 830 823 return 0; 831 824 } 832 - #endif 825 + 826 + /*-------------------------------------------------------------------------*/ 827 + 828 + /* NEC workaround */ 829 + static void ohci_quirk_nec_worker(struct work_struct *work) 830 + { 831 + struct ohci_hcd *ohci = container_of(work, struct ohci_hcd, nec_work); 832 + int status; 833 + 834 + status = ohci_init(ohci); 835 + if (status != 0) { 836 + ohci_err(ohci, "Restarting NEC controller failed " 837 + "in ohci_init, %d\n", status); 838 + return; 839 + } 840 + 841 + status = ohci_restart(ohci); 842 + if (status != 0) 843 + ohci_err(ohci, "Restarting NEC controller failed " 844 + "in ohci_restart, %d\n", status); 845 + } 833 846 834 847 /*-------------------------------------------------------------------------*/ 835 848
+3 -2
drivers/usb/host/ohci-hub.c
··· 55 55 static void finish_unlinks (struct ohci_hcd *, u16); 56 56 57 57 #ifdef CONFIG_PM 58 - static int ohci_restart(struct ohci_hcd *ohci); 59 - 60 58 static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop) 61 59 __releases(ohci->lock) 62 60 __acquires(ohci->lock) ··· 189 191 spin_unlock_irq (&ohci->lock); 190 192 (void) ohci_init (ohci); 191 193 status = ohci_restart (ohci); 194 + 195 + usb_root_hub_lost_power(hcd->self.root_hub); 196 + 192 197 spin_lock_irq (&ohci->lock); 193 198 } 194 199 return status;
+1
drivers/usb/host/ohci-mem.c
··· 28 28 ohci->next_statechange = jiffies; 29 29 spin_lock_init (&ohci->lock); 30 30 INIT_LIST_HEAD (&ohci->pending); 31 + INIT_WORK (&ohci->nec_work, ohci_quirk_nec_worker); 31 32 } 32 33 33 34 /*-------------------------------------------------------------------------*/
+16
drivers/usb/host/ohci-pci.c
··· 111 111 #endif 112 112 } 113 113 114 + /* Check for NEC chip and apply quirk for allegedly lost interrupts. 115 + */ 116 + static int ohci_quirk_nec(struct usb_hcd *hcd) 117 + { 118 + struct ohci_hcd *ohci = hcd_to_ohci (hcd); 119 + 120 + ohci->flags |= OHCI_QUIRK_NEC; 121 + ohci_dbg (ohci, "enabled NEC chipset lost interrupt quirk\n"); 122 + 123 + return 0; 124 + } 125 + 114 126 /* List of quirks for OHCI */ 115 127 static const struct pci_device_id ohci_pci_quirks[] = { 116 128 { ··· 144 132 { 145 133 PCI_DEVICE(PCI_VENDOR_ID_TOSHIBA_2, 0x01b6), 146 134 .driver_data = (unsigned long)ohci_quirk_toshiba_scc, 135 + }, 136 + { 137 + PCI_DEVICE(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_USB), 138 + .driver_data = (unsigned long)ohci_quirk_nec, 147 139 }, 148 140 { 149 141 /* Toshiba portege 4000 */
+2
drivers/usb/host/ohci.h
··· 397 397 #define OHCI_QUIRK_BE_DESC 0x08 /* BE descriptors */ 398 398 #define OHCI_QUIRK_BE_MMIO 0x10 /* BE registers */ 399 399 #define OHCI_QUIRK_ZFMICRO 0x20 /* Compaq ZFMicro chipset*/ 400 + #define OHCI_QUIRK_NEC 0x40 /* lost interrupts */ 400 401 // there are also chip quirks/bugs in init logic 401 402 403 + struct work_struct nec_work; /* Worker for NEC quirk */ 402 404 }; 403 405 404 406 /* convert between an hcd pointer and the corresponding ohci_hcd */