From: Stefani Seibold <stefani@seibold.net>
To: Oliver Neukum <oneukum@suse.de>
Cc: linux-kernel@vger.kernel.org, greg@kroah.com,
gregkh@linuxfoundation.org,
thomas.braunstorfinger@rohde-schwarz.com
Subject: Re: [PATCH] add new NRP power meter USB device driver
Date: Thu, 31 May 2012 09:43:41 +0200 [thread overview]
Message-ID: <1338450221.6454.16.camel@wall-e> (raw)
In-Reply-To: <201205301010.22913.oneukum@suse.de>
Hi Oliver,
Thanks for the review.
On Wed, 2012-05-30 at 10:10 +0200, Oliver Neukum wrote:
> Am Dienstag, 29. Mai 2012, 21:14:18 schrieb stefani@seibold.net:
>
> > The device will be controlled by a SCPI like interface.
>
> Are there other devices using that standard whose interface you might reuse?
>
>
There are a lot of instruments from many different manufacturer which
use the SCPI standard. But there is now way to bring them together. The
commands are well defined, but there are a lot of vendor specific
commands. And the communication path are differing, which can be
IEEE488, USB, Socket, Serial and more.
For more information see
https://en.wikipedia.org/wiki/Standard_Commands_for_Programmable_Instruments
> > +static ssize_t nrpz_read(struct file *file, char __user *buffer, size_t count,
> > + loff_t *ppos)
> > +{
> > + struct usb_nrpz *dev;
> > + int ret;
> > + struct urb *urb;
> > + size_t n;
> > +
> > + dev = (struct usb_nrpz *)file->private_data;
> > +
> > + /* verify that we actually have some data to read */
> > + if (!count)
> > + return 0;
> > +
> > + /* lock the read data */
> > + ret = mutex_lock_interruptible(&dev->read_mutex);
> > + if (ret)
> > + return ret;
> > +
> > + for (;;) {
> > + urb = urb_list_get(&dev->read_lock, &dev->in_avail);
> > + if (urb)
> > + break;
> > +
> > + if (file->f_flags & O_NONBLOCK) {
> > + ret = -EAGAIN;
> > + goto exit;
> > + }
>
> You should test for device disconnect before that. Otherwise
> a nonblocking reader would never learn of the problem. IIRC
> the skeleton has been fixed.
>
I did this 10 lines later, i will move this check before the O_NONBLOCK
check.
> > +
> > + ret = wait_event_interruptible(dev->wq,
> > + !list_empty(&dev->in_avail) || !dev->intf);
> > + if (ret) {
> > + ret = -ERESTARTSYS;
> > + goto exit;
> > + }
> > +
> > + /* verify that the device wasn't unplugged */
> > + if (!dev->intf) {
> > + ret = -ENODEV;
> > + goto exit;
> > + }
> > + }
> > +
> > + if (!urb->status) {
> > + n = min(count, urb->actual_length);
> > +
> > + if (copy_to_user(buffer, urb->transfer_buffer, n)) {
> > + urb_list_add(&dev->read_lock, urb, &dev->in_avail);
> > + ret = -EFAULT;
> > + goto exit;
>
> Is there a good reason you don't resubmit in this case?
>
The data was not successfully transfered to the user space, so it will
stay again on the top of the input urb list.
> > + }
> > + } else
> > + n = urb->status;
>
> You need to translate this to standard error codes
I will report -EPIPE in the next version of the driver.
> > +
> > + usb_anchor_urb(urb, &dev->in_running);
> > +
> > + ret = usb_submit_urb(urb, GFP_KERNEL);
> > + if (ret) {
> > + usb_unanchor_urb(urb);
> > + urb_list_add_tail(&dev->read_lock, urb, &dev->in_avail);
> > + nrpz_err("Failed submitting read urb (error %d)", ret);
> > + }
> > +
> > + ret = n;
> > +exit:
> > + mutex_unlock(&dev->read_mutex);
> > + return ret;
> > +}
>
> > +#define VRT_RESET_ALL 1
> > +#define VRT_GET_DEVICE_INFO 6
> > +#define VRI_DEVICE_NAME 5
> > +
> > +static long nrpz_compat_ioctl(struct file *file, unsigned int cmd,
> > + unsigned long arg)
> > +{
> > + struct usb_nrpz *dev;
> > + int ret;
> > +
> > + dev = (struct usb_nrpz *)file->private_data;
> > +
> > + /* verify that the device wasn't unplugged */
> > + if (!dev->intf)
> > + return -ENODEV;
>
> Locking?
>
I see no reason why. But i will change it in the next version.
> > +
> > + switch (cmd) {
> > + case NRPZ_GETSENSORINFO:
> > + {
> > + struct nrpz_sensor_info __user *sensor_info =
> > + (struct nrpz_sensor_info __user *)arg;
> > +
> > + if (!access_ok(VERIFY_WRITE, sensor_info, sizeof(*sensor_info)))
> > + return -EFAULT;
> > +
> > + __put_user(dev->udev->descriptor.bcdDevice,
> > + &sensor_info->bcdDevice);
> > + __put_user(dev->udev->descriptor.bcdUSB,
> > + &sensor_info->bcdUSB);
> > + __put_user(dev->udev->descriptor.bDescriptorType,
> > + &sensor_info->bDescriptorType);
> > + __put_user(dev->udev->descriptor.bDeviceClass,
> > + &sensor_info->bDeviceClass);
> > + __put_user(dev->udev->descriptor.bDeviceSubClass,
> > + &sensor_info->bDeviceSubClass);
> > + __put_user(dev->udev->descriptor.bDeviceProtocol,
> > + &sensor_info->bDeviceProtocol);
> > + __put_user(dev->udev->descriptor.bMaxPacketSize0,
> > + &sensor_info->bMaxPacketSize0);
> > + __put_user(dev->udev->descriptor.bNumConfigurations,
> > + &sensor_info->bNumConfigurations);
> > + __put_user(dev->udev->descriptor.iManufacturer,
> > + &sensor_info->iManufacturer);
> > + __put_user(dev->udev->descriptor.iProduct,
> > + &sensor_info->iProduct);
> > + __put_user(dev->udev->descriptor.iSerialNumber,
> > + &sensor_info->iSerialNumber);
> > + __put_user(dev->udev->descriptor.idVendor,
> > + &sensor_info->vendorId);
> > + __put_user(dev->udev->descriptor.idProduct,
> > + &sensor_info->productId);
> > + usb_string(dev->udev, dev->udev->descriptor.iManufacturer,
> > + (char __force *)sensor_info->manufacturer,
> > + sizeof(sensor_info->manufacturer));
> > + usb_string(dev->udev, dev->udev->descriptor.iProduct,
> > + (char __force *)sensor_info->productName,
> > + sizeof(sensor_info->productName));
> > + usb_string(dev->udev, dev->udev->descriptor.iSerialNumber,
> > + (char __force *)sensor_info->serialNumber,
> > + sizeof(sensor_info->serialNumber));
> > +
> > + return 0;
> > + }
> > + case NRPZ_START:
> > + {
> > + u8 device_state[128];
>
> DMA on stack. This may ail on some architectures. You need to use
> kmalloc() or better kzalloc().
>
Never heard about this problem. The driver will work since years on X86,
PPC and ARM platforms. But it will be fixes in the next version.
> > + memset(device_state, 0, sizeof(device_state));
> > +
> > + ret = usb_control_msg(dev->udev,
> > + usb_rcvctrlpipe(dev->udev, 0),
> > + VRT_GET_DEVICE_INFO,
> > + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
> > + 0,
> > + VRI_DEVICE_NAME,
> > + device_state,
> > + sizeof(device_state),
> > + 5000);
> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + nrpz_dbg("device state:%s", device_state);
> > +
> > + if (strncmp(device_state, "Boot ", 5))
> > + return 0;
> > +
> > + return usb_control_msg(dev->udev,
> > + usb_sndctrlpipe(dev->udev, 0),
> > + VRT_RESET_ALL,
> > + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
> > + 1,
> > + 1,
> > + device_state,
> > + sizeof(device_state),
> > + 5000);
> > + }
> > + case NRPZ_WRITE_DONE:
>
> EEEEEEK!
>
> > + if (arg) {
> > + ret = wait_event_interruptible_timeout(
> > + dev->out_running.wait,
> > + list_empty(&dev->out_running.urb_list),
> > + msecs_to_jiffies(arg));
> > + if (!ret)
> > + return -ETIMEDOUT;
> > + if (ret < 0)
> > + return ret;
> > + return 0;
> > + } else {
> > + return wait_event_interruptible(
> > + dev->out_running.wait,
> > + list_empty(&dev->out_running.urb_list));
> > + }
> > + break;
>
> This is very ugly. If you need fsync(), then implement it.
>
fsync() did not meat the requirements, since i need in some case a
timeout for the device. poll() will also not help, since it signals only
that there is space to write.
> > + case NRPZ_VENDOR_CONTROL_MSG_OUT:
> > + {
> > + struct nrpz_control_req ncr;
>
> DMA on stack.
>
Fixed in the next version.
> > + u16 size;
> > +
> > + if (copy_from_user(&ncr, (struct nrpz_control_req __user *)arg, sizeof(ncr)))
> > + return -EFAULT;
> > +
> > + if (ncr.data) {
> > + size = ncr.size;
> > +
> > + if (!access_ok(VERIFY_WRITE, (void __user *)ncr.data, size))
> > + return -EFAULT;
> > + } else {
> > + size = 0;
> > + }
> > +
> > + ret = usb_control_msg(dev->udev,
> > + usb_sndctrlpipe(dev->udev, 0),
> > + ncr.request,
> > + ncr.type,
> > + ncr.value,
> > + ncr.index,
> > + ncr.data,
> > + size,
> > + 0);
> > +
> > + return ret;
> > + }
> > + case NRPZ_VENDOR_CONTROL_MSG_IN:
> > + {
> > + struct nrpz_control_req ncr;
>
> DMA on stack
>
Fixed in the next version.
> > + u16 size;
> > +
> > + if (copy_from_user(&ncr, (struct nrpz_control_req __user *)arg, sizeof(ncr)))
> > + return -EFAULT;
> > +
> > + if (ncr.data) {
> > + size = ncr.size;
> > +
> > + if (!access_ok(VERIFY_READ, (void __user *)ncr.data, size))
> > + return -EFAULT;
> > + } else {
> > + size = 0;
> > + }
> > +
> > + ret = usb_control_msg(dev->udev,
> > + usb_rcvctrlpipe(dev->udev, 0),
> > + ncr.request,
> > + ncr.type,
> > + ncr.value,
> > + ncr.index,
> > + ncr.data,
> > + size,
> > + 0);
> > +
> > + return ret;
> > + }
> > + default:
> > + nrpz_err("Invalid ioctl call (%08x)", cmd);
> > + return -EOPNOTSUPP;
> > + }
> > +
> > + return ret;
> > +}
>
> > +static int nrpz_open(struct inode *inode, struct file *file)
> > +{
> > + struct usb_nrpz *dev;
> > + int minor;
> > + struct usb_interface *intf;
> > + int ret;
> > + unsigned i;
> > +
> > + minor = iminor(inode);
> > +
> > + intf = usb_find_interface(&nrpz_driver, minor);
> > + if (!intf) {
> > + nrpz_err("Can not find device for minor %d", minor);
> > + return -ENODEV;
> > + }
> > +
> > + dev = usb_get_intfdata(intf);
> > + if (!dev)
> > + return -ENODEV;
> > +
> > + if (test_and_set_bit(0, &dev->in_use))
> > + return -EBUSY;
> > +
> > + /* increment our usage count for the device */
> > + kref_get(&dev->kref);
> > +
> > + /* save our object in the file's private structure */
> > + file->private_data = dev;
> > +
> > + INIT_LIST_HEAD(&dev->in_avail);
> > + INIT_LIST_HEAD(&dev->out_avail);
> > +
> > + ret = bulks_init(dev,
> > + dev->in_urbs,
> > + ARRAY_SIZE(dev->in_urbs),
> > + usb_rcvbulkpipe(dev->udev, dev->in_epAddr),
> > + dev->in_size,
> > + nrpz_read_callback);
> > + if (ret)
> > + goto error;
> > +
> > + ret = bulks_init(dev,
> > + dev->out_urbs,
> > + ARRAY_SIZE(dev->out_urbs),
> > + usb_sndbulkpipe(dev->udev, dev->out_epAddr),
> > + dev->out_size,
> > + nrpz_write_callback);
> > + if (ret)
> > + goto error;
> > +
> > + for (i = 0; i != ARRAY_SIZE(dev->in_urbs); ++i) {
> > + usb_anchor_urb(&dev->in_urbs[i], &dev->in_running);
> > +
> > + ret = usb_submit_urb(&dev->in_urbs[i], GFP_KERNEL);
>
> You do this here and only here. So this will see a slow attrition if you don't resubmit for som reason.
>
No, i resubmit the urb in the nrpz_read() function.
> > + if (ret) {
> > + usb_kill_anchored_urbs(&dev->in_running);
> > + goto error;
> > + }
> > + }
> > +
> > + for (i = 0; i != ARRAY_SIZE(dev->out_urbs); ++i)
> > + list_add(&dev->out_urbs[i].urb_list, &dev->out_avail);
> > +
> > + return 0;
> > +error:
> > + clear_bit(0, &dev->in_use);
> > +
> > + bulks_release(dev, dev->out_urbs, ARRAY_SIZE(dev->out_urbs),
> > + dev->out_size);
> > + bulks_release(dev, dev->in_urbs, ARRAY_SIZE(dev->in_urbs),
> > + dev->in_size);
> > +
> > + return 0;
> > +}
> > +
> > +static int nrpz_release(struct inode *inode, struct file *file)
> > +{
> > + struct usb_nrpz *dev;
> > +
> > + dev = (struct usb_nrpz *)file->private_data;
> > + if (dev == NULL)
> > + return -ENODEV;
> > +
> > + usb_kill_anchored_urbs(&dev->in_running);
> > + usb_kill_anchored_urbs(&dev->out_running);
> > +
> > + bulks_release(dev, dev->out_urbs, ARRAY_SIZE(dev->out_urbs),
> > + dev->out_size);
> > + bulks_release(dev, dev->in_urbs, ARRAY_SIZE(dev->in_urbs),
> > + dev->in_size);
> > +
> > + /* decrement the count on our device */
> > + kref_put(&dev->kref, nrpz_delete);
> > +
> > + clear_bit(0, &dev->in_use);
> > +
> > + return 0;
> > +}
> > +
> > +static int nrpz_flush(struct file *file, fl_owner_t id)
> > +{
> > + struct usb_nrpz *dev;
> > + int ret;
> > +
> > + dev = (struct usb_nrpz *)file->private_data;
> > + if (dev == NULL)
> > + return -ENODEV;
> > +
> > + /* lock the write data */
> > + ret = mutex_lock_interruptible(&dev->write_mutex);
> > + if (ret)
> > + return ret;
> > +
> > + /* verify that the device wasn't unplugged */
> > + if (!dev->intf) {
> > + ret = -ENODEV;
> > + goto exit;
> > + }
> > +
> > + ret = wait_event_interruptible(dev->out_running.wait,
> > + list_empty(&dev->out_running.urb_list));
> > + if (ret) {
> > + ret = -ERESTARTSYS;
> > + goto exit;
> > + }
> > +exit:
> > + mutex_unlock(&dev->write_mutex);
> > +
> > + return ret;
> > +}
> > +
> > +static unsigned int nrpz_poll(struct file *file, poll_table *wait)
> > +{
> > + struct usb_nrpz *dev;
> > + int ret = 0;
> > +
> > + dev = (struct usb_nrpz *)file->private_data;
> > +
> > + poll_wait(file, &dev->wq, wait);
> > +
> > + if (!dev->intf)
> > + ret = POLLIN | POLLOUT | POLLPRI | POLLERR | POLLHUP;
> > + else {
> > + if (!list_empty(&dev->in_avail))
> > + ret |= POLLIN;
> > +
> > + if (!list_empty(&dev->out_avail))
> > + ret |= POLLOUT;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +static const struct file_operations nrpz_fops = {
> > + .owner = THIS_MODULE,
> > + .read = nrpz_read,
> > + .write = nrpz_write,
> > + .unlocked_ioctl = nrpz_ioctl,
> > + .compat_ioctl = nrpz_compat_ioctl,
> > + .open = nrpz_open,
> > + .release = nrpz_release,
> > + .flush = nrpz_flush,
> > + .poll = nrpz_poll,
> > + .llseek = noop_llseek,
> > +};
> > +
> > +static struct usb_class_driver nrpz_class = {
> > + .name = "nrpz%d",
> > + .fops = &nrpz_fops,
> > + .minor_base = NRPZ_MINOR_BASE,
> > +};
> > +
> > +static int nrpz_probe(struct usb_interface *intf,
> > + const struct usb_device_id *id)
> > +{
> > + int i;
> > + int ret;
> > + struct usb_endpoint_descriptor *endpoint;
> > + struct usb_host_interface *iface_desc;
> > + struct usb_nrpz *dev;
> > +
> > + /* allocate memory for our device state and intialize it */
> > + dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> > + if (!dev) {
> > + nrpz_err("Out of memory");
> > + return -ENOMEM;
> > + }
> > +
> > + ret = -EIO;
> > +
> > + init_waitqueue_head(&dev->wq);
> > + kref_init(&dev->kref);
> > +
> > + mutex_init(&dev->read_mutex);
> > + mutex_init(&dev->write_mutex);
> > +
> > + spin_lock_init(&dev->read_lock);
> > + spin_lock_init(&dev->write_lock);
> > +
> > + init_usb_anchor(&dev->in_running);
> > + init_usb_anchor(&dev->out_running);
> > +
> > + dev->in_use = 0;
> > + dev->udev = usb_get_dev(interface_to_usbdev(intf));
> > + dev->intf = intf;
> > +
> > + /* set up the endpoint information */
> > + /* check out the endpoints */
> > + iface_desc = intf->cur_altsetting;
> > + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
> > + endpoint = &iface_desc->endpoint[i].desc;
> > +
> > + if (!dev->in_epAddr && usb_endpoint_is_bulk_in(endpoint)) {
> > + /* we found a bulk in endpoint */
> > + dev->in_size = le16_to_cpu(endpoint->wMaxPacketSize);
> > + dev->in_epAddr = endpoint->bEndpointAddress;
> > + } else
> > + if (!dev->out_epAddr && usb_endpoint_is_bulk_out(endpoint)) {
> > + /* we found a bulk out endpoint */
> > + dev->out_size = le16_to_cpu(endpoint->wMaxPacketSize);
> > + dev->out_epAddr = endpoint->bEndpointAddress;
> > + }
> > + }
> > + if (!(dev->in_epAddr && dev->out_epAddr)) {
> > + nrpz_err("Could not find both bulk in and out endpoints");
> > + goto error;
> > + }
> > +
> > + usb_set_intfdata(intf, dev);
> > +
> > + ret = usb_register_dev(intf, &nrpz_class);
> > + if (ret) {
> > + nrpz_err("Not able to get a minor for this device\n");
> > + goto error;
> > + }
> > +
> > + dev->minor = intf->minor - NRPZ_MINOR_BASE;
> > +
> > + /* let the user know what node this device is now attached to */
> > + nrpz_info("Device now attached to USB nrpz%u", dev->minor);
> > +
> > + return 0;
> > +error:
> > + usb_set_intfdata(intf, NULL);
> > + nrpz_delete(&dev->kref);
> > + return ret;
> > +}
> > +
> > +static void nrpz_disconnect(struct usb_interface *intf)
> > +{
> > + struct usb_nrpz *dev;
> > + unsigned minor;
> > +
> > + dev = usb_get_intfdata(intf);
> > + usb_set_intfdata(intf, NULL);
> > +
> > + minor = dev->minor;
> > +
> > + /* give back our minor */
> > + usb_deregister_dev(intf, &nrpz_class);
> > +
> > + /* prevent more I/O from starting */
> > + dev->intf = NULL;
> > +
> > + usb_kill_anchored_urbs(&dev->in_running);
> > + usb_kill_anchored_urbs(&dev->out_running);
> > +
> > + wake_up_all(&dev->wq);
> > +
> > + /* decrement our usage count */
> > + kref_put(&dev->kref, nrpz_delete);
> > +
> > + nrpz_info("nrpz%u now disconnected", minor);
> > +}
> > +
> > +static void nrpz_draw_down(struct usb_nrpz *dev)
> > +{
> > + int time;
> > +
> > + time = usb_wait_anchor_empty_timeout(&dev->out_running, 1000);
> > + if (!time)
> > + usb_kill_anchored_urbs(&dev->out_running);
> > +
> > + usb_kill_anchored_urbs(&dev->in_running);
> > +}
> > +
> > +static int nrpz_suspend(struct usb_interface *intf, pm_message_t message)
> > +{
> > + struct usb_nrpz *dev = usb_get_intfdata(intf);
> > +
> > + if (dev)
> > + nrpz_draw_down(dev);
> > + return 0;
> > +}
> > +
> > +static int nrpz_resume(struct usb_interface *intf)
> > +{
> > + return 0;
> > +}
>
> And what restarts reading?
>
Fixed in the next version.
> > +
> > +static int nrpz_pre_reset(struct usb_interface *intf)
> > +{
> > + struct usb_nrpz *dev = usb_get_intfdata(intf);
> > +
> > + if (dev)
> > + nrpz_draw_down(dev);
> > + return 0;
> > +}
> > +
> > +static int nrpz_post_reset(struct usb_interface *intf)
> > +{
> > + return 0;
> > +}
>
> And you don't tell user space that the device has been reset?
> And what restarts reading?
>
I have no idea how to do this. But i will have a look in the kernel
source and figure it out.
Thanks again and best regards,
Stefani
next prev parent reply other threads:[~2012-05-31 7:43 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-05-29 19:14 [PATCH] add new NRP power meter USB device driver stefani
2012-05-29 23:15 ` Greg KH
2012-05-31 8:47 ` Stefani Seibold
2012-05-31 9:41 ` Greg KH
2012-06-02 7:10 ` Stefani Seibold
2012-06-02 12:10 ` Greg KH
2012-06-02 15:43 ` Stefani Seibold
2012-05-31 9:53 ` Greg KH
2012-05-31 10:17 ` Oliver Neukum
2012-05-31 12:04 ` Greg KH
2012-05-31 12:32 ` Oliver Neukum
2012-05-31 12:48 ` Greg KH
2012-05-31 14:22 ` Oliver Neukum
2012-05-31 14:32 ` Greg KH
2012-06-01 13:38 ` Oliver Neukum
2012-05-30 8:10 ` Oliver Neukum
2012-05-31 7:43 ` Stefani Seibold [this message]
2012-05-31 8:20 ` Oliver Neukum
2012-05-31 9:21 ` Stefani Seibold
2012-06-01 14:16 ` Oliver Neukum
2012-06-01 14:34 ` Alan Cox
2012-06-02 5:57 ` Stefani Seibold
-- strict thread matches above, loose matches on Subject: below --
2012-06-02 16:18 stefani
2012-06-02 20:24 ` Oliver Neukum
2012-06-03 5:15 ` Stefani Seibold
2012-06-03 8:46 stefani
2012-06-03 11:48 ` Joe Perches
2012-06-13 0:58 ` Greg KH
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1338450221.6454.16.camel@wall-e \
--to=stefani@seibold.net \
--cc=greg@kroah.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=oneukum@suse.de \
--cc=thomas.braunstorfinger@rohde-schwarz.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.