This patch makes the EHCI driver behave reasonably well in the
cardbus configurations I can test ... basically, it now sees
when a card is gone, and cleans up accordingly.  There are also
some related cleanups:  hardware handshakes will time out (not
that I've ever seen them fail), and some state management puts
a bit more effort into being strictly to-spec.

Please merge to Linus' latest.

- Dave
--- ./drivers/usb-dist/host/ehci-hcd.c  Thu May 30 15:16:45 2002
+++ ./drivers/usb/host/ehci-hcd.c       Thu Jul 25 11:30:55 2002
@@ -65,6 +65,8 @@
  *
  * HISTORY:
  *
+ * 2002-07-25  Sanity check PCI reads, mostly for better cardbus support,
+ *     clean up HC run state handshaking.
  * 2002-05-24  Preliminary FS/LS interrupts, using scheduling shortcuts
  * 2002-05-11  Clear TT errors for FS/LS ctrl/bulk.  Fill in some other
  *     missing pieces:  enabling 64bit dma, handoff from BIOS/SMM.
@@ -83,7 +85,7 @@
  * 2001-June   Works with usb-storage and NEC EHCI on 2.4
  */
 
-#define DRIVER_VERSION "2002-May-24"
+#define DRIVER_VERSION "2002-Jul-25"
 #define DRIVER_AUTHOR "David Brownell"
 #define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver"
 
@@ -113,42 +115,105 @@
 /*-------------------------------------------------------------------------*/
 
 /*
+ * handshake - spin reading hc until handshake completes or fails
+ * @ptr: address of hc register to be read
+ * @mask: bits to look at in result of read
+ * @done: value of those bits when handshake succeeds
+ * @usec: timeout in microseconds
+ *
+ * Returns negative errno, or zero on success
+ *
+ * Success happens when the "mask" bits have the specified value (hardware
+ * handshake done).  There are two failure modes:  "usec" have passed (major
+ * hardware flakeout), or the register reads as all-ones (hardware removed).
+ *
+ * That last failure should_only happen in cases like physical cardbus eject
+ * before driver shutdown. But it also seems to be caused by bugs in cardbus
+ * bridge shutdown:  shutting down the bridge before the devices using it.
+ */
+static int handshake (u32 *ptr, u32 mask, u32 done, int usec)
+{
+       u32     result;
+
+       do {
+               result = readl (ptr);
+               if (result == ~(u32)0)          /* card removed */
+                       return -ENODEV;
+               result &= mask;
+               if (result == done)
+                       return 0;
+               udelay (1);
+               usec--;
+       } while (usec > 0);
+       return -ETIMEDOUT;
+}
+
+/*
  * hc states include: unknown, halted, ready, running
  * transitional states are messy just now
  * trying to avoid "running" unless urbs are active
  * a "ready" hc can be finishing prefetched work
  */
 
-/* halt a non-running controller */
-static void ehci_reset (struct ehci_hcd *ehci)
+/* force HC to halt state from unknown (EHCI spec section 2.3) */
+static int ehci_halt (struct ehci_hcd *ehci)
+{
+       u32     temp = readl (&ehci->regs->status);
+
+       if ((temp & STS_HALT) != 0)
+               return 0;
+
+       temp = readl (&ehci->regs->command);
+       temp &= ~CMD_RUN;
+       writel (temp, &ehci->regs->command);
+       return handshake (&ehci->regs->status, STS_HALT, STS_HALT, 16 * 125);
+}
+
+/* reset a non-running (STS_HALT == 1) controller */
+static int ehci_reset (struct ehci_hcd *ehci)
 {
        u32     command = readl (&ehci->regs->command);
 
        command |= CMD_RESET;
        dbg_cmd (ehci, "reset", command);
        writel (command, &ehci->regs->command);
-       while (readl (&ehci->regs->command) & CMD_RESET)
-               continue;
        ehci->hcd.state = USB_STATE_HALT;
+       return handshake (&ehci->regs->command, CMD_RESET, 0, 050);
 }
 
 /* idle the controller (from running) */
 static void ehci_ready (struct ehci_hcd *ehci)
 {
-       u32     command;
+       u32     temp;
 
 #ifdef DEBUG
        if (!HCD_IS_RUNNING (ehci->hcd.state))
                BUG ();
 #endif
 
-       while (!(readl (&ehci->regs->status) & (STS_ASS | STS_PSS)))
-               udelay (100);
-       command = readl (&ehci->regs->command);
-       command &= ~(CMD_ASE | CMD_IAAD | CMD_PSE);
-       writel (command, &ehci->regs->command);
+       /* wait for any schedule enables/disables to take effect */
+       temp = 0;
+       if (ehci->async)
+               temp = STS_ASS;
+       if (ehci->next_uframe != -1)
+               temp |= STS_PSS;
+       if (handshake (&ehci->regs->status, STS_ASS | STS_PSS,
+                               temp, 16 * 125) != 0) {
+               ehci->hcd.state = USB_STATE_HALT;
+               return;
+       }
 
-       // hardware can take 16 microframes to turn off ...
+       /* then disable anything that's still active */
+       temp = readl (&ehci->regs->command);
+       temp &= ~(CMD_ASE | CMD_IAAD | CMD_PSE);
+       writel (temp, &ehci->regs->command);
+
+       /* hardware can take 16 microframes to turn off ... */
+       if (handshake (&ehci->regs->status, STS_ASS | STS_PSS,
+                               0, 16 * 125) != 0) {
+               ehci->hcd.state = USB_STATE_HALT;
+               return;
+       }
        ehci->hcd.state = USB_STATE_READY;
 }
 
@@ -236,6 +301,10 @@
        /* cache this readonly data; minimize PCI reads */
        ehci->hcs_params = readl (&ehci->caps->hcs_params);
 
+       /* force HC to halt state */
+       if ((retval = ehci_halt (ehci)) != 0)
+               return retval;
+
        /*
         * hw default: 1K periodic list heads, one per frame.
         * periodic_size can shrink by USBCMD update if hcc_params allows.
@@ -257,8 +326,10 @@
        /* controller state:  unknown --> reset */
 
        /* EHCI spec section 4.1 */
-       // FIXME require STS_HALT before reset...
-       ehci_reset (ehci);
+       if ((retval = ehci_reset (ehci)) != 0) {
+               ehci_mem_cleanup (ehci);
+               return retval;
+       }
        writel (INTR_MASK, &ehci->regs->intr_enable);
        writel (ehci->periodic_dma, &ehci->regs->frame_list);
 
@@ -335,8 +406,6 @@
        if (usb_register_root_hub (udev, &ehci->hcd.pdev->dev) != 0) {
                if (hcd->state == USB_STATE_RUNNING)
                        ehci_ready (ehci);
-               while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS))
-                       udelay (100);
                ehci_reset (ehci);
                hcd->self.root_hub = 0;
                usb_free_dev (udev); 
@@ -355,16 +424,14 @@
 
        dbg ("%s: stop", hcd->self.bus_name);
 
+       /* no more interrupts ... */
        if (hcd->state == USB_STATE_RUNNING)
                ehci_ready (ehci);
-       while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS))
-               udelay (100);
        ehci_reset (ehci);
 
-       // root hub is shut down separately (first, when possible)
-       scan_async (ehci);
-       if (ehci->next_uframe != -1)
-               scan_periodic (ehci);
+       /* root hub is shut down separately (first, when possible) */
+       tasklet_disable (&ehci->tasklet);
+       ehci_tasklet ((unsigned long) ehci);
        ehci_mem_cleanup (ehci);
 
        dbg_status (ehci, "ehci_stop completed", readl (&ehci->regs->status));
@@ -412,8 +479,6 @@
 
        if (hcd->state == USB_STATE_RUNNING)
                ehci_ready (ehci);
-       while (readl (&ehci->regs->status) & (STS_ASS | STS_PSS))
-               udelay (100);
        writel (readl (&ehci->regs->command) & ~CMD_RUN, &ehci->regs->command);
 
 // save pci FLADJ value
@@ -489,6 +554,12 @@
        u32                     status = readl (&ehci->regs->status);
        int                     bh;
 
+       /* e.g. cardbus physical eject */
+       if (status == ~(u32) 0) {
+               dbg ("%s: device removed!", hcd->self.bus_name);
+               goto dead;
+       }
+
        status &= INTR_MASK;
        if (!status)                    /* irq sharing? */
                return;
@@ -517,10 +588,13 @@
 
        /* PCI errors [4.15.2.4] */
        if (unlikely ((status & STS_FATAL) != 0)) {
-               err ("%s: fatal error, state %x", hcd->self.bus_name, hcd->state);
+               err ("%s: fatal error, state %x",
+                       hcd->self.bus_name, hcd->state);
+dead:
                ehci_reset (ehci);
-               // generic layer kills/unlinks all urbs
-               // then tasklet cleans up the rest
+               /* generic layer kills/unlinks all urbs, then
+                * uses ehci_stop to clean up the rest
+                */
                bh = 1;
        }
 
--- ./drivers/usb-dist/host/ehci-q.c    Wed Jul 17 08:41:22 2002
+++ ./drivers/usb/host/ehci-q.c Thu Jul 25 11:30:55 2002
@@ -718,8 +718,7 @@
                u32     cmd = readl (&ehci->regs->command);
 
                /* in case a clear of CMD_ASE didn't take yet */
-               while (readl (&ehci->regs->status) & STS_ASS)
-                       udelay (100);
+               (void) handshake (&ehci->regs->status, STS_ASS, 0, 150);
 
                qh->hw_info1 |= __constant_cpu_to_le32 (QH_HEAD); /* [4.8] */
                qh->qh_next.qh = qh;
@@ -917,11 +916,8 @@
                if (ehci->hcd.state != USB_STATE_HALT) {
                        if (cmd & CMD_PSE)
                                writel (cmd & ~CMD_ASE, &ehci->regs->command);
-                       else {
+                       else
                                ehci_ready (ehci);
-                               while (readl (&ehci->regs->status) & STS_ASS)
-                                       udelay (100);
-                       }
                }
                qh->qh_next.qh = ehci->async = 0;
 
--- ./drivers/usb-dist/host/ehci-sched.c        Thu May 30 15:16:45 2002
+++ ./drivers/usb/host/ehci-sched.c     Thu Jul 25 11:30:55 2002
@@ -171,15 +171,19 @@
 
 /*-------------------------------------------------------------------------*/
 
-static void enable_periodic (struct ehci_hcd *ehci)
+static int enable_periodic (struct ehci_hcd *ehci)
 {
        u32     cmd;
+       int     status;
 
        /* did clearing PSE did take effect yet?
         * takes effect only at frame boundaries...
         */
-       while (readl (&ehci->regs->status) & STS_PSS)
-               udelay (20);
+       status = handshake (&ehci->regs->status, STS_PSS, 0, 9 * 125);
+       if (status != 0) {
+               ehci->hcd.state = USB_STATE_HALT;
+               return status;
+       }
 
        cmd = readl (&ehci->regs->command) | CMD_PSE;
        writel (cmd, &ehci->regs->command);
@@ -189,23 +193,29 @@
        /* make sure tasklet scans these */
        ehci->next_uframe = readl (&ehci->regs->frame_index)
                                % (ehci->periodic_size << 3);
+       return 0;
 }
 
-static void disable_periodic (struct ehci_hcd *ehci)
+static int disable_periodic (struct ehci_hcd *ehci)
 {
        u32     cmd;
+       int     status;
 
        /* did setting PSE not take effect yet?
         * takes effect only at frame boundaries...
         */
-       while (!(readl (&ehci->regs->status) & STS_PSS))
-               udelay (20);
+       status = handshake (&ehci->regs->status, STS_PSS, STS_PSS, 9 * 125);
+       if (status != 0) {
+               ehci->hcd.state = USB_STATE_HALT;
+               return status;
+       }
 
        cmd = readl (&ehci->regs->command) & ~CMD_PSE;
        writel (cmd, &ehci->regs->command);
        /* posted write ... */
 
        ehci->next_uframe = -1;
+       return 0;
 }
 
 /*-------------------------------------------------------------------------*/
@@ -217,6 +227,7 @@
        unsigned        period
 ) {
        unsigned long   flags;
+       int             status;
 
        period >>= 3;           // FIXME microframe periods not handled yet
 
@@ -234,9 +245,11 @@
 
        /* maybe turn off periodic schedule */
        if (!ehci->periodic_urbs)
-               disable_periodic (ehci);
-       else
+               status = disable_periodic (ehci);
+       else {
+               status = 0;
                vdbg ("periodic schedule still enabled");
+       }
 
        spin_unlock_irqrestore (&ehci->lock, flags);
 
@@ -245,7 +258,7 @@
         * (yeech!) to be sure it's done.
         * No other threads may be mucking with this qh.
         */
-       if (((ehci_get_frame (&ehci->hcd) - frame) % period) == 0)
+       if (!status && ((ehci_get_frame (&ehci->hcd) - frame) % period) == 0)
                udelay (125);
 
        qh->qh_state = QH_STATE_IDLE;
@@ -501,7 +514,7 @@
 
                        /* maybe enable periodic schedule processing */
                        if (!ehci->periodic_urbs++)
-                               enable_periodic (ehci);
+                               status = enable_periodic (ehci);
                        break;
 
                } while (frame);
@@ -913,8 +926,12 @@
                usb_claim_bandwidth (urb->dev, urb, usecs, 1);
 
                /* maybe enable periodic schedule processing */
-               if (!ehci->periodic_urbs++)
-                       enable_periodic (ehci);
+               if (!ehci->periodic_urbs++) {
+                       if ((status =  enable_periodic (ehci)) != 0) {
+                               // FIXME deschedule right away
+                               err ("itd_schedule, enable = %d", status);
+                       }
+               }
 
                return 0;
 
@@ -994,7 +1011,7 @@
        /* defer stopping schedule; completion can submit */
        ehci->periodic_urbs--;
        if (!ehci->periodic_urbs)
-               disable_periodic (ehci);
+               (void) disable_periodic (ehci);
 
        return flags;
 }

Reply via email to