]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/usb/core/devio.c
Merge remote-tracking branch 'spi/topic/zynqmp' into spi-next
[karo-tx-linux.git] / drivers / usb / core / devio.c
index 59e7a3369084ab0b2d444991ce605a2259d47619..52c4461dfccddecff1bf1708c677bb605e9bc5a9 100644 (file)
@@ -50,6 +50,7 @@
 #include <linux/user_namespace.h>
 #include <linux/scatterlist.h>
 #include <linux/uaccess.h>
+#include <linux/dma-mapping.h>
 #include <asm/byteorder.h>
 #include <linux/moduleparam.h>
 
@@ -69,6 +70,7 @@ struct usb_dev_state {
        spinlock_t lock;            /* protects the async urb lists */
        struct list_head async_pending;
        struct list_head async_completed;
+       struct list_head memory_list;
        wait_queue_head_t wait;     /* wake up if a request completed */
        unsigned int discsignr;
        struct pid *disc_pid;
@@ -77,6 +79,19 @@ struct usb_dev_state {
        unsigned long ifclaimed;
        u32 secid;
        u32 disabled_bulk_eps;
+       bool privileges_dropped;
+       unsigned long interface_allowed_mask;
+};
+
+struct usb_memory {
+       struct list_head memlist;
+       int vma_use_count;
+       int urb_use_count;
+       u32 size;
+       void *mem;
+       dma_addr_t dma_handle;
+       unsigned long vm_start;
+       struct usb_dev_state *ps;
 };
 
 struct async {
@@ -89,6 +104,7 @@ struct async {
        void __user *userbuffer;
        void __user *userurb;
        struct urb *urb;
+       struct usb_memory *usbm;
        unsigned int mem_usage;
        int status;
        u32 secid;
@@ -162,6 +178,111 @@ static int connected(struct usb_dev_state *ps)
                        ps->dev->state != USB_STATE_NOTATTACHED);
 }
 
+static void dec_usb_memory_use_count(struct usb_memory *usbm, int *count)
+{
+       struct usb_dev_state *ps = usbm->ps;
+       unsigned long flags;
+
+       spin_lock_irqsave(&ps->lock, flags);
+       --*count;
+       if (usbm->urb_use_count == 0 && usbm->vma_use_count == 0) {
+               list_del(&usbm->memlist);
+               spin_unlock_irqrestore(&ps->lock, flags);
+
+               usb_free_coherent(ps->dev, usbm->size, usbm->mem,
+                               usbm->dma_handle);
+               usbfs_decrease_memory_usage(
+                       usbm->size + sizeof(struct usb_memory));
+               kfree(usbm);
+       } else {
+               spin_unlock_irqrestore(&ps->lock, flags);
+       }
+}
+
+static void usbdev_vm_open(struct vm_area_struct *vma)
+{
+       struct usb_memory *usbm = vma->vm_private_data;
+       unsigned long flags;
+
+       spin_lock_irqsave(&usbm->ps->lock, flags);
+       ++usbm->vma_use_count;
+       spin_unlock_irqrestore(&usbm->ps->lock, flags);
+}
+
+static void usbdev_vm_close(struct vm_area_struct *vma)
+{
+       struct usb_memory *usbm = vma->vm_private_data;
+
+       dec_usb_memory_use_count(usbm, &usbm->vma_use_count);
+}
+
+struct vm_operations_struct usbdev_vm_ops = {
+       .open = usbdev_vm_open,
+       .close = usbdev_vm_close
+};
+
+static int usbdev_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       struct usb_memory *usbm = NULL;
+       struct usb_dev_state *ps = file->private_data;
+       size_t size = vma->vm_end - vma->vm_start;
+       void *mem;
+       unsigned long flags;
+       dma_addr_t dma_handle;
+       int ret;
+
+       ret = usbfs_increase_memory_usage(size + sizeof(struct usb_memory));
+       if (ret)
+               goto error;
+
+       usbm = kzalloc(sizeof(struct usb_memory), GFP_KERNEL);
+       if (!usbm) {
+               ret = -ENOMEM;
+               goto error_decrease_mem;
+       }
+
+       mem = usb_alloc_coherent(ps->dev, size, GFP_USER, &dma_handle);
+       if (!mem) {
+               ret = -ENOMEM;
+               goto error_free_usbm;
+       }
+
+       memset(mem, 0, size);
+
+       usbm->mem = mem;
+       usbm->dma_handle = dma_handle;
+       usbm->size = size;
+       usbm->ps = ps;
+       usbm->vm_start = vma->vm_start;
+       usbm->vma_use_count = 1;
+       INIT_LIST_HEAD(&usbm->memlist);
+
+       if (remap_pfn_range(vma, vma->vm_start,
+                       virt_to_phys(usbm->mem) >> PAGE_SHIFT,
+                       size, vma->vm_page_prot) < 0) {
+               dec_usb_memory_use_count(usbm, &usbm->vma_use_count);
+               return -EAGAIN;
+       }
+
+       vma->vm_flags |= VM_IO;
+       vma->vm_flags |= (VM_DONTEXPAND | VM_DONTDUMP);
+       vma->vm_ops = &usbdev_vm_ops;
+       vma->vm_private_data = usbm;
+
+       spin_lock_irqsave(&ps->lock, flags);
+       list_add_tail(&usbm->memlist, &ps->memory_list);
+       spin_unlock_irqrestore(&ps->lock, flags);
+
+       return 0;
+
+error_free_usbm:
+       kfree(usbm);
+error_decrease_mem:
+       usbfs_decrease_memory_usage(size + sizeof(struct usb_memory));
+error:
+       return ret;
+}
+
 static ssize_t usbdev_read(struct file *file, char __user *buf, size_t nbytes,
                           loff_t *ppos)
 {
@@ -278,8 +399,13 @@ static void free_async(struct async *as)
                if (sg_page(&as->urb->sg[i]))
                        kfree(sg_virt(&as->urb->sg[i]));
        }
+
        kfree(as->urb->sg);
-       kfree(as->urb->transfer_buffer);
+       if (as->usbm == NULL)
+               kfree(as->urb->transfer_buffer);
+       else
+               dec_usb_memory_use_count(as->usbm, &as->usbm->urb_use_count);
+
        kfree(as->urb->setup_packet);
        usb_free_urb(as->urb);
        usbfs_decrease_memory_usage(as->mem_usage);
@@ -624,6 +750,10 @@ static int claimintf(struct usb_dev_state *ps, unsigned int ifnum)
        if (test_bit(ifnum, &ps->ifclaimed))
                return 0;
 
+       if (ps->privileges_dropped &&
+                       !test_bit(ifnum, &ps->interface_allowed_mask))
+               return -EACCES;
+
        intf = usb_ifnum_to_if(dev, ifnum);
        if (!intf)
                err = -ENOENT;
@@ -848,7 +978,7 @@ static struct usb_device *usbdev_lookup_by_devt(dev_t devt)
                              (void *) (unsigned long) devt, match_devt);
        if (!dev)
                return NULL;
-       return container_of(dev, struct usb_device, dev);
+       return to_usb_device(dev);
 }
 
 /*
@@ -861,7 +991,7 @@ static int usbdev_open(struct inode *inode, struct file *file)
        int ret;
 
        ret = -ENOMEM;
-       ps = kmalloc(sizeof(struct usb_dev_state), GFP_KERNEL);
+       ps = kzalloc(sizeof(struct usb_dev_state), GFP_KERNEL);
        if (!ps)
                goto out_free_ps;
 
@@ -889,16 +1019,15 @@ static int usbdev_open(struct inode *inode, struct file *file)
 
        ps->dev = dev;
        ps->file = file;
+       ps->interface_allowed_mask = 0xFFFFFFFF; /* 32 bits */
        spin_lock_init(&ps->lock);
        INIT_LIST_HEAD(&ps->list);
        INIT_LIST_HEAD(&ps->async_pending);
        INIT_LIST_HEAD(&ps->async_completed);
+       INIT_LIST_HEAD(&ps->memory_list);
        init_waitqueue_head(&ps->wait);
-       ps->discsignr = 0;
        ps->disc_pid = get_pid(task_pid(current));
        ps->cred = get_current_cred();
-       ps->disccontext = NULL;
-       ps->ifclaimed = 0;
        security_task_getsecid(current, &ps->secid);
        smp_wmb();
        list_add_tail(&ps->list, &dev->filelist);
@@ -945,6 +1074,7 @@ static int usbdev_release(struct inode *inode, struct file *file)
                free_async(as);
                as = async_getcompleted(ps);
        }
+
        kfree(ps);
        return 0;
 }
@@ -1198,6 +1328,28 @@ static int proc_connectinfo(struct usb_dev_state *ps, void __user *arg)
 
 static int proc_resetdevice(struct usb_dev_state *ps)
 {
+       struct usb_host_config *actconfig = ps->dev->actconfig;
+       struct usb_interface *interface;
+       int i, number;
+
+       /* Don't allow a device reset if the process has dropped the
+        * privilege to do such things and any of the interfaces are
+        * currently claimed.
+        */
+       if (ps->privileges_dropped && actconfig) {
+               for (i = 0; i < actconfig->desc.bNumInterfaces; ++i) {
+                       interface = actconfig->interface[i];
+                       number = interface->cur_altsetting->desc.bInterfaceNumber;
+                       if (usb_interface_claimed(interface) &&
+                                       !test_bit(number, &ps->ifclaimed)) {
+                               dev_warn(&ps->dev->dev,
+                                       "usbfs: interface %d claimed by %s while '%s' resets device\n",
+                                       number, interface->dev.driver->name, current->comm);
+                               return -EACCES;
+                       }
+               }
+       }
+
        return usb_reset_device(ps->dev);
 }
 
@@ -1266,6 +1418,31 @@ static int proc_setconfig(struct usb_dev_state *ps, void __user *arg)
        return status;
 }
 
+static struct usb_memory *
+find_memory_area(struct usb_dev_state *ps, const struct usbdevfs_urb *uurb)
+{
+       struct usb_memory *usbm = NULL, *iter;
+       unsigned long flags;
+       unsigned long uurb_start = (unsigned long)uurb->buffer;
+
+       spin_lock_irqsave(&ps->lock, flags);
+       list_for_each_entry(iter, &ps->memory_list, memlist) {
+               if (uurb_start >= iter->vm_start &&
+                               uurb_start < iter->vm_start + iter->size) {
+                       if (uurb->buffer_length > iter->vm_start + iter->size -
+                                       uurb_start) {
+                               usbm = ERR_PTR(-EINVAL);
+                       } else {
+                               usbm = iter;
+                               usbm->urb_use_count++;
+                       }
+                       break;
+               }
+       }
+       spin_unlock_irqrestore(&ps->lock, flags);
+       return usbm;
+}
+
 static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb,
                        struct usbdevfs_iso_packet_desc __user *iso_frame_desc,
                        void __user *arg)
@@ -1378,11 +1555,10 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
                number_of_packets = uurb->number_of_packets;
                isofrmlen = sizeof(struct usbdevfs_iso_packet_desc) *
                                   number_of_packets;
-               isopkt = kmalloc(isofrmlen, GFP_KERNEL);
-               if (!isopkt)
-                       return -ENOMEM;
-               if (copy_from_user(isopkt, iso_frame_desc, isofrmlen)) {
-                       ret = -EFAULT;
+               isopkt = memdup_user(iso_frame_desc, isofrmlen);
+               if (IS_ERR(isopkt)) {
+                       ret = PTR_ERR(isopkt);
+                       isopkt = NULL;
                        goto error;
                }
                for (totlen = u = 0; u < number_of_packets; u++) {
@@ -1422,6 +1598,19 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
                goto error;
        }
 
+       as->usbm = find_memory_area(ps, uurb);
+       if (IS_ERR(as->usbm)) {
+               ret = PTR_ERR(as->usbm);
+               as->usbm = NULL;
+               goto error;
+       }
+
+       /* do not use SG buffers when memory mapped segments
+        * are in use
+        */
+       if (as->usbm)
+               num_sgs = 0;
+
        u += sizeof(struct async) + sizeof(struct urb) + uurb->buffer_length +
             num_sgs * sizeof(struct scatterlist);
        ret = usbfs_increase_memory_usage(u);
@@ -1459,29 +1648,35 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
                        totlen -= u;
                }
        } else if (uurb->buffer_length > 0) {
-               as->urb->transfer_buffer = kmalloc(uurb->buffer_length,
-                               GFP_KERNEL);
-               if (!as->urb->transfer_buffer) {
-                       ret = -ENOMEM;
-                       goto error;
-               }
+               if (as->usbm) {
+                       unsigned long uurb_start = (unsigned long)uurb->buffer;
 
-               if (!is_in) {
-                       if (copy_from_user(as->urb->transfer_buffer,
-                                          uurb->buffer,
-                                          uurb->buffer_length)) {
-                               ret = -EFAULT;
+                       as->urb->transfer_buffer = as->usbm->mem +
+                                       (uurb_start - as->usbm->vm_start);
+               } else {
+                       as->urb->transfer_buffer = kmalloc(uurb->buffer_length,
+                                       GFP_KERNEL);
+                       if (!as->urb->transfer_buffer) {
+                               ret = -ENOMEM;
                                goto error;
                        }
-               } else if (uurb->type == USBDEVFS_URB_TYPE_ISO) {
-                       /*
-                        * Isochronous input data may end up being
-                        * discontiguous if some of the packets are short.
-                        * Clear the buffer so that the gaps don't leak
-                        * kernel data to userspace.
-                        */
-                       memset(as->urb->transfer_buffer, 0,
-                                       uurb->buffer_length);
+                       if (!is_in) {
+                               if (copy_from_user(as->urb->transfer_buffer,
+                                                  uurb->buffer,
+                                                  uurb->buffer_length)) {
+                                       ret = -EFAULT;
+                                       goto error;
+                               }
+                       } else if (uurb->type == USBDEVFS_URB_TYPE_ISO) {
+                               /*
+                                * Isochronous input data may end up being
+                                * discontiguous if some of the packets are
+                                * short. Clear the buffer so that the gaps
+                                * don't leak kernel data to userspace.
+                                */
+                               memset(as->urb->transfer_buffer, 0,
+                                               uurb->buffer_length);
+                       }
                }
        }
        as->urb->dev = ps->dev;
@@ -1528,10 +1723,14 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
        isopkt = NULL;
        as->ps = ps;
        as->userurb = arg;
-       if (is_in && uurb->buffer_length > 0)
+       if (as->usbm) {
+               unsigned long uurb_start = (unsigned long)uurb->buffer;
+
+               as->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+               as->urb->transfer_dma = as->usbm->dma_handle +
+                               (uurb_start - as->usbm->vm_start);
+       } else if (is_in && uurb->buffer_length > 0)
                as->userbuffer = uurb->buffer;
-       else
-               as->userbuffer = NULL;
        as->signr = uurb->signr;
        as->ifnum = ifnum;
        as->pid = get_pid(task_pid(current));
@@ -1587,6 +1786,8 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
        return 0;
 
  error:
+       if (as && as->usbm)
+               dec_usb_memory_use_count(as->usbm, &as->usbm->urb_use_count);
        kfree(isopkt);
        kfree(dr);
        if (as)
@@ -1903,7 +2104,7 @@ static int proc_releaseinterface(struct usb_dev_state *ps, void __user *arg)
        ret = releaseintf(ps, ifnum);
        if (ret < 0)
                return ret;
-       destroy_async_on_interface (ps, ifnum);
+       destroy_async_on_interface(ps, ifnum);
        return 0;
 }
 
@@ -1915,6 +2116,9 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl)
        struct usb_interface    *intf = NULL;
        struct usb_driver       *driver = NULL;
 
+       if (ps->privileges_dropped)
+               return -EACCES;
+
        /* alloc buffer */
        size = _IOC_SIZE(ctl->ioctl_code);
        if (size > 0) {
@@ -2040,7 +2244,8 @@ static int proc_get_capabilities(struct usb_dev_state *ps, void __user *arg)
        __u32 caps;
 
        caps = USBDEVFS_CAP_ZERO_PACKET | USBDEVFS_CAP_NO_PACKET_SIZE_LIM |
-                       USBDEVFS_CAP_REAP_AFTER_DISCONNECT;
+                       USBDEVFS_CAP_REAP_AFTER_DISCONNECT | USBDEVFS_CAP_MMAP |
+                       USBDEVFS_CAP_DROP_PRIVILEGES;
        if (!ps->dev->bus->no_stop_on_short)
                caps |= USBDEVFS_CAP_BULK_CONTINUATION;
        if (ps->dev->bus->sg_tablesize)
@@ -2067,6 +2272,9 @@ static int proc_disconnect_claim(struct usb_dev_state *ps, void __user *arg)
        if (intf->dev.driver) {
                struct usb_driver *driver = to_usb_driver(intf->dev.driver);
 
+               if (ps->privileges_dropped)
+                       return -EACCES;
+
                if ((dc.flags & USBDEVFS_DISCONNECT_CLAIM_IF_DRIVER) &&
                                strncmp(dc.driver, intf->dev.driver->name,
                                        sizeof(dc.driver)) != 0)
@@ -2123,6 +2331,23 @@ static int proc_free_streams(struct usb_dev_state *ps, void __user *arg)
        return r;
 }
 
+static int proc_drop_privileges(struct usb_dev_state *ps, void __user *arg)
+{
+       u32 data;
+
+       if (copy_from_user(&data, arg, sizeof(data)))
+               return -EFAULT;
+
+       /* This is an one way operation. Once privileges are
+        * dropped, you cannot regain them. You may however reissue
+        * this ioctl to shrink the allowed interfaces mask.
+        */
+       ps->interface_allowed_mask &= data;
+       ps->privileges_dropped = true;
+
+       return 0;
+}
+
 /*
  * NOTE:  All requests here that have interface numbers as parameters
  * are assuming that somehow the configuration has been prevented from
@@ -2311,6 +2536,9 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
        case USBDEVFS_FREE_STREAMS:
                ret = proc_free_streams(ps, p);
                break;
+       case USBDEVFS_DROP_PRIVILEGES:
+               ret = proc_drop_privileges(ps, p);
+               break;
        }
 
  done:
@@ -2366,6 +2594,7 @@ const struct file_operations usbdev_file_operations = {
 #ifdef CONFIG_COMPAT
        .compat_ioctl =   usbdev_compat_ioctl,
 #endif
+       .mmap =           usbdev_mmap,
        .open =           usbdev_open,
        .release =        usbdev_release,
 };