nuttx/drivers/usbdev/usbdev_fs.c
yangsong8 5a39e83c05 usbdev: extend the usb req len to size_t
Some USB controllers can receive or send multiple data packets then
generate one interrupt. This mechanism can reduce the number of data
copies. Extend req buf to accommodate this.

Signed-off-by: yangsong8 <yangsong8@xiaomi.com>
2024-08-30 01:32:02 +08:00

1538 lines
40 KiB
C

/****************************************************************************
* drivers/usbdev/usbdev_fs.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <debug.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <nuttx/nuttx.h>
#include <nuttx/kmalloc.h>
#include <nuttx/queue.h>
#include <nuttx/mutex.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/usbdev_trace.h>
#include <nuttx/usb/composite.h>
#include <nuttx/fs/fs.h>
#include <nuttx/wqueue.h>
#include "composite.h"
#include "usbdev_fs.h"
/****************************************************************************
* Private Types
****************************************************************************/
/* Container to support a list of requests */
struct usbdev_fs_req_s
{
sq_entry_t node; /* Implements a singly linked list */
FAR struct usbdev_req_s *req; /* The contained request */
uint16_t offset; /* Offset to valid data in the RX request */
};
/* Manage char device non blocking io */
typedef struct usbdev_fs_waiter_sem_s
{
sem_t sem;
FAR struct usbdev_fs_waiter_sem_s *next;
} usbdev_fs_waiter_sem_t;
/* This structure describes the char device */
struct usbdev_fs_ep_s
{
uint8_t crefs; /* Count of opened instances */
bool unlinked; /* Indicates if the driver has been unlinked */
mutex_t lock; /* Enforces device exclusive access */
FAR struct usbdev_ep_s *ep; /* EP entry */
FAR struct usbdev_fs_dev_s *dev; /* USB device */
FAR usbdev_fs_waiter_sem_t *sems; /* List of blocking request */
struct sq_queue_s reqq; /* Available request containers */
FAR struct usbdev_fs_req_s *reqbuffer; /* Request buffer */
FAR struct pollfd *fds[CONFIG_USBDEV_FS_NPOLLWAITERS];
};
struct usbdev_fs_dev_s
{
FAR struct composite_dev_s *cdev;
uint8_t config;
struct work_s work;
struct usbdev_devinfo_s devinfo;
FAR struct usbdev_fs_ep_s *eps;
bool uninitialized;
};
struct usbdev_fs_driver_s
{
struct usbdevclass_driver_s drvr;
struct usbdev_fs_dev_s dev;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* USB class device *********************************************************/
static int usbdev_fs_classbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static void usbdev_fs_classunbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static int usbdev_fs_classsetup(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev,
FAR const struct usb_ctrlreq_s *ctrl,
FAR uint8_t *dataout, size_t outlen);
static void
usbdev_fs_classdisconnect(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static void usbdev_fs_classsuspend(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static void usbdev_fs_classresume(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
/* Char device Operations ***************************************************/
static int usbdev_fs_open(FAR struct file *filep);
static int usbdev_fs_close(FAR struct file *filep);
static ssize_t usbdev_fs_read(FAR struct file *filep, FAR char *buffer,
size_t len);
static ssize_t usbdev_fs_write(FAR struct file *filep,
FAR const char *buffer, size_t len);
static int usbdev_fs_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
/****************************************************************************
* Private Data
****************************************************************************/
/* USB class device *********************************************************/
static const struct usbdevclass_driverops_s g_usbdev_fs_classops =
{
usbdev_fs_classbind, /* bind */
usbdev_fs_classunbind, /* unbind */
usbdev_fs_classsetup, /* setup */
usbdev_fs_classdisconnect, /* disconnect */
usbdev_fs_classsuspend, /* suspend */
usbdev_fs_classresume /* resume */
};
/* Char device **************************************************************/
static const struct file_operations g_usbdev_fs_fops =
{
usbdev_fs_open, /* open */
usbdev_fs_close, /* close */
usbdev_fs_read, /* read */
usbdev_fs_write, /* write */
NULL, /* seek */
NULL, /* ioctl */
NULL, /* mmap */
NULL, /* truncate */
usbdev_fs_poll /* poll */
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: usbdev_fs_notify
*
* Description:
* Notify threads waiting to read device. This function must be called
* with interrupt disabled.
*
****************************************************************************/
static void usbdev_fs_notify(FAR struct usbdev_fs_ep_s *fs_ep,
pollevent_t eventset)
{
/* Notify all of the waiting readers */
FAR usbdev_fs_waiter_sem_t *cur_sem = fs_ep->sems;
while (cur_sem != NULL)
{
nxsem_post(&cur_sem->sem);
cur_sem = cur_sem->next;
}
fs_ep->sems = NULL;
/* Notify all poll/select waiters */
poll_notify(fs_ep->fds, CONFIG_USBDEV_FS_NPOLLWAITERS, eventset);
}
/****************************************************************************
* Name: usbdev_fs_submit_wrreq
*
* Description:
* Handle completion of write request on the bulk IN endpoint.
*
****************************************************************************/
static int usbdev_fs_submit_wrreq(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_fs_req_s *container,
uint16_t len)
{
FAR struct usbdev_req_s *req = container->req;
req->len = len;
req->flags = 0;
req->priv = container;
return EP_SUBMIT(ep, req);
}
/****************************************************************************
* Name: usbdev_fs_submit_rdreq
*
* Description:
* Handle completion of read request on the bulk OUT endpoint.
*
****************************************************************************/
static int usbdev_fs_submit_rdreq(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_fs_req_s *container)
{
FAR struct usbdev_req_s *req = container->req;
req->len = ep->maxpacket;
return EP_SUBMIT(ep, req);
}
/****************************************************************************
* Name: usbdev_fs_rdcomplete
*
* Description:
* Handle completion of read request on the bulk OUT endpoint.
*
****************************************************************************/
static void usbdev_fs_rdcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
FAR struct usbdev_fs_req_s *container;
FAR struct usbdev_fs_ep_s *fs_ep;
irqstate_t flags;
/* Sanity check */
#ifdef CONFIG_DEBUG_FEATURES
if (!ep || !ep->priv || !req)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
/* Extract references to private data */
fs_ep = (FAR struct usbdev_fs_ep_s *)ep->fs;
container = (FAR struct usbdev_fs_req_s *)req->priv;
/* Process the received data unless this is some unusual condition */
switch (req->result)
{
case 0: /* Normal completion */
usbtrace(TRACE_CLASSRDCOMPLETE, sq_count(&fs_ep->reqq));
/* Restart request due to empty frame received */
if (req->xfrd <= 0)
{
goto restart_req;
}
/* Queue request and notify readers */
flags = enter_critical_section();
/* Put request on RX pending queue */
container->offset = 0;
sq_addlast(&container->node, &fs_ep->reqq);
usbdev_fs_notify(fs_ep, POLLIN);
leave_critical_section(flags);
return;
case -ESHUTDOWN: /* Disconnection */
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSHUTDOWN), 0);
return;
default: /* Some other error occurred */
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDUNEXPECTED),
(uint16_t)-req->result);
break;
};
restart_req:
/* Restart request */
usbdev_fs_submit_rdreq(fs_ep->ep, container);
}
/****************************************************************************
* Name: usbdev_fs_wrcomplete
*
* Description:
* Handle completion of write request. This function probably executes
* in the context of an interrupt handler.
*
****************************************************************************/
static void usbdev_fs_wrcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
FAR struct usbdev_fs_req_s *container;
FAR struct usbdev_fs_ep_s *fs_ep;
irqstate_t flags;
/* Sanity check */
#ifdef CONFIG_DEBUG_FEATURES
if (!ep || !ep->priv || !req || !req->priv)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
/* Extract references to private data */
fs_ep = (FAR struct usbdev_fs_ep_s *)ep->fs;
container = (FAR struct usbdev_fs_req_s *)req->priv;
/* Return the write request to the free list */
flags = enter_critical_section();
sq_addlast(&container->node, &fs_ep->reqq);
/* Check for termination condition */
switch (req->result)
{
case OK: /* Normal completion */
{
usbtrace(TRACE_CLASSWRCOMPLETE, sq_count(&fs_ep->reqq));
/* Notify all waiting writers that write req is available */
usbdev_fs_notify(fs_ep, POLLOUT);
}
break;
case -ESHUTDOWN: /* Disconnection */
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRSHUTDOWN),
sq_count(&fs_ep->reqq));
}
break;
default: /* Some other error occurred */
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRUNEXPECTED),
(uint16_t)-req->result);
}
break;
}
leave_critical_section(flags);
}
/****************************************************************************
* Name: usbdev_fs_blocking_io
*
* Description:
* Handle read/write blocking io.
*
****************************************************************************/
static int usbdev_fs_blocking_io(FAR struct usbdev_fs_ep_s *fs_ep,
FAR usbdev_fs_waiter_sem_t **list,
FAR struct sq_queue_s *queue)
{
usbdev_fs_waiter_sem_t sem;
irqstate_t flags;
int ret;
flags = enter_critical_section();
if (!sq_empty(queue))
{
/* Queue not empty after all */
leave_critical_section(flags);
return 0;
}
nxsem_init(&sem.sem, 0, 0);
/* Register waiter semaphore */
sem.next = *list;
*list = &sem;
leave_critical_section(flags);
nxmutex_unlock(&fs_ep->lock);
/* Wait for USB device to notify */
ret = nxsem_wait(&sem.sem);
/* Interrupted wait, unregister semaphore
* TODO ensure that lock wait does not fail (ECANCELED)
*/
nxmutex_lock(&fs_ep->lock);
if (ret < 0)
{
flags = enter_critical_section();
FAR usbdev_fs_waiter_sem_t *cur_sem = *list;
if (cur_sem == &sem)
{
*list = sem.next;
}
else
{
while (cur_sem)
{
if (cur_sem->next == &sem)
{
cur_sem->next = sem.next;
break;
}
cur_sem = cur_sem->next;
}
}
leave_critical_section(flags);
nxmutex_unlock(&fs_ep->lock);
}
nxsem_destroy(&sem.sem);
return ret;
}
/****************************************************************************
* Name: usbdev_fs_open
*
* Description:
* Open usbdev fs device. Only one open() instance is supported.
*
****************************************************************************/
static int usbdev_fs_open(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct usbdev_fs_ep_s *fs_ep = inode->i_private;
int ret;
/* Get exclusive access to the device structures */
ret = nxmutex_lock(&fs_ep->lock);
if (ret < 0)
{
return ret;
}
finfo("entry: <%s> %d\n", inode->i_name, fs_ep->crefs);
fs_ep->crefs += 1;
ASSERT(fs_ep->crefs != 0);
nxmutex_unlock(&fs_ep->lock);
return ret;
}
/****************************************************************************
* Name: usbdev_fs_close
*
* Description:
* Close usbdev fs device.
*
****************************************************************************/
static int usbdev_fs_close(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct usbdev_fs_ep_s *fs_ep = inode->i_private;
FAR struct usbdev_fs_dev_s *fs = fs_ep->dev;
int ret;
int i;
/* Get exclusive access to the device structures */
ret = nxmutex_lock(&fs_ep->lock);
if (ret < 0)
{
return ret;
}
finfo("entry: <%s> %d\n", inode->i_name, fs_ep->crefs);
fs_ep->crefs -= 1;
if (fs_ep->unlinked && fs_ep->crefs == 0)
{
bool do_free = true;
nxmutex_destroy(&fs_ep->lock);
for (i = 0; i < fs->devinfo.nendpoints; i++)
{
if (fs->eps[i].crefs > 0)
{
do_free = false;
}
}
if (do_free && fs->uninitialized)
{
FAR struct usbdev_fs_driver_s *alloc = container_of(
fs, FAR struct usbdev_fs_driver_s, dev);
kmm_free(fs->eps);
fs->eps = NULL;
kmm_free(alloc);
}
}
else
{
nxmutex_unlock(&fs_ep->lock);
}
return OK;
}
/****************************************************************************
* Name: usbdev_fs_read
*
* Description:
* Read usbdev fs device.
*
****************************************************************************/
static ssize_t usbdev_fs_read(FAR struct file *filep, FAR char *buffer,
size_t len)
{
FAR struct inode *inode = filep->f_inode;
FAR struct usbdev_fs_ep_s *fs_ep = inode->i_private;
size_t retlen = 0;
irqstate_t flags;
int ret;
ret = nxmutex_lock(&fs_ep->lock);
if (ret < 0)
{
return ret;
}
/* Check if the usbdev device has been unbind */
if (fs_ep->unlinked)
{
nxmutex_unlock(&fs_ep->lock);
return -ENOTCONN;
}
/* Check for available data */
if (sq_empty(&fs_ep->reqq))
{
if (filep->f_oflags & O_NONBLOCK)
{
nxmutex_unlock(&fs_ep->lock);
return -EAGAIN;
}
do
{
/* RX queue seems empty. Check again with interrupts disabled */
ret = usbdev_fs_blocking_io(
fs_ep, &fs_ep->sems, &fs_ep->reqq);
if (ret < 0)
{
return ret;
}
}
while (sq_empty(&fs_ep->reqq));
}
/* Device ready for read */
while (!sq_empty(&fs_ep->reqq))
{
FAR struct usbdev_fs_req_s *container;
uint16_t reqlen;
/* Process each packet in the priv->reqq list */
container = container_of(sq_peek(&fs_ep->reqq),
struct usbdev_fs_req_s, node);
reqlen = container->req->xfrd - container->offset;
if (reqlen > len)
{
/* Output buffer full */
if (buffer != NULL)
{
memcpy(&buffer[retlen],
&container->req->buf[container->offset],
len);
}
container->offset += len;
retlen += len;
break;
}
if (buffer != NULL)
{
memcpy(&buffer[retlen],
&container->req->buf[container->offset], reqlen);
}
retlen += reqlen;
len -= reqlen;
/* The entire packet was processed and may be removed from the
* pending RX list.
*/
/* FIXME use atomic queue primitives ? */
flags = enter_critical_section();
sq_remfirst(&fs_ep->reqq);
leave_critical_section(flags);
ret = usbdev_fs_submit_rdreq(fs_ep->ep, container);
if (ret < 0)
{
/* TODO handle error */
PANIC();
}
/* The container buffer length is less than the maximum length.
* It is an independent packet of requests and needs to be
* returned directly.
*/
if (reqlen < fs_ep->ep->maxpacket)
{
break;
}
}
nxmutex_unlock(&fs_ep->lock);
return retlen;
}
/****************************************************************************
* Name: usbdev_fs_write
*
* Description:
* Write usbdev fs device.
*
****************************************************************************/
static ssize_t usbdev_fs_write(FAR struct file *filep,
FAR const char *buffer, size_t len)
{
FAR struct inode *inode = filep->f_inode;
FAR struct usbdev_fs_ep_s *fs_ep = inode->i_private;
FAR struct usbdev_fs_req_s *container;
FAR struct usbdev_req_s *req;
irqstate_t flags;
int wlen = 0;
int ret;
ret = nxmutex_lock(&fs_ep->lock);
if (ret < 0)
{
return ret;
}
/* Check if the usbdev device has been unbind */
if (fs_ep->unlinked)
{
nxmutex_unlock(&fs_ep->lock);
return -ENOTCONN;
}
/* Check for available write request */
if (sq_empty(&fs_ep->reqq))
{
if (filep->f_oflags & O_NONBLOCK)
{
ret = -EAGAIN;
goto errout;
}
do
{
/* TX queue seems empty. Check again with interrupts disabled */
ret = usbdev_fs_blocking_io(
fs_ep, &fs_ep->sems, &fs_ep->reqq);
if (ret < 0)
{
return ret;
}
}
while (sq_empty(&fs_ep->reqq));
}
/* Device ready for write */
while (!sq_empty(&fs_ep->reqq))
{
uint16_t cur_len;
/* Get available TX request slot */
flags = enter_critical_section();
container = container_of(sq_remfirst(&fs_ep->reqq),
struct usbdev_fs_req_s, node);
leave_critical_section(flags);
req = container->req;
/* Fill the request with data */
if (len > fs_ep->ep->maxpacket)
{
cur_len = fs_ep->ep->maxpacket;
}
else
{
cur_len = len;
}
memcpy(req->buf, &buffer[wlen], cur_len);
/* Then submit the request to the endpoint */
ret = usbdev_fs_submit_wrreq(fs_ep->ep, container, cur_len);
if (ret < 0)
{
/* TODO add tx request back in txfree queue */
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL),
(uint16_t)-ret);
PANIC();
break;
}
wlen += cur_len;
len -= cur_len;
if (len == 0)
{
break;
}
}
ret = wlen;
errout:
nxmutex_unlock(&fs_ep->lock);
return ret;
}
/****************************************************************************
* Name: usbdev_fs_poll
*
* Description:
* Poll usbdev fs device.
*
****************************************************************************/
static int usbdev_fs_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct inode *inode = filep->f_inode;
FAR struct usbdev_fs_ep_s *fs_ep = inode->i_private;
pollevent_t eventset;
irqstate_t flags;
int ret;
int i;
ret = nxmutex_lock(&fs_ep->lock);
if (ret < 0)
{
return ret;
}
if (!setup)
{
/* This is a request to tear down the poll. */
FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
/* Remove all memory of the poll setup */
*slot = NULL;
fds->priv = NULL;
goto errout;
}
/* FIXME only parts of this function required interrupt disabled */
flags = enter_critical_section();
/* This is a request to set up the poll. Find an available
* slot for the poll structure reference
*/
for (i = 0; i < CONFIG_USBDEV_FS_NPOLLWAITERS; i++)
{
/* Find an available slot */
if (!fs_ep->fds[i])
{
/* Bind the poll structure and this slot */
fs_ep->fds[i] = fds;
fds->priv = &fs_ep->fds[i];
break;
}
}
if (i >= CONFIG_USBDEV_FS_NPOLLWAITERS)
{
fds->priv = NULL;
ret = -EBUSY;
goto exit_leave_critical;
}
eventset = 0;
/* Check if the usbdev device has been unbind */
if (fs_ep->unlinked)
{
eventset |= POLLHUP;
}
/* Notify the POLLIN/POLLOUT event if at least one request is available */
else if (!sq_empty(&fs_ep->reqq))
{
if (USB_ISEPIN(fs_ep->ep->eplog))
{
eventset |= POLLOUT;
}
else
{
eventset |= POLLIN;
}
}
poll_notify(fs_ep->fds, CONFIG_USBDEV_FS_NPOLLWAITERS, eventset);
exit_leave_critical:
leave_critical_section(flags);
errout:
nxmutex_unlock(&fs_ep->lock);
return ret;
}
/****************************************************************************
* Name: usbdev_fs_connect
*
* Description:
* Notify usbdev fs device connect state.
*
****************************************************************************/
static void usbdev_fs_connect(FAR struct usbdev_fs_dev_s *fs, int connect)
{
FAR struct usbdev_devinfo_s *devinfo = &fs->devinfo;
FAR struct usbdev_fs_ep_s *fs_ep;
uint16_t cnt;
irqstate_t flags = enter_critical_section();
if (connect)
{
/* Notify poll/select with POLLPRI */
for (cnt = 0; cnt < devinfo->nendpoints; cnt++)
{
fs_ep = &fs->eps[cnt];
poll_notify(fs_ep->fds, CONFIG_USBDEV_FS_NPOLLWAITERS, POLLPRI);
}
}
else
{
/* Notify all of the char device */
for (cnt = 0; cnt < devinfo->nendpoints; cnt++)
{
fs_ep = &fs->eps[cnt];
usbdev_fs_notify(fs_ep, POLLERR | POLLHUP);
}
}
leave_critical_section(flags);
}
/****************************************************************************
* Name: usbdev_fs_ep_bind
*
* Description:
* Bind usbdev fs device.
*
****************************************************************************/
static int usbdev_fs_ep_bind(FAR struct usbdev_s *dev, uint8_t epno,
FAR const struct usbdev_epinfo_s *epinfo,
FAR struct usbdev_fs_ep_s *fs_ep)
{
#if defined(CONFIG_USBDEV_SUPERSPEED)
size_t reqsize = epinfo->sssize;
#elif defined(CONFIG_USBDEV_DUALSPEED)
size_t reqsize = epinfo->hssize;
#else
size_t reqsize = epinfo->fssize;
#endif
uint16_t i;
/* Initialize fs ep lock */
nxmutex_init(&fs_ep->lock);
/* Initialize request queue */
sq_init(&fs_ep->reqq);
/* Pre-allocate the endpoint */
fs_ep->ep = DEV_ALLOCEP(dev, epno,
USB_ISEPIN(epinfo->desc.addr),
epinfo->desc.attr & USB_EP_ATTR_XFERTYPE_MASK);
if (fs_ep->ep == NULL)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINALLOCFAIL), 0);
return -ENODEV;
}
#ifdef CONFIG_USBDEV_SUPERSPEED
if (dev->speed == USB_SPEED_SUPER ||
dev->speed == USB_SPEED_SUPER_PLUS)
{
uint8_t transtpye;
transtpye = epinfo->desc.attr & USB_EP_ATTR_XFERTYPE_MASK;
if (transtpye == USB_EP_ATTR_XFER_BULK)
{
if (epinfo->compdesc.mxburst >= USB_SS_BULK_EP_MAXBURST)
{
reqsize = reqsize * USB_SS_BULK_EP_MAXBURST;
}
else
{
reqsize = reqsize * (epinfo->compdesc.mxburst + 1);
}
}
else if (transtpye == USB_EP_ATTR_XFER_INT)
{
if (epinfo->compdesc.mxburst >= USB_SS_INT_EP_MAXBURST)
{
reqsize = reqsize * USB_SS_INT_EP_MAXBURST;
}
else
{
reqsize = reqsize * (epinfo->compdesc.mxburst + 1);
}
}
}
#endif
fs_ep->ep->fs = fs_ep;
/* Initialize request buffer */
fs_ep->reqbuffer =
kmm_zalloc(sizeof(struct usbdev_fs_req_s) * epinfo->reqnum);
if (!fs_ep->reqbuffer)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), 0);
return -ENOMEM;
}
for (i = 0; i < epinfo->reqnum; i++)
{
FAR struct usbdev_fs_req_s *container;
container = &fs_ep->reqbuffer[i];
container->req = usbdev_allocreq(fs_ep->ep, reqsize);
if (container->req == NULL)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), -ENOMEM);
return -ENOMEM;
}
container->req->priv = container;
if (USB_ISEPIN(epinfo->desc.addr))
{
container->req->callback = usbdev_fs_wrcomplete;
sq_addlast(&container->node, &fs_ep->reqq);
}
}
fs_ep->crefs = 0;
return 0;
}
/****************************************************************************
* Name: usbdev_fs_ep_unbind
*
* Description:
* Unbind usbdev fs endpoint.
*
****************************************************************************/
static void usbdev_fs_ep_unbind(FAR const char *devname,
FAR struct usbdev_s *dev,
FAR const struct usbdev_epinfo_s *epinfo,
FAR struct usbdev_fs_ep_s *fs_ep)
{
uint16_t i;
/* Release request buffer */
nxmutex_lock(&fs_ep->lock);
if (fs_ep->reqbuffer)
{
for (i = 0; i < epinfo->reqnum; i++)
{
FAR struct usbdev_fs_req_s *container =
&fs_ep->reqbuffer[i];
if (container->req)
{
usbdev_freereq(fs_ep->ep, container->req);
}
}
kmm_free(fs_ep->reqbuffer);
fs_ep->reqbuffer = NULL;
}
sq_init(&fs_ep->reqq);
/* Release endpoint */
if (fs_ep->ep != NULL)
{
fs_ep->ep->fs = NULL;
DEV_FREEEP(dev, fs_ep->ep);
fs_ep->ep = NULL;
}
unregister_driver(devname);
fs_ep->unlinked = true;
/* Notify the usbdev device has been unbind */
poll_notify(fs_ep->fds, CONFIG_USBDEV_FS_NPOLLWAITERS, POLLHUP | POLLERR);
if (fs_ep->crefs <= 0)
{
nxmutex_unlock(&fs_ep->lock);
nxmutex_destroy(&fs_ep->lock);
}
else
{
nxmutex_unlock(&fs_ep->lock);
}
}
/****************************************************************************
* Name: usbdev_fs_classresetconfig
*
* Description:
* Mark the device as not configured and disable all endpoints.
*
****************************************************************************/
static void usbdev_fs_classresetconfig(FAR struct usbdev_fs_dev_s *fs)
{
FAR struct usbdev_devinfo_s *devinfo = &fs->devinfo;
uint16_t i;
/* Are we configured? */
if (fs->config != COMPOSITE_CONFIGIDNONE)
{
/* Yes.. but not anymore */
usbdev_fs_connect(fs, 0);
/* Disable endpoints. This should force completion of all pending
* transfers.
*/
for (i = 0; i < devinfo->nendpoints; i++)
{
EP_DISABLE(fs->eps[i].ep);
}
}
}
/****************************************************************************
* Name: usbdev_fs_register_driver
*
* Description:
* Register the driver after successful set configuration.
*
****************************************************************************/
static void usbdev_fs_register_driver(FAR void *arg)
{
FAR struct usbdev_fs_dev_s *fs = arg;
FAR struct usbdev_devinfo_s *devinfo = &fs->devinfo;
int i;
for (i = 0; i < devinfo->nendpoints; i++)
{
char devname[32];
int ret;
snprintf(devname, sizeof(devname), "%s/ep%d",
devinfo->name, i + 1);
ret = register_driver(devname, &g_usbdev_fs_fops, 0666, &fs->eps[i]);
if (ret < 0)
{
uerr("Failed to register driver:%s, ret:%d\n", devname, ret);
while (i--)
{
snprintf(devname, sizeof(devname), "%s/ep%d",
devinfo->name, i + 1);
unregister_driver(devname);
}
break;
}
}
}
/****************************************************************************
* Name: usbdev_fs_classsetconfig
*
* Description:
* Set the device configuration by allocating and configuring endpoints and
* by allocating and queue read and write requests.
*
****************************************************************************/
static int usbdev_fs_classsetconfig(FAR struct usbdev_fs_dev_s *fs,
uint8_t config)
{
FAR struct usbdev_devinfo_s *devinfo = &fs->devinfo;
struct usb_ss_epdesc_s epdesc;
uint16_t i;
uint16_t j;
int ret;
/* Discard the previous configuration data */
usbdev_fs_classresetconfig(fs);
/* Was this a request to simply discard the current configuration? */
if (config == COMPOSITE_CONFIGIDNONE)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGNONE), 0);
return 0;
}
for (i = 0; i < devinfo->nendpoints; i++)
{
FAR struct usbdev_fs_ep_s *fs_ep = &fs->eps[i];
usbdev_copy_epdesc(&epdesc.epdesc, devinfo->epno[i],
fs->cdev->usbdev->speed, devinfo->epinfos[i]);
ret = EP_CONFIGURE(fs_ep->ep, &epdesc.epdesc,
(i == (devinfo->nendpoints - 1)));
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINCONFIGFAIL), 0);
goto errout;
}
fs_ep->ep->priv = fs;
if (USB_ISEPOUT(fs_ep->ep->eplog))
{
for (j = 0; j < devinfo->epinfos[i]->reqnum; j++)
{
FAR struct usbdev_fs_req_s *container;
container = &fs_ep->reqbuffer[j];
container->req->callback = usbdev_fs_rdcomplete;
usbdev_fs_submit_rdreq(fs_ep->ep, container);
}
}
}
fs->config = config;
work_queue(HPWORK, &fs->work, usbdev_fs_register_driver, fs, 0);
/* We are successfully configured. Char device is now active */
usbdev_fs_connect(fs, 1);
return OK;
errout:
usbdev_fs_classresetconfig(fs);
return ret;
}
/****************************************************************************
* Name: usbdev_fs_classbind
*
* Description:
* Invoked when the driver is bound to a USB device driver
*
****************************************************************************/
static int usbdev_fs_classbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct usbdev_fs_driver_s *fs_drvr = container_of(
driver, FAR struct usbdev_fs_driver_s, drvr);
FAR struct usbdev_fs_dev_s *fs = &fs_drvr->dev;
FAR struct usbdev_devinfo_s *devinfo = &fs->devinfo;
uint16_t i;
int ret;
/* Bind the composite device */
fs->cdev = dev->ep0->priv;
/* Initialize fs eqs */
fs->eps = kmm_zalloc(devinfo->nendpoints * sizeof(struct usbdev_fs_ep_s));
if (fs->eps == NULL)
{
uerr("Failed to malloc fs eqs");
return -ENOMEM;
}
for (i = 0; i < devinfo->nendpoints; i++)
{
fs->eps[i].dev = fs;
ret = usbdev_fs_ep_bind(dev,
devinfo->epno[i],
devinfo->epinfos[i],
&fs->eps[i]);
if (ret < 0)
{
uerr("Failed to bind fs ep");
goto errout;
}
}
return OK;
errout:
usbdev_fs_classunbind(driver, dev);
return ret;
}
/****************************************************************************
* Name: usbdev_fs_classunbind
*
* Description:
* Invoked when the driver is unbound from a USB device driver
*
****************************************************************************/
static void usbdev_fs_classunbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct usbdev_fs_driver_s *fs_drvr = container_of(
driver, FAR struct usbdev_fs_driver_s, drvr);
FAR struct usbdev_fs_dev_s *fs = &fs_drvr->dev;
FAR struct usbdev_devinfo_s *devinfo = &fs->devinfo;
bool do_free = true;
char devname[32];
uint16_t i;
if (fs->eps != NULL)
{
for (i = 0; i < devinfo->nendpoints; i++)
{
snprintf(devname, sizeof(devname), "%s/ep%d",
devinfo->name, i + 1);
usbdev_fs_ep_unbind(devname, dev,
devinfo->epinfos[i],
&fs->eps[i]);
if (fs->eps[i].crefs > 0)
{
do_free = false;
}
}
if (do_free)
{
kmm_free(fs->eps);
fs->eps = NULL;
}
}
fs->cdev = NULL;
}
/****************************************************************************
* Name: usbdev_fs_classsetup
*
* Description:
* Invoked for ep0 control requests. This function probably executes
* in the context of an interrupt handler.
*
****************************************************************************/
static int usbdev_fs_classsetup(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev,
FAR const struct usb_ctrlreq_s *ctrl,
FAR uint8_t *dataout, size_t outlen)
{
FAR struct usbdev_fs_driver_s *fs_drvr = container_of(
driver, FAR struct usbdev_fs_driver_s, drvr);
FAR struct usbdev_fs_dev_s *fs = &fs_drvr->dev;
uint16_t value;
int ret = -EOPNOTSUPP;
/* Extract the little-endian 16-bit values to host order */
value = GETUINT16(ctrl->value);
if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD &&
ctrl->req == USB_REQ_SETCONFIGURATION &&
ctrl->type == 0)
{
ret = usbdev_fs_classsetconfig(fs, value);
}
else
{
/* send to userspace???? */
}
/* Returning a negative value will cause a STALL */
return ret;
}
/****************************************************************************
* Name: usbdev_fs_classdisconnect
*
* Description:
* Invoked after all transfers have been stopped, when the host is
* disconnected. This function is probably called from the context of an
* interrupt handler.
*
****************************************************************************/
static void
usbdev_fs_classdisconnect(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct usbdev_fs_driver_s *fs_drvr = container_of(
driver, FAR struct usbdev_fs_driver_s, drvr);
FAR struct usbdev_fs_dev_s *fs = &fs_drvr->dev;
/* Reset the configuration */
usbdev_fs_classresetconfig(fs);
}
/****************************************************************************
* Name: usbdev_fs_classsuspend
*
* Description:
* Handle the USB suspend event.
*
****************************************************************************/
static void usbdev_fs_classsuspend(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct usbdev_fs_driver_s *fs_drvr = container_of(
driver, FAR struct usbdev_fs_driver_s, drvr);
FAR struct usbdev_fs_dev_s *fs = &fs_drvr->dev;
usbdev_fs_connect(fs, 0);
}
/****************************************************************************
* Name: usbdev_fs_classresume
*
* Description:
* Handle the USB resume event.
*
****************************************************************************/
static void usbdev_fs_classresume(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct usbdev_fs_driver_s *fs_drvr = container_of(
driver, FAR struct usbdev_fs_driver_s, drvr);
FAR struct usbdev_fs_dev_s *fs = &fs_drvr->dev;
usbdev_fs_connect(fs, 1);
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: usbclass_classobject
*
* Description:
* Register USB driver and return the class object.
*
* Returned Value:
* 0 on success, negative error code on failure.
*
****************************************************************************/
int usbdev_fs_classobject(int minor,
FAR struct usbdev_devinfo_s *devinfo,
FAR struct usbdevclass_driver_s **classdev)
{
FAR struct usbdev_fs_driver_s *alloc;
if (devinfo->nendpoints > CONFIG_USBDEV_FS_EPNUM)
{
uerr("class epnum error");
return -EINVAL;
}
alloc = kmm_zalloc(sizeof(struct usbdev_fs_driver_s));
if (!alloc)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCDEVSTRUCT), 0);
return -ENOMEM;
}
/* Save the caller provided device description */
memcpy(&alloc->dev.devinfo, devinfo,
sizeof(struct usbdev_devinfo_s));
/* Initialize the USB class driver structure */
alloc->drvr.ops = &g_usbdev_fs_classops;
*classdev = &alloc->drvr;
return OK;
}
/****************************************************************************
* Name: usbdev_fs_classuninitialize
*
* Description:
* Free allocated class memory
*
****************************************************************************/
void usbdev_fs_classuninitialize(FAR struct usbdevclass_driver_s *classdev)
{
FAR struct usbdev_fs_driver_s *alloc = container_of(
classdev, FAR struct usbdev_fs_driver_s, drvr);
FAR struct usbdev_fs_dev_s *fs = &alloc->dev;
int i;
fs->uninitialized = true;
for (i = 0; i < fs->devinfo.nendpoints; i++)
{
if (fs->eps != NULL && fs->eps[i].crefs > 0)
{
return;
}
}
kmm_free(alloc);
}
/****************************************************************************
* Name: usbdev_fs_initialize
*
* Description:
* USBDEV fs initialize
*
* Returned Value:
* 0 on success, negative error code on failure.
*
****************************************************************************/
FAR void *usbdev_fs_initialize(FAR const struct usbdev_devdescs_s *devdescs,
FAR struct composite_devdesc_s *pdevice)
{
return composite_initialize(devdescs, pdevice, 1);
}
/****************************************************************************
* Name: usbdev_fs_uninitialize
*
* Description:
* USBDEV fs uninitialize
*
* Returned Value:
* 0 on success, negative error code on failure.
*
****************************************************************************/
void usbdev_fs_uninitialize(FAR void *handle)
{
composite_uninitialize(handle);
}