Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[cascardo/linux.git] / drivers / usb / gadget / function / f_fs.c
index dc30adf..7c6771d 100644 (file)
@@ -155,13 +155,18 @@ struct ffs_io_data {
        struct usb_request *req;
 };
 
+struct ffs_desc_helper {
+       struct ffs_data *ffs;
+       unsigned interfaces_count;
+       unsigned eps_count;
+};
+
 static int  __must_check ffs_epfiles_create(struct ffs_data *ffs);
 static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count);
 
-static struct inode *__must_check
+static struct dentry *
 ffs_sb_create_file(struct super_block *sb, const char *name, void *data,
-                  const struct file_operations *fops,
-                  struct dentry **dentry_p);
+                  const struct file_operations *fops);
 
 /* Devices management *******************************************************/
 
@@ -1026,6 +1031,29 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
                case FUNCTIONFS_ENDPOINT_REVMAP:
                        ret = epfile->ep->num;
                        break;
+               case FUNCTIONFS_ENDPOINT_DESC:
+               {
+                       int desc_idx;
+                       struct usb_endpoint_descriptor *desc;
+
+                       switch (epfile->ffs->gadget->speed) {
+                       case USB_SPEED_SUPER:
+                               desc_idx = 2;
+                               break;
+                       case USB_SPEED_HIGH:
+                               desc_idx = 1;
+                               break;
+                       default:
+                               desc_idx = 0;
+                       }
+                       desc = epfile->ep->descs[desc_idx];
+
+                       spin_unlock_irq(&epfile->ffs->eps_lock);
+                       ret = copy_to_user((void *)value, desc, sizeof(*desc));
+                       if (ret)
+                               ret = -EFAULT;
+                       return ret;
+               }
                default:
                        ret = -ENOTTY;
                }
@@ -1090,10 +1118,9 @@ ffs_sb_make_inode(struct super_block *sb, void *data,
 }
 
 /* Create "regular" file */
-static struct inode *ffs_sb_create_file(struct super_block *sb,
+static struct dentry *ffs_sb_create_file(struct super_block *sb,
                                        const char *name, void *data,
-                                       const struct file_operations *fops,
-                                       struct dentry **dentry_p)
+                                       const struct file_operations *fops)
 {
        struct ffs_data *ffs = sb->s_fs_info;
        struct dentry   *dentry;
@@ -1112,10 +1139,7 @@ static struct inode *ffs_sb_create_file(struct super_block *sb,
        }
 
        d_add(dentry, inode);
-       if (dentry_p)
-               *dentry_p = dentry;
-
-       return inode;
+       return dentry;
 }
 
 /* Super block */
@@ -1160,7 +1184,7 @@ static int ffs_sb_fill(struct super_block *sb, void *_data, int silent)
 
        /* EP0 file */
        if (unlikely(!ffs_sb_create_file(sb, "ep0", ffs,
-                                        &ffs_ep0_operations, NULL)))
+                                        &ffs_ep0_operations)))
                return -ENOMEM;
 
        return 0;
@@ -1528,10 +1552,14 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
                epfile->ffs = ffs;
                mutex_init(&epfile->mutex);
                init_waitqueue_head(&epfile->wait);
-               sprintf(epfiles->name, "ep%u",  i);
-               if (!unlikely(ffs_sb_create_file(ffs->sb, epfiles->name, epfile,
-                                                &ffs_epfile_operations,
-                                                &epfile->dentry))) {
+               if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
+                       sprintf(epfiles->name, "ep%02x", ffs->eps_addrmap[i]);
+               else
+                       sprintf(epfiles->name, "ep%u", i);
+               epfile->dentry = ffs_sb_create_file(ffs->sb, epfiles->name,
+                                                epfile,
+                                                &ffs_epfile_operations);
+               if (unlikely(!epfile->dentry)) {
                        ffs_epfiles_destroy(epfiles, i - 1);
                        return -ENOMEM;
                }
@@ -1830,7 +1858,8 @@ static int __ffs_data_do_entity(enum ffs_entity_type type,
                                u8 *valuep, struct usb_descriptor_header *desc,
                                void *priv)
 {
-       struct ffs_data *ffs = priv;
+       struct ffs_desc_helper *helper = priv;
+       struct usb_endpoint_descriptor *d;
 
        ENTER();
 
@@ -1844,8 +1873,8 @@ static int __ffs_data_do_entity(enum ffs_entity_type type,
                 * encountered interface "n" then there are at least
                 * "n+1" interfaces.
                 */
-               if (*valuep >= ffs->interfaces_count)
-                       ffs->interfaces_count = *valuep + 1;
+               if (*valuep >= helper->interfaces_count)
+                       helper->interfaces_count = *valuep + 1;
                break;
 
        case FFS_STRING:
@@ -1853,14 +1882,22 @@ static int __ffs_data_do_entity(enum ffs_entity_type type,
                 * Strings are indexed from 1 (0 is magic ;) reserved
                 * for languages list or some such)
                 */
-               if (*valuep > ffs->strings_count)
-                       ffs->strings_count = *valuep;
+               if (*valuep > helper->ffs->strings_count)
+                       helper->ffs->strings_count = *valuep;
                break;
 
        case FFS_ENDPOINT:
-               /* Endpoints are indexed from 1 as well. */
-               if ((*valuep & USB_ENDPOINT_NUMBER_MASK) > ffs->eps_count)
-                       ffs->eps_count = (*valuep & USB_ENDPOINT_NUMBER_MASK);
+               d = (void *)desc;
+               helper->eps_count++;
+               if (helper->eps_count >= 15)
+                       return -EINVAL;
+               /* Check if descriptors for any speed were already parsed */
+               if (!helper->ffs->eps_count && !helper->ffs->interfaces_count)
+                       helper->ffs->eps_addrmap[helper->eps_count] =
+                               d->bEndpointAddress;
+               else if (helper->ffs->eps_addrmap[helper->eps_count] !=
+                               d->bEndpointAddress)
+                       return -EINVAL;
                break;
        }
 
@@ -2053,6 +2090,7 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
        char *data = _data, *raw_descs;
        unsigned os_descs_count = 0, counts[3], flags;
        int ret = -EINVAL, i;
+       struct ffs_desc_helper helper;
 
        ENTER();
 
@@ -2067,10 +2105,12 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
                break;
        case FUNCTIONFS_DESCRIPTORS_MAGIC_V2:
                flags = get_unaligned_le32(data + 8);
+               ffs->user_flags = flags;
                if (flags & ~(FUNCTIONFS_HAS_FS_DESC |
                              FUNCTIONFS_HAS_HS_DESC |
                              FUNCTIONFS_HAS_SS_DESC |
-                             FUNCTIONFS_HAS_MS_OS_DESC)) {
+                             FUNCTIONFS_HAS_MS_OS_DESC |
+                             FUNCTIONFS_VIRTUAL_ADDR)) {
                        ret = -ENOSYS;
                        goto error;
                }
@@ -2101,13 +2141,29 @@ static int __ffs_data_got_descs(struct ffs_data *ffs,
 
        /* Read descriptors */
        raw_descs = data;
+       helper.ffs = ffs;
        for (i = 0; i < 3; ++i) {
                if (!counts[i])
                        continue;
+               helper.interfaces_count = 0;
+               helper.eps_count = 0;
                ret = ffs_do_descs(counts[i], data, len,
-                                  __ffs_data_do_entity, ffs);
+                                  __ffs_data_do_entity, &helper);
                if (ret < 0)
                        goto error;
+               if (!ffs->eps_count && !ffs->interfaces_count) {
+                       ffs->eps_count = helper.eps_count;
+                       ffs->interfaces_count = helper.interfaces_count;
+               } else {
+                       if (ffs->eps_count != helper.eps_count) {
+                               ret = -EINVAL;
+                               goto error;
+                       }
+                       if (ffs->interfaces_count != helper.interfaces_count) {
+                               ret = -EINVAL;
+                               goto error;
+                       }
+               }
                data += ret;
                len  -= ret;
        }
@@ -2314,7 +2370,8 @@ static void __ffs_event_add(struct ffs_data *ffs,
                break;
 
        default:
-               BUG();
+               WARN(1, "%d: unknown event, this should not happen\n", type);
+               return;
        }
 
        {
@@ -2342,9 +2399,18 @@ static void ffs_event_add(struct ffs_data *ffs,
        spin_unlock_irqrestore(&ffs->ev.waitq.lock, flags);
 }
 
-
 /* Bind/unbind USB function hooks *******************************************/
 
+static int ffs_ep_addr2idx(struct ffs_data *ffs, u8 endpoint_address)
+{
+       int i;
+
+       for (i = 1; i < ARRAY_SIZE(ffs->eps_addrmap); ++i)
+               if (ffs->eps_addrmap[i] == endpoint_address)
+                       return i;
+       return -ENOENT;
+}
+
 static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
                                    struct usb_descriptor_header *desc,
                                    void *priv)
@@ -2352,7 +2418,8 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
        struct usb_endpoint_descriptor *ds = (void *)desc;
        struct ffs_function *func = priv;
        struct ffs_ep *ffs_ep;
-       unsigned ep_desc_id, idx;
+       unsigned ep_desc_id;
+       int idx;
        static const char *speed_names[] = { "full", "high", "super" };
 
        if (type != FFS_DESCRIPTOR)
@@ -2378,7 +2445,10 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
        if (!desc || desc->bDescriptorType != USB_DT_ENDPOINT)
                return 0;
 
-       idx = (ds->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK) - 1;
+       idx = ffs_ep_addr2idx(func->ffs, ds->bEndpointAddress) - 1;
+       if (idx < 0)
+               return idx;
+
        ffs_ep = func->eps + idx;
 
        if (unlikely(ffs_ep->descs[ep_desc_id])) {
@@ -2397,7 +2467,13 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
        } else {
                struct usb_request *req;
                struct usb_ep *ep;
+               u8 bEndpointAddress;
 
+               /*
+                * We back up bEndpointAddress because autoconfig overwrites
+                * it with physical endpoint address.
+                */
+               bEndpointAddress = ds->bEndpointAddress;
                pr_vdebug("autoconfig\n");
                ep = usb_ep_autoconfig(func->gadget, ds);
                if (unlikely(!ep))
@@ -2412,6 +2488,12 @@ static int __ffs_func_bind_do_descs(enum ffs_entity_type type, u8 *valuep,
                ffs_ep->req = req;
                func->eps_revmap[ds->bEndpointAddress &
                                 USB_ENDPOINT_NUMBER_MASK] = idx + 1;
+               /*
+                * If we use virtual address mapping, we restore
+                * original bEndpointAddress value.
+                */
+               if (func->ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
+                       ds->bEndpointAddress = bEndpointAddress;
        }
        ffs_dump_mem(": Rewritten ep desc", ds, ds->bLength);
 
@@ -2856,6 +2938,8 @@ static int ffs_func_setup(struct usb_function *f,
                ret = ffs_func_revmap_ep(func, le16_to_cpu(creq->wIndex));
                if (unlikely(ret < 0))
                        return ret;
+               if (func->ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
+                       ret = func->ffs->eps_addrmap[ret];
                break;
 
        default: