[PATCH] RESEND - KDB USB EHCI Support
Aaron Young <[email protected]>
| Newsgroups | gmane.linux.kernel.debugging |
|---|---|
| Message-ID | <[email protected]> |
This patch is a resend to fix a locking problem found on X86_64 in linux/drivers/usb/host/ehci-q.c:qh_completions_kdb(). -- This patch adds support to KDB for USB keyboards attached via EHCI. This happens when USB keyboards are connected via a USB2.0 hub forcing the Linux kernel EHCI driver to take control instead of the "companion" OHCI or UHCI drivers. It also adds a new boot parameter, "kdbnousb", which when set will disable KDB from configuring and using USB keyboards. This may prove useful if someone experiences a problem with KDB and USB keyboards by allowing KDB to still be used via a serial attached keyboard. -- Patch is against 2.6.24-rc2 (and should be applied after kdb-v4.4-2.6.24-rc2-*-2 patch sets) Signed-off-by: Aaron Young ([email protected]) Index: linux/arch/ia64/kdb/kdba_io.c =================================================================== --- linux.orig/arch/ia64/kdb/kdba_io.c 2007-11-14 13:20:14.000000000 -0600 +++ linux/arch/ia64/kdb/kdba_io.c 2007-11-14 16:49:28.490189077 -0600 @@ -44,6 +44,8 @@ #define KDB_USB_NUM_KEYBOARDS 8 struct kdb_usb_kbd_info kdb_usb_kbds[KDB_USB_NUM_KEYBOARDS]; +extern int kdb_no_usb; + static unsigned char kdb_usb_keycode[256] = { 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, @@ -73,6 +75,9 @@ int i; int rc = -1; + if (kdb_no_usb) + return 0; + /* * Search through the array of KDB USB keyboards (kdb_usb_kbds) * looking for a free index. If found, assign the keyboard to @@ -107,6 +112,9 @@ int i; int rc = -1; + if (kdb_no_usb) + return 0; + /* * Search through the array of KDB USB keyboards (kdb_usb_kbds) * looking for the index with the matching URB. If found, @@ -144,6 +152,9 @@ int ret; unsigned char keycode, spec; extern u_short plain_map[], shift_map[], ctrl_map[]; + + if (kdb_no_usb) + return -1; /* * Loop through all the USB keyboard(s) and return Index: linux/arch/x86/kdb/kdba_io_32.c =================================================================== --- linux.orig/arch/x86/kdb/kdba_io_32.c 2007-11-14 13:20:15.000000000 -0600 +++ linux/arch/x86/kdb/kdba_io_32.c 2007-11-14 16:49:28.490189077 -0600 @@ -34,6 +34,8 @@ #define KDB_USB_NUM_KEYBOARDS 8 struct kdb_usb_kbd_info kdb_usb_kbds[KDB_USB_NUM_KEYBOARDS]; +extern int kdb_no_usb; + static unsigned char kdb_usb_keycode[256] = { 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, @@ -63,6 +65,9 @@ int i; int rc = -1; + if (kdb_no_usb) + return 0; + /* * Search through the array of KDB USB keyboards (kdb_usb_kbds) * looking for a free index. If found, assign the keyboard to @@ -97,6 +102,9 @@ int i; int rc = -1; + if (kdb_no_usb) + return 0; + /* * Search through the array of KDB USB keyboards (kdb_usb_kbds) * looking for the index with the matching URB. If found, @@ -134,6 +142,9 @@ int ret; unsigned char keycode, spec; extern u_short plain_map[], shift_map[], ctrl_map[]; + + if (kdb_no_usb) + return -1; /* * Loop through all the USB keyboard(s) and return Index: linux/arch/x86/kdb/kdba_io_64.c =================================================================== --- linux.orig/arch/x86/kdb/kdba_io_64.c 2007-11-14 13:20:15.000000000 -0600 +++ linux/arch/x86/kdb/kdba_io_64.c 2007-11-14 16:49:28.490189077 -0600 @@ -34,6 +34,8 @@ #define KDB_USB_NUM_KEYBOARDS 8 struct kdb_usb_kbd_info kdb_usb_kbds[KDB_USB_NUM_KEYBOARDS]; +extern int kdb_no_usb; + static unsigned char kdb_usb_keycode[256] = { 0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3, @@ -63,6 +65,9 @@ int i; int rc = -1; + if (kdb_no_usb) + return 0; + /* * Search through the array of KDB USB keyboards (kdb_usb_kbds) * looking for a free index. If found, assign the keyboard to @@ -97,6 +102,9 @@ int i; int rc = -1; + if (kdb_no_usb) + return 0; + /* * Search through the array of KDB USB keyboards (kdb_usb_kbds) * looking for the index with the matching URB. If found, @@ -134,6 +142,9 @@ int ret; unsigned char keycode, spec; extern u_short plain_map[], shift_map[], ctrl_map[]; + + if (kdb_no_usb) + return -1; /* * Loop through all the USB keyboard(s) and return Index: linux/drivers/usb/host/ehci-hcd.c =================================================================== --- linux.orig/drivers/usb/host/ehci-hcd.c 2007-11-14 13:20:14.000000000 -0600 +++ linux/drivers/usb/host/ehci-hcd.c 2007-11-14 16:49:28.490189077 -0600 @@ -933,8 +933,25 @@ int ehci_kdb_poll_char(struct urb *urb) { - /* routine not yet implemented */ - return -1; + struct ehci_hcd *ehci; + + /* just to make sure */ + if (!urb || !urb->dev || !urb->dev->bus) + return -1; + + ehci = (struct ehci_hcd *) hcd_to_ehci(bus_to_hcd(urb->dev->bus)); + + /* make sure */ + if (!ehci) + return -1; + + if (!HC_IS_RUNNING (ehci_to_hcd(ehci)->state)) + return -1; + + if (qh_completions_kdb(ehci, urb->hcpriv, urb)) + return 0; + + return -1; } #endif /* CONFIG_KDB_USB */ Index: linux/drivers/usb/host/ehci-q.c =================================================================== --- linux.orig/drivers/usb/host/ehci-q.c 2007-11-14 13:18:35.000000000 -0600 +++ linux/drivers/usb/host/ehci-q.c 2007-11-16 08:57:23.008297555 -0600 @@ -444,6 +444,228 @@ return count; } +#ifdef CONFIG_KDB_USB +/* + * This routine is basically a copy of qh_completions() for use by KDB. + * It is modified to only work on qtds which are associated + * with 'kdburb'. Also, there are some fixups related to locking. + */ +unsigned +qh_completions_kdb(struct ehci_hcd *ehci, struct ehci_qh *qh, struct urb *kdburb) +{ + struct ehci_qtd *last = NULL, *end = qh->dummy; + struct list_head *entry, *tmp; + int last_status = -EINPROGRESS; + int stopped; + unsigned count = 0; + int do_status = 0; + u8 state; + u32 halt = HALT_BIT(ehci); + + /* verify params are valid */ + if (!qh || !kdburb) + return 0; + + if (unlikely (list_empty (&qh->qtd_list))) + return count; + + /* completions (or tasks on other cpus) must never clobber HALT + * till we've gone through and cleaned everything up, even when + * they add urbs to this qh's queue or mark them for unlinking. + * + * NOTE: unlinking expects to be done in queue order. + */ + state = qh->qh_state; + qh->qh_state = QH_STATE_COMPLETING; + stopped = (state == QH_STATE_IDLE); + + /* remove de-activated QTDs from front of queue. + * after faults (including short reads), cleanup this urb + * then let the queue advance. + * if queue is stopped, handles unlinks. + */ + list_for_each_safe (entry, tmp, &qh->qtd_list) { + struct ehci_qtd *qtd; + struct urb *urb; + u32 token = 0; + int qtd_status; + + qtd = list_entry (entry, struct ehci_qtd, qtd_list); + urb = qtd->urb; + + if (urb != kdburb) + continue; + + /* clean up any state from previous QTD ...*/ + if (last) { + if (likely (last->urb != urb)) { + /* + * Lock hackery here... + * ehci_urb_done() makes the assumption + * that it's called with ehci->lock held. + * So, lock it if it isn't already. + */ + if (!spin_is_locked(&ehci->lock)) + spin_lock(&ehci->lock); + + ehci_urb_done(ehci, last->urb, last_status); + + /* + * ehci_urb_done() releases and reacquires + * ehci->lock, so release it here. + */ + if (spin_is_locked(&ehci->lock)) + spin_unlock (&ehci->lock); + + count++; + } + ehci_qtd_free (ehci, last); + last = NULL; + last_status = -EINPROGRESS; + } + + /* ignore urbs submitted during completions we reported */ + if (qtd == end) + break; + + /* hardware copies qtd out of qh overlay */ + rmb (); + token = hc32_to_cpu(ehci, qtd->hw_token); + + /* always clean up qtds the hc de-activated */ + if ((token & QTD_STS_ACTIVE) == 0) { + + if ((token & QTD_STS_HALT) != 0) { + stopped = 1; + + /* magic dummy for some short reads; qh won't advance. + * that silicon quirk can kick in with this dummy too. + */ + } else if (IS_SHORT_READ (token) + && !(qtd->hw_alt_next + & EHCI_LIST_END(ehci))) { + stopped = 1; + goto halt; + } + + /* stop scanning when we reach qtds the hc is using */ + } else if (likely (!stopped + && HC_IS_RUNNING (ehci_to_hcd(ehci)->state))) { + break; + + } else { + stopped = 1; + + if (unlikely (!HC_IS_RUNNING (ehci_to_hcd(ehci)->state))) + last_status = -ESHUTDOWN; + + /* ignore active urbs unless some previous qtd + * for the urb faulted (including short read) or + * its urb was canceled. we may patch qh or qtds. + */ + if (likely(last_status == -EINPROGRESS && + !urb->unlinked)) + continue; + + /* issue status after short control reads */ + if (unlikely (do_status != 0) + && QTD_PID (token) == 0 /* OUT */) { + do_status = 0; + continue; + } + + /* token in overlay may be most current */ + if (state == QH_STATE_IDLE + && cpu_to_hc32(ehci, qtd->qtd_dma) + == qh->hw_current) + token = hc32_to_cpu(ehci, qh->hw_token); + + /* force halt for unlinked or blocked qh, so we'll + * patch the qh later and so that completions can't + * activate it while we "know" it's stopped. + */ + if ((halt & qh->hw_token) == 0) { +halt: + qh->hw_token |= halt; + wmb (); + } + } + + /* remove it from the queue */ + qtd_status = qtd_copy_status(ehci, urb, qtd->length, token); + if (unlikely(qtd_status == -EREMOTEIO)) { + do_status = (!urb->unlinked && + usb_pipecontrol(urb->pipe)); + qtd_status = 0; + } + if (likely(last_status == -EINPROGRESS)) + last_status = qtd_status; + + if (stopped && qtd->qtd_list.prev != &qh->qtd_list) { + last = list_entry (qtd->qtd_list.prev, + struct ehci_qtd, qtd_list); + last->hw_next = qtd->hw_next; + } + list_del (&qtd->qtd_list); + last = qtd; + } + + /* last urb's completion might still need calling */ + if (likely (last != NULL)) { + /* + * Lock hackery here... + * ehci_urb_done() makes the assumption + * that it's called with ehci->lock held. + * So, lock it if it isn't already. + */ + if (!spin_is_locked(&ehci->lock)) + spin_lock(&ehci->lock); + + ehci_urb_done(ehci, last->urb, last_status); + + /* + * ehci_urb_done() releases and reacquires + * ehci->lock, so release it here. + */ + if (spin_is_locked(&ehci->lock)) + spin_unlock (&ehci->lock); + + count++; + ehci_qtd_free (ehci, last); + } + + /* restore original state; caller must unlink or relink */ + qh->qh_state = state; + + /* be sure the hardware's done with the qh before refreshing + * it after fault cleanup, or recovering from silicon wrongly + * overlaying the dummy qtd (which reduces DMA chatter). + */ + if (stopped != 0 || qh->hw_qtd_next == EHCI_LIST_END(ehci)) { + switch (state) { + case QH_STATE_IDLE: + qh_refresh(ehci, qh); + break; + case QH_STATE_LINKED: + /* should be rare for periodic transfers, + * except maybe high bandwidth ... + */ + if ((cpu_to_hc32(ehci, QH_SMASK) + & qh->hw_info2) != 0) { + intr_deschedule (ehci, qh); + (void) qh_schedule (ehci, qh); + } else + unlink_async (ehci, qh); + break; + /* otherwise, unlink already started */ + } + } + + return count; +} + +#endif /* CONFIG_KDB_USB */ + /*-------------------------------------------------------------------------*/ // high bandwidth multiplier, as encoded in highspeed endpoint descriptors Index: linux/drivers/usb/host/ohci-hcd.c =================================================================== --- linux.orig/drivers/usb/host/ohci-hcd.c 2007-11-14 13:20:13.000000000 -0600 +++ linux/drivers/usb/host/ohci-hcd.c 2007-11-14 16:49:28.490189077 -0600 @@ -971,10 +971,19 @@ struct ohci_hcd *ohci; struct ohci_regs * regs; - if (!urb) /* should not happen */ - return -1; + /* just to make sure */ + if (!urb || !urb->dev || !urb->dev->bus) + return -1; ohci = (struct ohci_hcd *) hcd_to_ohci(bus_to_hcd(urb->dev->bus)); + + /* make sure */ + if (!ohci) + return -1; + + if (!HC_IS_RUNNING (ohci_to_hcd(ohci)->state)) + return -1; + regs = ohci->regs; /* if the urb is not currently in progress resubmit it */ Index: linux/kdb/kdb_io.c =================================================================== --- linux.orig/kdb/kdb_io.c 2007-11-14 13:20:13.000000000 -0600 +++ linux/kdb/kdb_io.c 2007-11-14 16:49:28.490189077 -0600 @@ -659,4 +659,18 @@ return; } +#ifdef CONFIG_KDB_USB + +int kdb_no_usb = 0; + +static int __init opt_kdbnousb(char *str) +{ + kdb_no_usb = 1; + return 0; +} + +early_param("kdbnousb", opt_kdbnousb); + +#endif + EXPORT_SYMBOL(kdb_read); --------------------------- Use http://oss.sgi.com/ecartis to modify your settings or to unsubscribe.