diff options
Diffstat (limited to 'drivers/usb/core/devio.c')
-rw-r--r-- | drivers/usb/core/devio.c | 1483 |
1 files changed, 1483 insertions, 0 deletions
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c new file mode 100644 index 000000000000..a047bc392983 --- /dev/null +++ b/drivers/usb/core/devio.c @@ -0,0 +1,1483 @@ +/*****************************************************************************/ + +/* + * devio.c -- User space communication with USB devices. + * + * Copyright (C) 1999-2000 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: devio.c,v 1.7 2000/02/01 17:28:48 fliegl Exp $ + * + * This file implements the usbfs/x/y files, where + * x is the bus number and y the device number. + * + * It allows user space programs/"drivers" to communicate directly + * with USB devices without intervening kernel driver. + * + * Revision history + * 22.12.1999 0.1 Initial release (split from proc_usb.c) + * 04.01.2000 0.2 Turned into its own filesystem + */ + +/*****************************************************************************/ + +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/signal.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/usbdevice_fs.h> +#include <asm/uaccess.h> +#include <asm/byteorder.h> +#include <linux/moduleparam.h> + +#include "hcd.h" /* for usbcore internals */ +#include "usb.h" + +struct async { + struct list_head asynclist; + struct dev_state *ps; + struct task_struct *task; + unsigned int signr; + unsigned int ifnum; + void __user *userbuffer; + void __user *userurb; + struct urb *urb; +}; + +static int usbfs_snoop = 0; +module_param (usbfs_snoop, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC (usbfs_snoop, "true to log all usbfs traffic"); + +#define snoop(dev, format, arg...) \ + do { \ + if (usbfs_snoop) \ + dev_info( dev , format , ## arg); \ + } while (0) + + +#define MAX_USBFS_BUFFER_SIZE 16384 + +static inline int connected (struct usb_device *dev) +{ + return dev->state != USB_STATE_NOTATTACHED; +} + +static loff_t usbdev_lseek(struct file *file, loff_t offset, int orig) +{ + loff_t ret; + + lock_kernel(); + + switch (orig) { + case 0: + file->f_pos = offset; + ret = file->f_pos; + break; + case 1: + file->f_pos += offset; + ret = file->f_pos; + break; + case 2: + default: + ret = -EINVAL; + } + + unlock_kernel(); + return ret; +} + +static ssize_t usbdev_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) +{ + struct dev_state *ps = (struct dev_state *)file->private_data; + struct usb_device *dev = ps->dev; + ssize_t ret = 0; + unsigned len; + loff_t pos; + int i; + + pos = *ppos; + usb_lock_device(dev); + if (!connected(dev)) { + ret = -ENODEV; + goto err; + } else if (pos < 0) { + ret = -EINVAL; + goto err; + } + + if (pos < sizeof(struct usb_device_descriptor)) { + struct usb_device_descriptor *desc = kmalloc(sizeof(*desc), GFP_KERNEL); + if (!desc) { + ret = -ENOMEM; + goto err; + } + memcpy(desc, &dev->descriptor, sizeof(dev->descriptor)); + le16_to_cpus(&desc->bcdUSB); + le16_to_cpus(&desc->idVendor); + le16_to_cpus(&desc->idProduct); + le16_to_cpus(&desc->bcdDevice); + + len = sizeof(struct usb_device_descriptor) - pos; + if (len > nbytes) + len = nbytes; + if (copy_to_user(buf, ((char *)desc) + pos, len)) { + kfree(desc); + ret = -EFAULT; + goto err; + } + kfree(desc); + + *ppos += len; + buf += len; + nbytes -= len; + ret += len; + } + + pos = sizeof(struct usb_device_descriptor); + for (i = 0; nbytes && i < dev->descriptor.bNumConfigurations; i++) { + struct usb_config_descriptor *config = + (struct usb_config_descriptor *)dev->rawdescriptors[i]; + unsigned int length = le16_to_cpu(config->wTotalLength); + + if (*ppos < pos + length) { + + /* The descriptor may claim to be longer than it + * really is. Here is the actual allocated length. */ + unsigned alloclen = + le16_to_cpu(dev->config[i].desc.wTotalLength); + + len = length - (*ppos - pos); + if (len > nbytes) + len = nbytes; + + /* Simply don't write (skip over) unallocated parts */ + if (alloclen > (*ppos - pos)) { + alloclen -= (*ppos - pos); + if (copy_to_user(buf, + dev->rawdescriptors[i] + (*ppos - pos), + min(len, alloclen))) { + ret = -EFAULT; + goto err; + } + } + + *ppos += len; + buf += len; + nbytes -= len; + ret += len; + } + + pos += length; + } + +err: + usb_unlock_device(dev); + return ret; +} + +/* + * async list handling + */ + +static struct async *alloc_async(unsigned int numisoframes) +{ + unsigned int assize = sizeof(struct async) + numisoframes * sizeof(struct usb_iso_packet_descriptor); + struct async *as = kmalloc(assize, GFP_KERNEL); + if (!as) + return NULL; + memset(as, 0, assize); + as->urb = usb_alloc_urb(numisoframes, GFP_KERNEL); + if (!as->urb) { + kfree(as); + return NULL; + } + return as; +} + +static void free_async(struct async *as) +{ + if (as->urb->transfer_buffer) + kfree(as->urb->transfer_buffer); + if (as->urb->setup_packet) + kfree(as->urb->setup_packet); + usb_free_urb(as->urb); + kfree(as); +} + +static inline void async_newpending(struct async *as) +{ + struct dev_state *ps = as->ps; + unsigned long flags; + + spin_lock_irqsave(&ps->lock, flags); + list_add_tail(&as->asynclist, &ps->async_pending); + spin_unlock_irqrestore(&ps->lock, flags); +} + +static inline void async_removepending(struct async *as) +{ + struct dev_state *ps = as->ps; + unsigned long flags; + + spin_lock_irqsave(&ps->lock, flags); + list_del_init(&as->asynclist); + spin_unlock_irqrestore(&ps->lock, flags); +} + +static inline struct async *async_getcompleted(struct dev_state *ps) +{ + unsigned long flags; + struct async *as = NULL; + + spin_lock_irqsave(&ps->lock, flags); + if (!list_empty(&ps->async_completed)) { + as = list_entry(ps->async_completed.next, struct async, asynclist); + list_del_init(&as->asynclist); + } + spin_unlock_irqrestore(&ps->lock, flags); + return as; +} + +static inline struct async *async_getpending(struct dev_state *ps, void __user *userurb) +{ + unsigned long flags; + struct async *as; + + spin_lock_irqsave(&ps->lock, flags); + list_for_each_entry(as, &ps->async_pending, asynclist) + if (as->userurb == userurb) { + list_del_init(&as->asynclist); + spin_unlock_irqrestore(&ps->lock, flags); + return as; + } + spin_unlock_irqrestore(&ps->lock, flags); + return NULL; +} + +static void async_completed(struct urb *urb, struct pt_regs *regs) +{ + struct async *as = (struct async *)urb->context; + struct dev_state *ps = as->ps; + struct siginfo sinfo; + + spin_lock(&ps->lock); + list_move_tail(&as->asynclist, &ps->async_completed); + spin_unlock(&ps->lock); + if (as->signr) { + sinfo.si_signo = as->signr; + sinfo.si_errno = as->urb->status; + sinfo.si_code = SI_ASYNCIO; + sinfo.si_addr = as->userurb; + send_sig_info(as->signr, &sinfo, as->task); + } + wake_up(&ps->wait); +} + +static void destroy_async (struct dev_state *ps, struct list_head *list) +{ + struct async *as; + unsigned long flags; + + spin_lock_irqsave(&ps->lock, flags); + while (!list_empty(list)) { + as = list_entry(list->next, struct async, asynclist); + list_del_init(&as->asynclist); + + /* drop the spinlock so the completion handler can run */ + spin_unlock_irqrestore(&ps->lock, flags); + usb_kill_urb(as->urb); + spin_lock_irqsave(&ps->lock, flags); + } + spin_unlock_irqrestore(&ps->lock, flags); + as = async_getcompleted(ps); + while (as) { + free_async(as); + as = async_getcompleted(ps); + } +} + +static void destroy_async_on_interface (struct dev_state *ps, unsigned int ifnum) +{ + struct list_head *p, *q, hitlist; + unsigned long flags; + + INIT_LIST_HEAD(&hitlist); + spin_lock_irqsave(&ps->lock, flags); + list_for_each_safe(p, q, &ps->async_pending) + if (ifnum == list_entry(p, struct async, asynclist)->ifnum) + list_move_tail(p, &hitlist); + spin_unlock_irqrestore(&ps->lock, flags); + destroy_async(ps, &hitlist); +} + +static inline void destroy_all_async(struct dev_state *ps) +{ + destroy_async(ps, &ps->async_pending); +} + +/* + * interface claims are made only at the request of user level code, + * which can also release them (explicitly or by closing files). + * they're also undone when devices disconnect. + */ + +static int driver_probe (struct usb_interface *intf, + const struct usb_device_id *id) +{ + return -ENODEV; +} + +static void driver_disconnect(struct usb_interface *intf) +{ + struct dev_state *ps = usb_get_intfdata (intf); + unsigned int ifnum = intf->altsetting->desc.bInterfaceNumber; + + if (!ps) + return; + + /* NOTE: this relies on usbcore having canceled and completed + * all pending I/O requests; 2.6 does that. + */ + + if (likely(ifnum < 8*sizeof(ps->ifclaimed))) + clear_bit(ifnum, &ps->ifclaimed); + else + warn("interface number %u out of range", ifnum); + + usb_set_intfdata (intf, NULL); + + /* force async requests to complete */ + destroy_async_on_interface(ps, ifnum); +} + +struct usb_driver usbfs_driver = { + .owner = THIS_MODULE, + .name = "usbfs", + .probe = driver_probe, + .disconnect = driver_disconnect, +}; + +static int claimintf(struct dev_state *ps, unsigned int ifnum) +{ + struct usb_device *dev = ps->dev; + struct usb_interface *intf; + int err; + + if (ifnum >= 8*sizeof(ps->ifclaimed)) + return -EINVAL; + /* already claimed */ + if (test_bit(ifnum, &ps->ifclaimed)) + return 0; + + /* lock against other changes to driver bindings */ + down_write(&usb_bus_type.subsys.rwsem); + intf = usb_ifnum_to_if(dev, ifnum); + if (!intf) + err = -ENOENT; + else + err = usb_driver_claim_interface(&usbfs_driver, intf, ps); + up_write(&usb_bus_type.subsys.rwsem); + if (err == 0) + set_bit(ifnum, &ps->ifclaimed); + return err; +} + +static int releaseintf(struct dev_state *ps, unsigned int ifnum) +{ + struct usb_device *dev; + struct usb_interface *intf; + int err; + + err = -EINVAL; + if (ifnum >= 8*sizeof(ps->ifclaimed)) + return err; + dev = ps->dev; + /* lock against other changes to driver bindings */ + down_write(&usb_bus_type.subsys.rwsem); + intf = usb_ifnum_to_if(dev, ifnum); + if (!intf) + err = -ENOENT; + else if (test_and_clear_bit(ifnum, &ps->ifclaimed)) { + usb_driver_release_interface(&usbfs_driver, intf); + err = 0; + } + up_write(&usb_bus_type.subsys.rwsem); + return err; +} + +static int checkintf(struct dev_state *ps, unsigned int ifnum) +{ + if (ps->dev->state != USB_STATE_CONFIGURED) + return -EHOSTUNREACH; + if (ifnum >= 8*sizeof(ps->ifclaimed)) + return -EINVAL; + if (test_bit(ifnum, &ps->ifclaimed)) + return 0; + /* if not yet claimed, claim it for the driver */ + dev_warn(&ps->dev->dev, "usbfs: process %d (%s) did not claim interface %u before use\n", + current->pid, current->comm, ifnum); + return claimintf(ps, ifnum); +} + +static int findintfep(struct usb_device *dev, unsigned int ep) +{ + unsigned int i, j, e; + struct usb_interface *intf; + struct usb_host_interface *alts; + struct usb_endpoint_descriptor *endpt; + + if (ep & ~(USB_DIR_IN|0xf)) + return -EINVAL; + if (!dev->actconfig) + return -ESRCH; + for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) { + intf = dev->actconfig->interface[i]; + for (j = 0; j < intf->num_altsetting; j++) { + alts = &intf->altsetting[j]; + for (e = 0; e < alts->desc.bNumEndpoints; e++) { + endpt = &alts->endpoint[e].desc; + if (endpt->bEndpointAddress == ep) + return alts->desc.bInterfaceNumber; + } + } + } + return -ENOENT; +} + +static int check_ctrlrecip(struct dev_state *ps, unsigned int requesttype, unsigned int index) +{ + int ret = 0; + + if (ps->dev->state != USB_STATE_CONFIGURED) + return -EHOSTUNREACH; + if (USB_TYPE_VENDOR == (USB_TYPE_MASK & requesttype)) + return 0; + + index &= 0xff; + switch (requesttype & USB_RECIP_MASK) { + case USB_RECIP_ENDPOINT: + if ((ret = findintfep(ps->dev, index)) >= 0) + ret = checkintf(ps, ret); + break; + + case USB_RECIP_INTERFACE: + ret = checkintf(ps, index); + break; + } + return ret; +} + +/* + * file operations + */ +static int usbdev_open(struct inode *inode, struct file *file) +{ + struct usb_device *dev; + struct dev_state *ps; + int ret; + + /* + * no locking necessary here, as chrdev_open has the kernel lock + * (still acquire the kernel lock for safety) + */ + ret = -ENOMEM; + if (!(ps = kmalloc(sizeof(struct dev_state), GFP_KERNEL))) + goto out_nolock; + + lock_kernel(); + ret = -ENOENT; + dev = usb_get_dev(inode->u.generic_ip); + if (!dev) { + kfree(ps); + goto out; + } + ret = 0; + ps->dev = dev; + ps->file = file; + spin_lock_init(&ps->lock); + INIT_LIST_HEAD(&ps->async_pending); + INIT_LIST_HEAD(&ps->async_completed); + init_waitqueue_head(&ps->wait); + ps->discsignr = 0; + ps->disctask = current; + ps->disccontext = NULL; + ps->ifclaimed = 0; + wmb(); + list_add_tail(&ps->list, &dev->filelist); + file->private_data = ps; + out: + unlock_kernel(); + out_nolock: + return ret; +} + +static int usbdev_release(struct inode *inode, struct file *file) +{ + struct dev_state *ps = (struct dev_state *)file->private_data; + struct usb_device *dev = ps->dev; + unsigned int ifnum; + + usb_lock_device(dev); + list_del_init(&ps->list); + for (ifnum = 0; ps->ifclaimed && ifnum < 8*sizeof(ps->ifclaimed); + ifnum++) { + if (test_bit(ifnum, &ps->ifclaimed)) + releaseintf(ps, ifnum); + } + destroy_all_async(ps); + usb_unlock_device(dev); + usb_put_dev(dev); + ps->dev = NULL; + kfree(ps); + return 0; +} + +static int proc_control(struct dev_state *ps, void __user *arg) +{ + struct usb_device *dev = ps->dev; + struct usbdevfs_ctrltransfer ctrl; + unsigned int tmo; + unsigned char *tbuf; + int i, j, ret; + + if (copy_from_user(&ctrl, arg, sizeof(ctrl))) + return -EFAULT; + if ((ret = check_ctrlrecip(ps, ctrl.bRequestType, ctrl.wIndex))) + return ret; + if (ctrl.wLength > PAGE_SIZE) + return -EINVAL; + if (!(tbuf = (unsigned char *)__get_free_page(GFP_KERNEL))) + return -ENOMEM; + tmo = ctrl.timeout; + if (ctrl.bRequestType & 0x80) { + if (ctrl.wLength && !access_ok(VERIFY_WRITE, ctrl.data, ctrl.wLength)) { + free_page((unsigned long)tbuf); + return -EINVAL; + } + snoop(&dev->dev, "control read: bRequest=%02x bRrequestType=%02x wValue=%04x wIndex=%04x\n", + ctrl.bRequest, ctrl.bRequestType, ctrl.wValue, ctrl.wIndex); + + usb_unlock_device(dev); + i = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), ctrl.bRequest, ctrl.bRequestType, + ctrl.wValue, ctrl.wIndex, tbuf, ctrl.wLength, tmo); + usb_lock_device(dev); + if ((i > 0) && ctrl.wLength) { + if (usbfs_snoop) { + dev_info(&dev->dev, "control read: data "); + for (j = 0; j < ctrl.wLength; ++j) + printk ("%02x ", (unsigned char)(tbuf)[j]); + printk("\n"); + } + if (copy_to_user(ctrl.data, tbuf, ctrl.wLength)) { + free_page((unsigned long)tbuf); + return -EFAULT; + } + } + } else { + if (ctrl.wLength) { + if (copy_from_user(tbuf, ctrl.data, ctrl.wLength)) { + free_page((unsigned long)tbuf); + return -EFAULT; + } + } + snoop(&dev->dev, "control write: bRequest=%02x bRrequestType=%02x wValue=%04x wIndex=%04x\n", + ctrl.bRequest, ctrl.bRequestType, ctrl.wValue, ctrl.wIndex); + if (usbfs_snoop) { + dev_info(&dev->dev, "control write: data: "); + for (j = 0; j < ctrl.wLength; ++j) + printk ("%02x ", (unsigned char)(tbuf)[j]); + printk("\n"); + } + usb_unlock_device(dev); + i = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), ctrl.bRequest, ctrl.bRequestType, + ctrl.wValue, ctrl.wIndex, tbuf, ctrl.wLength, tmo); + usb_lock_device(dev); + } + free_page((unsigned long)tbuf); + if (i<0 && i != -EPIPE) { + dev_printk(KERN_DEBUG, &dev->dev, "usbfs: USBDEVFS_CONTROL " + "failed cmd %s rqt %u rq %u len %u ret %d\n", + current->comm, ctrl.bRequestType, ctrl.bRequest, + ctrl.wLength, i); + } + return i; +} + +static int proc_bulk(struct dev_state *ps, void __user *arg) +{ + struct usb_device *dev = ps->dev; + struct usbdevfs_bulktransfer bulk; + unsigned int tmo, len1, pipe; + int len2; + unsigned char *tbuf; + int i, ret; + + if (copy_from_user(&bulk, arg, sizeof(bulk))) + return -EFAULT; + if ((ret = findintfep(ps->dev, bulk.ep)) < 0) + return ret; + if ((ret = checkintf(ps, ret))) + return ret; + if (bulk.ep & USB_DIR_IN) + pipe = usb_rcvbulkpipe(dev, bulk.ep & 0x7f); + else + pipe = usb_sndbulkpipe(dev, bulk.ep & 0x7f); + if (!usb_maxpacket(dev, pipe, !(bulk.ep & USB_DIR_IN))) + return -EINVAL; + len1 = bulk.len; + if (len1 > MAX_USBFS_BUFFER_SIZE) + return -EINVAL; + if (!(tbuf = kmalloc(len1, GFP_KERNEL))) + return -ENOMEM; + tmo = bulk.timeout; + if (bulk.ep & 0x80) { + if (len1 && !access_ok(VERIFY_WRITE, bulk.data, len1)) { + kfree(tbuf); + return -EINVAL; + } + usb_unlock_device(dev); + i = usb_bulk_msg(dev, pipe, tbuf, len1, &len2, tmo); + usb_lock_device(dev); + if (!i && len2) { + if (copy_to_user(bulk.data, tbuf, len2)) { + kfree(tbuf); + return -EFAULT; + } + } + } else { + if (len1) { + if (copy_from_user(tbuf, bulk.data, len1)) { + kfree(tbuf); + return -EFAULT; + } + } + usb_unlock_device(dev); + i = usb_bulk_msg(dev, pipe, tbuf, len1, &len2, tmo); + usb_lock_device(dev); + } + kfree(tbuf); + if (i < 0) + return i; + return len2; +} + +static int proc_resetep(struct dev_state *ps, void __user *arg) +{ + unsigned int ep; + int ret; + + if (get_user(ep, (unsigned int __user *)arg)) + return -EFAULT; + if ((ret = findintfep(ps->dev, ep)) < 0) + return ret; + if ((ret = checkintf(ps, ret))) + return ret; + usb_settoggle(ps->dev, ep & 0xf, !(ep & USB_DIR_IN), 0); + return 0; +} + +static int proc_clearhalt(struct dev_state *ps, void __user *arg) +{ + unsigned int ep; + int pipe; + int ret; + + if (get_user(ep, (unsigned int __user *)arg)) + return -EFAULT; + if ((ret = findintfep(ps->dev, ep)) < 0) + return ret; + if ((ret = checkintf(ps, ret))) + return ret; + if (ep & USB_DIR_IN) + pipe = usb_rcvbulkpipe(ps->dev, ep & 0x7f); + else + pipe = usb_sndbulkpipe(ps->dev, ep & 0x7f); + + return usb_clear_halt(ps->dev, pipe); +} + + +static int proc_getdriver(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_getdriver gd; + struct usb_interface *intf; + int ret; + + if (copy_from_user(&gd, arg, sizeof(gd))) + return -EFAULT; + down_read(&usb_bus_type.subsys.rwsem); + intf = usb_ifnum_to_if(ps->dev, gd.interface); + if (!intf || !intf->dev.driver) + ret = -ENODATA; + else { + strncpy(gd.driver, intf->dev.driver->name, + sizeof(gd.driver)); + ret = (copy_to_user(arg, &gd, sizeof(gd)) ? -EFAULT : 0); + } + up_read(&usb_bus_type.subsys.rwsem); + return ret; +} + +static int proc_connectinfo(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_connectinfo ci; + + ci.devnum = ps->dev->devnum; + ci.slow = ps->dev->speed == USB_SPEED_LOW; + if (copy_to_user(arg, &ci, sizeof(ci))) + return -EFAULT; + return 0; +} + +static int proc_resetdevice(struct dev_state *ps) +{ + return usb_reset_device(ps->dev); + +} + +static int proc_setintf(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_setinterface setintf; + int ret; + + if (copy_from_user(&setintf, arg, sizeof(setintf))) + return -EFAULT; + if ((ret = checkintf(ps, setintf.interface))) + return ret; + return usb_set_interface(ps->dev, setintf.interface, + setintf.altsetting); +} + +static int proc_setconfig(struct dev_state *ps, void __user *arg) +{ + unsigned int u; + int status = 0; + struct usb_host_config *actconfig; + + if (get_user(u, (unsigned int __user *)arg)) + return -EFAULT; + + actconfig = ps->dev->actconfig; + + /* Don't touch the device if any interfaces are claimed. + * It could interfere with other drivers' operations, and if + * an interface is claimed by usbfs it could easily deadlock. + */ + if (actconfig) { + int i; + + for (i = 0; i < actconfig->desc.bNumInterfaces; ++i) { + if (usb_interface_claimed(actconfig->interface[i])) { + dev_warn (&ps->dev->dev, + "usbfs: interface %d claimed " + "while '%s' sets config #%d\n", + actconfig->interface[i] + ->cur_altsetting + ->desc.bInterfaceNumber, + current->comm, u); +#if 0 /* FIXME: enable in 2.6.10 or so */ + status = -EBUSY; + break; +#endif + } + } + } + + /* SET_CONFIGURATION is often abused as a "cheap" driver reset, + * so avoid usb_set_configuration()'s kick to sysfs + */ + if (status == 0) { + if (actconfig && actconfig->desc.bConfigurationValue == u) + status = usb_reset_configuration(ps->dev); + else + status = usb_set_configuration(ps->dev, u); + } + + return status; +} + + +static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, + struct usbdevfs_iso_packet_desc __user *iso_frame_desc, + void __user *arg) +{ + struct usbdevfs_iso_packet_desc *isopkt = NULL; + struct usb_host_endpoint *ep; + struct async *as; + struct usb_ctrlrequest *dr = NULL; + unsigned int u, totlen, isofrmlen; + int ret, interval = 0, ifnum = -1; + + if (uurb->flags & ~(USBDEVFS_URB_ISO_ASAP|USBDEVFS_URB_SHORT_NOT_OK| + URB_NO_FSBR|URB_ZERO_PACKET)) + return -EINVAL; + if (!uurb->buffer) + return -EINVAL; + if (uurb->signr != 0 && (uurb->signr < SIGRTMIN || uurb->signr > SIGRTMAX)) + return -EINVAL; + if (!(uurb->type == USBDEVFS_URB_TYPE_CONTROL && (uurb->endpoint & ~USB_ENDPOINT_DIR_MASK) == 0)) { + if ((ifnum = findintfep(ps->dev, uurb->endpoint)) < 0) + return ifnum; + if ((ret = checkintf(ps, ifnum))) + return ret; + } + if ((uurb->endpoint & USB_ENDPOINT_DIR_MASK) != 0) + ep = ps->dev->ep_in [uurb->endpoint & USB_ENDPOINT_NUMBER_MASK]; + else + ep = ps->dev->ep_out [uurb->endpoint & USB_ENDPOINT_NUMBER_MASK]; + if (!ep) + return -ENOENT; + switch(uurb->type) { + case USBDEVFS_URB_TYPE_CONTROL: + if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + != USB_ENDPOINT_XFER_CONTROL) + return -EINVAL; + /* min 8 byte setup packet, max arbitrary */ + if (uurb->buffer_length < 8 || uurb->buffer_length > PAGE_SIZE) + return -EINVAL; + if (!(dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL))) + return -ENOMEM; + if (copy_from_user(dr, uurb->buffer, 8)) { + kfree(dr); + return -EFAULT; + } + if (uurb->buffer_length < (le16_to_cpup(&dr->wLength) + 8)) { + kfree(dr); + return -EINVAL; + } + if ((ret = check_ctrlrecip(ps, dr->bRequestType, le16_to_cpup(&dr->wIndex)))) { + kfree(dr); + return ret; + } + uurb->endpoint = (uurb->endpoint & ~USB_ENDPOINT_DIR_MASK) | (dr->bRequestType & USB_ENDPOINT_DIR_MASK); + uurb->number_of_packets = 0; + uurb->buffer_length = le16_to_cpup(&dr->wLength); + uurb->buffer += 8; + if (!access_ok((uurb->endpoint & USB_DIR_IN) ? VERIFY_WRITE : VERIFY_READ, uurb->buffer, uurb->buffer_length)) { + kfree(dr); + return -EFAULT; + } + break; + + case USBDEVFS_URB_TYPE_BULK: + switch (ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_CONTROL: + case USB_ENDPOINT_XFER_ISOC: + return -EINVAL; + /* allow single-shot interrupt transfers, at bogus rates */ + } + uurb->number_of_packets = 0; + if (uurb->buffer_length > MAX_USBFS_BUFFER_SIZE) + return -EINVAL; + if (!access_ok((uurb->endpoint & USB_DIR_IN) ? VERIFY_WRITE : VERIFY_READ, uurb->buffer, uurb->buffer_length)) + return -EFAULT; + break; + + case USBDEVFS_URB_TYPE_ISO: + /* arbitrary limit */ + if (uurb->number_of_packets < 1 || uurb->number_of_packets > 128) + return -EINVAL; + if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + != USB_ENDPOINT_XFER_ISOC) + return -EINVAL; + interval = 1 << min (15, ep->desc.bInterval - 1); + isofrmlen = sizeof(struct usbdevfs_iso_packet_desc) * uurb->number_of_packets; + if (!(isopkt = kmalloc(isofrmlen, GFP_KERNEL))) + return -ENOMEM; + if (copy_from_user(isopkt, iso_frame_desc, isofrmlen)) { + kfree(isopkt); + return -EFAULT; + } + for (totlen = u = 0; u < uurb->number_of_packets; u++) { + if (isopkt[u].length > 1023) { + kfree(isopkt); + return -EINVAL; + } + totlen += isopkt[u].length; + } + if (totlen > 32768) { + kfree(isopkt); + return -EINVAL; + } + uurb->buffer_length = totlen; + break; + + case USBDEVFS_URB_TYPE_INTERRUPT: + uurb->number_of_packets = 0; + if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + != USB_ENDPOINT_XFER_INT) + return -EINVAL; + if (ps->dev->speed == USB_SPEED_HIGH) + interval = 1 << min (15, ep->desc.bInterval - 1); + else + interval = ep->desc.bInterval; + if (uurb->buffer_length > MAX_USBFS_BUFFER_SIZE) + return -EINVAL; + if (!access_ok((uurb->endpoint & USB_DIR_IN) ? VERIFY_WRITE : VERIFY_READ, uurb->buffer, uurb->buffer_length)) + return -EFAULT; + break; + + default: + return -EINVAL; + } + if (!(as = alloc_async(uurb->number_of_packets))) { + if (isopkt) + kfree(isopkt); + if (dr) + kfree(dr); + return -ENOMEM; + } + if (!(as->urb->transfer_buffer = kmalloc(uurb->buffer_length, GFP_KERNEL))) { + if (isopkt) + kfree(isopkt); + if (dr) + kfree(dr); + free_async(as); + return -ENOMEM; + } + as->urb->dev = ps->dev; + as->urb->pipe = (uurb->type << 30) | __create_pipe(ps->dev, uurb->endpoint & 0xf) | (uurb->endpoint & USB_DIR_IN); + as->urb->transfer_flags = uurb->flags; + as->urb->transfer_buffer_length = uurb->buffer_length; + as->urb->setup_packet = (unsigned char*)dr; + as->urb->start_frame = uurb->start_frame; + as->urb->number_of_packets = uurb->number_of_packets; + as->urb->interval = interval; + as->urb->context = as; + as->urb->complete = async_completed; + for (totlen = u = 0; u < uurb->number_of_packets; u++) { + as->urb->iso_frame_desc[u].offset = totlen; + as->urb->iso_frame_desc[u].length = isopkt[u].length; + totlen += isopkt[u].length; + } + if (isopkt) + kfree(isopkt); + as->ps = ps; + as->userurb = arg; + if (uurb->endpoint & USB_DIR_IN) + as->userbuffer = uurb->buffer; + else + as->userbuffer = NULL; + as->signr = uurb->signr; + as->ifnum = ifnum; + as->task = current; + if (!(uurb->endpoint & USB_DIR_IN)) { + if (copy_from_user(as->urb->transfer_buffer, uurb->buffer, as->urb->transfer_buffer_length)) { + free_async(as); + return -EFAULT; + } + } + async_newpending(as); + if ((ret = usb_submit_urb(as->urb, GFP_KERNEL))) { + dev_printk(KERN_DEBUG, &ps->dev->dev, "usbfs: usb_submit_urb returned %d\n", ret); + async_removepending(as); + free_async(as); + return ret; + } + return 0; +} + +static int proc_submiturb(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_urb uurb; + + if (copy_from_user(&uurb, arg, sizeof(uurb))) + return -EFAULT; + + return proc_do_submiturb(ps, &uurb, (((struct usbdevfs_urb __user *)arg)->iso_frame_desc), arg); +} + +static int proc_unlinkurb(struct dev_state *ps, void __user *arg) +{ + struct async *as; + + as = async_getpending(ps, arg); + if (!as) + return -EINVAL; + usb_kill_urb(as->urb); + return 0; +} + +static int processcompl(struct async *as, void __user * __user *arg) +{ + struct urb *urb = as->urb; + struct usbdevfs_urb __user *userurb = as->userurb; + void __user *addr = as->userurb; + unsigned int i; + + if (as->userbuffer) + if (copy_to_user(as->userbuffer, urb->transfer_buffer, urb->transfer_buffer_length)) + return -EFAULT; + if (put_user(urb->status, &userurb->status)) + return -EFAULT; + if (put_user(urb->actual_length, &userurb->actual_length)) + return -EFAULT; + if (put_user(urb->error_count, &userurb->error_count)) + return -EFAULT; + + if (!(usb_pipeisoc(urb->pipe))) + return 0; + for (i = 0; i < urb->number_of_packets; i++) { + if (put_user(urb->iso_frame_desc[i].actual_length, + &userurb->iso_frame_desc[i].actual_length)) + return -EFAULT; + if (put_user(urb->iso_frame_desc[i].status, + &userurb->iso_frame_desc[i].status)) + return -EFAULT; + } + + free_async(as); + + if (put_user(addr, (void __user * __user *)arg)) + return -EFAULT; + return 0; +} + +static struct async* reap_as(struct dev_state *ps) +{ + DECLARE_WAITQUEUE(wait, current); + struct async *as = NULL; + struct usb_device *dev = ps->dev; + + add_wait_queue(&ps->wait, &wait); + for (;;) { + __set_current_state(TASK_INTERRUPTIBLE); + if ((as = async_getcompleted(ps))) + break; + if (signal_pending(current)) + break; + usb_unlock_device(dev); + schedule(); + usb_lock_device(dev); + } + remove_wait_queue(&ps->wait, &wait); + set_current_state(TASK_RUNNING); + return as; +} + +static int proc_reapurb(struct dev_state *ps, void __user *arg) +{ + struct async *as = reap_as(ps); + if (as) + return processcompl(as, (void __user * __user *)arg); + if (signal_pending(current)) + return -EINTR; + return -EIO; +} + +static int proc_reapurbnonblock(struct dev_state *ps, void __user *arg) +{ + struct async *as; + + if (!(as = async_getcompleted(ps))) + return -EAGAIN; + return processcompl(as, (void __user * __user *)arg); +} + +#ifdef CONFIG_COMPAT + +static int get_urb32(struct usbdevfs_urb *kurb, + struct usbdevfs_urb32 __user *uurb) +{ + __u32 uptr; + if (get_user(kurb->type, &uurb->type) || + __get_user(kurb->endpoint, &uurb->endpoint) || + __get_user(kurb->status, &uurb->status) || + __get_user(kurb->flags, &uurb->flags) || + __get_user(kurb->buffer_length, &uurb->buffer_length) || + __get_user(kurb->actual_length, &uurb->actual_length) || + __get_user(kurb->start_frame, &uurb->start_frame) || + __get_user(kurb->number_of_packets, &uurb->number_of_packets) || + __get_user(kurb->error_count, &uurb->error_count) || + __get_user(kurb->signr, &uurb->signr)) + return -EFAULT; + + if (__get_user(uptr, &uurb->buffer)) + return -EFAULT; + kurb->buffer = compat_ptr(uptr); + if (__get_user(uptr, &uurb->buffer)) + return -EFAULT; + kurb->usercontext = compat_ptr(uptr); + + return 0; +} + +static int proc_submiturb_compat(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_urb uurb; + + if (get_urb32(&uurb,(struct usbdevfs_urb32 *)arg)) + return -EFAULT; + + return proc_do_submiturb(ps, &uurb, ((struct usbdevfs_urb __user *)arg)->iso_frame_desc, arg); +} + +static int processcompl_compat(struct async *as, void __user * __user *arg) +{ + struct urb *urb = as->urb; + struct usbdevfs_urb32 __user *userurb = as->userurb; + void __user *addr = as->userurb; + unsigned int i; + + if (as->userbuffer) + if (copy_to_user(as->userbuffer, urb->transfer_buffer, urb->transfer_buffer_length)) + return -EFAULT; + if (put_user(urb->status, &userurb->status)) + return -EFAULT; + if (put_user(urb->actual_length, &userurb->actual_length)) + return -EFAULT; + if (put_user(urb->error_count, &userurb->error_count)) + return -EFAULT; + + if (!(usb_pipeisoc(urb->pipe))) + return 0; + for (i = 0; i < urb->number_of_packets; i++) { + if (put_user(urb->iso_frame_desc[i].actual_length, + &userurb->iso_frame_desc[i].actual_length)) + return -EFAULT; + if (put_user(urb->iso_frame_desc[i].status, + &userurb->iso_frame_desc[i].status)) + return -EFAULT; + } + + free_async(as); + if (put_user((u32)(u64)addr, (u32 __user *)arg)) + return -EFAULT; + return 0; +} + +static int proc_reapurb_compat(struct dev_state *ps, void __user *arg) +{ + struct async *as = reap_as(ps); + if (as) + return processcompl_compat(as, (void __user * __user *)arg); + if (signal_pending(current)) + return -EINTR; + return -EIO; +} + +static int proc_reapurbnonblock_compat(struct dev_state *ps, void __user *arg) +{ + struct async *as; + + printk("reapurbnblock\n"); + if (!(as = async_getcompleted(ps))) + return -EAGAIN; + printk("reap got as %p\n", as); + return processcompl_compat(as, (void __user * __user *)arg); +} + +#endif + +static int proc_disconnectsignal(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_disconnectsignal ds; + + if (copy_from_user(&ds, arg, sizeof(ds))) + return -EFAULT; + if (ds.signr != 0 && (ds.signr < SIGRTMIN || ds.signr > SIGRTMAX)) + return -EINVAL; + ps->discsignr = ds.signr; + ps->disccontext = ds.context; + return 0; +} + +static int proc_claiminterface(struct dev_state *ps, void __user *arg) +{ + unsigned int ifnum; + + if (get_user(ifnum, (unsigned int __user *)arg)) + return -EFAULT; + return claimintf(ps, ifnum); +} + +static int proc_releaseinterface(struct dev_state *ps, void __user *arg) +{ + unsigned int ifnum; + int ret; + + if (get_user(ifnum, (unsigned int __user *)arg)) + return -EFAULT; + if ((ret = releaseintf(ps, ifnum)) < 0) + return ret; + destroy_async_on_interface (ps, ifnum); + return 0; +} + +static int proc_ioctl (struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_ioctl ctrl; + int size; + void *buf = NULL; + int retval = 0; + struct usb_interface *intf = NULL; + struct usb_driver *driver = NULL; + int i; + + /* get input parameters and alloc buffer */ + if (copy_from_user(&ctrl, arg, sizeof (ctrl))) + return -EFAULT; + if ((size = _IOC_SIZE (ctrl.ioctl_code)) > 0) { + if ((buf = kmalloc (size, GFP_KERNEL)) == NULL) + return -ENOMEM; + if ((_IOC_DIR(ctrl.ioctl_code) & _IOC_WRITE)) { + if (copy_from_user (buf, ctrl.data, size)) { + kfree (buf); + return -EFAULT; + } + } else { + memset (buf, 0, size); + } + } + + if (!connected(ps->dev)) { + if (buf) + kfree(buf); + return -ENODEV; + } + + if (ps->dev->state != USB_STATE_CONFIGURED) + retval = -EHOSTUNREACH; + else if (!(intf = usb_ifnum_to_if (ps->dev, ctrl.ifno))) + retval = -EINVAL; + else switch (ctrl.ioctl_code) { + + /* disconnect kernel driver from interface */ + case USBDEVFS_DISCONNECT: + + /* don't allow the user to unbind the hub driver from + * a hub with children to manage */ + for (i = 0; i < ps->dev->maxchild; ++i) { + if (ps->dev->children[i]) + retval = -EBUSY; + } + if (retval) + break; + + down_write(&usb_bus_type.subsys.rwsem); + if (intf->dev.driver) { + driver = to_usb_driver(intf->dev.driver); + dev_dbg (&intf->dev, "disconnect by usbfs\n"); + usb_driver_release_interface(driver, intf); + } else + retval = -ENODATA; + up_write(&usb_bus_type.subsys.rwsem); + break; + + /* let kernel drivers try to (re)bind to the interface */ + case USBDEVFS_CONNECT: + usb_unlock_device(ps->dev); + usb_lock_all_devices(); + bus_rescan_devices(intf->dev.bus); + usb_unlock_all_devices(); + usb_lock_device(ps->dev); + break; + + /* talk directly to the interface's driver */ + default: + down_read(&usb_bus_type.subsys.rwsem); + if (intf->dev.driver) + driver = to_usb_driver(intf->dev.driver); + if (driver == NULL || driver->ioctl == NULL) { + retval = -ENOTTY; + } else { + retval = driver->ioctl (intf, ctrl.ioctl_code, buf); + if (retval == -ENOIOCTLCMD) + retval = -ENOTTY; + } + up_read(&usb_bus_type.subsys.rwsem); + } + + /* cleanup and return */ + if (retval >= 0 + && (_IOC_DIR (ctrl.ioctl_code) & _IOC_READ) != 0 + && size > 0 + && copy_to_user (ctrl.data, buf, size) != 0) + retval = -EFAULT; + if (buf != NULL) + kfree (buf); + return retval; +} + +/* + * NOTE: All requests here that have interface numbers as parameters + * are assuming that somehow the configuration has been prevented from + * changing. But there's no mechanism to ensure that... + */ +static int usbdev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + struct dev_state *ps = (struct dev_state *)file->private_data; + struct usb_device *dev = ps->dev; + void __user *p = (void __user *)arg; + int ret = -ENOTTY; + + if (!(file->f_mode & FMODE_WRITE)) + return -EPERM; + usb_lock_device(dev); + if (!connected(dev)) { + usb_unlock_device(dev); + return -ENODEV; + } + + switch (cmd) { + case USBDEVFS_CONTROL: + snoop(&dev->dev, "%s: CONTROL\n", __FUNCTION__); + ret = proc_control(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_BULK: + snoop(&dev->dev, "%s: BULK\n", __FUNCTION__); + ret = proc_bulk(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_RESETEP: + snoop(&dev->dev, "%s: RESETEP\n", __FUNCTION__); + ret = proc_resetep(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_RESET: + snoop(&dev->dev, "%s: RESET\n", __FUNCTION__); + ret = proc_resetdevice(ps); + break; + + case USBDEVFS_CLEAR_HALT: + snoop(&dev->dev, "%s: CLEAR_HALT\n", __FUNCTION__); + ret = proc_clearhalt(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_GETDRIVER: + snoop(&dev->dev, "%s: GETDRIVER\n", __FUNCTION__); + ret = proc_getdriver(ps, p); + break; + + case USBDEVFS_CONNECTINFO: + snoop(&dev->dev, "%s: CONNECTINFO\n", __FUNCTION__); + ret = proc_connectinfo(ps, p); + break; + + case USBDEVFS_SETINTERFACE: + snoop(&dev->dev, "%s: SETINTERFACE\n", __FUNCTION__); + ret = proc_setintf(ps, p); + break; + + case USBDEVFS_SETCONFIGURATION: + snoop(&dev->dev, "%s: SETCONFIGURATION\n", __FUNCTION__); + ret = proc_setconfig(ps, p); + break; + + case USBDEVFS_SUBMITURB: + snoop(&dev->dev, "%s: SUBMITURB\n", __FUNCTION__); + ret = proc_submiturb(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + +#ifdef CONFIG_COMPAT + + case USBDEVFS_SUBMITURB32: + snoop(&dev->dev, "%s: SUBMITURB32\n", __FUNCTION__); + ret = proc_submiturb_compat(ps, p); + if (ret >= 0) + inode->i_mtime = CURRENT_TIME; + break; + + case USBDEVFS_REAPURB32: + snoop(&dev->dev, "%s: REAPURB32\n", __FUNCTION__); + ret = proc_reapurb_compat(ps, p); + break; + + case USBDEVFS_REAPURBNDELAY32: + snoop(&dev->dev, "%s: REAPURBDELAY32\n", __FUNCTION__); + ret = proc_reapurbnonblock_compat(ps, p); + break; + +#endif + + case USBDEVFS_DISCARDURB: + snoop(&dev->dev, "%s: DISCARDURB\n", __FUNCTION__); + ret = proc_unlinkurb(ps, p); + break; + + case USBDEVFS_REAPURB: + snoop(&dev->dev, "%s: REAPURB\n", __FUNCTION__); + ret = proc_reapurb(ps, p); + break; + + case USBDEVFS_REAPURBNDELAY: + snoop(&dev->dev, "%s: REAPURBDELAY\n", __FUNCTION__); + ret = proc_reapurbnonblock(ps, p); + break; + + case USBDEVFS_DISCSIGNAL: + snoop(&dev->dev, "%s: DISCSIGNAL\n", __FUNCTION__); + ret = proc_disconnectsignal(ps, p); + break; + + case USBDEVFS_CLAIMINTERFACE: + snoop(&dev->dev, "%s: CLAIMINTERFACE\n", __FUNCTION__); + ret = proc_claiminterface(ps, p); + break; + + case USBDEVFS_RELEASEINTERFACE: + snoop(&dev->dev, "%s: RELEASEINTERFACE\n", __FUNCTION__); + ret = proc_releaseinterface(ps, p); + break; + + case USBDEVFS_IOCTL: + snoop(&dev->dev, "%s: IOCTL\n", __FUNCTION__); + ret = proc_ioctl(ps, p); + break; + } + usb_unlock_device(dev); + if (ret >= 0) + inode->i_atime = CURRENT_TIME; + return ret; +} + +/* No kernel lock - fine */ +static unsigned int usbdev_poll(struct file *file, struct poll_table_struct *wait) +{ + struct dev_state *ps = (struct dev_state *)file->private_data; + unsigned int mask = 0; + + poll_wait(file, &ps->wait, wait); + if (file->f_mode & FMODE_WRITE && !list_empty(&ps->async_completed)) + mask |= POLLOUT | POLLWRNORM; + if (!connected(ps->dev)) + mask |= POLLERR | POLLHUP; + return mask; +} + +struct file_operations usbfs_device_file_operations = { + .llseek = usbdev_lseek, + .read = usbdev_read, + .poll = usbdev_poll, + .ioctl = usbdev_ioctl, + .open = usbdev_open, + .release = usbdev_release, +}; |