]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/infiniband/hw/ipath/ipath_iba6110.c
IB/ipath: Fix driver crash (in interrupt or during unload) after chip reset
[linux-2.6-omap-h63xx.git] / drivers / infiniband / hw / ipath / ipath_iba6110.c
index 9e4e8d4c6e2035769ddef2a080a1f7163bef8678..8e0794d316fb69a9fbe7886447af75ef8c92c821 100644 (file)
@@ -38,6 +38,7 @@
 
 #include <linux/pci.h>
 #include <linux/delay.h>
+#include <linux/htirq.h>
 
 #include "ipath_kernel.h"
 #include "ipath_registers.h"
@@ -207,8 +208,8 @@ static const struct ipath_kregs ipath_ht_kregs = {
        .kr_serdesstatus = IPATH_KREG_OFFSET(SerdesStatus),
        .kr_xgxsconfig = IPATH_KREG_OFFSET(XGXSConfig),
        /*
-        * These should not be used directly via ipath_read_kreg64(),
-        * use them with ipath_read_kreg64_port(),
+        * These should not be used directly via ipath_write_kreg64(),
+        * use them with ipath_write_kreg64_port(),
         */
        .kr_rcvhdraddr = IPATH_KREG_OFFSET(RcvHdrAddr0),
        .kr_rcvhdrtailaddr = IPATH_KREG_OFFSET(RcvHdrTailAddr0)
@@ -283,6 +284,14 @@ static const struct ipath_cregs ipath_ht_cregs = {
 #define INFINIPATH_EXTS_MEMBIST_ENDTEST     0x0000000000004000
 #define INFINIPATH_EXTS_MEMBIST_CORRECT     0x0000000000008000
 
+
+/* TID entries (memory), HT-only */
+#define INFINIPATH_RT_ADDR_MASK 0xFFFFFFFFFFULL        /* 40 bits valid */
+#define INFINIPATH_RT_VALID 0x8000000000000000ULL
+#define INFINIPATH_RT_ADDR_SHIFT 0
+#define INFINIPATH_RT_BUFSIZE_MASK 0x3FFFULL
+#define INFINIPATH_RT_BUFSIZE_SHIFT 48
+
 /*
  * masks and bits that are different in different chips, or present only
  * in one
@@ -401,6 +410,14 @@ static const struct ipath_hwerror_msgs ipath_6110_hwerror_msgs[] = {
        INFINIPATH_HWE_MSG(SERDESPLLFAILED, "SerDes PLL"),
 };
 
+#define TXE_PIO_PARITY ((INFINIPATH_HWE_TXEMEMPARITYERR_PIOBUF | \
+                       INFINIPATH_HWE_TXEMEMPARITYERR_PIOPBC) \
+                       << INFINIPATH_HWE_TXEMEMPARITYERR_SHIFT)
+#define RXE_EAGER_PARITY (INFINIPATH_HWE_RXEMEMPARITYERR_EAGERTID \
+                         << INFINIPATH_HWE_RXEMEMPARITYERR_SHIFT)
+
+static int ipath_ht_txe_recover(struct ipath_devdata *);
+
 /**
  * ipath_ht_handle_hwerrors - display hardware errors.
  * @dd: the infinipath device
@@ -449,13 +466,12 @@ static void ipath_ht_handle_hwerrors(struct ipath_devdata *dd, char *msg,
 
        /*
         * make sure we get this much out, unless told to be quiet,
+        * it's a parity error we may recover from,
         * or it's occurred within the last 5 seconds
         */
-       if ((hwerrs & ~(dd->ipath_lasthwerror |
-                       ((INFINIPATH_HWE_TXEMEMPARITYERR_PIOBUF |
-                         INFINIPATH_HWE_TXEMEMPARITYERR_PIOPBC)
-                       << INFINIPATH_HWE_TXEMEMPARITYERR_SHIFT))) ||
-           (ipath_debug & __IPATH_VERBDBG))
+       if ((hwerrs & ~(dd->ipath_lasthwerror | TXE_PIO_PARITY |
+               RXE_EAGER_PARITY)) ||
+               (ipath_debug & __IPATH_VERBDBG))
                dev_info(&dd->pcidev->dev, "Hardware error: hwerr=0x%llx "
                         "(cleared)\n", (unsigned long long) hwerrs);
        dd->ipath_lasthwerror |= hwerrs;
@@ -466,7 +482,7 @@ static void ipath_ht_handle_hwerrors(struct ipath_devdata *dd, char *msg,
                              (hwerrs & ~dd->ipath_hwe_bitsextant));
 
        ctrl = ipath_read_kreg32(dd, dd->ipath_kregs->kr_control);
-       if (ctrl & INFINIPATH_C_FREEZEMODE) {
+       if ((ctrl & INFINIPATH_C_FREEZEMODE) && !ipath_diag_inuse) {
                /*
                 * parity errors in send memory are recoverable,
                 * just cancel the send (if indicated in * sendbuffererror),
@@ -475,50 +491,14 @@ static void ipath_ht_handle_hwerrors(struct ipath_devdata *dd, char *msg,
                 * occur if a processor speculative read is done to the PIO
                 * buffer while we are sending a packet, for example.
                 */
-               if (hwerrs & ((INFINIPATH_HWE_TXEMEMPARITYERR_PIOBUF |
-                              INFINIPATH_HWE_TXEMEMPARITYERR_PIOPBC)
-                             << INFINIPATH_HWE_TXEMEMPARITYERR_SHIFT)) {
-                       ipath_stats.sps_txeparity++;
-                       ipath_dbg("Recovering from TXE parity error (%llu), "
-                                 "hwerrstatus=%llx\n",
-                                 (unsigned long long) ipath_stats.sps_txeparity,
-                                 (unsigned long long) hwerrs);
-                       ipath_disarm_senderrbufs(dd);
-                       hwerrs &= ~((INFINIPATH_HWE_TXEMEMPARITYERR_PIOBUF |
-                                    INFINIPATH_HWE_TXEMEMPARITYERR_PIOPBC)
-                                   << INFINIPATH_HWE_TXEMEMPARITYERR_SHIFT);
-                       if (!hwerrs) { /* else leave in freeze mode */
-                               ipath_write_kreg(dd,
-                                                dd->ipath_kregs->kr_control,
-                                                dd->ipath_control);
-                               return;
-                       }
-               }
-               if (hwerrs) {
-                       /*
-                        * if any set that we aren't ignoring; only
-                        * make the complaint once, in case it's stuck
-                        * or recurring, and we get here multiple
-                        * times.
-                        */
-                       if (dd->ipath_flags & IPATH_INITTED) {
-                               ipath_dev_err(dd, "Fatal Hardware Error (freeze "
-                                             "mode), no longer usable, SN %.16s\n",
-                                                 dd->ipath_serial);
-                               isfatal = 1;
-                       }
-                       *dd->ipath_statusp &= ~IPATH_STATUS_IB_READY;
-                       /* mark as having had error */
-                       *dd->ipath_statusp |= IPATH_STATUS_HWERROR;
-                       /*
-                        * mark as not usable, at a minimum until driver
-                        * is reloaded, probably until reboot, since no
-                        * other reset is possible.
-                        */
-                       dd->ipath_flags &= ~IPATH_INITTED;
-               } else {
-                       ipath_dbg("Clearing freezemode on ignored hardware "
-                                 "error\n");
+               if ((hwerrs & TXE_PIO_PARITY) && ipath_ht_txe_recover(dd))
+                       hwerrs &= ~TXE_PIO_PARITY;
+               if (hwerrs & RXE_EAGER_PARITY)
+                       ipath_dev_err(dd, "RXE parity, Eager TID error is not "
+                               "recoverable\n");
+               if (!hwerrs) {
+                       ipath_dbg("Clearing freezemode on ignored or "
+                                 "recovered hardware error\n");
                        ctrl &= ~INFINIPATH_C_FREEZEMODE;
                        ipath_write_kreg(dd, dd->ipath_kregs->kr_control,
                                         ctrl);
@@ -586,7 +566,32 @@ static void ipath_ht_handle_hwerrors(struct ipath_devdata *dd, char *msg,
                                 dd->ipath_hwerrmask);
        }
 
-       ipath_dev_err(dd, "%s hardware error\n", msg);
+       if (hwerrs) {
+               /*
+                * if any set that we aren't ignoring; only
+                * make the complaint once, in case it's stuck
+                * or recurring, and we get here multiple
+                * times.
+                */
+               ipath_dev_err(dd, "%s hardware error\n", msg);
+               if (dd->ipath_flags & IPATH_INITTED) {
+                       ipath_dev_err(dd, "Fatal Hardware Error (freeze "
+                                         "mode), no longer usable, SN %.16s\n",
+                                         dd->ipath_serial);
+                       isfatal = 1;
+               }
+               *dd->ipath_statusp &= ~IPATH_STATUS_IB_READY;
+               /* mark as having had error */
+               *dd->ipath_statusp |= IPATH_STATUS_HWERROR;
+               /*
+                * mark as not usable, at a minimum until driver
+                * is reloaded, probably until reboot, since no
+                * other reset is possible.
+                */
+               dd->ipath_flags &= ~IPATH_INITTED;
+       }
+       else
+               *msg = 0; /* recovered from all of them */
        if (isfatal && !ipath_diag_inuse && dd->ipath_freezemsg)
                /*
                 * for status file; if no trailing brace is copied,
@@ -657,7 +662,8 @@ static int ipath_ht_boardname(struct ipath_devdata *dd, char *name,
        if (n)
                snprintf(name, namelen, "%s", n);
 
-       if (dd->ipath_majrev != 3 || (dd->ipath_minrev < 2 || dd->ipath_minrev > 3)) {
+       if (dd->ipath_majrev != 3 || (dd->ipath_minrev < 2 ||
+               dd->ipath_minrev > 3)) {
                /*
                 * This version of the driver only supports Rev 3.2 and 3.3
                 */
@@ -913,49 +919,40 @@ static void slave_or_pri_blk(struct ipath_devdata *dd, struct pci_dev *pdev,
        }
 }
 
-static int set_int_handler(struct ipath_devdata *dd, struct pci_dev *pdev,
-                           int pos)
+static int ipath_ht_intconfig(struct ipath_devdata *dd)
 {
-       u32 int_handler_addr_lower;
-       u32 int_handler_addr_upper;
-       u64 ihandler;
-       u32 intvec;
+       int ret;
+
+       if (dd->ipath_intconfig) {
+               ipath_write_kreg(dd, dd->ipath_kregs->kr_interruptconfig,
+                                dd->ipath_intconfig);  /* interrupt address */
+               ret = 0;
+       } else {
+               ipath_dev_err(dd, "No interrupts enabled, couldn't setup "
+                             "interrupt address\n");
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
 
-       /* use indirection register to get the intr handler */
-       pci_write_config_byte(pdev, pos + HT_INTR_REG_INDEX, 0x10);
-       pci_read_config_dword(pdev, pos + 4, &int_handler_addr_lower);
-       pci_write_config_byte(pdev, pos + HT_INTR_REG_INDEX, 0x11);
-       pci_read_config_dword(pdev, pos + 4, &int_handler_addr_upper);
+static void ipath_ht_irq_update(struct pci_dev *dev, int irq,
+                               struct ht_irq_msg *msg)
+{
+       struct ipath_devdata *dd = pci_get_drvdata(dev);
+       u64 prev_intconfig = dd->ipath_intconfig;
 
-       ihandler = (u64) int_handler_addr_lower |
-               ((u64) int_handler_addr_upper << 32);
+       dd->ipath_intconfig = msg->address_lo;
+       dd->ipath_intconfig |= ((u64) msg->address_hi) << 32;
 
        /*
-        * kernels with CONFIG_PCI_MSI set the vector in the irq field of
-        * struct pci_device, so we use that to program the internal
-        * interrupt register (not config space) with that value. The BIOS
-        * must still have done the basic MSI setup.
-        */
-       intvec = pdev->irq;
-       /*
-        * clear any vector bits there; normally not set but we'll overload
-        * this for some debug purposes (setting the HTC debug register
-        * value from software, rather than GPIOs), so it might be set on a
-        * driver reload.
+        * If the previous value of dd->ipath_intconfig is zero, we're
+        * getting configured for the first time, and must not program the
+        * intconfig register here (it will be programmed later, when the
+        * hardware is ready).  Otherwise, we should.
         */
-       ihandler &= ~0xff0000;
-       /* x86 vector goes in intrinfo[23:16] */
-       ihandler |= intvec << 16;
-       ipath_cdbg(VERBOSE, "ihandler lower %x, upper %x, intvec %x, "
-                  "interruptconfig %llx\n", int_handler_addr_lower,
-                  int_handler_addr_upper, intvec,
-                  (unsigned long long) ihandler);
-
-       /* can't program yet, so save for interrupt setup */
-       dd->ipath_intconfig = ihandler;
-       /* keep going, so we find link control stuff also */
-
-       return ihandler != 0;
+       if (prev_intconfig)
+               ipath_ht_intconfig(dd);
 }
 
 /**
@@ -971,12 +968,19 @@ static int set_int_handler(struct ipath_devdata *dd, struct pci_dev *pdev,
 static int ipath_setup_ht_config(struct ipath_devdata *dd,
                                 struct pci_dev *pdev)
 {
-       int pos, ret = 0;
-       int ihandler = 0;
+       int pos, ret;
+
+       ret = __ht_create_irq(pdev, 0, ipath_ht_irq_update);
+       if (ret < 0) {
+               ipath_dev_err(dd, "Couldn't create interrupt handler: "
+                             "err %d\n", ret);
+               goto bail;
+       }
+       dd->ipath_irq = ret;
+       ret = 0;
 
        /*
-        * Read the capability info to find the interrupt info, and also
-        * handle clearing CRC errors in linkctrl register if necessary.  We
+        * Handle clearing CRC errors in linkctrl register if necessary.  We
         * do this early, before we ever enable errors or hardware errors,
         * mostly to avoid causing the chip to enter freeze mode.
         */
@@ -1000,17 +1004,9 @@ static int ipath_setup_ht_config(struct ipath_devdata *dd,
                }
                if (!(cap_type & 0xE0))
                        slave_or_pri_blk(dd, pdev, pos, cap_type);
-               else if (cap_type == HT_INTR_DISC_CONFIG)
-                       ihandler = set_int_handler(dd, pdev, pos);
        } while ((pos = pci_find_next_capability(pdev, pos,
                                                 PCI_CAP_ID_HT)));
 
-       if (!ihandler) {
-               ipath_dev_err(dd, "Couldn't find interrupt handler in "
-                             "config space\n");
-               ret = -ENODEV;
-       }
-
 bail:
        return ret;
 }
@@ -1172,6 +1168,8 @@ static void ipath_ht_init_hwerrors(struct ipath_devdata *dd)
 
        if (!(extsval & INFINIPATH_EXTS_MEMBIST_ENDTEST))
                ipath_dev_err(dd, "MemBIST did not complete!\n");
+       if (extsval & INFINIPATH_EXTS_MEMBIST_CORRECT)
+               ipath_dbg("MemBIST corrected\n");
 
        ipath_check_htlink(dd);
 
@@ -1360,25 +1358,6 @@ static void ipath_ht_quiet_serdes(struct ipath_devdata *dd)
        ipath_write_kreg(dd, dd->ipath_kregs->kr_serdesconfig0, val);
 }
 
-static int ipath_ht_intconfig(struct ipath_devdata *dd)
-{
-       int ret;
-
-       if (!dd->ipath_intconfig) {
-               ipath_dev_err(dd, "No interrupts enabled, couldn't setup "
-                             "interrupt address\n");
-               ret = 1;
-               goto bail;
-       }
-
-       ipath_write_kreg(dd, dd->ipath_kregs->kr_interruptconfig,
-                        dd->ipath_intconfig);  /* interrupt address */
-       ret = 0;
-
-bail:
-       return ret;
-}
-
 /**
  * ipath_pe_put_tid - write a TID in chip
  * @dd: the infinipath device
@@ -1394,6 +1373,9 @@ static void ipath_ht_put_tid(struct ipath_devdata *dd,
                             u64 __iomem *tidptr, u32 type,
                             unsigned long pa)
 {
+       if (!dd->ipath_kregbase)
+               return;
+
        if (pa != dd->ipath_tidinvalid) {
                if (unlikely((pa & ~INFINIPATH_RT_ADDR_MASK))) {
                        dev_info(&dd->pcidev->dev,
@@ -1410,10 +1392,10 @@ static void ipath_ht_put_tid(struct ipath_devdata *dd,
                        pa |= lenvalid | INFINIPATH_RT_VALID;
                }
        }
-       if (dd->ipath_kregbase)
-               writeq(pa, tidptr);
+       writeq(pa, tidptr);
 }
 
+
 /**
  * ipath_ht_clear_tid - clear all TID entries for a port, expected and eager
  * @dd: the infinipath device
@@ -1475,7 +1457,7 @@ static void ipath_ht_tidtemplate(struct ipath_devdata *dd)
 static int ipath_ht_early_init(struct ipath_devdata *dd)
 {
        u32 __iomem *piobuf;
-       u32 pioincr, val32, egrsize;
+       u32 pioincr, val32;
        int i;
 
        /*
@@ -1495,7 +1477,6 @@ static int ipath_ht_early_init(struct ipath_devdata *dd)
         * errors interrupts if we ever see one).
         */
        dd->ipath_rcvegrbufsize = dd->ipath_piosize2k;
-       egrsize = dd->ipath_rcvegrbufsize;
 
        /*
         * the min() check here is currently a nop, but it may not
@@ -1544,7 +1525,7 @@ static int ipath_ht_early_init(struct ipath_devdata *dd)
                         INFINIPATH_S_ABORT);
 
        ipath_get_eeprom_info(dd);
-       if(dd->ipath_boardrev == 5 && dd->ipath_serial[0] == '1' &&
+       if (dd->ipath_boardrev == 5 && dd->ipath_serial[0] == '1' &&
                dd->ipath_serial[1] == '2' && dd->ipath_serial[2] == '8') {
                /*
                 * Later production QHT7040 has same changes as QHT7140, so
@@ -1557,13 +1538,31 @@ static int ipath_ht_early_init(struct ipath_devdata *dd)
        return 0;
 }
 
+
+static int ipath_ht_txe_recover(struct ipath_devdata *dd)
+{
+       int cnt = ++ipath_stats.sps_txeparity;
+       if (cnt >= IPATH_MAX_PARITY_ATTEMPTS)  {
+               if (cnt == IPATH_MAX_PARITY_ATTEMPTS)
+                       ipath_dev_err(dd,
+                               "Too many attempts to recover from "
+                               "TXE parity, giving up\n");
+               return 0;
+       }
+       dev_info(&dd->pcidev->dev,
+               "Recovering from TXE PIO parity error\n");
+       ipath_disarm_senderrbufs(dd, 1);
+       return 1;
+}
+
+
 /**
  * ipath_init_ht_get_base_info - set chip-specific flags for user code
  * @dd: the infinipath device
  * @kbase: ipath_base_info pointer
  *
  * We set the PCIE flag because the lower bandwidth on PCIe vs
- * HyperTransport can affect some user packet algorithims.
+ * HyperTransport can affect some user packet algorithms.
  */
 static int ipath_ht_get_base_info(struct ipath_portdata *pd, void *kbase)
 {
@@ -1575,6 +1574,14 @@ static int ipath_ht_get_base_info(struct ipath_portdata *pd, void *kbase)
        return 0;
 }
 
+static void ipath_ht_free_irq(struct ipath_devdata *dd)
+{
+       free_irq(dd->ipath_irq, dd);
+       ht_destroy_irq(dd->ipath_irq);
+       dd->ipath_irq = 0;
+       dd->ipath_intconfig = 0;
+}
+
 /**
  * ipath_init_iba6110_funcs - set up the chip-specific function pointers
  * @dd: the infinipath device
@@ -1598,6 +1605,7 @@ void ipath_init_iba6110_funcs(struct ipath_devdata *dd)
        dd->ipath_f_cleanup = ipath_setup_ht_cleanup;
        dd->ipath_f_setextled = ipath_setup_ht_setextled;
        dd->ipath_f_get_base_info = ipath_ht_get_base_info;
+       dd->ipath_f_free_irq = ipath_ht_free_irq;
 
        /*
         * initialize chip-specific variables