usb: usbtmc: Add ioctl for vendor specific write
authorGuido Kiener <guido@kiener-muenchen.de>
Wed, 12 Sep 2018 08:50:52 +0000 (10:50 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 20 Sep 2018 11:04:01 +0000 (13:04 +0200)
The new ioctl USBTMC_IOCTL_WRITE sends a generic message to bulk OUT.
This ioctl is used for vendor specific or asynchronous I/O as well.

The message is split into chunks of 4k (page size).
Message size is aligned to 32 bit boundaries.

With flag USBTMC_FLAG_ASYNC the ioctl is non blocking.
With flag USBTMC_FLAG_APPEND additional urbs are queued and
out_status/out_transfer_size is not reset. EPOLLOUT | EPOLLWRNORM
is signaled when all submitted urbs are completed.

Flush flying urbs when file handle is closed or device is
suspended or reset.

This ioctl does not support compatibility for 32 bit
applications running on 64 bit systems. However all other
convenient ioctls of the USBTMC driver can still be used in 32
bit applications as well. Note that 32 bit applications running
on 32 bit target systems are not affected by this limitation.

Signed-off-by: Guido Kiener <guido.kiener@rohde-schwarz.com>
Reviewed-by: Steve Bayless <steve_bayless@keysight.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/class/usbtmc.c
include/uapi/linux/usb/tmc.h

index 7e69bd05c6310fb108aac4fcf8168e756b7caa4c..915c3fefc4e3b32091eef4185a78d01d85fd552f 100644 (file)
@@ -37,6 +37,8 @@
 /* Default USB timeout (in milliseconds) */
 #define USBTMC_TIMEOUT         5000
 
+/* Max number of urbs used in write transfers */
+#define MAX_URBS_IN_FLIGHT     16
 /* I/O buffer size used in generic read/write functions */
 #define USBTMC_BUFSIZE         (4096)
 
@@ -125,13 +127,24 @@ struct usbtmc_file_data {
        u32            timeout;
        u8             srq_byte;
        atomic_t       srq_asserted;
+
        u8             eom_val;
        u8             term_char;
        bool           term_char_enabled;
+
+       spinlock_t     err_lock; /* lock for errors */
+
+       struct usb_anchor submitted;
+
+       /* data for generic_write */
+       struct semaphore limit_write_sem;
+       u32 out_transfer_size;
+       int out_status;
 };
 
 /* Forward declarations */
 static struct usb_driver usbtmc_driver;
+static void usbtmc_draw_down(struct usbtmc_file_data *file_data);
 
 static void usbtmc_delete(struct kref *kref)
 {
@@ -157,6 +170,10 @@ static int usbtmc_open(struct inode *inode, struct file *filp)
        if (!file_data)
                return -ENOMEM;
 
+       spin_lock_init(&file_data->err_lock);
+       sema_init(&file_data->limit_write_sem, MAX_URBS_IN_FLIGHT);
+       init_usb_anchor(&file_data->submitted);
+
        data = usb_get_intfdata(intf);
        /* Protect reference to data from file structure until release */
        kref_get(&data->kref);
@@ -182,6 +199,36 @@ static int usbtmc_open(struct inode *inode, struct file *filp)
        return 0;
 }
 
+/*
+ * usbtmc_flush - called before file handle is closed
+ */
+static int usbtmc_flush(struct file *file, fl_owner_t id)
+{
+       struct usbtmc_file_data *file_data;
+       struct usbtmc_device_data *data;
+
+       file_data = file->private_data;
+       if (file_data == NULL)
+               return -ENODEV;
+
+       data = file_data->data;
+
+       /* wait for io to stop */
+       mutex_lock(&data->io_mutex);
+
+       usbtmc_draw_down(file_data);
+
+       spin_lock_irq(&file_data->err_lock);
+       file_data->out_status = 0;
+       file_data->out_transfer_size = 0;
+       spin_unlock_irq(&file_data->err_lock);
+
+       wake_up_interruptible_all(&data->waitq);
+       mutex_unlock(&data->io_mutex);
+
+       return 0;
+}
+
 static int usbtmc_release(struct inode *inode, struct file *file)
 {
        struct usbtmc_file_data *file_data = file->private_data;
@@ -614,6 +661,238 @@ static int usbtmc488_ioctl_trigger(struct usbtmc_file_data *file_data)
        return 0;
 }
 
+static struct urb *usbtmc_create_urb(void)
+{
+       const size_t bufsize = USBTMC_BUFSIZE;
+       u8 *dmabuf = NULL;
+       struct urb *urb = usb_alloc_urb(0, GFP_KERNEL);
+
+       if (!urb)
+               return NULL;
+
+       dmabuf = kmalloc(bufsize, GFP_KERNEL);
+       if (!dmabuf) {
+               usb_free_urb(urb);
+               return NULL;
+       }
+
+       urb->transfer_buffer = dmabuf;
+       urb->transfer_buffer_length = bufsize;
+       urb->transfer_flags |= URB_FREE_BUFFER;
+       return urb;
+}
+
+static void usbtmc_write_bulk_cb(struct urb *urb)
+{
+       struct usbtmc_file_data *file_data = urb->context;
+       int wakeup = 0;
+       unsigned long flags;
+
+       spin_lock_irqsave(&file_data->err_lock, flags);
+       file_data->out_transfer_size += urb->actual_length;
+
+       /* sync/async unlink faults aren't errors */
+       if (urb->status) {
+               if (!(urb->status == -ENOENT ||
+                       urb->status == -ECONNRESET ||
+                       urb->status == -ESHUTDOWN))
+                       dev_err(&file_data->data->intf->dev,
+                               "%s - nonzero write bulk status received: %d\n",
+                               __func__, urb->status);
+
+               if (!file_data->out_status) {
+                       file_data->out_status = urb->status;
+                       wakeup = 1;
+               }
+       }
+       spin_unlock_irqrestore(&file_data->err_lock, flags);
+
+       dev_dbg(&file_data->data->intf->dev,
+               "%s - write bulk total size: %u\n",
+               __func__, file_data->out_transfer_size);
+
+       up(&file_data->limit_write_sem);
+       if (usb_anchor_empty(&file_data->submitted) || wakeup)
+               wake_up_interruptible(&file_data->data->waitq);
+}
+
+static ssize_t usbtmc_generic_write(struct usbtmc_file_data *file_data,
+                                   const void __user *user_buffer,
+                                   u32 transfer_size,
+                                   u32 *transferred,
+                                   u32 flags)
+{
+       struct usbtmc_device_data *data = file_data->data;
+       struct device *dev;
+       u32 done = 0;
+       u32 remaining;
+       unsigned long expire;
+       const u32 bufsize = USBTMC_BUFSIZE;
+       struct urb *urb = NULL;
+       int retval = 0;
+       u32 timeout;
+
+       *transferred = 0;
+
+       /* Get pointer to private data structure */
+       dev = &data->intf->dev;
+
+       dev_dbg(dev, "%s: size=%u flags=0x%X sema=%u\n",
+               __func__, transfer_size, flags,
+               file_data->limit_write_sem.count);
+
+       if (flags & USBTMC_FLAG_APPEND) {
+               spin_lock_irq(&file_data->err_lock);
+               retval = file_data->out_status;
+               spin_unlock_irq(&file_data->err_lock);
+               if (retval < 0)
+                       return retval;
+       } else {
+               spin_lock_irq(&file_data->err_lock);
+               file_data->out_transfer_size = 0;
+               file_data->out_status = 0;
+               spin_unlock_irq(&file_data->err_lock);
+       }
+
+       remaining = transfer_size;
+       if (remaining > INT_MAX)
+               remaining = INT_MAX;
+
+       timeout = file_data->timeout;
+       expire = msecs_to_jiffies(timeout);
+
+       while (remaining > 0) {
+               u32 this_part, aligned;
+               u8 *buffer = NULL;
+
+               if (flags & USBTMC_FLAG_ASYNC) {
+                       if (down_trylock(&file_data->limit_write_sem)) {
+                               retval = (done)?(0):(-EAGAIN);
+                               goto exit;
+                       }
+               } else {
+                       retval = down_timeout(&file_data->limit_write_sem,
+                                             expire);
+                       if (retval < 0) {
+                               retval = -ETIMEDOUT;
+                               goto error;
+                       }
+               }
+
+               spin_lock_irq(&file_data->err_lock);
+               retval = file_data->out_status;
+               spin_unlock_irq(&file_data->err_lock);
+               if (retval < 0) {
+                       up(&file_data->limit_write_sem);
+                       goto error;
+               }
+
+               /* prepare next urb to send */
+               urb = usbtmc_create_urb();
+               if (!urb) {
+                       retval = -ENOMEM;
+                       up(&file_data->limit_write_sem);
+                       goto error;
+               }
+               buffer = urb->transfer_buffer;
+
+               if (remaining > bufsize)
+                       this_part = bufsize;
+               else
+                       this_part = remaining;
+
+               if (copy_from_user(buffer, user_buffer + done, this_part)) {
+                       retval = -EFAULT;
+                       up(&file_data->limit_write_sem);
+                       goto error;
+               }
+
+               print_hex_dump_debug("usbtmc ", DUMP_PREFIX_NONE,
+                       16, 1, buffer, this_part, true);
+
+               /* fill bulk with 32 bit alignment to meet USBTMC specification
+                * (size + 3 & ~3) rounds up and simplifies user code
+                */
+               aligned = (this_part + 3) & ~3;
+               dev_dbg(dev, "write(size:%u align:%u done:%u)\n",
+                       (unsigned int)this_part,
+                       (unsigned int)aligned,
+                       (unsigned int)done);
+
+               usb_fill_bulk_urb(urb, data->usb_dev,
+                       usb_sndbulkpipe(data->usb_dev, data->bulk_out),
+                       urb->transfer_buffer, aligned,
+                       usbtmc_write_bulk_cb, file_data);
+
+               usb_anchor_urb(urb, &file_data->submitted);
+               retval = usb_submit_urb(urb, GFP_KERNEL);
+               if (unlikely(retval)) {
+                       usb_unanchor_urb(urb);
+                       up(&file_data->limit_write_sem);
+                       goto error;
+               }
+
+               usb_free_urb(urb);
+               urb = NULL; /* urb will be finally released by usb driver */
+
+               remaining -= this_part;
+               done += this_part;
+       }
+
+       /* All urbs are on the fly */
+       if (!(flags & USBTMC_FLAG_ASYNC)) {
+               if (!usb_wait_anchor_empty_timeout(&file_data->submitted,
+                                                  timeout)) {
+                       retval = -ETIMEDOUT;
+                       goto error;
+               }
+       }
+
+       retval = 0;
+       goto exit;
+
+error:
+       usb_kill_anchored_urbs(&file_data->submitted);
+exit:
+       usb_free_urb(urb);
+
+       spin_lock_irq(&file_data->err_lock);
+       if (!(flags & USBTMC_FLAG_ASYNC))
+               done = file_data->out_transfer_size;
+       if (!retval && file_data->out_status)
+               retval = file_data->out_status;
+       spin_unlock_irq(&file_data->err_lock);
+
+       *transferred = done;
+
+       dev_dbg(dev, "%s: done=%u, retval=%d, urbstat=%d\n",
+               __func__, done, retval, file_data->out_status);
+
+       return retval;
+}
+
+static ssize_t usbtmc_ioctl_generic_write(struct usbtmc_file_data *file_data,
+                                         void __user *arg)
+{
+       struct usbtmc_message msg;
+       ssize_t retval = 0;
+
+       /* mutex already locked */
+
+       if (copy_from_user(&msg, arg, sizeof(struct usbtmc_message)))
+               return -EFAULT;
+
+       retval = usbtmc_generic_write(file_data, msg.message,
+                                     msg.transfer_size, &msg.transferred,
+                                     msg.flags);
+
+       if (put_user(msg.transferred,
+                    &((struct usbtmc_message __user *)arg)->transferred))
+               return -EFAULT;
+
+       return retval;
+}
+
 /*
  * Sends a REQUEST_DEV_DEP_MSG_IN message on the Bulk-OUT endpoint.
  * @transfer_size: number of bytes to request from the device.
@@ -1081,6 +1360,15 @@ static int usbtmc_ioctl_clear_in_halt(struct usbtmc_device_data *data)
        return 0;
 }
 
+static int usbtmc_ioctl_cancel_io(struct usbtmc_file_data *file_data)
+{
+       spin_lock_irq(&file_data->err_lock);
+       file_data->out_status = -ECANCELED;
+       spin_unlock_irq(&file_data->err_lock);
+       usb_kill_anchored_urbs(&file_data->submitted);
+       return 0;
+}
+
 static int get_capabilities(struct usbtmc_device_data *data)
 {
        struct device *dev = &data->usb_dev->dev;
@@ -1455,6 +1743,11 @@ static long usbtmc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
                                                   (void __user *)arg);
                break;
 
+       case USBTMC_IOCTL_WRITE:
+               retval = usbtmc_ioctl_generic_write(file_data,
+                                                   (void __user *)arg);
+               break;
+
        case USBTMC488_IOCTL_GET_CAPS:
                retval = copy_to_user((void __user *)arg,
                                &data->usb488_caps,
@@ -1515,7 +1808,19 @@ static __poll_t usbtmc_poll(struct file *file, poll_table *wait)
 
        poll_wait(file, &data->waitq, wait);
 
-       mask = (atomic_read(&file_data->srq_asserted)) ? EPOLLPRI : 0;
+       mask = 0;
+       if (atomic_read(&file_data->srq_asserted))
+               mask |= EPOLLPRI;
+
+       if (usb_anchor_empty(&file_data->submitted))
+               mask |= (EPOLLOUT | EPOLLWRNORM);
+
+       spin_lock_irq(&file_data->err_lock);
+       if (file_data->out_status)
+               mask |= EPOLLERR;
+       spin_unlock_irq(&file_data->err_lock);
+
+       dev_dbg(&data->intf->dev, "poll mask = %x\n", mask);
 
 no_poll:
        mutex_unlock(&data->io_mutex);
@@ -1528,6 +1833,7 @@ static const struct file_operations fops = {
        .write          = usbtmc_write,
        .open           = usbtmc_open,
        .release        = usbtmc_release,
+       .flush          = usbtmc_flush,
        .unlocked_ioctl = usbtmc_ioctl,
 #ifdef CONFIG_COMPAT
        .compat_ioctl   = usbtmc_ioctl,
@@ -1753,6 +2059,7 @@ err_put:
 static void usbtmc_disconnect(struct usb_interface *intf)
 {
        struct usbtmc_device_data *data  = usb_get_intfdata(intf);
+       struct list_head *elem;
 
        usb_deregister_dev(intf, &usbtmc_class);
        sysfs_remove_group(&intf->dev.kobj, &capability_attr_grp);
@@ -1760,14 +2067,46 @@ static void usbtmc_disconnect(struct usb_interface *intf)
        mutex_lock(&data->io_mutex);
        data->zombie = 1;
        wake_up_interruptible_all(&data->waitq);
+       list_for_each(elem, &data->file_list) {
+               struct usbtmc_file_data *file_data;
+
+               file_data = list_entry(elem,
+                                      struct usbtmc_file_data,
+                                      file_elem);
+               usb_kill_anchored_urbs(&file_data->submitted);
+       }
        mutex_unlock(&data->io_mutex);
        usbtmc_free_int(data);
        kref_put(&data->kref, usbtmc_delete);
 }
 
+static void usbtmc_draw_down(struct usbtmc_file_data *file_data)
+{
+       int time;
+
+       time = usb_wait_anchor_empty_timeout(&file_data->submitted, 1000);
+       if (!time)
+               usb_kill_anchored_urbs(&file_data->submitted);
+}
+
 static int usbtmc_suspend(struct usb_interface *intf, pm_message_t message)
 {
-       /* this driver does not have pending URBs */
+       struct usbtmc_device_data *data = usb_get_intfdata(intf);
+       struct list_head *elem;
+
+       if (!data)
+               return 0;
+
+       mutex_lock(&data->io_mutex);
+       list_for_each(elem, &data->file_list) {
+               struct usbtmc_file_data *file_data;
+
+               file_data = list_entry(elem,
+                                      struct usbtmc_file_data,
+                                      file_elem);
+               usbtmc_draw_down(file_data);
+       }
+       mutex_unlock(&data->io_mutex);
        return 0;
 }
 
@@ -1776,6 +2115,37 @@ static int usbtmc_resume(struct usb_interface *intf)
        return 0;
 }
 
+static int usbtmc_pre_reset(struct usb_interface *intf)
+{
+       struct usbtmc_device_data *data  = usb_get_intfdata(intf);
+       struct list_head *elem;
+
+       if (!data)
+               return 0;
+
+       mutex_lock(&data->io_mutex);
+
+       list_for_each(elem, &data->file_list) {
+               struct usbtmc_file_data *file_data;
+
+               file_data = list_entry(elem,
+                                      struct usbtmc_file_data,
+                                      file_elem);
+               usbtmc_ioctl_cancel_io(file_data);
+       }
+
+       return 0;
+}
+
+static int usbtmc_post_reset(struct usb_interface *intf)
+{
+       struct usbtmc_device_data *data  = usb_get_intfdata(intf);
+
+       mutex_unlock(&data->io_mutex);
+
+       return 0;
+}
+
 static struct usb_driver usbtmc_driver = {
        .name           = "usbtmc",
        .id_table       = usbtmc_devices,
@@ -1783,6 +2153,8 @@ static struct usb_driver usbtmc_driver = {
        .disconnect     = usbtmc_disconnect,
        .suspend        = usbtmc_suspend,
        .resume         = usbtmc_resume,
+       .pre_reset      = usbtmc_pre_reset,
+       .post_reset     = usbtmc_post_reset,
 };
 
 module_usb_driver(usbtmc_driver);
index 5e12928ed1e50edae35582b91e486c0c4612bd8d..44dc88f3479d2ad95800e942ea370e4c15caa89b 100644 (file)
@@ -59,6 +59,19 @@ struct usbtmc_termchar {
        __u8 term_char_enabled;
 } __attribute__ ((packed));
 
+/*
+ * usbtmc_message->flags:
+ */
+#define USBTMC_FLAG_ASYNC              0x0001
+#define USBTMC_FLAG_APPEND             0x0002
+
+struct usbtmc_message {
+       __u32 transfer_size; /* size of bytes to transfer */
+       __u32 transferred; /* size of received/written bytes */
+       __u32 flags; /* bit 0: 0 = synchronous; 1 = asynchronous */
+       void __user *message; /* pointer to header and data in user space */
+} __attribute__ ((packed));
+
 /* Request values for USBTMC driver's ioctl entry point */
 #define USBTMC_IOC_NR                  91
 #define USBTMC_IOCTL_INDICATOR_PULSE   _IO(USBTMC_IOC_NR, 1)
@@ -72,6 +85,7 @@ struct usbtmc_termchar {
 #define USBTMC_IOCTL_SET_TIMEOUT       _IOW(USBTMC_IOC_NR, 10, __u32)
 #define USBTMC_IOCTL_EOM_ENABLE                _IOW(USBTMC_IOC_NR, 11, __u8)
 #define USBTMC_IOCTL_CONFIG_TERMCHAR   _IOW(USBTMC_IOC_NR, 12, struct usbtmc_termchar)
+#define USBTMC_IOCTL_WRITE             _IOWR(USBTMC_IOC_NR, 13, struct usbtmc_message)
 
 #define USBTMC488_IOCTL_GET_CAPS       _IOR(USBTMC_IOC_NR, 17, unsigned char)
 #define USBTMC488_IOCTL_READ_STB       _IOR(USBTMC_IOC_NR, 18, unsigned char)