IB/ipath: add new minor device to allow sending of diag packets
authorBryan O'Sullivan <bos@pathscale.com>
Fri, 25 Aug 2006 18:24:36 +0000 (11:24 -0700)
committerRoland Dreier <rolandd@cisco.com>
Fri, 22 Sep 2006 22:22:34 +0000 (15:22 -0700)
Signed-off-by: Bryan O'Sullivan <bryan.osullivan@qlogic.com>
Signed-off-by: Roland Dreier <rolandd@cisco.com>
drivers/infiniband/hw/ipath/ipath_common.h
drivers/infiniband/hw/ipath/ipath_diag.c
drivers/infiniband/hw/ipath/ipath_driver.c
drivers/infiniband/hw/ipath/ipath_kernel.h

index f8df3b771c267af9128349ebcb231ca1a2713d6f..f577905e3acaafc3d624c04a00dca92b6f08be35 100644 (file)
@@ -463,6 +463,13 @@ struct __ipath_sendpkt {
        struct ipath_iovec sps_iov[4];
 };
 
+/* Passed into diag data special file's ->write method. */
+struct ipath_diag_pkt {
+       __u32 unit;
+       __u64 data;
+       __u32 len;
+};
+
 /*
  * Data layout in I2C flash (for GUID, etc.)
  * All fields are little-endian binary unless otherwise stated
index 5d77a74aa57b1954cba8fef7119e4451583d49d3..28b6b46c106aebf3245640a8f04de5421416bcbe 100644 (file)
@@ -41,6 +41,7 @@
  * through the /sys/bus/pci resource mmap interface.
  */
 
+#include <linux/io.h>
 #include <linux/pci.h>
 #include <asm/uaccess.h>
 
@@ -273,6 +274,158 @@ bail:
        return ret;
 }
 
+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 struct cdev *diagpkt_cdev;
+static struct class_device *diagpkt_class_dev;
+
+int __init ipath_diagpkt_add(void)
+{
+       return ipath_cdev_init(IPATH_DIAGPKT_MINOR,
+                              "ipath_diagpkt", &diagpkt_file_ops,
+                              &diagpkt_cdev, &diagpkt_class_dev);
+}
+
+void __exit ipath_diagpkt_remove(void)
+{
+       ipath_cdev_cleanup(&diagpkt_cdev, &diagpkt_class_dev);
+}
+
+/**
+ * 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);
index 520c38f13868c015009f9e849fe3b2368ffc59c5..8c908b30984e19cabb157f1c1e7ef6733bd8a6f6 100644 (file)
@@ -1881,8 +1881,18 @@ static int __init infinipath_init(void)
                goto bail_group;
        }
 
+       ret = ipath_diagpkt_add();
+       if (ret < 0) {
+               printk(KERN_ERR IPATH_DRV_NAME ": Unable to create "
+                      "diag data device: error %d\n", -ret);
+               goto bail_ipathfs;
+       }
+
        goto bail;
 
+bail_ipathfs:
+       ipath_exit_ipathfs();
+
 bail_group:
        ipath_driver_remove_group(&ipath_driver.driver);
 
@@ -1993,6 +2003,8 @@ static void __exit infinipath_cleanup(void)
        struct ipath_devdata *dd, *tmp;
        unsigned long flags;
 
+       ipath_diagpkt_remove();
+
        ipath_exit_ipathfs();
 
        ipath_driver_remove_group(&ipath_driver.driver);
index 0ae2729c7ea27fe231fc0145557f85322cfeaa5c..f8accc79b92f15ae3b3852fba05eece78d158142 100644 (file)
@@ -789,6 +789,9 @@ int ipath_device_create_group(struct device *, struct ipath_devdata *);
 void ipath_device_remove_group(struct device *, struct ipath_devdata *);
 int ipath_expose_reset(struct device *);
 
+int ipath_diagpkt_add(void);
+void ipath_diagpkt_remove(void);
+
 int ipath_init_ipathfs(void);
 void ipath_exit_ipathfs(void);
 int ipathfs_add_device(struct ipath_devdata *);
@@ -813,6 +816,7 @@ extern struct mutex ipath_mutex;
 #define IPATH_DRV_NAME         "ib_ipath"
 #define IPATH_MAJOR            233
 #define IPATH_USER_MINOR_BASE  0
+#define IPATH_DIAGPKT_MINOR    127
 #define IPATH_DIAG_MINOR_BASE  129
 #define IPATH_NMINORS          255