Implement support for the USB488 defined READ_STATUS_BYTE and SRQ
notifications with ioctl, fasync and poll/select in order to be able
to synchronize with variable duration instrument operations.

Add ioctls for other USB488 requests: REN_CONTROL, GOTO_LOCAL and
LOCAL_LOCKOUT.

Add convenience ioctl to return all device capabilities.

Signed-off-by: Dave Penkler <dpenk...@gmail.com>
---
 drivers/usb/class/usbtmc.c   | 359 +++++++++++++++++++++++++++++++++++++++++++
 include/uapi/linux/usb/tmc.h |  42 +++--
 2 files changed, 391 insertions(+), 10 deletions(-)

diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c
index 7a11a82..83c8416 100644
--- a/drivers/usb/class/usbtmc.c
+++ b/drivers/usb/class/usbtmc.c
@@ -27,6 +27,7 @@
 #include <linux/uaccess.h>
 #include <linux/kref.h>
 #include <linux/slab.h>
+#include <linux/poll.h>
 #include <linux/mutex.h>
 #include <linux/usb.h>
 #include <linux/usb/tmc.h>
@@ -87,6 +88,24 @@ struct usbtmc_device_data {
        u8 bTag_last_write;     /* needed for abort */
        u8 bTag_last_read;      /* needed for abort */
 
+
+       /* data for interrupt in endpoint handling */
+       u8             bNotify1;
+       u8             bNotify2;
+       u16            ifnum;
+       u8             iin_bTag;
+       u8            *iin_buffer;
+       atomic_t       iin_data_valid;
+       unsigned int   iin_ep;
+       int            iin_ep_present;
+       int            iin_interval;
+       struct urb    *iin_urb;
+       u16            iin_wMaxPacketSize;
+       atomic_t       srq_asserted;
+
+       /* coalesced usb488_caps from usbtmc_dev_capabilities */
+       u8 usb488_caps;
+
        u8 rigol_quirk;
 
        /* attributes from the USB TMC spec for this device */
@@ -99,6 +118,8 @@ struct usbtmc_device_data {
        struct usbtmc_dev_capabilities  capabilities;
        struct kref kref;
        struct mutex io_mutex;  /* only one i/o function running at a time */
+       wait_queue_head_t waitq;
+       struct fasync_struct *fasync;
 };
 #define to_usbtmc_data(d) container_of(d, struct usbtmc_device_data, kref)
 
@@ -373,6 +394,158 @@ exit:
        return rv;
 }
 
+static int usbtmc488_ioctl_read_stb(struct usbtmc_device_data *data,
+                               unsigned long arg)
+{
+       u8 *buffer;
+       struct device *dev;
+       int rv;
+       u8 tag, stb;
+
+       dev = &data->intf->dev;
+
+       dev_dbg(dev, "Enter ioctl_read_stb iin_ep_present: %d\n",
+               data->iin_ep_present);
+
+       buffer = kmalloc(8, GFP_KERNEL);
+       if (!buffer)
+               return -ENOMEM;
+
+
+       atomic_set(&data->iin_data_valid, 0);
+
+       /* must issue read_stb before using poll or select */
+       atomic_set(&data->srq_asserted, 0);
+
+       rv = usb_control_msg(data->usb_dev,
+                       usb_rcvctrlpipe(data->usb_dev, 0),
+                       USBTMC488_REQUEST_READ_STATUS_BYTE,
+                       USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       data->iin_bTag,
+                       data->ifnum,
+                       buffer, 0x03, USBTMC_TIMEOUT);
+
+       if (rv < 0) {
+               dev_err(dev, "stb usb_control_msg returned %d\n", rv);
+               goto exit;
+       }
+
+       if (buffer[0] != USBTMC_STATUS_SUCCESS) {
+               dev_err(dev, "control status returned %x\n",
+                       buffer[0]);
+               rv = -EIO;
+               goto exit;
+       }
+
+       if (data->iin_ep_present) {
+
+               rv = wait_event_interruptible_timeout(
+                       data->waitq,
+                       (atomic_read(&data->iin_data_valid) != 0),
+                       USBTMC_TIMEOUT
+                       );
+
+               if (rv < 0) {
+                       dev_dbg(dev, "wait interrupted %d\n", rv);
+                       goto exit;
+               }
+
+               if (rv == 0) {
+                       dev_dbg(dev, "wait timed out\n");
+                       rv = -ETIME;
+                       goto exit;
+               }
+
+               tag = data->bNotify1 & 0x7f;
+
+               if (tag != data->iin_bTag) {
+                       dev_err(dev, "expected bTag %x got %x\n",
+                               data->iin_bTag, tag);
+               }
+
+               stb = data->bNotify2;
+       } else {
+               stb = buffer[2];
+       }
+
+       /* bump interrupt bTag */
+       data->iin_bTag += 1;
+       if (data->iin_bTag > 127)
+               data->iin_bTag = 2;
+
+       rv = copy_to_user((void *)arg, &stb, sizeof(stb));
+       if (rv)
+               rv = -EFAULT;
+
+ exit:
+       kfree(buffer);
+       return rv;
+
+}
+
+static int usbtmc488_ioctl_simple(struct usbtmc_device_data *data,
+                               unsigned long arg,
+                               unsigned int cmd)
+{
+       u8 *buffer;
+       struct device *dev;
+       int rv;
+       unsigned int val;
+       u16 wValue;
+
+       dev = &data->intf->dev;
+
+       if (0 == (data->usb488_caps & USBTMC488_CAPABILITY_SIMPLE))
+               return -EINVAL;
+
+       buffer = kmalloc(8, GFP_KERNEL);
+       if (!buffer)
+               return -ENOMEM;
+
+
+       if (cmd == USBTMC488_REQUEST_REN_CONTROL) {
+               rv = copy_from_user(&val, (void *)arg, sizeof(val));
+               if (rv) {
+                       rv = -EFAULT;
+                       goto exit;
+               }
+               wValue = (val) ? 1 : 0;
+       } else {
+               wValue = 0;
+       }
+
+       rv = usb_control_msg(data->usb_dev,
+                       usb_rcvctrlpipe(data->usb_dev, 0),
+                       cmd,
+                       USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                       wValue,
+                       data->ifnum,
+                       buffer, 0x01, USBTMC_TIMEOUT);
+
+       if (rv < 0) {
+               dev_err(dev, "simple usb_control_msg failed %d\n", rv);
+               goto exit;
+       } else if (rv != 1) {
+               dev_warn(dev, "simple usb_control_msg returned %d\n", rv);
+               rv = -EIO;
+               goto exit;
+       }
+
+       if (buffer[0] != USBTMC_STATUS_SUCCESS) {
+               dev_err(dev, "simple control status returned %x\n",
+                       buffer[0]);
+               rv = -EIO;
+               goto exit;
+       } else {
+               rv = 0;
+       }
+
+ exit:
+       kfree(buffer);
+       return rv;
+}
+
+
 /*
  * Sends a REQUEST_DEV_DEP_MSG_IN message on the Bulk-IN endpoint.
  * @transfer_size: number of bytes to request from the device.
@@ -895,6 +1068,7 @@ static int get_capabilities(struct usbtmc_device_data 
*data)
        data->capabilities.device_capabilities = buffer[5];
        data->capabilities.usb488_interface_capabilities = buffer[14];
        data->capabilities.usb488_device_capabilities = buffer[15];
+       data->usb488_caps = (buffer[14] & 0x07) | ((buffer[15] & 0x0f) << 4);
        rv = 0;
 
 err_out:
@@ -1069,6 +1243,33 @@ static long usbtmc_ioctl(struct file *file, unsigned int 
cmd, unsigned long arg)
        case USBTMC_IOCTL_ABORT_BULK_IN:
                retval = usbtmc_ioctl_abort_bulk_in(data);
                break;
+
+       case USBTMC488_IOCTL_GET_CAPABILITIES:
+               retval = copy_to_user((void *)arg,
+                               &data->usb488_caps,
+                               sizeof(data->usb488_caps));
+               if (retval)
+                       retval = -EFAULT;
+               break;
+
+       case USBTMC488_IOCTL_READ_STB:
+               retval = usbtmc488_ioctl_read_stb(data, arg);
+               break;
+
+       case USBTMC488_IOCTL_REN_CONTROL:
+               retval = usbtmc488_ioctl_simple(data, arg,
+                                               USBTMC488_REQUEST_REN_CONTROL);
+               break;
+
+       case USBTMC488_IOCTL_GOTO_LOCAL:
+               retval = usbtmc488_ioctl_simple(data, arg,
+                                               USBTMC488_REQUEST_GOTO_LOCAL);
+               break;
+
+       case USBTMC488_IOCTL_LOCAL_LOCKOUT:
+               retval = usbtmc488_ioctl_simple(data, arg,
+                                               
USBTMC488_REQUEST_LOCAL_LOCKOUT);
+               break;
        }
 
 skip_io_on_zombie:
@@ -1076,6 +1277,33 @@ skip_io_on_zombie:
        return retval;
 }
 
+static int usbtmc_fasync(int fd, struct file *file, int on)
+{
+       struct usbtmc_device_data *data = file->private_data;
+
+       return fasync_helper(fd, file, on, &data->fasync);
+}
+
+static unsigned int usbtmc_poll(struct file *file, poll_table *wait)
+{
+       struct usbtmc_device_data *data = file->private_data;
+       unsigned int mask = 0;
+
+       mutex_lock(&data->io_mutex);
+
+       if (data->zombie)
+               goto no_poll;
+
+       poll_wait(file, &data->waitq, wait);
+
+       mask = data->zombie ? POLLHUP | POLLERR :
+               (atomic_read(&data->srq_asserted)) ? POLLIN | POLLRDNORM : 0;
+
+no_poll:
+       mutex_unlock(&data->io_mutex);
+       return mask;
+}
+
 static const struct file_operations fops = {
        .owner          = THIS_MODULE,
        .read           = usbtmc_read,
@@ -1083,6 +1311,8 @@ static const struct file_operations fops = {
        .open           = usbtmc_open,
        .release        = usbtmc_release,
        .unlocked_ioctl = usbtmc_ioctl,
+       .fasync         = usbtmc_fasync,
+       .poll           = usbtmc_poll,
        .llseek         = default_llseek,
 };
 
@@ -1092,6 +1322,79 @@ static struct usb_class_driver usbtmc_class = {
        .minor_base =   USBTMC_MINOR_BASE,
 };
 
+static void usbtmc_interrupt(struct urb *urb)
+{
+       struct usbtmc_device_data *data = urb->context;
+       int status = urb->status;
+       int rv;
+
+       dev_dbg(&data->intf->dev, "int status: %d len %d\n",
+               status, urb->actual_length);
+
+       switch (status) {
+       case 0: /* SUCCESS */
+
+               if (data->iin_buffer[0] & 0x80) {
+                       /* check for valid STB notification */
+                       if ((data->iin_buffer[0] & 0x7f) > 1) {
+                               data->bNotify1 = data->iin_buffer[0];
+                               data->bNotify2 = data->iin_buffer[1];
+                               atomic_set(&data->iin_data_valid, 1);
+                               wake_up_interruptible(&data->waitq);
+                               goto exit;
+                       /* check for SRQ notification */
+                       }
+                       if ((data->iin_buffer[0] & 0x7f) == 1) {
+                               if (data->fasync)
+                                       kill_fasync(&data->fasync,
+                                               SIGIO, POLL_IN);
+
+                               atomic_set(&data->srq_asserted, 1);
+                               wake_up_interruptible(&data->waitq);
+                               goto exit;
+                       }
+               }
+               dev_warn(&data->intf->dev, "invalid notification: %x\n",
+                       data->iin_buffer[0]);
+               break;
+       case -EOVERFLOW:
+               dev_err(&data->intf->dev,
+                       "%s - overflow with length %d, actual length is %d\n",
+                       __func__, data->iin_wMaxPacketSize, urb->actual_length);
+       case -ECONNRESET:
+       case -ENOENT:
+       case -ESHUTDOWN:
+       case -EILSEQ:
+       case -ETIME:
+               /* urb terminated, clean up */
+               dev_dbg(&data->intf->dev,
+                       "%s - urb terminated, status: %d\n",
+                       __func__, status);
+               return;
+       default:
+               dev_err(&data->intf->dev,
+                       "%s - unknown status received: %d\n",
+                       __func__, status);
+       }
+exit:
+       rv = usb_submit_urb(urb, GFP_ATOMIC);
+       if (rv) {
+               dev_err(&data->intf->dev, "%s - usb_submit_urb failed: %d\n",
+                       __func__, rv);
+       }
+}
+
+static void usbtmc_free_int(struct usbtmc_device_data *data)
+{
+       if (data->iin_ep_present) {
+               if (data->iin_urb) {
+                       usb_kill_urb(data->iin_urb);
+                       kfree(data->iin_buffer);
+                       usb_free_urb(data->iin_urb);
+                       kref_put(&data->kref, usbtmc_delete);
+               }
+       }
+}
 
 static int usbtmc_probe(struct usb_interface *intf,
                        const struct usb_device_id *id)
@@ -1114,6 +1417,9 @@ static int usbtmc_probe(struct usb_interface *intf,
        usb_set_intfdata(intf, data);
        kref_init(&data->kref);
        mutex_init(&data->io_mutex);
+       init_waitqueue_head(&data->waitq);
+       atomic_set(&data->iin_data_valid, 0);
+       atomic_set(&data->srq_asserted, 0);
        data->zombie = 0;
 
        /* Determine if it is a Rigol or not */
@@ -1134,9 +1440,12 @@ static int usbtmc_probe(struct usb_interface *intf,
        data->bTag      = 1;
        data->TermCharEnabled = 0;
        data->TermChar = '\n';
+       /*  2 <= bTag <= 127   USBTMC-USB488 subclass specification 4.3.1 */
+       data->iin_bTag = 2;
 
        /* USBTMC devices have only one setting, so use that */
        iface_desc = data->intf->cur_altsetting;
+       data->ifnum = iface_desc->desc.bInterfaceNumber;
 
        /* Find bulk in endpoint */
        for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) {
@@ -1161,6 +1470,20 @@ static int usbtmc_probe(struct usb_interface *intf,
                        break;
                }
        }
+       /* Find int endpoint */
+       for (n = 0; n < iface_desc->desc.bNumEndpoints; n++) {
+               endpoint = &iface_desc->endpoint[n].desc;
+
+               if (usb_endpoint_is_int_in(endpoint)) {
+                       data->iin_ep_present = 1;
+                       data->iin_ep = endpoint->bEndpointAddress;
+                       data->iin_wMaxPacketSize = usb_endpoint_maxp(endpoint);
+                       data->iin_interval = endpoint->bInterval;
+                       dev_dbg(&intf->dev, "Found Int in endpoint at %u\n",
+                               data->iin_ep);
+                       break;
+               }
+       }
 
        retcode = get_capabilities(data);
        if (retcode)
@@ -1169,6 +1492,40 @@ static int usbtmc_probe(struct usb_interface *intf,
                retcode = sysfs_create_group(&intf->dev.kobj,
                                             &capability_attr_grp);
 
+
+       if (data->iin_ep_present) {
+               /* allocate int urb */
+               data->iin_urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (!data->iin_urb) {
+                       dev_err(&intf->dev, "Failed to allocate int urb\n");
+                       goto error_register;
+               }
+
+               /* will reference data in int urb */
+               kref_get(&data->kref);
+
+               /* allocate buffer for interrupt in */
+               data->iin_buffer = kmalloc(data->iin_wMaxPacketSize,
+                                       GFP_KERNEL);
+               if (!data->iin_buffer) {
+                       dev_err(&intf->dev, "Failed to allocate int buf\n");
+                       goto error_register;
+               }
+
+               /* fill interrupt urb */
+               usb_fill_int_urb(data->iin_urb, data->usb_dev,
+                               usb_rcvintpipe(data->usb_dev, data->iin_ep),
+                               data->iin_buffer, data->iin_wMaxPacketSize,
+                               usbtmc_interrupt,
+                               data, data->iin_interval);
+
+               if (usb_submit_urb(data->iin_urb, GFP_KERNEL)) {
+                       retcode = -EIO;
+                       dev_err(&intf->dev, "Failed to submit iin_urb\n");
+                       goto error_register;
+               }
+       }
+
        retcode = sysfs_create_group(&intf->dev.kobj, &data_attr_grp);
 
        retcode = usb_register_dev(intf, &usbtmc_class);
@@ -1185,6 +1542,7 @@ static int usbtmc_probe(struct usb_interface *intf,
 error_register:
        sysfs_remove_group(&intf->dev.kobj, &capability_attr_grp);
        sysfs_remove_group(&intf->dev.kobj, &data_attr_grp);
+       usbtmc_free_int(data);
        kref_put(&data->kref, usbtmc_delete);
        return retcode;
 }
@@ -1196,6 +1554,7 @@ static void usbtmc_disconnect(struct usb_interface *intf)
        dev_dbg(&intf->dev, "usbtmc_disconnect called\n");
 
        data = usb_get_intfdata(intf);
+       usbtmc_free_int(data);
        usb_deregister_dev(intf, &usbtmc_class);
        sysfs_remove_group(&intf->dev.kobj, &capability_attr_grp);
        sysfs_remove_group(&intf->dev.kobj, &data_attr_grp);
diff --git a/include/uapi/linux/usb/tmc.h b/include/uapi/linux/usb/tmc.h
index c045ae1..ac91c06 100644
--- a/include/uapi/linux/usb/tmc.h
+++ b/include/uapi/linux/usb/tmc.h
@@ -2,12 +2,14 @@
  * Copyright (C) 2007 Stefan Kopp, Gechingen, Germany
  * Copyright (C) 2008 Novell, Inc.
  * Copyright (C) 2008 Greg Kroah-Hartman <gre...@suse.de>
+ * Copyright (C) 2015 Dave Penkler <dpenk...@gmail.com>
  *
  * This file holds USB constants defined by the USB Device Class
- * Definition for Test and Measurement devices published by the USB-IF.
+ * and USB488 Subclass Definitions for Test and Measurement devices
+ * published by the USB-IF.
  *
- * It also has the ioctl definitions for the usbtmc kernel driver that
- * userspace needs to know about.
+ * It also has the ioctl and capability definitions for the
+ * usbtmc kernel driver that userspace needs to know about.
  */
 
 #ifndef __LINUX_USB_TMC_H
@@ -30,14 +32,34 @@
 #define USBTMC_REQUEST_CHECK_CLEAR_STATUS              6
 #define USBTMC_REQUEST_GET_CAPABILITIES                        7
 #define USBTMC_REQUEST_INDICATOR_PULSE                 64
+#define USBTMC488_REQUEST_READ_STATUS_BYTE              128
+#define USBTMC488_REQUEST_REN_CONTROL                   160
+#define USBTMC488_REQUEST_GOTO_LOCAL                    161
+#define USBTMC488_REQUEST_LOCAL_LOCKOUT                 162
 
 /* Request values for USBTMC driver's ioctl entry point */
-#define USBTMC_IOC_NR                  91
-#define USBTMC_IOCTL_INDICATOR_PULSE   _IO(USBTMC_IOC_NR, 1)
-#define USBTMC_IOCTL_CLEAR             _IO(USBTMC_IOC_NR, 2)
-#define USBTMC_IOCTL_ABORT_BULK_OUT    _IO(USBTMC_IOC_NR, 3)
-#define USBTMC_IOCTL_ABORT_BULK_IN     _IO(USBTMC_IOC_NR, 4)
-#define USBTMC_IOCTL_CLEAR_OUT_HALT    _IO(USBTMC_IOC_NR, 6)
-#define USBTMC_IOCTL_CLEAR_IN_HALT     _IO(USBTMC_IOC_NR, 7)
+#define USBTMC_IOC_NR                    91
+#define USBTMC_IOCTL_INDICATOR_PULSE     _IO(USBTMC_IOC_NR, 1)
+#define USBTMC_IOCTL_CLEAR               _IO(USBTMC_IOC_NR, 2)
+#define USBTMC_IOCTL_ABORT_BULK_OUT      _IO(USBTMC_IOC_NR, 3)
+#define USBTMC_IOCTL_ABORT_BULK_IN       _IO(USBTMC_IOC_NR, 4)
+#define USBTMC_IOCTL_CLEAR_OUT_HALT      _IO(USBTMC_IOC_NR, 6)
+#define USBTMC_IOCTL_CLEAR_IN_HALT       _IO(USBTMC_IOC_NR, 7)
+#define USBTMC488_IOCTL_GET_CAPABILITIES _IO(USBTMC_IOC_NR, 17)
+#define USBTMC488_IOCTL_READ_STB         _IOR(USBTMC_IOC_NR, 18, unsigned char)
+#define USBTMC488_IOCTL_REN_CONTROL      _IOW(USBTMC_IOC_NR, 19, unsigned char)
+#define USBTMC488_IOCTL_GOTO_LOCAL       _IO(USBTMC_IOC_NR, 20)
+#define USBTMC488_IOCTL_LOCAL_LOCKOUT    _IO(USBTMC_IOC_NR, 21)
 
+/* Driver encoded usb488 capabilities */
+#define USBTMC488_CAPABILITY_TRIGGER         1
+#define USBTMC488_CAPABILITY_SIMPLE          2
+#define USBTMC488_CAPABILITY_REN_CONTROL     2
+#define USBTMC488_CAPABILITY_GOTO_LOCAL      2
+#define USBTMC488_CAPABILITY_LOCAL_LOCKOUT   2
+#define USBTMC488_CAPABILITY_488_DOT_2       4
+#define USBTMC488_CAPABILITY_DT1             16
+#define USBTMC488_CAPABILITY_RL1             32
+#define USBTMC488_CAPABILITY_SR1             64
+#define USBTMC488_CAPABILITY_FULL_SCPI       128
 #endif
-- 
2.5.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to