fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / infiniband / hw / ipath / ipath_diag.c
index 28ddceb..28c087b 100644 (file)
@@ -1,4 +1,5 @@
 /*
+ * Copyright (c) 2006 QLogic, Inc. All rights reserved.
  * Copyright (c) 2003, 2004, 2005, 2006 PathScale, Inc. All rights reserved.
  *
  * This software is available to you under a choice of one of two
  * through the /sys/bus/pci resource mmap interface.
  */
 
+#include <linux/io.h>
 #include <linux/pci.h>
+#include <linux/vmalloc.h>
 #include <asm/uaccess.h>
 
-#include "ipath_common.h"
 #include "ipath_kernel.h"
-#include "ips_common.h"
-#include "ipath_layer.h"
+#include "ipath_common.h"
 
 int ipath_diag_inuse;
 static int diag_set_link;
@@ -66,18 +67,55 @@ static struct file_operations diag_file_ops = {
        .release = ipath_diag_release
 };
 
-static struct cdev *diag_cdev;
-static struct class_device *diag_class_dev;
+static ssize_t ipath_diagpkt_write(struct file *fp,
+                                  const char __user *data,
+                                  size_t count, loff_t *off);
+
+static struct file_operations diagpkt_file_ops = {
+       .owner = THIS_MODULE,
+       .write = ipath_diagpkt_write,
+};
+
+static atomic_t diagpkt_count = ATOMIC_INIT(0);
+static struct cdev *diagpkt_cdev;
+static struct class_device *diagpkt_class_dev;
 
-int ipath_diag_init(void)
+int ipath_diag_add(struct ipath_devdata *dd)
 {
-       return ipath_cdev_init(IPATH_DIAG_MINOR, "ipath_diag",
-                              &diag_file_ops, &diag_cdev, &diag_class_dev);
+       char name[16];
+       int ret = 0;
+
+       if (atomic_inc_return(&diagpkt_count) == 1) {
+               ret = ipath_cdev_init(IPATH_DIAGPKT_MINOR,
+                                     "ipath_diagpkt", &diagpkt_file_ops,
+                                     &diagpkt_cdev, &diagpkt_class_dev);
+
+               if (ret) {
+                       ipath_dev_err(dd, "Couldn't create ipath_diagpkt "
+                                     "device: %d", ret);
+                       goto done;
+               }
+       }
+
+       snprintf(name, sizeof(name), "ipath_diag%d", dd->ipath_unit);
+
+       ret = ipath_cdev_init(IPATH_DIAG_MINOR_BASE + dd->ipath_unit, name,
+                             &diag_file_ops, &dd->diag_cdev,
+                             &dd->diag_class_dev);
+       if (ret)
+               ipath_dev_err(dd, "Couldn't create %s device: %d",
+                             name, ret);
+
+done:
+       return ret;
 }
 
-void ipath_diag_cleanup(void)
+void ipath_diag_remove(struct ipath_devdata *dd)
 {
-       ipath_cdev_cleanup(&diag_cdev, &diag_class_dev);
+       if (atomic_dec_and_test(&diagpkt_count))
+               ipath_cdev_cleanup(&diagpkt_cdev, &diagpkt_class_dev);
+
+       ipath_cdev_cleanup(&dd->diag_cdev, &dd->diag_class_dev);
 }
 
 /**
@@ -101,8 +139,7 @@ static int ipath_read_umem64(struct ipath_devdata *dd, void __user *uaddr,
        int ret;
 
        /* not very efficient, but it works for now */
-       if (reg_addr < dd->ipath_kregbase ||
-           reg_end > dd->ipath_kregend) {
+       if (reg_addr < dd->ipath_kregbase || reg_end > dd->ipath_kregend) {
                ret = -EINVAL;
                goto bail;
        }
@@ -113,7 +150,7 @@ static int ipath_read_umem64(struct ipath_devdata *dd, void __user *uaddr,
                        goto bail;
                }
                reg_addr++;
-               uaddr++;
+               uaddr += sizeof(u64);
        }
        ret = 0;
 bail:
@@ -139,8 +176,7 @@ static int ipath_write_umem64(struct ipath_devdata *dd, void __iomem *caddr,
        int ret;
 
        /* not very efficient, but it works for now */
-       if (reg_addr < dd->ipath_kregbase ||
-           reg_end > dd->ipath_kregend) {
+       if (reg_addr < dd->ipath_kregbase || reg_end > dd->ipath_kregend) {
                ret = -EINVAL;
                goto bail;
        }
@@ -153,7 +189,7 @@ static int ipath_write_umem64(struct ipath_devdata *dd, void __iomem *caddr,
                writeq(data, reg_addr);
 
                reg_addr++;
-               uaddr++;
+               uaddr += sizeof(u64);
        }
        ret = 0;
 bail:
@@ -191,7 +227,8 @@ static int ipath_read_umem32(struct ipath_devdata *dd, void __user *uaddr,
                }
 
                reg_addr++;
-               uaddr++;
+               uaddr += sizeof(u32);
+
        }
        ret = 0;
 bail:
@@ -230,7 +267,7 @@ static int ipath_write_umem32(struct ipath_devdata *dd, void __iomem *caddr,
                writel(data, reg_addr);
 
                reg_addr++;
-               uaddr++;
+               uaddr += sizeof(u32);
        }
        ret = 0;
 bail:
@@ -239,59 +276,173 @@ bail:
 
 static int ipath_diag_open(struct inode *in, struct file *fp)
 {
+       int unit = iminor(in) - IPATH_DIAG_MINOR_BASE;
        struct ipath_devdata *dd;
-       int unit = 0; /* XXX this is bogus */
-       unsigned long flags;
        int ret;
 
-       dd = ipath_lookup(unit);
-
        mutex_lock(&ipath_mutex);
-       spin_lock_irqsave(&ipath_devs_lock, flags);
 
        if (ipath_diag_inuse) {
                ret = -EBUSY;
                goto bail;
        }
 
-       list_for_each_entry(dd, &ipath_dev_list, ipath_list) {
-               /*
-                * we need at least one infinipath device to be present
-                * (don't use INITTED, because we want to be able to open
-                * even if device is in freeze mode, which cleared INITTED).
-                * There is a small amount of risk to this, which is why we
-                * also verify kregbase is set.
-                */
-
-               if (!(dd->ipath_flags & IPATH_PRESENT) ||
-                   !dd->ipath_kregbase)
-                       continue;
-
-               ipath_diag_inuse = 1;
-               diag_set_link = 0;
-               ret = 0;
+       dd = ipath_lookup(unit);
+
+       if (dd == NULL || !(dd->ipath_flags & IPATH_PRESENT) ||
+           !dd->ipath_kregbase) {
+               ret = -ENODEV;
                goto bail;
        }
 
-       ret = -ENODEV;
-
-bail:
-       spin_unlock_irqrestore(&ipath_devs_lock, flags);
+       fp->private_data = dd;
+       ipath_diag_inuse = 1;
+       diag_set_link = 0;
+       ret = 0;
 
        /* Only expose a way to reset the device if we
           make it into diag mode. */
-       if (ret == 0)
-               ipath_expose_reset(&dd->pcidev->dev);
+       ipath_expose_reset(&dd->pcidev->dev);
 
+bail:
        mutex_unlock(&ipath_mutex);
 
        return ret;
 }
 
-static int ipath_diag_release(struct inode *i, struct file *f)
+/**
+ * ipath_diagpkt_write - write an IB packet
+ * @fp: the diag data device file pointer
+ * @data: ipath_diag_pkt structure saying where to get the packet
+ * @count: size of data to write
+ * @off: unused by this code
+ */
+static ssize_t ipath_diagpkt_write(struct file *fp,
+                                  const char __user *data,
+                                  size_t count, loff_t *off)
+{
+       u32 __iomem *piobuf;
+       u32 plen, clen, pbufn;
+       struct ipath_diag_pkt dp;
+       u32 *tmpbuf = NULL;
+       struct ipath_devdata *dd;
+       ssize_t ret = 0;
+       u64 val;
+
+       if (count < sizeof(dp)) {
+               ret = -EINVAL;
+               goto bail;
+       }
+
+       if (copy_from_user(&dp, data, sizeof(dp))) {
+               ret = -EFAULT;
+               goto bail;
+       }
+
+       /* send count must be an exact number of dwords */
+       if (dp.len & 3) {
+               ret = -EINVAL;
+               goto bail;
+       }
+
+       clen = dp.len >> 2;
+
+       dd = ipath_lookup(dp.unit);
+       if (!dd || !(dd->ipath_flags & IPATH_PRESENT) ||
+           !dd->ipath_kregbase) {
+               ipath_cdbg(VERBOSE, "illegal unit %u for diag data send\n",
+                          dp.unit);
+               ret = -ENODEV;
+               goto bail;
+       }
+
+       if (ipath_diag_inuse && !diag_set_link &&
+           !(dd->ipath_flags & IPATH_LINKACTIVE)) {
+               diag_set_link = 1;
+               ipath_cdbg(VERBOSE, "Trying to set to set link active for "
+                          "diag pkt\n");
+               ipath_set_linkstate(dd, IPATH_IB_LINKARM);
+               ipath_set_linkstate(dd, IPATH_IB_LINKACTIVE);
+       }
+
+       if (!(dd->ipath_flags & IPATH_INITTED)) {
+               /* no hardware, freeze, etc. */
+               ipath_cdbg(VERBOSE, "unit %u not usable\n", dd->ipath_unit);
+               ret = -ENODEV;
+               goto bail;
+       }
+       val = dd->ipath_lastibcstat & IPATH_IBSTATE_MASK;
+       if (val != IPATH_IBSTATE_INIT && val != IPATH_IBSTATE_ARM &&
+           val != IPATH_IBSTATE_ACTIVE) {
+               ipath_cdbg(VERBOSE, "unit %u not ready (state %llx)\n",
+                          dd->ipath_unit, (unsigned long long) val);
+               ret = -EINVAL;
+               goto bail;
+       }
+
+       /* need total length before first word written */
+       /* +1 word is for the qword padding */
+       plen = sizeof(u32) + dp.len;
+
+       if ((plen + 4) > dd->ipath_ibmaxlen) {
+               ipath_dbg("Pkt len 0x%x > ibmaxlen %x\n",
+                         plen - 4, dd->ipath_ibmaxlen);
+               ret = -EINVAL;
+               goto bail;      /* before writing pbc */
+       }
+       tmpbuf = vmalloc(plen);
+       if (!tmpbuf) {
+               dev_info(&dd->pcidev->dev, "Unable to allocate tmp buffer, "
+                        "failing\n");
+               ret = -ENOMEM;
+               goto bail;
+       }
+
+       if (copy_from_user(tmpbuf,
+                          (const void __user *) (unsigned long) dp.data,
+                          dp.len)) {
+               ret = -EFAULT;
+               goto bail;
+       }
+
+       piobuf = ipath_getpiobuf(dd, &pbufn);
+       if (!piobuf) {
+               ipath_cdbg(VERBOSE, "No PIO buffers avail unit for %u\n",
+                          dd->ipath_unit);
+               ret = -EBUSY;
+               goto bail;
+       }
+
+       plen >>= 2;             /* in dwords */
+
+       if (ipath_debug & __IPATH_PKTDBG)
+               ipath_cdbg(VERBOSE, "unit %u 0x%x+1w pio%d\n",
+                          dd->ipath_unit, plen - 1, pbufn);
+
+       /* we have to flush after the PBC for correctness on some cpus
+        * or WC buffer can be written out of order */
+       writeq(plen, piobuf);
+       ipath_flush_wc();
+       /* copy all by the trigger word, then flush, so it's written
+        * to chip before trigger word, then write trigger word, then
+        * flush again, so packet is sent. */
+       __iowrite32_copy(piobuf + 2, tmpbuf, clen - 1);
+       ipath_flush_wc();
+       __raw_writel(tmpbuf[clen - 1], piobuf + clen + 1);
+       ipath_flush_wc();
+
+       ret = sizeof(dp);
+
+bail:
+       vfree(tmpbuf);
+       return ret;
+}
+
+static int ipath_diag_release(struct inode *in, struct file *fp)
 {
        mutex_lock(&ipath_mutex);
        ipath_diag_inuse = 0;
+       fp->private_data = NULL;
        mutex_unlock(&ipath_mutex);
        return 0;
 }
@@ -299,17 +450,10 @@ static int ipath_diag_release(struct inode *i, struct file *f)
 static ssize_t ipath_diag_read(struct file *fp, char __user *data,
                               size_t count, loff_t *off)
 {
-       int unit = 0; /* XXX provide for reads on other units some day */
-       struct ipath_devdata *dd;
+       struct ipath_devdata *dd = fp->private_data;
        void __iomem *kreg_base;
        ssize_t ret;
 
-       dd = ipath_lookup(unit);
-       if (!dd) {
-               ret = -ENODEV;
-               goto bail;
-       }
-
        kreg_base = dd->ipath_kregbase;
 
        if (count == 0)
@@ -328,23 +472,16 @@ static ssize_t ipath_diag_read(struct file *fp, char __user *data,
                ret = count;
        }
 
-bail:
        return ret;
 }
 
 static ssize_t ipath_diag_write(struct file *fp, const char __user *data,
                                size_t count, loff_t *off)
 {
-       int unit = 0; /* XXX this is bogus */
-       struct ipath_devdata *dd;
+       struct ipath_devdata *dd = fp->private_data;
        void __iomem *kreg_base;
        ssize_t ret;
 
-       dd = ipath_lookup(unit);
-       if (!dd) {
-               ret = -ENODEV;
-               goto bail;
-       }
        kreg_base = dd->ipath_kregbase;
 
        if (count == 0)
@@ -363,6 +500,5 @@ static ssize_t ipath_diag_write(struct file *fp, const char __user *data,
                ret = count;
        }
 
-bail:
        return ret;
 }