diff --git a/drivers/usbdev/CMakeLists.txt b/drivers/usbdev/CMakeLists.txt index 579168cd7d..ebe6311d56 100644 --- a/drivers/usbdev/CMakeLists.txt +++ b/drivers/usbdev/CMakeLists.txt @@ -59,7 +59,12 @@ if(CONFIG_USBDEV) list(APPEND SRCS cdcecm.c) endif() - list(APPEND SRCS composite.c usbdev_req.c usbdev_trace.c usbdev_trprintf.c) + if(CONFIG_USBDEV_FS) + list(APPEND SRCS usbdev_fs.c) + endif() + + list(APPEND SRCS composite.c usbdev_desc.c usbdev_req.c) + list(APPEND SRCS usbdev_trace.c usbdev_trprintf.c) target_sources(drivers PRIVATE ${SRCS}) endif() diff --git a/drivers/usbdev/Kconfig b/drivers/usbdev/Kconfig index 81b8fba35d..e9b6efd8ea 100644 --- a/drivers/usbdev/Kconfig +++ b/drivers/usbdev/Kconfig @@ -1210,4 +1210,26 @@ config CDCECM_PRODUCTSTR endif # !CDCECM_COMPOSITE endif # CDCECM +config USBDEV_FS + bool + default n + ---help--- + Enables USBDEV FS support, userspace can use fs to control USB device. + +if USBDEV_FS + +config USBDEV_FS_EPNUM + int "Number of EP for USBDEV FS" + default 4 + ---help--- + Maximum number of eps for USBDEV FS. + +config USBDEV_FS_NPOLLWAITERS + int "Number of poll waiters for USBDEV FS" + default 2 + ---help--- + Maximum number of threads that can be waiting on poll(). + +endif #USBDEV_FS + endif # USBDEV diff --git a/drivers/usbdev/Make.defs b/drivers/usbdev/Make.defs index 427f66cada..7820e64723 100644 --- a/drivers/usbdev/Make.defs +++ b/drivers/usbdev/Make.defs @@ -58,7 +58,11 @@ ifeq ($(CONFIG_NET_CDCECM),y) CSRCS += cdcecm.c endif -CSRCS += composite.c usbdev_req.c +ifeq ($(CONFIG_USBDEV_FS),y) + CSRCS += usbdev_fs.c +endif + +CSRCS += composite.c usbdev_desc.c usbdev_req.c CSRCS += usbdev_trace.c usbdev_trprintf.c # Include USB device build support diff --git a/drivers/usbdev/usbdev_desc.c b/drivers/usbdev/usbdev_desc.c new file mode 100644 index 0000000000..bf065d07ef --- /dev/null +++ b/drivers/usbdev/usbdev_desc.c @@ -0,0 +1,72 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_desc.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 + +#include + +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbdev_copy_epdesc + * + * Description: + * Copies the requested Endpoint Description into the buffer given. + * Returns the number of Bytes filled in ( sizeof(struct usb_epdesc_s) ). + * This function is provided by various classes. + * + ****************************************************************************/ + +void usbdev_copy_epdesc(FAR struct usb_epdesc_s *epdesc, + uint8_t epno, bool hispeed, + FAR const struct usbdev_epinfo_s *epinfo) +{ +#ifndef CONFIG_USBDEV_DUALSPEED + UNUSED(hispeed); +#endif + + memcpy(epdesc, &epinfo->desc, sizeof(struct usb_epdesc_s)); + epdesc->addr |= epno; + +#ifdef CONFIG_USBDEV_DUALSPEED + if (hispeed) + { + /* Maximum packet size (high speed) */ + + epdesc->mxpacketsize[0] = LSBYTE(epinfo->hssize); + epdesc->mxpacketsize[1] = MSBYTE(epinfo->hssize); + } + else +#endif + { + /* Maximum packet size (full speed) */ + + epdesc->mxpacketsize[0] = LSBYTE(epinfo->fssize); + epdesc->mxpacketsize[1] = MSBYTE(epinfo->fssize); + } +} diff --git a/drivers/usbdev/usbdev_fs.c b/drivers/usbdev/usbdev_fs.c new file mode 100644 index 0000000000..9f0ffb0def --- /dev/null +++ b/drivers/usbdev/usbdev_fs.c @@ -0,0 +1,1382 @@ +/**************************************************************************** + * 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 + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 */ + mutex_t lock; /* Enforces device exclusive access */ + FAR struct usbdev_ep_s *ep; /* EP entry */ + 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; + bool registered; + uint8_t config; + struct usbdev_devinfo_s devinfo; + FAR struct usbdev_fs_ep_s *eps; +}; + +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 either no reader or + * empty frame received. + */ + + if (fs_ep->crefs == 0) + { + uwarn("drop frame\n"); + goto restart_req; + } + + 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) +{ + FAR 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; + } + } + } + + 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; + 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 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; + + assert(len > 0 && buffer != NULL); + + ret = nxmutex_lock(&fs_ep->lock); + if (ret < 0) + { + return ret; + } + + /* 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) && len > 0) + { + 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 */ + + memcpy(&buffer[retlen], + &container->req->buf[container->offset], + len); + container->offset += len; + retlen += len; + break; + } + + 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(); + } + } + + 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 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 (len > 0 && !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; + } + + assert(wlen > 0); + 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; + + /* Notify the POLLIN/POLLOUT event if at least one request is available */ + + 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 POLLIN */ + + for (cnt = 0; cnt < devinfo->nendpoints; cnt++) + { + fs_ep = &fs->eps[cnt]; + poll_notify(fs_ep->fds, CONFIG_USBDEV_FS_NPOLLWAITERS, POLLIN); + } + } + 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 const char *devname, + FAR struct usbdev_s *dev, uint8_t epno, + FAR const struct usbdev_epinfo_s *epinfo, + FAR struct usbdev_fs_ep_s *fs_ep) +{ +#ifdef CONFIG_USBDEV_DUALSPEED + uint16_t reqsize = epinfo->hssize; +#else + uint16_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; + } + + 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 register_driver(devname, &g_usbdev_fs_fops, 0666, fs_ep); +} + +/**************************************************************************** + * 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 */ + + 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; + } + + fs_ep->crefs = 0; + unregister_driver(devname); + + nxmutex_destroy(&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_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_epdesc_s epdesc; + bool hispeed = false; + 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; + } + +#ifdef CONFIG_USBDEV_DUALSPEED + hispeed = (fs->cdev->usbdev->speed == USB_SPEED_HIGH); +#endif + + for (i = 0; i < devinfo->nendpoints; i++) + { + FAR struct usbdev_fs_ep_s *fs_ep = &fs->eps[i]; + + usbdev_copy_epdesc(&epdesc, devinfo->epno[i], + hispeed, devinfo->epinfos[i]); + ret = EP_CONFIGURE(fs_ep->ep, &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; + + /* 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; + char devname[32]; + 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++) + { + snprintf(devname, sizeof(devname), "%s/ep%d", + devinfo->name, i + 1); + ret = usbdev_fs_ep_bind(devname, 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; + char devname[32]; + uint16_t i; + + if (fs->eps != NULL) + { + for (i = 0; i < devinfo->ninterfaces; i++) + { + snprintf(devname, sizeof(devname), "%s/ep%d", + devinfo->name, i + 1); + usbdev_fs_ep_unbind(devname, dev, + devinfo->epinfos[i], + &fs->eps[i]); + } + + 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; + alloc->dev.registered = true; + 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); + + if (alloc->dev.registered) + { + alloc->dev.registered = false; + } + else + { + 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); +} diff --git a/drivers/usbdev/usbdev_fs.h b/drivers/usbdev/usbdev_fs.h new file mode 100644 index 0000000000..d3b0534330 --- /dev/null +++ b/drivers/usbdev/usbdev_fs.h @@ -0,0 +1,88 @@ +/**************************************************************************** + * drivers/usbdev/usbdev_fs.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __DRIVERS_USBDEV_FS_H +#define __DRIVERS_USBDEV_FS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * 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); + +/**************************************************************************** + * 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); + +/**************************************************************************** + * 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); + +/**************************************************************************** + * Name: usbdev_fs_classuninitialize + * + * Description: + * Free allocated class memory + * + ****************************************************************************/ + +void usbdev_fs_classuninitialize(FAR struct usbdevclass_driver_s *classdev); + +#endif /* __DRIVERS_USBDEV_FS_H */ diff --git a/include/nuttx/usb/usbdev.h b/include/nuttx/usb/usbdev.h index 927d7b034e..01fcd7eae4 100644 --- a/include/nuttx/usb/usbdev.h +++ b/include/nuttx/usb/usbdev.h @@ -190,10 +190,21 @@ struct usbdev_devdescs_s #endif }; +struct usbdev_epinfo_s +{ + struct usb_epdesc_s desc; + uint16_t fssize; +#ifdef CONFIG_USBDEV_DUALSPEED + uint16_t hssize; +#endif + uint16_t reqnum; +}; + /* usbdev_devinfo_s - describes the low level bindings of an usb device */ struct usbdev_devinfo_s { + FAR const char *name; int ninterfaces; /* Number of interfaces in the configuration */ int ifnobase; /* Offset to Interface-IDs */ @@ -202,6 +213,7 @@ struct usbdev_devinfo_s int nendpoints; /* Number of Endpoints referenced in the following allay */ int epno[5]; /* Array holding the endpoint configuration for this device */ + FAR const struct usbdev_epinfo_s **epinfos; }; struct usbdevclass_driver_s; @@ -297,6 +309,7 @@ struct usbdev_ep_s uint8_t eplog; /* Logical endpoint address */ uint16_t maxpacket; /* Maximum packet size for this endpoint */ FAR void *priv; /* For use by class driver */ + FAR void *fs; /* USB fs device this ep belongs */ }; /* struct usbdev_s represents a usb device */ @@ -398,6 +411,20 @@ FAR struct usbdev_req_s *usbdev_allocreq(FAR struct usbdev_ep_s *ep, void usbdev_freereq(FAR struct usbdev_ep_s *ep, FAR struct usbdev_req_s *req); +/**************************************************************************** + * Name: usbdev_copy_epdesc + * + * Description: + * Copies the requested Endpoint Description into the buffer given. + * Returns the number of Bytes filled in ( sizeof(struct usb_epdesc_s) ). + * This function is provided by various classes. + * + ****************************************************************************/ + +void usbdev_copy_epdesc(FAR struct usb_epdesc_s *epdesc, + uint8_t epno, bool hispeed, + FAR const struct usbdev_epinfo_s *epinfo); + /**************************************************************************** * Name: usbdevclass_register *