Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  f_fs.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0+
/*
 * f_fs.c -- user mode file system API for USB composite function controllers
 *
 * Copyright (C) 2010 Samsung Electronics
 * Author: Michal Nazarewicz <mina86@mina86.com>
 *
 * Based on inode.c (GadgetFS) which was:
 * Copyright (C) 2003-2004 David Brownell
 * Copyright (C) 2003 Agilent Technologies
 */



/* #define DEBUG */
/* #define VERBOSE_DEBUG */

#include <linux/blkdev.h>
#include <linux/dma-buf.h>
#include <linux/dma-fence.h>
#include <linux/dma-resv.h>
#include <linux/pagemap.h>
#include <linux/export.h>
#include <linux/fs_parser.h>
#include <linux/hid.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/scatterlist.h>
#include <linux/sched/signal.h>
#include <linux/uio.h>
#include <linux/vmalloc.h>
#include <linux/unaligned.h>

#include <linux/usb/ccid.h>
#include <linux/usb/composite.h>
#include <linux/usb/functionfs.h>
#include <linux/usb/func_utils.h>

#include <linux/aio.h>
#include <linux/kthread.h>
#include <linux/poll.h>
#include <linux/eventfd.h>

#include "u_fs.h"
#include "u_os_desc.h"
#include "configfs.h"

#define FUNCTIONFS_MAGIC 0xa647361 /* Chosen by a honest dice roll ;) */
#define MAX_ALT_SETTINGS 2    /* Allow up to 2 alt settings to be set. */

#define DMABUF_ENQUEUE_TIMEOUT_MS 5000

MODULE_IMPORT_NS("DMA_BUF");

/* Reference counter handling */
static void ffs_data_get(struct ffs_data *ffs);
static void ffs_data_put(struct ffs_data *ffs);
/* Creates new ffs_data object. */
static struct ffs_data *__must_check ffs_data_new(const char *dev_name)
 __attribute__((malloc));

/* Opened counter handling. */
static void ffs_data_opened(struct ffs_data *ffs);
static void ffs_data_closed(struct ffs_data *ffs);

/* Called with ffs->mutex held; take over ownership of data. */
static int __must_check
__ffs_data_got_descs(struct ffs_data *ffs, char *data, size_t len);
static int __must_check
__ffs_data_got_strings(struct ffs_data *ffs, char *data, size_t len);


/* The function structure ***************************************************/

struct ffs_ep;

struct ffs_function {
 struct usb_configuration *conf;
 struct usb_gadget  *gadget;
 struct ffs_data   *ffs;

 struct ffs_ep   *eps;
 u8    eps_revmap[16];
 short    *interfaces_nums;

 struct usb_function  function;
 int    cur_alt[MAX_CONFIG_INTERFACES];
};


static struct ffs_function *ffs_func_from_usb(struct usb_function *f)
{
 return container_of(f, struct ffs_function, function);
}


static inline enum ffs_setup_state
ffs_setup_state_clear_cancelled(struct ffs_data *ffs)
{
 return (enum ffs_setup_state)
  cmpxchg(&ffs->setup_state, FFS_SETUP_CANCELLED, FFS_NO_SETUP);
}


static void ffs_func_eps_disable(struct ffs_function *func);
static int __must_check ffs_func_eps_enable(struct ffs_function *func);

static int ffs_func_bind(struct usb_configuration *,
    struct usb_function *);
static int ffs_func_set_alt(struct usb_function *, unsignedunsigned);
static int ffs_func_get_alt(struct usb_function *f, unsigned int intf);
static void ffs_func_disable(struct usb_function *);
static int ffs_func_setup(struct usb_function *,
     const struct usb_ctrlrequest *);
static bool ffs_func_req_match(struct usb_function *,
          const struct usb_ctrlrequest *,
          bool config0);
static void ffs_func_suspend(struct usb_function *);
static void ffs_func_resume(struct usb_function *);


static int ffs_func_revmap_ep(struct ffs_function *func, u8 num);
static int ffs_func_revmap_intf(struct ffs_function *func, u8 intf);


/* The endpoints structures *************************************************/

struct ffs_ep {
 struct usb_ep   *ep; /* P: ffs->eps_lock */
 struct usb_request  *req; /* P: epfile->mutex */

 /* [0]: full speed, [1]: high speed, [2]: super speed */
 struct usb_endpoint_descriptor *descs[3];

 u8    num;
};

struct ffs_dmabuf_priv {
 struct list_head entry;
 struct kref ref;
 struct ffs_data *ffs;
 struct dma_buf_attachment *attach;
 struct sg_table *sgt;
 enum dma_data_direction dir;
 spinlock_t lock;
 u64 context;
 struct usb_request *req; /* P: ffs->eps_lock */
 struct usb_ep *ep;  /* P: ffs->eps_lock */
};

struct ffs_dma_fence {
 struct dma_fence base;
 struct ffs_dmabuf_priv *priv;
 struct work_struct work;
};

struct ffs_epfile {
 /* Protects ep->ep and ep->req. */
 struct mutex   mutex;

 struct ffs_data   *ffs;
 struct ffs_ep   *ep; /* P: ffs->eps_lock */

 struct dentry   *dentry;

 /*
 * Buffer for holding data from partial reads which may happen since
 * we’re rounding user read requests to a multiple of a max packet size.
 *
 * The pointer is initialised with NULL value and may be set by
 * __ffs_epfile_read_data function to point to a temporary buffer.
 *
 * In normal operation, calls to __ffs_epfile_read_buffered will consume
 * data from said buffer and eventually free it.  Importantly, while the
 * function is using the buffer, it sets the pointer to NULL.  This is
 * all right since __ffs_epfile_read_data and __ffs_epfile_read_buffered
 * can never run concurrently (they are synchronised by epfile->mutex)
 * so the latter will not assign a new value to the pointer.
 *
 * Meanwhile ffs_func_eps_disable frees the buffer (if the pointer is
 * valid) and sets the pointer to READ_BUFFER_DROP value.  This special
 * value is crux of the synchronisation between ffs_func_eps_disable and
 * __ffs_epfile_read_data.
 *
 * Once __ffs_epfile_read_data is about to finish it will try to set the
 * pointer back to its old value (as described above), but seeing as the
 * pointer is not-NULL (namely READ_BUFFER_DROP) it will instead free
 * the buffer.
 *
 * == State transitions ==
 *
 * • ptr == NULL:  (initial state)
 *   ◦ __ffs_epfile_read_buffer_free: go to ptr == DROP
 *   ◦ __ffs_epfile_read_buffered:    nop
 *   ◦ __ffs_epfile_read_data allocates temp buffer: go to ptr == buf
 *   ◦ reading finishes:              n/a, not in ‘and reading’ state
 * • ptr == DROP:
 *   ◦ __ffs_epfile_read_buffer_free: nop
 *   ◦ __ffs_epfile_read_buffered:    go to ptr == NULL
 *   ◦ __ffs_epfile_read_data allocates temp buffer: free buf, nop
 *   ◦ reading finishes:              n/a, not in ‘and reading’ state
 * • ptr == buf:
 *   ◦ __ffs_epfile_read_buffer_free: free buf, go to ptr == DROP
 *   ◦ __ffs_epfile_read_buffered:    go to ptr == NULL and reading
 *   ◦ __ffs_epfile_read_data:        n/a, __ffs_epfile_read_buffered
 *                                    is always called first
 *   ◦ reading finishes:              n/a, not in ‘and reading’ state
 * • ptr == NULL and reading:
 *   ◦ __ffs_epfile_read_buffer_free: go to ptr == DROP and reading
 *   ◦ __ffs_epfile_read_buffered:    n/a, mutex is held
 *   ◦ __ffs_epfile_read_data:        n/a, mutex is held
 *   ◦ reading finishes and …
 *     … all data read:               free buf, go to ptr == NULL
 *     … otherwise:                   go to ptr == buf and reading
 * • ptr == DROP and reading:
 *   ◦ __ffs_epfile_read_buffer_free: nop
 *   ◦ __ffs_epfile_read_buffered:    n/a, mutex is held
 *   ◦ __ffs_epfile_read_data:        n/a, mutex is held
 *   ◦ reading finishes:              free buf, go to ptr == DROP
 */

 struct ffs_buffer  *read_buffer;
#define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN))

 char    name[5];

 unsigned char   in; /* P: ffs->eps_lock */
 unsigned char   isoc; /* P: ffs->eps_lock */

 unsigned char   _pad;

 /* Protects dmabufs */
 struct mutex   dmabufs_mutex;
 struct list_head  dmabufs; /* P: dmabufs_mutex */
 atomic_t   seqno;
};

struct ffs_buffer {
 size_t length;
 char *data;
 char storage[] __counted_by(length);
};

/*  ffs_io_data structure ***************************************************/

struct ffs_io_data {
 bool aio;
 bool read;

 struct kiocb *kiocb;
 struct iov_iter data;
 const void *to_free;
 char *buf;

 struct mm_struct *mm;
 struct work_struct work;

 struct usb_ep *ep;
 struct usb_request *req;
 struct sg_table sgt;
 bool use_sg;

 struct ffs_data *ffs;

 int status;
 struct completion done;
};

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 dentry *
ffs_sb_create_file(struct super_block *sb, const char *name, void *data,
     const struct file_operations *fops);

/* Devices management *******************************************************/

DEFINE_MUTEX(ffs_lock);
EXPORT_SYMBOL_GPL(ffs_lock);

static struct ffs_dev *_ffs_find_dev(const char *name);
static struct ffs_dev *_ffs_alloc_dev(void);
static void _ffs_free_dev(struct ffs_dev *dev);
static int ffs_acquire_dev(const char *dev_name, struct ffs_data *ffs_data);
static void ffs_release_dev(struct ffs_dev *ffs_dev);
static int ffs_ready(struct ffs_data *ffs);
static void ffs_closed(struct ffs_data *ffs);

/* Misc helper functions ****************************************************/

static int ffs_mutex_lock(struct mutex *mutex, unsigned nonblock)
 __attribute__((warn_unused_result, nonnull));
static char *ffs_prepare_buffer(const char __user *buf, size_t len)
 __attribute__((warn_unused_result, nonnull));


/* Control file aka ep0 *****************************************************/

static void ffs_ep0_complete(struct usb_ep *ep, struct usb_request *req)
{
 struct ffs_data *ffs = req->context;

 complete(&ffs->ep0req_completion);
}

static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len)
 __releases(&ffs->ev.waitq.lock)
{
 struct usb_request *req = ffs->ep0req;
 int ret;

 if (!req) {
  spin_unlock_irq(&ffs->ev.waitq.lock);
  return -EINVAL;
 }

 req->zero     = len < le16_to_cpu(ffs->ev.setup.wLength);

 spin_unlock_irq(&ffs->ev.waitq.lock);

 req->buf      = data;
 req->length   = len;

 /*
 * UDC layer requires to provide a buffer even for ZLP, but should
 * not use it at all. Let's provide some poisoned pointer to catch
 * possible bug in the driver.
 */

 if (req->buf == NULL)
  req->buf = (void *)0xDEADBABE;

 reinit_completion(&ffs->ep0req_completion);

 ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC);
 if (ret < 0)
  return ret;

 ret = wait_for_completion_interruptible(&ffs->ep0req_completion);
 if (ret) {
  usb_ep_dequeue(ffs->gadget->ep0, req);
  return -EINTR;
 }

 ffs->setup_state = FFS_NO_SETUP;
 return req->status ? req->status : req->actual;
}

static int __ffs_ep0_stall(struct ffs_data *ffs)
{
 if (ffs->ev.can_stall) {
  pr_vdebug("ep0 stall\n");
  usb_ep_set_halt(ffs->gadget->ep0);
  ffs->setup_state = FFS_NO_SETUP;
  return -EL2HLT;
 } else {
  pr_debug("bogus ep0 stall!\n");
  return -ESRCH;
 }
}

static ssize_t ffs_ep0_write(struct file *file, const char __user *buf,
        size_t len, loff_t *ptr)
{
 struct ffs_data *ffs = file->private_data;
 ssize_t ret;
 char *data;

 /* Fast check if setup was canceled */
 if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED)
  return -EIDRM;

 /* Acquire mutex */
 ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
 if (ret < 0)
  return ret;

 /* Check state */
 switch (ffs->state) {
 case FFS_READ_DESCRIPTORS:
 case FFS_READ_STRINGS:
  /* Copy data */
  if (len < 16) {
   ret = -EINVAL;
   break;
  }

  data = ffs_prepare_buffer(buf, len);
  if (IS_ERR(data)) {
   ret = PTR_ERR(data);
   break;
  }

  /* Handle data */
  if (ffs->state == FFS_READ_DESCRIPTORS) {
   pr_info("read descriptors\n");
   ret = __ffs_data_got_descs(ffs, data, len);
   if (ret < 0)
    break;

   ffs->state = FFS_READ_STRINGS;
   ret = len;
  } else {
   pr_info("read strings\n");
   ret = __ffs_data_got_strings(ffs, data, len);
   if (ret < 0)
    break;

   ret = ffs_epfiles_create(ffs);
   if (ret) {
    ffs->state = FFS_CLOSING;
    break;
   }

   ffs->state = FFS_ACTIVE;
   mutex_unlock(&ffs->mutex);

   ret = ffs_ready(ffs);
   if (ret < 0) {
    ffs->state = FFS_CLOSING;
    return ret;
   }

   return len;
  }
  break;

 case FFS_ACTIVE:
  data = NULL;
  /*
 * We're called from user space, we can use _irq
 * rather then _irqsave
 */

  spin_lock_irq(&ffs->ev.waitq.lock);
  switch (ffs_setup_state_clear_cancelled(ffs)) {
  case FFS_SETUP_CANCELLED:
   ret = -EIDRM;
   goto done_spin;

  case FFS_NO_SETUP:
   ret = -ESRCH;
   goto done_spin;

  case FFS_SETUP_PENDING:
   break;
  }

  /* FFS_SETUP_PENDING */
  if (!(ffs->ev.setup.bRequestType & USB_DIR_IN)) {
   spin_unlock_irq(&ffs->ev.waitq.lock);
   ret = __ffs_ep0_stall(ffs);
   break;
  }

  /* FFS_SETUP_PENDING and not stall */
  len = min_t(size_t, len, le16_to_cpu(ffs->ev.setup.wLength));

  spin_unlock_irq(&ffs->ev.waitq.lock);

  data = ffs_prepare_buffer(buf, len);
  if (IS_ERR(data)) {
   ret = PTR_ERR(data);
   break;
  }

  spin_lock_irq(&ffs->ev.waitq.lock);

  /*
 * We are guaranteed to be still in FFS_ACTIVE state
 * but the state of setup could have changed from
 * FFS_SETUP_PENDING to FFS_SETUP_CANCELLED so we need
 * to check for that.  If that happened we copied data
 * from user space in vain but it's unlikely.
 *
 * For sure we are not in FFS_NO_SETUP since this is
 * the only place FFS_SETUP_PENDING -> FFS_NO_SETUP
 * transition can be performed and it's protected by
 * mutex.
 */

  if (ffs_setup_state_clear_cancelled(ffs) ==
      FFS_SETUP_CANCELLED) {
   ret = -EIDRM;
done_spin:
   spin_unlock_irq(&ffs->ev.waitq.lock);
  } else {
   /* unlocks spinlock */
   ret = __ffs_ep0_queue_wait(ffs, data, len);
  }
  kfree(data);
  break;

 default:
  ret = -EBADFD;
  break;
 }

 mutex_unlock(&ffs->mutex);
 return ret;
}

/* Called with ffs->ev.waitq.lock and ffs->mutex held, both released on exit. */
static ssize_t __ffs_ep0_read_events(struct ffs_data *ffs, char __user *buf,
         size_t n)
 __releases(&ffs->ev.waitq.lock)
{
 /*
 * n cannot be bigger than ffs->ev.count, which cannot be bigger than
 * size of ffs->ev.types array (which is four) so that's how much space
 * we reserve.
 */

 struct usb_functionfs_event events[ARRAY_SIZE(ffs->ev.types)];
 const size_t size = n * sizeof *events;
 unsigned i = 0;

 memset(events, 0, size);

 do {
  events[i].type = ffs->ev.types[i];
  if (events[i].type == FUNCTIONFS_SETUP) {
   events[i].u.setup = ffs->ev.setup;
   ffs->setup_state = FFS_SETUP_PENDING;
  }
 } while (++i < n);

 ffs->ev.count -= n;
 if (ffs->ev.count)
  memmove(ffs->ev.types, ffs->ev.types + n,
   ffs->ev.count * sizeof *ffs->ev.types);

 spin_unlock_irq(&ffs->ev.waitq.lock);
 mutex_unlock(&ffs->mutex);

 return copy_to_user(buf, events, size) ? -EFAULT : size;
}

static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
       size_t len, loff_t *ptr)
{
 struct ffs_data *ffs = file->private_data;
 char *data = NULL;
 size_t n;
 int ret;

 /* Fast check if setup was canceled */
 if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED)
  return -EIDRM;

 /* Acquire mutex */
 ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
 if (ret < 0)
  return ret;

 /* Check state */
 if (ffs->state != FFS_ACTIVE) {
  ret = -EBADFD;
  goto done_mutex;
 }

 /*
 * We're called from user space, we can use _irq rather then
 * _irqsave
 */

 spin_lock_irq(&ffs->ev.waitq.lock);

 switch (ffs_setup_state_clear_cancelled(ffs)) {
 case FFS_SETUP_CANCELLED:
  ret = -EIDRM;
  break;

 case FFS_NO_SETUP:
  n = len / sizeof(struct usb_functionfs_event);
  if (!n) {
   ret = -EINVAL;
   break;
  }

  if ((file->f_flags & O_NONBLOCK) && !ffs->ev.count) {
   ret = -EAGAIN;
   break;
  }

  if (wait_event_interruptible_exclusive_locked_irq(ffs->ev.waitq,
       ffs->ev.count)) {
   ret = -EINTR;
   break;
  }

  /* unlocks spinlock */
  return __ffs_ep0_read_events(ffs, buf,
          min_t(size_t, n, ffs->ev.count));

 case FFS_SETUP_PENDING:
  if (ffs->ev.setup.bRequestType & USB_DIR_IN) {
   spin_unlock_irq(&ffs->ev.waitq.lock);
   ret = __ffs_ep0_stall(ffs);
   goto done_mutex;
  }

  len = min_t(size_t, len, le16_to_cpu(ffs->ev.setup.wLength));

  spin_unlock_irq(&ffs->ev.waitq.lock);

  if (len) {
   data = kmalloc(len, GFP_KERNEL);
   if (!data) {
    ret = -ENOMEM;
    goto done_mutex;
   }
  }

  spin_lock_irq(&ffs->ev.waitq.lock);

  /* See ffs_ep0_write() */
  if (ffs_setup_state_clear_cancelled(ffs) ==
      FFS_SETUP_CANCELLED) {
   ret = -EIDRM;
   break;
  }

  /* unlocks spinlock */
  ret = __ffs_ep0_queue_wait(ffs, data, len);
  if ((ret > 0) && (copy_to_user(buf, data, len)))
   ret = -EFAULT;
  goto done_mutex;

 default:
  ret = -EBADFD;
  break;
 }

 spin_unlock_irq(&ffs->ev.waitq.lock);
done_mutex:
 mutex_unlock(&ffs->mutex);
 kfree(data);
 return ret;
}

static int ffs_ep0_open(struct inode *inode, struct file *file)
{
 struct ffs_data *ffs = inode->i_private;

 if (ffs->state == FFS_CLOSING)
  return -EBUSY;

 file->private_data = ffs;
 ffs_data_opened(ffs);

 return stream_open(inode, file);
}

static int ffs_ep0_release(struct inode *inode, struct file *file)
{
 struct ffs_data *ffs = file->private_data;

 ffs_data_closed(ffs);

 return 0;
}

static long ffs_ep0_ioctl(struct file *file, unsigned code, unsigned long value)
{
 struct ffs_data *ffs = file->private_data;
 struct usb_gadget *gadget = ffs->gadget;
 long ret;

 if (code == FUNCTIONFS_INTERFACE_REVMAP) {
  struct ffs_function *func = ffs->func;
  ret = func ? ffs_func_revmap_intf(func, value) : -ENODEV;
 } else if (gadget && gadget->ops->ioctl) {
  ret = gadget->ops->ioctl(gadget, code, value);
 } else {
  ret = -ENOTTY;
 }

 return ret;
}

static __poll_t ffs_ep0_poll(struct file *file, poll_table *wait)
{
 struct ffs_data *ffs = file->private_data;
 __poll_t mask = EPOLLWRNORM;
 int ret;

 poll_wait(file, &ffs->ev.waitq, wait);

 ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
 if (ret < 0)
  return mask;

 switch (ffs->state) {
 case FFS_READ_DESCRIPTORS:
 case FFS_READ_STRINGS:
  mask |= EPOLLOUT;
  break;

 case FFS_ACTIVE:
  switch (ffs->setup_state) {
  case FFS_NO_SETUP:
   if (ffs->ev.count)
    mask |= EPOLLIN;
   break;

  case FFS_SETUP_PENDING:
  case FFS_SETUP_CANCELLED:
   mask |= (EPOLLIN | EPOLLOUT);
   break;
  }
  break;

 case FFS_CLOSING:
  break;
 case FFS_DEACTIVATED:
  break;
 }

 mutex_unlock(&ffs->mutex);

 return mask;
}

static const struct file_operations ffs_ep0_operations = {

 .open =  ffs_ep0_open,
 .write = ffs_ep0_write,
 .read =  ffs_ep0_read,
 .release = ffs_ep0_release,
 .unlocked_ioctl = ffs_ep0_ioctl,
 .poll =  ffs_ep0_poll,
};


/* "Normal" endpoints operations ********************************************/

static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req)
{
 struct ffs_io_data *io_data = req->context;

 if (req->status)
  io_data->status = req->status;
 else
  io_data->status = req->actual;

 complete(&io_data->done);
}

static ssize_t ffs_copy_to_iter(void *data, int data_len, struct iov_iter *iter)
{
 ssize_t ret = copy_to_iter(data, data_len, iter);
 if (ret == data_len)
  return ret;

 if (iov_iter_count(iter))
  return -EFAULT;

 /*
 * Dear user space developer!
 *
 * TL;DR: To stop getting below error message in your kernel log, change
 * user space code using functionfs to align read buffers to a max
 * packet size.
 *
 * Some UDCs (e.g. dwc3) require request sizes to be a multiple of a max
 * packet size.  When unaligned buffer is passed to functionfs, it
 * internally uses a larger, aligned buffer so that such UDCs are happy.
 *
 * Unfortunately, this means that host may send more data than was
 * requested in read(2) system call.  f_fs doesn’t know what to do with
 * that excess data so it simply drops it.
 *
 * Was the buffer aligned in the first place, no such problem would
 * happen.
 *
 * Data may be dropped only in AIO reads.  Synchronous reads are handled
 * by splitting a request into multiple parts.  This splitting may still
 * be a problem though so it’s likely best to align the buffer
 * regardless of it being AIO or not..
 *
 * This only affects OUT endpoints, i.e. reading data with a read(2),
 * aio_read(2) etc. system calls.  Writing data to an IN endpoint is not
 * affected.
 */

 pr_err("functionfs read size %d > requested size %zd, dropping excess data. "
        "Align read buffer size to max packet size to avoid the problem.\n",
        data_len, ret);

 return ret;
}

/*
 * allocate a virtually contiguous buffer and create a scatterlist describing it
 * @sg_table - pointer to a place to be filled with sg_table contents
 * @size - required buffer size
 */

static void *ffs_build_sg_list(struct sg_table *sgt, size_t sz)
{
 struct page **pages;
 void *vaddr, *ptr;
 unsigned int n_pages;
 int i;

 vaddr = vmalloc(sz);
 if (!vaddr)
  return NULL;

 n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT;
 pages = kvmalloc_array(n_pages, sizeof(struct page *), GFP_KERNEL);
 if (!pages) {
  vfree(vaddr);

  return NULL;
 }
 for (i = 0, ptr = vaddr; i < n_pages; ++i, ptr += PAGE_SIZE)
  pages[i] = vmalloc_to_page(ptr);

 if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL)) {
  kvfree(pages);
  vfree(vaddr);

  return NULL;
 }
 kvfree(pages);

 return vaddr;
}

static inline void *ffs_alloc_buffer(struct ffs_io_data *io_data,
 size_t data_len)
{
 if (io_data->use_sg)
  return ffs_build_sg_list(&io_data->sgt, data_len);

 return kmalloc(data_len, GFP_KERNEL);
}

static inline void ffs_free_buffer(struct ffs_io_data *io_data)
{
 if (!io_data->buf)
  return;

 if (io_data->use_sg) {
  sg_free_table(&io_data->sgt);
  vfree(io_data->buf);
 } else {
  kfree(io_data->buf);
 }
}

static void ffs_user_copy_worker(struct work_struct *work)
{
 struct ffs_io_data *io_data = container_of(work, struct ffs_io_data,
         work);
 int ret = io_data->status;
 bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD;

 if (io_data->read && ret > 0) {
  kthread_use_mm(io_data->mm);
  ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data);
  kthread_unuse_mm(io_data->mm);
 }

 io_data->kiocb->ki_complete(io_data->kiocb, ret);

 if (io_data->ffs->ffs_eventfd && !kiocb_has_eventfd)
  eventfd_signal(io_data->ffs->ffs_eventfd);

 usb_ep_free_request(io_data->ep, io_data->req);

 if (io_data->read)
  kfree(io_data->to_free);
 ffs_free_buffer(io_data);
 kfree(io_data);
}

static void ffs_epfile_async_io_complete(struct usb_ep *_ep,
      struct usb_request *req)
{
 struct ffs_io_data *io_data = req->context;
 struct ffs_data *ffs = io_data->ffs;

 io_data->status = req->status ? req->status : req->actual;

 INIT_WORK(&io_data->work, ffs_user_copy_worker);
 queue_work(ffs->io_completion_wq, &io_data->work);
}

static void __ffs_epfile_read_buffer_free(struct ffs_epfile *epfile)
{
 /*
 * See comment in struct ffs_epfile for full read_buffer pointer
 * synchronisation story.
 */

 struct ffs_buffer *buf = xchg(&epfile->read_buffer, READ_BUFFER_DROP);
 if (buf && buf != READ_BUFFER_DROP)
  kfree(buf);
}

/* Assumes epfile->mutex is held. */
static ssize_t __ffs_epfile_read_buffered(struct ffs_epfile *epfile,
       struct iov_iter *iter)
{
 /*
 * Null out epfile->read_buffer so ffs_func_eps_disable does not free
 * the buffer while we are using it.  See comment in struct ffs_epfile
 * for full read_buffer pointer synchronisation story.
 */

 struct ffs_buffer *buf = xchg(&epfile->read_buffer, NULL);
 ssize_t ret;
 if (!buf || buf == READ_BUFFER_DROP)
  return 0;

 ret = copy_to_iter(buf->data, buf->length, iter);
 if (buf->length == ret) {
  kfree(buf);
  return ret;
 }

 if (iov_iter_count(iter)) {
  ret = -EFAULT;
 } else {
  buf->length -= ret;
  buf->data += ret;
 }

 if (cmpxchg(&epfile->read_buffer, NULL, buf))
  kfree(buf);

 return ret;
}

/* Assumes epfile->mutex is held. */
static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile,
          void *data, int data_len,
          struct iov_iter *iter)
{
 struct ffs_buffer *buf;

 ssize_t ret = copy_to_iter(data, data_len, iter);
 if (data_len == ret)
  return ret;

 if (iov_iter_count(iter))
  return -EFAULT;

 /* See ffs_copy_to_iter for more context. */
 pr_warn("functionfs read size %d > requested size %zd, splitting request into multiple reads.",
  data_len, ret);

 data_len -= ret;
 buf = kmalloc(struct_size(buf, storage, data_len), GFP_KERNEL);
 if (!buf)
  return -ENOMEM;
 buf->length = data_len;
 buf->data = buf->storage;
 memcpy(buf->storage, data + ret, flex_array_size(buf, storage, data_len));

 /*
 * At this point read_buffer is NULL or READ_BUFFER_DROP (if
 * ffs_func_eps_disable has been called in the meanwhile).  See comment
 * in struct ffs_epfile for full read_buffer pointer synchronisation
 * story.
 */

 if (cmpxchg(&epfile->read_buffer, NULL, buf))
  kfree(buf);

 return ret;
}

static struct ffs_ep *ffs_epfile_wait_ep(struct file *file)
{
 struct ffs_epfile *epfile = file->private_data;
 struct ffs_ep *ep;
 int ret;

 /* Wait for endpoint to be enabled */
 ep = epfile->ep;
 if (!ep) {
  if (file->f_flags & O_NONBLOCK)
   return ERR_PTR(-EAGAIN);

  ret = wait_event_interruptible(
    epfile->ffs->wait, (ep = epfile->ep));
  if (ret)
   return ERR_PTR(-EINTR);
 }

 return ep;
}

static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
{
 struct ffs_epfile *epfile = file->private_data;
 struct usb_request *req;
 struct ffs_ep *ep;
 char *data = NULL;
 ssize_t ret, data_len = -EINVAL;
 int halt;

 /* Are we still active? */
 if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
  return -ENODEV;

 ep = ffs_epfile_wait_ep(file);
 if (IS_ERR(ep))
  return PTR_ERR(ep);

 /* Do we halt? */
 halt = (!io_data->read == !epfile->in);
 if (halt && epfile->isoc)
  return -EINVAL;

 /* We will be using request and read_buffer */
 ret = ffs_mutex_lock(&epfile->mutex, file->f_flags & O_NONBLOCK);
 if (ret)
  goto error;

 /* Allocate & copy */
 if (!halt) {
  struct usb_gadget *gadget;

  /*
 * Do we have buffered data from previous partial read?  Check
 * that for synchronous case only because we do not have
 * facility to ‘wake up’ a pending asynchronous read and push
 * buffered data to it which we would need to make things behave
 * consistently.
 */

  if (!io_data->aio && io_data->read) {
   ret = __ffs_epfile_read_buffered(epfile, &io_data->data);
   if (ret)
    goto error_mutex;
  }

  /*
 * if we _do_ wait above, the epfile->ffs->gadget might be NULL
 * before the waiting completes, so do not assign to 'gadget'
 * earlier
 */

  gadget = epfile->ffs->gadget;

  spin_lock_irq(&epfile->ffs->eps_lock);
  /* In the meantime, endpoint got disabled or changed. */
  if (epfile->ep != ep) {
   ret = -ESHUTDOWN;
   goto error_lock;
  }
  data_len = iov_iter_count(&io_data->data);
  /*
 * Controller may require buffer size to be aligned to
 * maxpacketsize of an out endpoint.
 */

  if (io_data->read)
   data_len = usb_ep_align_maybe(gadget, ep->ep, data_len);

  io_data->use_sg = gadget->sg_supported && data_len > PAGE_SIZE;
  spin_unlock_irq(&epfile->ffs->eps_lock);

  data = ffs_alloc_buffer(io_data, data_len);
  if (!data) {
   ret = -ENOMEM;
   goto error_mutex;
  }
  if (!io_data->read &&
      !copy_from_iter_full(data, data_len, &io_data->data)) {
   ret = -EFAULT;
   goto error_mutex;
  }
 }

 spin_lock_irq(&epfile->ffs->eps_lock);

 if (epfile->ep != ep) {
  /* In the meantime, endpoint got disabled or changed. */
  ret = -ESHUTDOWN;
 } else if (halt) {
  ret = usb_ep_set_halt(ep->ep);
  if (!ret)
   ret = -EBADMSG;
 } else if (data_len == -EINVAL) {
  /*
 * Sanity Check: even though data_len can't be used
 * uninitialized at the time I write this comment, some
 * compilers complain about this situation.
 * In order to keep the code clean from warnings, data_len is
 * being initialized to -EINVAL during its declaration, which
 * means we can't rely on compiler anymore to warn no future
 * changes won't result in data_len being used uninitialized.
 * For such reason, we're adding this redundant sanity check
 * here.
 */

  WARN(1, "%s: data_len == -EINVAL\n", __func__);
  ret = -EINVAL;
 } else if (!io_data->aio) {
  bool interrupted = false;

  req = ep->req;
  if (io_data->use_sg) {
   req->buf = NULL;
   req->sg = io_data->sgt.sgl;
   req->num_sgs = io_data->sgt.nents;
  } else {
   req->buf = data;
   req->num_sgs = 0;
  }
  req->length = data_len;

  io_data->buf = data;

  init_completion(&io_data->done);
  req->context  = io_data;
  req->complete = ffs_epfile_io_complete;

  ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC);
  if (ret < 0)
   goto error_lock;

  spin_unlock_irq(&epfile->ffs->eps_lock);

  if (wait_for_completion_interruptible(&io_data->done)) {
   spin_lock_irq(&epfile->ffs->eps_lock);
   if (epfile->ep != ep) {
    ret = -ESHUTDOWN;
    goto error_lock;
   }
   /*
 * To avoid race condition with ffs_epfile_io_complete,
 * dequeue the request first then check
 * status. usb_ep_dequeue API should guarantee no race
 * condition with req->complete callback.
 */

   usb_ep_dequeue(ep->ep, req);
   spin_unlock_irq(&epfile->ffs->eps_lock);
   wait_for_completion(&io_data->done);
   interrupted = io_data->status < 0;
  }

  if (interrupted)
   ret = -EINTR;
  else if (io_data->read && io_data->status > 0)
   ret = __ffs_epfile_read_data(epfile, data, io_data->status,
           &io_data->data);
  else
   ret = io_data->status;
  goto error_mutex;
 } else if (!(req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC))) {
  ret = -ENOMEM;
 } else {
  if (io_data->use_sg) {
   req->buf = NULL;
   req->sg = io_data->sgt.sgl;
   req->num_sgs = io_data->sgt.nents;
  } else {
   req->buf = data;
   req->num_sgs = 0;
  }
  req->length = data_len;

  io_data->buf = data;
  io_data->ep = ep->ep;
  io_data->req = req;
  io_data->ffs = epfile->ffs;

  req->context  = io_data;
  req->complete = ffs_epfile_async_io_complete;

  ret = usb_ep_queue(ep->ep, req, GFP_ATOMIC);
  if (ret) {
   io_data->req = NULL;
   usb_ep_free_request(ep->ep, req);
   goto error_lock;
  }

  ret = -EIOCBQUEUED;
  /*
 * Do not kfree the buffer in this function.  It will be freed
 * by ffs_user_copy_worker.
 */

  data = NULL;
 }

error_lock:
 spin_unlock_irq(&epfile->ffs->eps_lock);
error_mutex:
 mutex_unlock(&epfile->mutex);
error:
 if (ret != -EIOCBQUEUED) /* don't free if there is iocb queued */
  ffs_free_buffer(io_data);
 return ret;
}

static int
ffs_epfile_open(struct inode *inode, struct file *file)
{
 struct ffs_epfile *epfile = inode->i_private;

 if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
  return -ENODEV;

 file->private_data = epfile;
 ffs_data_opened(epfile->ffs);

 return stream_open(inode, file);
}

static int ffs_aio_cancel(struct kiocb *kiocb)
{
 struct ffs_io_data *io_data = kiocb->private;
 int value;

 if (io_data && io_data->ep && io_data->req)
  value = usb_ep_dequeue(io_data->ep, io_data->req);
 else
  value = -EINVAL;

 return value;
}

static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from)
{
 struct ffs_io_data io_data, *p = &io_data;
 ssize_t res;

 if (!is_sync_kiocb(kiocb)) {
  p = kzalloc(sizeof(io_data), GFP_KERNEL);
  if (!p)
   return -ENOMEM;
  p->aio = true;
 } else {
  memset(p, 0, sizeof(*p));
  p->aio = false;
 }

 p->read = false;
 p->kiocb = kiocb;
 p->data = *from;
 p->mm = current->mm;

 kiocb->private = p;

 if (p->aio)
  kiocb_set_cancel_fn(kiocb, ffs_aio_cancel);

 res = ffs_epfile_io(kiocb->ki_filp, p);
 if (res == -EIOCBQUEUED)
  return res;
 if (p->aio)
  kfree(p);
 else
  *from = p->data;
 return res;
}

static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to)
{
 struct ffs_io_data io_data, *p = &io_data;
 ssize_t res;

 if (!is_sync_kiocb(kiocb)) {
  p = kzalloc(sizeof(io_data), GFP_KERNEL);
  if (!p)
   return -ENOMEM;
  p->aio = true;
 } else {
  memset(p, 0, sizeof(*p));
  p->aio = false;
 }

 p->read = true;
 p->kiocb = kiocb;
 if (p->aio) {
  p->to_free = dup_iter(&p->data, to, GFP_KERNEL);
  if (!iter_is_ubuf(&p->data) && !p->to_free) {
   kfree(p);
   return -ENOMEM;
  }
 } else {
  p->data = *to;
  p->to_free = NULL;
 }
 p->mm = current->mm;

 kiocb->private = p;

 if (p->aio)
  kiocb_set_cancel_fn(kiocb, ffs_aio_cancel);

 res = ffs_epfile_io(kiocb->ki_filp, p);
 if (res == -EIOCBQUEUED)
  return res;

 if (p->aio) {
  kfree(p->to_free);
  kfree(p);
 } else {
  *to = p->data;
 }
 return res;
}

static void ffs_dmabuf_release(struct kref *ref)
{
 struct ffs_dmabuf_priv *priv = container_of(ref, struct ffs_dmabuf_priv, ref);
 struct dma_buf_attachment *attach = priv->attach;
 struct dma_buf *dmabuf = attach->dmabuf;

 pr_vdebug("FFS DMABUF release\n");
 dma_resv_lock(dmabuf->resv, NULL);
 dma_buf_unmap_attachment(attach, priv->sgt, priv->dir);
 dma_resv_unlock(dmabuf->resv);

 dma_buf_detach(attach->dmabuf, attach);
 dma_buf_put(dmabuf);
 kfree(priv);
}

static void ffs_dmabuf_get(struct dma_buf_attachment *attach)
{
 struct ffs_dmabuf_priv *priv = attach->importer_priv;

 kref_get(&priv->ref);
}

static void ffs_dmabuf_put(struct dma_buf_attachment *attach)
{
 struct ffs_dmabuf_priv *priv = attach->importer_priv;

 kref_put(&priv->ref, ffs_dmabuf_release);
}

static int
ffs_epfile_release(struct inode *inode, struct file *file)
{
 struct ffs_epfile *epfile = inode->i_private;
 struct ffs_dmabuf_priv *priv, *tmp;
 struct ffs_data *ffs = epfile->ffs;

 mutex_lock(&epfile->dmabufs_mutex);

 /* Close all attached DMABUFs */
 list_for_each_entry_safe(priv, tmp, &epfile->dmabufs, entry) {
  /* Cancel any pending transfer */
  spin_lock_irq(&ffs->eps_lock);
  if (priv->ep && priv->req)
   usb_ep_dequeue(priv->ep, priv->req);
  spin_unlock_irq(&ffs->eps_lock);

  list_del(&priv->entry);
  ffs_dmabuf_put(priv->attach);
 }

 mutex_unlock(&epfile->dmabufs_mutex);

 __ffs_epfile_read_buffer_free(epfile);
 ffs_data_closed(epfile->ffs);

 return 0;
}

static void ffs_dmabuf_cleanup(struct work_struct *work)
{
 struct ffs_dma_fence *dma_fence =
  container_of(work, struct ffs_dma_fence, work);
 struct ffs_dmabuf_priv *priv = dma_fence->priv;
 struct dma_buf_attachment *attach = priv->attach;
 struct dma_fence *fence = &dma_fence->base;

 ffs_dmabuf_put(attach);
 dma_fence_put(fence);
}

static void ffs_dmabuf_signal_done(struct ffs_dma_fence *dma_fence, int ret)
{
 struct ffs_dmabuf_priv *priv = dma_fence->priv;
 struct dma_fence *fence = &dma_fence->base;
 bool cookie = dma_fence_begin_signalling();

 dma_fence_get(fence);
 fence->error = ret;
 dma_fence_signal(fence);
 dma_fence_end_signalling(cookie);

 /*
 * The fence will be unref'd in ffs_dmabuf_cleanup.
 * It can't be done here, as the unref functions might try to lock
 * the resv object, which would deadlock.
 */

 INIT_WORK(&dma_fence->work, ffs_dmabuf_cleanup);
 queue_work(priv->ffs->io_completion_wq, &dma_fence->work);
}

static void ffs_epfile_dmabuf_io_complete(struct usb_ep *ep,
       struct usb_request *req)
{
 pr_vdebug("FFS: DMABUF transfer complete, status=%d\n", req->status);
 ffs_dmabuf_signal_done(req->context, req->status);
 usb_ep_free_request(ep, req);
}

static const char *ffs_dmabuf_get_driver_name(struct dma_fence *fence)
{
 return "functionfs";
}

static const char *ffs_dmabuf_get_timeline_name(struct dma_fence *fence)
{
 return "";
}

static void ffs_dmabuf_fence_release(struct dma_fence *fence)
{
 struct ffs_dma_fence *dma_fence =
  container_of(fence, struct ffs_dma_fence, base);

 kfree(dma_fence);
}

static const struct dma_fence_ops ffs_dmabuf_fence_ops = {
 .get_driver_name = ffs_dmabuf_get_driver_name,
 .get_timeline_name = ffs_dmabuf_get_timeline_name,
 .release  = ffs_dmabuf_fence_release,
};

static int ffs_dma_resv_lock(struct dma_buf *dmabuf, bool nonblock)
{
 if (!nonblock)
  return dma_resv_lock_interruptible(dmabuf->resv, NULL);

 if (!dma_resv_trylock(dmabuf->resv))
  return -EBUSY;

 return 0;
}

static struct dma_buf_attachment *
ffs_dmabuf_find_attachment(struct ffs_epfile *epfile, struct dma_buf *dmabuf)
{
 struct device *dev = epfile->ffs->gadget->dev.parent;
 struct dma_buf_attachment *attach = NULL;
 struct ffs_dmabuf_priv *priv;

 mutex_lock(&epfile->dmabufs_mutex);

 list_for_each_entry(priv, &epfile->dmabufs, entry) {
  if (priv->attach->dev == dev
      && priv->attach->dmabuf == dmabuf) {
   attach = priv->attach;
   break;
  }
 }

 if (attach)
  ffs_dmabuf_get(attach);

 mutex_unlock(&epfile->dmabufs_mutex);

 return attach ?: ERR_PTR(-EPERM);
}

static int ffs_dmabuf_attach(struct file *file, int fd)
{
 bool nonblock = file->f_flags & O_NONBLOCK;
 struct ffs_epfile *epfile = file->private_data;
 struct usb_gadget *gadget = epfile->ffs->gadget;
 struct dma_buf_attachment *attach;
 struct ffs_dmabuf_priv *priv;
 enum dma_data_direction dir;
 struct sg_table *sg_table;
 struct dma_buf *dmabuf;
 int err;

 if (!gadget || !gadget->sg_supported)
  return -EPERM;

 dmabuf = dma_buf_get(fd);
 if (IS_ERR(dmabuf))
  return PTR_ERR(dmabuf);

 attach = dma_buf_attach(dmabuf, gadget->dev.parent);
 if (IS_ERR(attach)) {
  err = PTR_ERR(attach);
  goto err_dmabuf_put;
 }

 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
 if (!priv) {
  err = -ENOMEM;
  goto err_dmabuf_detach;
 }

 dir = epfile->in ? DMA_FROM_DEVICE : DMA_TO_DEVICE;

 err = ffs_dma_resv_lock(dmabuf, nonblock);
 if (err)
  goto err_free_priv;

 sg_table = dma_buf_map_attachment(attach, dir);
 dma_resv_unlock(dmabuf->resv);

 if (IS_ERR(sg_table)) {
  err = PTR_ERR(sg_table);
  goto err_free_priv;
 }

 attach->importer_priv = priv;

 priv->sgt = sg_table;
 priv->dir = dir;
 priv->ffs = epfile->ffs;
 priv->attach = attach;
 spin_lock_init(&priv->lock);
 kref_init(&priv->ref);
 priv->context = dma_fence_context_alloc(1);

 mutex_lock(&epfile->dmabufs_mutex);
 list_add(&priv->entry, &epfile->dmabufs);
 mutex_unlock(&epfile->dmabufs_mutex);

 return 0;

err_free_priv:
 kfree(priv);
err_dmabuf_detach:
 dma_buf_detach(dmabuf, attach);
err_dmabuf_put:
 dma_buf_put(dmabuf);

 return err;
}

static int ffs_dmabuf_detach(struct file *file, int fd)
{
 struct ffs_epfile *epfile = file->private_data;
 struct ffs_data *ffs = epfile->ffs;
 struct device *dev = ffs->gadget->dev.parent;
 struct ffs_dmabuf_priv *priv, *tmp;
 struct dma_buf *dmabuf;
 int ret = -EPERM;

 dmabuf = dma_buf_get(fd);
 if (IS_ERR(dmabuf))
  return PTR_ERR(dmabuf);

 mutex_lock(&epfile->dmabufs_mutex);

 list_for_each_entry_safe(priv, tmp, &epfile->dmabufs, entry) {
  if (priv->attach->dev == dev
      && priv->attach->dmabuf == dmabuf) {
   /* Cancel any pending transfer */
   spin_lock_irq(&ffs->eps_lock);
   if (priv->ep && priv->req)
    usb_ep_dequeue(priv->ep, priv->req);
   spin_unlock_irq(&ffs->eps_lock);

   list_del(&priv->entry);

   /* Unref the reference from ffs_dmabuf_attach() */
   ffs_dmabuf_put(priv->attach);
   ret = 0;
   break;
  }
 }

 mutex_unlock(&epfile->dmabufs_mutex);
 dma_buf_put(dmabuf);

 return ret;
}

static int ffs_dmabuf_transfer(struct file *file,
          const struct usb_ffs_dmabuf_transfer_req *req)
{
 bool nonblock = file->f_flags & O_NONBLOCK;
 struct ffs_epfile *epfile = file->private_data;
 struct dma_buf_attachment *attach;
 struct ffs_dmabuf_priv *priv;
 struct ffs_dma_fence *fence;
 struct usb_request *usb_req;
 enum dma_resv_usage resv_dir;
 struct dma_buf *dmabuf;
 unsigned long timeout;
 struct ffs_ep *ep;
 bool cookie;
 u32 seqno;
 long retl;
 int ret;

 if (req->flags & ~USB_FFS_DMABUF_TRANSFER_MASK)
  return -EINVAL;

 dmabuf = dma_buf_get(req->fd);
 if (IS_ERR(dmabuf))
  return PTR_ERR(dmabuf);

 if (req->length > dmabuf->size || req->length == 0) {
  ret = -EINVAL;
  goto err_dmabuf_put;
 }

 attach = ffs_dmabuf_find_attachment(epfile, dmabuf);
 if (IS_ERR(attach)) {
  ret = PTR_ERR(attach);
  goto err_dmabuf_put;
 }

 priv = attach->importer_priv;

 ep = ffs_epfile_wait_ep(file);
 if (IS_ERR(ep)) {
  ret = PTR_ERR(ep);
  goto err_attachment_put;
 }

 ret = ffs_dma_resv_lock(dmabuf, nonblock);
 if (ret)
  goto err_attachment_put;

 /* Make sure we don't have writers */
 timeout = nonblock ? 0 : msecs_to_jiffies(DMABUF_ENQUEUE_TIMEOUT_MS);
 retl = dma_resv_wait_timeout(dmabuf->resv,
         dma_resv_usage_rw(epfile->in),
         true, timeout);
 if (retl == 0)
  retl = -EBUSY;
 if (retl < 0) {
  ret = (int)retl;
  goto err_resv_unlock;
 }

 ret = dma_resv_reserve_fences(dmabuf->resv, 1);
 if (ret)
  goto err_resv_unlock;

 fence = kmalloc(sizeof(*fence), GFP_KERNEL);
 if (!fence) {
  ret = -ENOMEM;
  goto err_resv_unlock;
 }

 fence->priv = priv;

 spin_lock_irq(&epfile->ffs->eps_lock);

 /* In the meantime, endpoint got disabled or changed. */
 if (epfile->ep != ep) {
  ret = -ESHUTDOWN;
  goto err_fence_put;
 }

 usb_req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC);
 if (!usb_req) {
  ret = -ENOMEM;
  goto err_fence_put;
 }

 /*
 * usb_ep_queue() guarantees that all transfers are processed in the
 * order they are enqueued, so we can use a simple incrementing
 * sequence number for the dma_fence.
 */

 seqno = atomic_add_return(1, &epfile->seqno);

 dma_fence_init(&fence->base, &ffs_dmabuf_fence_ops,
         &priv->lock, priv->context, seqno);

 resv_dir = epfile->in ? DMA_RESV_USAGE_WRITE : DMA_RESV_USAGE_READ;

 dma_resv_add_fence(dmabuf->resv, &fence->base, resv_dir);
 dma_resv_unlock(dmabuf->resv);

 /* Now that the dma_fence is in place, queue the transfer. */

 usb_req->length = req->length;
 usb_req->buf = NULL;
 usb_req->sg = priv->sgt->sgl;
 usb_req->num_sgs = sg_nents_for_len(priv->sgt->sgl, req->length);
 usb_req->sg_was_mapped = true;
 usb_req->context  = fence;
 usb_req->complete = ffs_epfile_dmabuf_io_complete;

 cookie = dma_fence_begin_signalling();
 ret = usb_ep_queue(ep->ep, usb_req, GFP_ATOMIC);
 dma_fence_end_signalling(cookie);
 if (!ret) {
  priv->req = usb_req;
  priv->ep = ep->ep;
 } else {
  pr_warn("FFS: Failed to queue DMABUF: %d\n", ret);
  ffs_dmabuf_signal_done(fence, ret);
  usb_ep_free_request(ep->ep, usb_req);
 }

 spin_unlock_irq(&epfile->ffs->eps_lock);
 dma_buf_put(dmabuf);

 return ret;

err_fence_put:
 spin_unlock_irq(&epfile->ffs->eps_lock);
 dma_fence_put(&fence->base);
err_resv_unlock:
 dma_resv_unlock(dmabuf->resv);
err_attachment_put:
 ffs_dmabuf_put(attach);
err_dmabuf_put:
 dma_buf_put(dmabuf);

 return ret;
}

static long ffs_epfile_ioctl(struct file *file, unsigned code,
        unsigned long value)
{
 struct ffs_epfile *epfile = file->private_data;
 struct ffs_ep *ep;
 int ret;

 if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
  return -ENODEV;

 switch (code) {
 case FUNCTIONFS_DMABUF_ATTACH:
 {
  int fd;

  if (copy_from_user(&fd, (void __user *)value, sizeof(fd))) {
   ret = -EFAULT;
   break;
  }

  return ffs_dmabuf_attach(file, fd);
 }
 case FUNCTIONFS_DMABUF_DETACH:
 {
  int fd;

  if (copy_from_user(&fd, (void __user *)value, sizeof(fd))) {
   ret = -EFAULT;
   break;
  }

  return ffs_dmabuf_detach(file, fd);
 }
 case FUNCTIONFS_DMABUF_TRANSFER:
 {
  struct usb_ffs_dmabuf_transfer_req req;

  if (copy_from_user(&req, (void __user *)value, sizeof(req))) {
   ret = -EFAULT;
   break;
  }

  return ffs_dmabuf_transfer(file, &req);
 }
 default:
  break;
 }

 /* Wait for endpoint to be enabled */
 ep = ffs_epfile_wait_ep(file);
 if (IS_ERR(ep))
  return PTR_ERR(ep);

 spin_lock_irq(&epfile->ffs->eps_lock);

 /* In the meantime, endpoint got disabled or changed. */
 if (epfile->ep != ep) {
  spin_unlock_irq(&epfile->ffs->eps_lock);
  return -ESHUTDOWN;
 }

 switch (code) {
 case FUNCTIONFS_FIFO_STATUS:
  ret = usb_ep_fifo_status(epfile->ep->ep);
  break;
 case FUNCTIONFS_FIFO_FLUSH:
  usb_ep_fifo_flush(epfile->ep->ep);
  ret = 0;
  break;
 case FUNCTIONFS_CLEAR_HALT:
  ret = usb_ep_clear_halt(epfile->ep->ep);
  break;
 case FUNCTIONFS_ENDPOINT_REVMAP:
  ret = epfile->ep->num;
  break;
 case FUNCTIONFS_ENDPOINT_DESC:
 {
  int desc_idx;
  struct usb_endpoint_descriptor desc1, *desc;

  switch (epfile->ffs->gadget->speed) {
  case USB_SPEED_SUPER:
  case USB_SPEED_SUPER_PLUS:
   desc_idx = 2;
   break;
  case USB_SPEED_HIGH:
   desc_idx = 1;
   break;
  default:
   desc_idx = 0;
  }

  desc = epfile->ep->descs[desc_idx];
  memcpy(&desc1, desc, desc->bLength);

  spin_unlock_irq(&epfile->ffs->eps_lock);
  ret = copy_to_user((void __user *)value, &desc1, desc1.bLength);
  if (ret)
   ret = -EFAULT;
  return ret;
 }
 default:
  ret = -ENOTTY;
 }
 spin_unlock_irq(&epfile->ffs->eps_lock);

 return ret;
}

static const struct file_operations ffs_epfile_operations = {

 .open =  ffs_epfile_open,
 .write_iter = ffs_epfile_write_iter,
 .read_iter = ffs_epfile_read_iter,
 .release = ffs_epfile_release,
 .unlocked_ioctl = ffs_epfile_ioctl,
 .compat_ioctl = compat_ptr_ioctl,
};


/* File system and super block operations ***********************************/

/*
 * Mounting the file system creates a controller file, used first for
 * function configuration then later for event monitoring.
 */


static struct inode *__must_check
ffs_sb_make_inode(struct super_block *sb, void *data,
    const struct file_operations *fops,
    const struct inode_operations *iops,
    struct ffs_file_perms *perms)
{
 struct inode *inode;

 inode = new_inode(sb);

 if (inode) {
  struct timespec64 ts = inode_set_ctime_current(inode);

  inode->i_ino  = get_next_ino();
  inode->i_mode    = perms->mode;
  inode->i_uid     = perms->uid;
  inode->i_gid     = perms->gid;
  inode_set_atime_to_ts(inode, ts);
  inode_set_mtime_to_ts(inode, ts);
  inode->i_private = data;
  if (fops)
   inode->i_fop = fops;
  if (iops)
   inode->i_op  = iops;
 }

 return inode;
}

/* Create "regular" file */
static struct dentry *ffs_sb_create_file(struct super_block *sb,
     const char *name, void *data,
     const struct file_operations *fops)
{
 struct ffs_data *ffs = sb->s_fs_info;
 struct dentry *dentry;
 struct inode *inode;

 dentry = d_alloc_name(sb->s_root, name);
 if (!dentry)
  return NULL;

 inode = ffs_sb_make_inode(sb, data, fops, NULL, &ffs->file_perms);
 if (!inode) {
  dput(dentry);
  return NULL;
 }

 d_add(dentry, inode);
 return dentry;
}

/* Super block */
static const struct super_operations ffs_sb_operations = {
 .statfs = simple_statfs,
 .drop_inode = generic_delete_inode,
};

struct ffs_sb_fill_data {
 struct ffs_file_perms perms;
 umode_t root_mode;
 const char *dev_name;
 bool no_disconnect;
 struct ffs_data *ffs_data;
};

static int ffs_sb_fill(struct super_block *sb, struct fs_context *fc)
{
 struct ffs_sb_fill_data *data = fc->fs_private;
 struct inode *inode;
 struct ffs_data *ffs = data->ffs_data;

 ffs->sb              = sb;
 data->ffs_data       = NULL;
 sb->s_fs_info        = ffs;
 sb->s_blocksize      = PAGE_SIZE;
 sb->s_blocksize_bits = PAGE_SHIFT;
 sb->s_magic          = FUNCTIONFS_MAGIC;
 sb->s_op             = &ffs_sb_operations;
 sb->s_time_gran      = 1;

 /* Root inode */
 data->perms.mode = data->root_mode;
 inode = ffs_sb_make_inode(sb, NULL,
      &simple_dir_operations,
      &simple_dir_inode_operations,
      &data->perms);
 sb->s_root = d_make_root(inode);
 if (!sb->s_root)
  return -ENOMEM;

 /* EP0 file */
 if (!ffs_sb_create_file(sb, "ep0", ffs, &ffs_ep0_operations))
  return -ENOMEM;

 return 0;
}

enum {
 Opt_no_disconnect,
 Opt_rmode,
 Opt_fmode,
 Opt_mode,
 Opt_uid,
 Opt_gid,
};

static const struct fs_parameter_spec ffs_fs_fs_parameters[] = {
 fsparam_bool ("no_disconnect", Opt_no_disconnect),
 fsparam_u32 ("rmode",  Opt_rmode),
 fsparam_u32 ("fmode",  Opt_fmode),
 fsparam_u32 ("mode",  Opt_mode),
 fsparam_u32 ("uid",   Opt_uid),
 fsparam_u32 ("gid",   Opt_gid),
 {}
};

static int ffs_fs_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
 struct ffs_sb_fill_data *data = fc->fs_private;
 struct fs_parse_result result;
 int opt;

 opt = fs_parse(fc, ffs_fs_fs_parameters, param, &result);
 if (opt < 0)
  return opt;

 switch (opt) {
 case Opt_no_disconnect:
  data->no_disconnect = result.boolean;
  break;
 case Opt_rmode:
  data->root_mode  = (result.uint_32 & 0555) | S_IFDIR;
  break;
 case Opt_fmode:
  data->perms.mode = (result.uint_32 & 0666) | S_IFREG;
  break;
 case Opt_mode:
  data->root_mode  = (result.uint_32 & 0555) | S_IFDIR;
  data->perms.mode = (result.uint_32 & 0666) | S_IFREG;
  break;

 case Opt_uid:
  data->perms.uid = make_kuid(current_user_ns(), result.uint_32);
  if (!uid_valid(data->perms.uid))
   goto unmapped_value;
  break;
 case Opt_gid:
  data->perms.gid = make_kgid(current_user_ns(), result.uint_32);
  if (!gid_valid(data->perms.gid))
   goto unmapped_value;
  break;

 default:
  return -ENOPARAM;
 }

 return 0;

unmapped_value:
 return invalf(fc, "%s: unmapped value: %u", param->key, result.uint_32);
}

/*
 * Set up the superblock for a mount.
 */

static int ffs_fs_get_tree(struct fs_context *fc)
{
 struct ffs_sb_fill_data *ctx = fc->fs_private;
 struct ffs_data *ffs;
 int ret;

 if (!fc->source)
  return invalf(fc, "No source specified");

 ffs = ffs_data_new(fc->source);
 if (!ffs)
  return -ENOMEM;
 ffs->file_perms = ctx->perms;
 ffs->no_disconnect = ctx->no_disconnect;

 ffs->dev_name = kstrdup(fc->source, GFP_KERNEL);
 if (!ffs->dev_name) {
  ffs_data_put(ffs);
  return -ENOMEM;
 }

 ret = ffs_acquire_dev(ffs->dev_name, ffs);
 if (ret) {
  ffs_data_put(ffs);
  return ret;
 }

 ctx->ffs_data = ffs;
 return get_tree_nodev(fc, ffs_sb_fill);
}

static void ffs_fs_free_fc(struct fs_context *fc)
{
 struct ffs_sb_fill_data *ctx = fc->fs_private;

 if (ctx) {
  if (ctx->ffs_data) {
   ffs_data_put(ctx->ffs_data);
  }

  kfree(ctx);
 }
}

static const struct fs_context_operations ffs_fs_context_ops = {
 .free  = ffs_fs_free_fc,
 .parse_param = ffs_fs_parse_param,
 .get_tree = ffs_fs_get_tree,
};

static int ffs_fs_init_fs_context(struct fs_context *fc)
{
 struct ffs_sb_fill_data *ctx;

 ctx = kzalloc(sizeof(struct ffs_sb_fill_data), GFP_KERNEL);
 if (!ctx)
  return -ENOMEM;

 ctx->perms.mode = S_IFREG | 0600;
 ctx->perms.uid = GLOBAL_ROOT_UID;
 ctx->perms.gid = GLOBAL_ROOT_GID;
 ctx->root_mode = S_IFDIR | 0500;
 ctx->no_disconnect = false;

 fc->fs_private = ctx;
 fc->ops = &ffs_fs_context_ops;
 return 0;
}

static void
ffs_fs_kill_sb(struct super_block *sb)
{
 kill_litter_super(sb);
 if (sb->s_fs_info)
  ffs_data_closed(sb->s_fs_info);
}

static struct file_system_type ffs_fs_type = {
 .owner  = THIS_MODULE,
 .name  = "functionfs",
 .init_fs_context = ffs_fs_init_fs_context,
 .parameters = ffs_fs_fs_parameters,
 .kill_sb = ffs_fs_kill_sb,
};
MODULE_ALIAS_FS("functionfs");


/* Driver's main init/cleanup functions *************************************/

static int functionfs_init(void)
{
 int ret;

 ret = register_filesystem(&ffs_fs_type);
 if (!ret)
  pr_info("file system registered\n");
 else
  pr_err("failed registering file system (%d)\n", ret);

 return ret;
}

static void functionfs_cleanup(void)
{
 pr_info("unloading\n");
 unregister_filesystem(&ffs_fs_type);
}


/* ffs_data and ffs_function construction and destruction code **************/

static void ffs_data_clear(struct ffs_data *ffs);
static void ffs_data_reset(struct ffs_data *ffs);

static void ffs_data_get(struct ffs_data *ffs)
{
 refcount_inc(&ffs->ref);
}

static void ffs_data_opened(struct ffs_data *ffs)
{
 refcount_inc(&ffs->ref);
 if (atomic_add_return(1, &ffs->opened) == 1 &&
   ffs->state == FFS_DEACTIVATED) {
  ffs->state = FFS_CLOSING;
  ffs_data_reset(ffs);
 }
}

static void ffs_data_put(struct ffs_data *ffs)
{
 if (refcount_dec_and_test(&ffs->ref)) {
  pr_info("%s(): freeing\n", __func__);
  ffs_data_clear(ffs);
  ffs_release_dev(ffs->private_data);
  BUG_ON(waitqueue_active(&ffs->ev.waitq) ||
         swait_active(&ffs->ep0req_completion.wait) ||
         waitqueue_active(&ffs->wait));
  destroy_workqueue(ffs->io_completion_wq);
  kfree(ffs->dev_name);
  kfree(ffs);
 }
}

static void ffs_data_closed(struct ffs_data *ffs)
{
 struct ffs_epfile *epfiles;
 unsigned long flags;

 if (atomic_dec_and_test(&ffs->opened)) {
  if (ffs->no_disconnect) {
   ffs->state = FFS_DEACTIVATED;
   spin_lock_irqsave(&ffs->eps_lock, flags);
   epfiles = ffs->epfiles;
   ffs->epfiles = NULL;
   spin_unlock_irqrestore(&ffs->eps_lock,
       flags);

   if (epfiles)
    ffs_epfiles_destroy(epfiles,
       ffs->eps_count);

   if (ffs->setup_state == FFS_SETUP_PENDING)
    __ffs_ep0_stall(ffs);
  } else {
   ffs->state = FFS_CLOSING;
   ffs_data_reset(ffs);
  }
 }
 if (atomic_read(&ffs->opened) < 0) {
  ffs->state = FFS_CLOSING;
  ffs_data_reset(ffs);
 }

 ffs_data_put(ffs);
}

static struct ffs_data *ffs_data_new(const char *dev_name)
{
 struct ffs_data *ffs = kzalloc(sizeof *ffs, GFP_KERNEL);
 if (!ffs)
  return NULL;

 ffs->io_completion_wq = alloc_ordered_workqueue("%s", 0, dev_name);
 if (!ffs->io_completion_wq) {
  kfree(ffs);
  return NULL;
 }

 refcount_set(&ffs->ref, 1);
 atomic_set(&ffs->opened, 0);
 ffs->state = FFS_READ_DESCRIPTORS;
 mutex_init(&ffs->mutex);
 spin_lock_init(&ffs->eps_lock);
 init_waitqueue_head(&ffs->ev.waitq);
 init_waitqueue_head(&ffs->wait);
 init_completion(&ffs->ep0req_completion);

 /* XXX REVISIT need to update it in some places, or do we? */
 ffs->ev.can_stall = 1;

 return ffs;
}

static void ffs_data_clear(struct ffs_data *ffs)
{
 struct ffs_epfile *epfiles;
 unsigned long flags;

 ffs_closed(ffs);

 BUG_ON(ffs->gadget);

 spin_lock_irqsave(&ffs->eps_lock, flags);
 epfiles = ffs->epfiles;
 ffs->epfiles = NULL;
 spin_unlock_irqrestore(&ffs->eps_lock, flags);

 /*
 * potential race possible between ffs_func_eps_disable
 * & ffs_epfile_release therefore maintaining a local
 * copy of epfile will save us from use-after-free.
 */

 if (epfiles) {
  ffs_epfiles_destroy(epfiles, ffs->eps_count);
  ffs->epfiles = NULL;
 }

 if (ffs->ffs_eventfd) {
  eventfd_ctx_put(ffs->ffs_eventfd);
  ffs->ffs_eventfd = NULL;
 }

 kfree(ffs->raw_descs_data);
 kfree(ffs->raw_strings);
 kfree(ffs->stringtabs);
}

static void ffs_data_reset(struct ffs_data *ffs)
{
 ffs_data_clear(ffs);

 ffs->raw_descs_data = NULL;
 ffs->raw_descs = NULL;
 ffs->raw_strings = NULL;
 ffs->stringtabs = NULL;

 ffs->raw_descs_length = 0;
 ffs->fs_descs_count = 0;
 ffs->hs_descs_count = 0;
 ffs->ss_descs_count = 0;

 ffs->strings_count = 0;
 ffs->interfaces_count = 0;
 ffs->eps_count = 0;

 ffs->ev.count = 0;

 ffs->state = FFS_READ_DESCRIPTORS;
 ffs->setup_state = FFS_NO_SETUP;
 ffs->flags = 0;

 ffs->ms_os_descs_ext_prop_count = 0;
 ffs->ms_os_descs_ext_prop_name_len = 0;
 ffs->ms_os_descs_ext_prop_data_len = 0;
}


static int functionfs_bind(struct ffs_data *ffs, struct usb_composite_dev *cdev)
{
 struct usb_gadget_strings **lang;
 int first_id;

 if ((ffs->state != FFS_ACTIVE
   || test_and_set_bit(FFS_FL_BOUND, &ffs->flags)))
  return -EBADFD;

 first_id = usb_string_ids_n(cdev, ffs->strings_count);
 if (first_id < 0)
  return first_id;

 ffs->ep0req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL);
 if (!ffs->ep0req)
  return -ENOMEM;
 ffs->ep0req->complete = ffs_ep0_complete;
 ffs->ep0req->context = ffs;

 lang = ffs->stringtabs;
 if (lang) {
  for (; *lang; ++lang) {
   struct usb_string *str = (*lang)->strings;
   int id = first_id;
   for (; str->s; ++id, ++str)
    str->id = id;
  }
 }

 ffs->gadget = cdev->gadget;
 ffs_data_get(ffs);
 return 0;
}

static void functionfs_unbind(struct ffs_data *ffs)
{
 if (!WARN_ON(!ffs->gadget)) {
  /* dequeue before freeing ep0req */
  usb_ep_dequeue(ffs->gadget->ep0, ffs->ep0req);
  mutex_lock(&ffs->mutex);
  usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req);
  ffs->ep0req = NULL;
  ffs->gadget = NULL;
  clear_bit(FFS_FL_BOUND, &ffs->flags);
  mutex_unlock(&ffs->mutex);
  ffs_data_put(ffs);
 }
}

static int ffs_epfiles_create(struct ffs_data *ffs)
{
 struct ffs_epfile *epfile, *epfiles;
 unsigned i, count;

 count = ffs->eps_count;
 epfiles = kcalloc(count, sizeof(*epfiles), GFP_KERNEL);
 if (!epfiles)
  return -ENOMEM;

 epfile = epfiles;
 for (i = 1; i <= count; ++i, ++epfile) {
  epfile->ffs = ffs;
  mutex_init(&epfile->mutex);
  mutex_init(&epfile->dmabufs_mutex);
  INIT_LIST_HEAD(&epfile->dmabufs);
  if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR)
   sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]);
  else
   sprintf(epfile->name, "ep%u", i);
  epfile->dentry = ffs_sb_create_file(ffs->sb, epfile->name,
       epfile,
       &ffs_epfile_operations);
  if (!epfile->dentry) {
   ffs_epfiles_destroy(epfiles, i - 1);
   return -ENOMEM;
  }
 }

 ffs->epfiles = epfiles;
 return 0;
}

static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count)
{
 struct ffs_epfile *epfile = epfiles;

 for (; count; --count, ++epfile) {
  BUG_ON(mutex_is_locked(&epfile->mutex));
  if (epfile->dentry) {
   simple_recursive_removal(epfile->dentry, NULL);
   epfile->dentry = NULL;
  }
 }

 kfree(epfiles);
}

static void ffs_func_eps_disable(struct ffs_function *func)
{
 struct ffs_ep *ep;
 struct ffs_epfile *epfile;
 unsigned short count;
 unsigned long flags;

 spin_lock_irqsave(&func->ffs->eps_lock, flags);
 count = func->ffs->eps_count;
 epfile = func->ffs->epfiles;
 ep = func->eps;
 while (count--) {
  /* pending requests get nuked */
  if (ep->ep)
   usb_ep_disable(ep->ep);
  ++ep;

  if (epfile) {
   epfile->ep = NULL;
   __ffs_epfile_read_buffer_free(epfile);
   ++epfile;
  }
 }
 spin_unlock_irqrestore(&func->ffs->eps_lock, flags);
}

static int ffs_func_eps_enable(struct ffs_function *func)
{
 struct ffs_data *ffs;
 struct ffs_ep *ep;
 struct ffs_epfile *epfile;
 unsigned short count;
 unsigned long flags;
 int ret = 0;

 spin_lock_irqsave(&func->ffs->eps_lock, flags);
 ffs = func->ffs;
 ep = func->eps;
 epfile = ffs->epfiles;
 count = ffs->eps_count;
 if (!epfile) {
  ret = -ENOMEM;
  goto done;
 }

 while (count--) {
  ep->ep->driver_data = ep;

  ret = config_ep_by_speed(func->gadget, &func->function, ep->ep);
  if (ret) {
   pr_err("%s: config_ep_by_speed(%s) returned %d\n",
     __func__, ep->ep->name, ret);
   break;
  }

  ret = usb_ep_enable(ep->ep);
  if (!ret) {
   epfile->ep = ep;
   epfile->in = usb_endpoint_dir_in(ep->ep->desc);
   epfile->isoc = usb_endpoint_xfer_isoc(ep->ep->desc);
  } else {
   break;
  }

  ++ep;
  ++epfile;
 }

 wake_up_interruptible(&ffs->wait);
done:
 spin_unlock_irqrestore(&func->ffs->eps_lock, flags);

 return ret;
}


/* Parsing and building descriptors and strings *****************************/

/*
 * This validates if data pointed by data is a valid USB descriptor as
 * well as record how many interfaces, endpoints and strings are
 * required by given configuration.  Returns address after the
 * descriptor or NULL if data is invalid.
 */


enum ffs_entity_type {
 FFS_DESCRIPTOR, FFS_INTERFACE, FFS_STRING, FFS_ENDPOINT
};

enum ffs_os_desc_type {
 FFS_OS_DESC, FFS_OS_DESC_EXT_COMPAT, FFS_OS_DESC_EXT_PROP
};

typedef int (*ffs_entity_callback)(enum ffs_entity_type entity,
       u8 *valuep,
       struct usb_descriptor_header *desc,
       void *priv);

typedef int (*ffs_os_desc_callback)(enum ffs_os_desc_type entity,
        struct usb_os_desc_header *h, void *data,
        unsigned len, void *priv);

static int __must_check ffs_do_single_desc(char *data, unsigned len,
        ffs_entity_callback entity,
        void *priv, int *current_class, int *current_subclass)
{
 struct usb_descriptor_header *_ds = (void *)data;
 u8 length;
 int ret;

 /* At least two bytes are required: length and type */
 if (len < 2) {
  pr_vdebug("descriptor too short\n");
  return -EINVAL;
 }

 /* If we have at least as many bytes as the descriptor takes? */
 length = _ds->bLength;
 if (len < length) {
  pr_vdebug("descriptor longer then available data\n");
  return -EINVAL;
 }

#define __entity_check_INTERFACE(val)  1
#define __entity_check_STRING(val)     (val)
#define __entity_check_ENDPOINT(val)   ((val) & USB_ENDPOINT_NUMBER_MASK)
#define __entity(type, val) do {     \
  pr_vdebug("entity " #type "(%02x)\n", (val));  \
  if (!__entity_check_ ##type(val)) {   \
   pr_vdebug("invalid entity's value\n");  \
   return -EINVAL;     \
  }       \
  ret = entity(FFS_ ##type, &val, _ds, priv);  \
  if (ret < 0) {      \
   pr_debug("entity " #type "(%02x); ret = %d\n", \
     (val), ret);    \
   return ret;     \
  }       \
 } while (0)

 /* Parse descriptor depending on type. */
 switch (_ds->bDescriptorType) {
 case USB_DT_DEVICE:
 case USB_DT_CONFIG:
 case USB_DT_STRING:
 case USB_DT_DEVICE_QUALIFIER:
  /* function can't have any of those */
  pr_vdebug("descriptor reserved for gadget: %d\n",
        _ds->bDescriptorType);
  return -EINVAL;

 case USB_DT_INTERFACE: {
  struct usb_interface_descriptor *ds = (void *)_ds;
  pr_vdebug("interface descriptor\n");
  if (length != sizeof *ds)
   goto inv_length;

  __entity(INTERFACE, ds->bInterfaceNumber);
  if (ds->iInterface)
   __entity(STRING, ds->iInterface);
  *current_class = ds->bInterfaceClass;
  *current_subclass = ds->bInterfaceSubClass;
 }
  break;

 case USB_DT_ENDPOINT: {
  struct usb_endpoint_descriptor *ds = (void *)_ds;
  pr_vdebug("endpoint descriptor\n");
  if (length != USB_DT_ENDPOINT_SIZE &&
      length != USB_DT_ENDPOINT_AUDIO_SIZE)
   goto inv_length;
  __entity(ENDPOINT, ds->bEndpointAddress);
 }
  break;

 case USB_TYPE_CLASS | 0x01:
  if (*current_class == USB_INTERFACE_CLASS_HID) {
   pr_vdebug("hid descriptor\n");
   if (length != sizeof(struct hid_descriptor))
    goto inv_length;
   break;
  } else if (*current_class == USB_INTERFACE_CLASS_CCID) {
   pr_vdebug("ccid descriptor\n");
   if (length != sizeof(struct ccid_descriptor))
    goto inv_length;
   break;
  } else if (*current_class == USB_CLASS_APP_SPEC &&
      *current_subclass == USB_SUBCLASS_DFU) {
   pr_vdebug("dfu functional descriptor\n");
   if (length != sizeof(struct usb_dfu_functional_descriptor))
    goto inv_length;
   break;
  } else {
   pr_vdebug("unknown descriptor: %d for class %d\n",
         _ds->bDescriptorType, *current_class);
   return -EINVAL;
  }

 case USB_DT_OTG:
  if (length != sizeof(struct usb_otg_descriptor))
   goto inv_length;
  break;

 case USB_DT_INTERFACE_ASSOCIATION: {
  struct usb_interface_assoc_descriptor *ds = (void *)_ds;
  pr_vdebug("interface association descriptor\n");
  if (length != sizeof *ds)
   goto inv_length;
  if (ds->iFunction)
   __entity(STRING, ds->iFunction);
 }
  break;

 case USB_DT_SS_ENDPOINT_COMP:
  pr_vdebug("EP SS companion descriptor\n");
  if (length != sizeof(struct usb_ss_ep_comp_descriptor))
   goto inv_length;
  break;

 case USB_DT_OTHER_SPEED_CONFIG:
 case USB_DT_INTERFACE_POWER:
 case USB_DT_DEBUG:
 case USB_DT_SECURITY:
 case USB_DT_CS_RADIO_CONTROL:
  /* TODO */
  pr_vdebug("unimplemented descriptor: %d\n", _ds->bDescriptorType);
  return -EINVAL;

 default:
  /* We should never be here */
  pr_vdebug("unknown descriptor: %d\n", _ds->bDescriptorType);
  return -EINVAL;

inv_length:
  pr_vdebug("invalid length: %d (descriptor %d)\n",
     _ds->bLength, _ds->bDescriptorType);
  return -EINVAL;
 }

#undef __entity
#undef __entity_check_DESCRIPTOR
#undef __entity_check_INTERFACE
#undef __entity_check_STRING
#undef __entity_check_ENDPOINT

 return length;
}

static int __must_check ffs_do_descs(unsigned count, char *data, unsigned len,
         ffs_entity_callback entity, void *priv)
{
 const unsigned _len = len;
 unsigned long num = 0;
 int current_class = -1;
 int current_subclass = -1;

 for (;;) {
  int ret;

  if (num == count)
   data = NULL;

  /* Record "descriptor" entity */
  ret = entity(FFS_DESCRIPTOR, (u8 *)num, (void *)data, priv);
  if (ret < 0) {
   pr_debug("entity DESCRIPTOR(%02lx); ret = %d\n",
     num, ret);
   return ret;
  }

  if (!data)
   return _len - len;

  ret = ffs_do_single_desc(data, len, entity, priv,
   ¤t_class, ¤t_subclass);
  if (ret < 0) {
   pr_debug("%s returns %d\n", __func__, ret);
   return ret;
  }

  len -= ret;
  data += ret;
  ++num;
 }
}

static int __ffs_data_do_entity(enum ffs_entity_type type,
    u8 *valuep, struct usb_descriptor_header *desc,
    void *priv)
{
 struct ffs_desc_helper *helper = priv;
 struct usb_endpoint_descriptor *d;

 switch (type) {
 case FFS_DESCRIPTOR:
  break;

 case FFS_INTERFACE:
  /*
 * Interfaces are indexed from zero so if we
 * encountered interface "n" then there are at least
 * "n+1" interfaces.
 */

  if (*valuep >= helper->interfaces_count)
   helper->interfaces_count = *valuep + 1;
  break;

 case FFS_STRING:
  /*
 * Strings are indexed from 1 (0 is reserved
 * for languages list)
 */

  if (*valuep > helper->ffs->strings_count)
   helper->ffs->strings_count = *valuep;
  break;

 case FFS_ENDPOINT:
  d = (void *)desc;
  helper->eps_count++;
  if (helper->eps_count >= FFS_MAX_EPS_COUNT)
   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;
 }

 return 0;
}

static int __ffs_do_os_desc_header(enum ffs_os_desc_type *next_type,
       struct usb_os_desc_header *desc)
{
 u16 bcd_version = le16_to_cpu(desc->bcdVersion);
 u16 w_index = le16_to_cpu(desc->wIndex);

 if (bcd_version == 0x1) {
  pr_warn("bcdVersion must be 0x0100, stored in Little Endian order. "
   "Userspace driver should be fixed, accepting 0x0001 for compatibility.\n");
 } else if (bcd_version != 0x100) {
  pr_vdebug("unsupported os descriptors version: 0x%x\n",
     bcd_version);
  return -EINVAL;
 }
 switch (w_index) {
 case 0x4:
  *next_type = FFS_OS_DESC_EXT_COMPAT;
  break;
 case 0x5:
  *next_type = FFS_OS_DESC_EXT_PROP;
  break;
 default:
  pr_vdebug("unsupported os descriptor type: %d", w_index);
  return -EINVAL;
 }

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=94 G=94

¤ Dauer der Verarbeitung: 0.36 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge