* drivers/usbdev/cdcacm.c
*
* SPDX-License-Identifier: Apache-2.0
*
* 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 <nuttx/spinlock.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/spinlock.h>
#include <nuttx/kmalloc.h>
#include <nuttx/queue.h>
#include <nuttx/wdog.h>
#include <nuttx/arch.h>
#include <nuttx/serial/serial.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/cdc.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/cdcacm.h>
#include <nuttx/usb/usbdev_trace.h>
#include "cdcacm.h"
#ifdef CONFIG_CDCACM_COMPOSITE
# include <nuttx/usb/composite.h>
# include "composite.h"
#endif
* Pre-processor Definitions
****************************************************************************/
* second
*/
#define CDCACM_RXDELAY (CLK_TCK / 5)
* Private Types
****************************************************************************/
struct cdcacm_wrreq_s
{
FAR struct cdcacm_wrreq_s *flink;
FAR struct usbdev_req_s *req;
};
struct cdcacm_rdreq_s
{
FAR struct cdcacm_rdreq_s *flink;
FAR struct usbdev_req_s *req;
uint16_t offset;
};
struct cdcacm_dev_s
{
FAR struct uart_dev_s serdev;
FAR struct usbdev_s *usbdev;
uint8_t config;
uint8_t nwrq;
uint8_t nrdq;
uint8_t minor;
uint8_t ctrlline;
#ifdef CONFIG_CDCACM_IFLOWCONTROL
uint8_t serialstate;
bool iflow;
bool iactive;
bool upper;
#endif
bool rxenabled;
bool ispolling;
spinlock_t lock;
struct cdc_linecoding_s linecoding;
cdcacm_callback_t callback;
FAR struct usbdev_ep_s *epintin;
FAR struct usbdev_ep_s *epbulkin;
FAR struct usbdev_ep_s *epbulkout;
FAR struct usbdev_req_s *ctrlreq;
struct wdog_s rxfailsafe;
struct sq_queue_s txfree;
struct sq_queue_s rxpending;
struct usbdev_devinfo_s devinfo;
* be linked in a free list (txfree), and used to send requests to
* EPBULKIN; Read requests will be queued in the EBULKOUT.
*/
struct cdcacm_wrreq_s wrreqs[CONFIG_CDCACM_NWRREQS];
struct cdcacm_rdreq_s rdreqs[CONFIG_CDCACM_NRDREQS];
#ifdef CONFIG_CDCACM_DISABLE_RXBUF
FAR struct cdcacm_rdreq_s *rdcontainer;
#else
char rxbuffer[CONFIG_CDCACM_RXBUFSIZE];
#endif
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
FAR struct cdcacm_wrreq_s *wrcontainer;
#else
char txbuffer[CONFIG_CDCACM_TXBUFSIZE];
#endif
};
struct cdcacm_driver_s
{
struct usbdevclass_driver_s drvr;
FAR struct cdcacm_dev_s *dev;
};
struct cdcacm_alloc_s
{
struct cdcacm_dev_s dev;
struct cdcacm_driver_s drvr;
};
* Private Function Prototypes
****************************************************************************/
static int cdcacm_sndpacket(FAR struct cdcacm_dev_s *priv);
#ifdef CONFIG_CDCACM_DISABLE_RXBUF
static void cdcacm_rcvpacket(FAR struct cdcacm_dev_s *priv);
#endif
static int cdcacm_requeue_rdrequest(FAR struct cdcacm_dev_s *priv,
FAR struct cdcacm_rdreq_s *rdcontainer);
static int cdcacm_release_rxpending(FAR struct cdcacm_dev_s *priv);
static void cdcacm_rxtimeout(wdparm_t arg);
#ifdef CONFIG_CDCACM_IFLOWCONTROL
static int cdcacm_serialstate(FAR struct cdcacm_dev_s *priv);
#endif
static void cdcacm_resetconfig(FAR struct cdcacm_dev_s *priv);
static int cdcacm_epconfigure(FAR struct usbdev_ep_s *ep,
enum cdcacm_epdesc_e epid, bool last,
FAR struct usbdev_devinfo_s *devinfo,
uint8_t speed);
static int cdcacm_setconfig(FAR struct cdcacm_dev_s *priv,
uint8_t config);
static void cdcacm_ep0incomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static void cdcacm_rdcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static void cdcacm_wrcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static int cdcacm_bind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static void cdcacm_unbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static int cdcacm_setup(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 cdcacm_disconnect(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
#ifdef CONFIG_SERIAL_REMOVABLE
static void cdcacm_suspend(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static void cdcacm_resume(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
#endif
static int cdcuart_setup(FAR struct uart_dev_s *dev);
static void cdcuart_shutdown(FAR struct uart_dev_s *dev);
static int cdcuart_attach(FAR struct uart_dev_s *dev);
static void cdcuart_detach(FAR struct uart_dev_s *dev);
static int cdcuart_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
static void cdcuart_rxint(FAR struct uart_dev_s *dev, bool enable);
#ifdef CONFIG_SERIAL_IFLOWCONTROL
static bool cdcuart_rxflowcontrol(FAR struct uart_dev_s *dev,
unsigned int nbuffered, bool upper);
#endif
static void cdcuart_txint(FAR struct uart_dev_s *dev, bool enable);
static bool cdcuart_txempty(FAR struct uart_dev_s *dev);
static int cdcuart_release(FAR struct uart_dev_s *dev);
static bool cdcuart_rxavailable(FAR struct uart_dev_s *dev);
static ssize_t cdcuart_recvbuf(FAR struct uart_dev_s *dev,
FAR void *buf, size_t len);
static bool cdcuart_txready(FAR struct uart_dev_s *dev);
static ssize_t cdcuart_sendbuf(FAR struct uart_dev_s *dev,
FAR const void *buf, size_t len);
#ifndef CONFIG_CDCACM_DISABLE_TXBUF
static void cdcuart_dmasend(FAR struct uart_dev_s *dev);
#endif
#ifndef CONFIG_CDCACM_DISABLE_RXBUF
static void cdcuart_dmareceive(FAR struct uart_dev_s *dev);
#endif
* Private Data
****************************************************************************/
#ifdef CONFIG_SYSLOG_CDCACM
static FAR struct cdcacm_dev_s *g_syslog_cdcacm;
#endif
static const struct usbdevclass_driverops_s g_driverops =
{
cdcacm_bind,
cdcacm_unbind,
cdcacm_setup,
cdcacm_disconnect,
#ifdef CONFIG_SERIAL_REMOVABLE
cdcacm_suspend,
cdcacm_resume,
#else
NULL,
NULL,
#endif
};
static const struct uart_ops_s g_uartops =
{
cdcuart_setup,
cdcuart_shutdown,
cdcuart_attach,
cdcuart_detach,
cdcuart_ioctl,
NULL,
cdcuart_rxint,
cdcuart_rxavailable,
#ifdef CONFIG_SERIAL_IFLOWCONTROL
cdcuart_rxflowcontrol,
#endif
#ifdef CONFIG_SERIAL_TXDMA
#ifndef CONFIG_CDCACM_DISABLE_TXBUF
cdcuart_dmasend,
#else
NULL,
#endif
#endif
#ifdef CONFIG_SERIAL_RXDMA
#ifndef CONFIG_CDCACM_DISABLE_RXBUF
cdcuart_dmareceive,
#else
NULL,
#endif
NULL,
#endif
#ifdef CONFIG_SERIAL_TXDMA
NULL,
#endif
NULL,
cdcuart_txint,
cdcuart_txready,
cdcuart_txempty,
cdcuart_release,
cdcuart_recvbuf,
cdcuart_sendbuf
};
* Private Functions
****************************************************************************/
* Name: cdcuart_txready
*
* Description:
* Check if tx buf is ready or not.
*
****************************************************************************/
static bool cdcuart_txready(FAR struct uart_dev_s *dev)
{
FAR struct cdcacm_dev_s *priv = dev->priv;
FAR struct usbdev_ep_s *ep = priv->epbulkin;
if (sq_empty(&priv->txfree))
{
priv->ispolling = true;
EP_POLL(ep);
priv->ispolling = false;
}
return !sq_empty(&priv->txfree);
}
* Name: cdcuart_sendbuf
*
* Description:
* This function transfers the TX data into the request, and submits the
* requests to the USB controller.
*
****************************************************************************/
static ssize_t cdcuart_sendbuf(FAR struct uart_dev_s *dev,
FAR const void *buf, size_t len)
{
FAR struct cdcacm_dev_s *priv = dev->priv;
FAR struct usbdev_ep_s *ep = priv->epbulkin;
FAR struct cdcacm_wrreq_s *wrcontainer;
FAR struct usbdev_req_s *req;
irqstate_t flags;
size_t reqlen;
size_t nbytes;
int ret;
reqlen = MIN(CONFIG_CDCACM_BULKIN_REQLEN, ep->maxpacket);
flags = spin_lock_irqsave(&priv->lock);
wrcontainer = (FAR struct cdcacm_wrreq_s *)sq_remfirst(&priv->txfree);
req = wrcontainer->req;
priv->nwrq--;
spin_unlock_irqrestore(&priv->lock, flags);
nbytes = MIN(reqlen, len);
memcpy(req->buf, buf, nbytes);
req->len = nbytes;
req->priv = wrcontainer;
req->flags = USBDEV_REQFLAGS_NULLPKT;
priv->ispolling = true;
ret = EP_SUBMIT(ep, req);
priv->ispolling = false;
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL),
(uint16_t)-ret);
return ret;
}
return nbytes;
}
* Name: cdcacm_sndpacket
*
* Description:
* This function obtains write requests, transfers the TX data into the
* request, and submits the requests to the USB controller. This
* continues until either (1) there are no further packets available, or
* (2) there is no further data to send.
*
****************************************************************************/
static int cdcacm_sndpacket(FAR struct cdcacm_dev_s *priv)
{
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
FAR struct usbdev_ep_s *ep = priv->epbulkin;
FAR struct uart_dev_s *dev = &priv->serdev;
FAR struct cdcacm_wrreq_s *wrcontainer;
FAR struct usbdev_req_s *req;
irqstate_t flags;
int ret;
#endif
#ifdef CONFIG_DEBUG_FEATURES
if (priv == NULL)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return -EINVAL;
}
#endif
if (priv->ispolling)
{
goto out;
}
uinfo("head=%d tail=%d nwrq=%d empty=%d\n",
priv->serdev.xmit.head, priv->serdev.xmit.tail,
priv->nwrq, sq_empty(&priv->txfree));
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
if (priv->wrcontainer)
{
wrcontainer = priv->wrcontainer;
req = wrcontainer->req;
req->len = dev->xmit.head - dev->xmit.tail;
req->flags = USBDEV_REQFLAGS_NULLPKT;
req->priv = wrcontainer;
ret = EP_SUBMIT(ep, req);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL),
(uint16_t)-ret);
flags = spin_lock_irqsave_nopreempt(&priv->lock);
dev->xmit.head = 0;
dev->xmit.tail = 0;
uart_datasent(dev);
spin_unlock_irqrestore_nopreempt(&priv->lock, flags);
goto out;
}
}
flags = spin_lock_irqsave_nopreempt(&priv->lock);
priv->wrcontainer = NULL;
if (!sq_empty(&priv->txfree))
{
priv->wrcontainer = (FAR struct cdcacm_wrreq_s *)
sq_remfirst(&priv->txfree);
dev->xmit.buffer = (FAR char *)priv->wrcontainer->req->buf;
priv->nwrq--;
dev->xmit.head = 0;
dev->xmit.tail = 0;
uart_datasent(dev);
}
spin_unlock_irqrestore_nopreempt(&priv->lock, flags);
#else
if (!sq_empty(&priv->txfree))
{
uart_xmitchars_dma(&priv->serdev);
}
#endif
out:
return OK;
}
* Name: cdcuart_rxavailable
*
* Description:
* Check if data has been saved in rx buf.
*
****************************************************************************/
static bool cdcuart_rxavailable(FAR struct uart_dev_s *dev)
{
FAR struct cdcacm_dev_s *priv = dev->priv;
FAR struct usbdev_ep_s *ep = priv->epbulkout;
if (sq_empty(&priv->rxpending))
{
priv->ispolling = true;
EP_POLL(ep);
priv->ispolling = false;
}
return !sq_empty(&priv->rxpending);
}
* Name: cdcuart_recvbuf
*
* Description:
* This function handles the USB packet and provides the received data to
* the uart RX buffer.
*
****************************************************************************/
static ssize_t cdcuart_recvbuf(FAR struct uart_dev_s *dev,
FAR void *buf, size_t len)
{
FAR struct cdcacm_dev_s *priv = dev->priv;
FAR struct cdcacm_rdreq_s *rdcontainer;
FAR struct usbdev_req_s *req;
FAR uint8_t *reqbuf;
size_t reqlen;
size_t nbytes;
int ret;
rdcontainer = (FAR struct cdcacm_rdreq_s *)sq_peek(&priv->rxpending);
DEBUGASSERT(rdcontainer != NULL);
req = rdcontainer->req;
DEBUGASSERT(req != NULL);
reqbuf = &req->buf[rdcontainer->offset];
reqlen = req->xfrd - rdcontainer->offset;
nbytes = MIN(reqlen, len);
memcpy(buf, reqbuf, nbytes);
rdcontainer->offset += nbytes;
* pending RX list.
*/
if (rdcontainer->offset >= req->xfrd)
{
sq_remfirst(&priv->rxpending);
ret = cdcacm_requeue_rdrequest(priv, rdcontainer);
if (ret < 0)
{
return ret;
}
}
return nbytes;
}
* Name: cdcacm_requeue_rdrequest
*
* Description:
* Add any pending RX packets to the upper half serial drivers RX buffer.
*
****************************************************************************/
static int cdcacm_requeue_rdrequest(FAR struct cdcacm_dev_s *priv,
FAR struct cdcacm_rdreq_s *rdcontainer)
{
FAR struct usbdev_req_s *req;
FAR struct usbdev_ep_s *ep;
int ret;
DEBUGASSERT(priv != NULL && rdcontainer != NULL);
rdcontainer->offset = 0;
req = rdcontainer->req;
DEBUGASSERT(req != NULL);
ep = priv->epbulkout;
req->len = MIN(CONFIG_CDCACM_BULKOUT_REQLEN, ep->maxpacket);
ret = EP_SUBMIT(ep, req);
if (ret != OK)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT),
(uint16_t)-req->result);
}
return ret;
}
* Name: cdcacm_release_rxpending
*
* Description:
* Add any pending RX packets to the upper half serial drivers RX buffer.
*
****************************************************************************/
static int cdcacm_release_rxpending(FAR struct cdcacm_dev_s *priv)
{
irqstate_t flags;
int ret = -EBUSY;
* may be modified by interrupt level processing and, hence, interrupts
* must be disabled throughout the following.
*/
flags = spin_lock_irqsave_nopreempt(&priv->lock);
if (priv->ispolling)
{
goto out;
}
wd_cancel(&priv->rxfailsafe);
* effect, then pass the packet at the head of the pending RX packet list
* to the upper serial layer. Otherwise, let the packet continue to pend
* the priv->rxpending list until the upper serial layer is able to buffer
* it.
*/
#ifdef CONFIG_CDCACM_IFLOWCONTROL
if (priv->rxenabled && !priv->iactive)
#else
if (priv->rxenabled)
#endif
{
* no errors occur. NOTE that the priv->rxpending queue is accessed
* from interrupt level processing and, hence, interrupts must be
* disabled throughout the following.
*/
ret = OK;
#ifndef CONFIG_CDCACM_DISABLE_RXBUF
if (!sq_empty(&priv->rxpending))
{
uart_recvchars_dma(&priv->serdev);
}
#else
cdcacm_rcvpacket(priv);
#endif
}
* priv->rxpending. This could happen if either RX "interrupts" are
* disable, RX flow control is in effect of if the upper serial drivers
* RX buffer is full and cannot accept additional data.
*
* If/when the timer expires, cdcacm_release_rxpending() will be called
* the timer handler (at interrupt level).
*
* The timer may not be necessary, but it is a failsafe to be certain
* that data cannot stall in priv->rxpending.
*/
if (!sq_empty(&priv->rxpending))
{
wd_start(&priv->rxfailsafe, CDCACM_RXDELAY,
cdcacm_rxtimeout, (wdparm_t)priv);
}
out:
spin_unlock_irqrestore_nopreempt(&priv->lock, flags);
return ret;
}
* Name: cdcacm_rxtimeout
*
* Description:
* Timer expiration handler. Whenever cdcacm_release_rxpending()
* terminates with pending RX data in priv->rxpending, it will set a
* timer to recheck the queued RX data can be processed later. This
* failsafe timer may not be necessary, but this reduces my paranoia
* about stalls in the RX pending FIFO .
*
****************************************************************************/
static void cdcacm_rxtimeout(wdparm_t arg)
{
FAR struct cdcacm_dev_s *priv = (FAR struct cdcacm_dev_s *)arg;
DEBUGASSERT(priv != NULL);
cdcacm_release_rxpending(priv);
}
* Name: cdcacm_serialstate
*
* Description:
* Send the serial state message.
*
* 1. Format and send a request header with:
*
* bmRequestType:
* USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS |
* USB_REQ_RECIPIENT_INTERFACE
* bRequest: ACM_SERIAL_STATE
* wValue: 0
* wIndex: 0
* wLength: Length of data = 2
*
* 2. Followed by the notification data
*
****************************************************************************/
#ifdef CONFIG_CDCACM_IFLOWCONTROL
static int cdcacm_serialstate(FAR struct cdcacm_dev_s *priv)
{
FAR struct usbdev_ep_s *ep;
FAR struct usbdev_req_s *req;
FAR struct cdcacm_wrreq_s *wrcontainer;
FAR struct cdc_notification_s *notify;
irqstate_t flags;
int ret;
DEBUGASSERT(priv != NULL && priv->epintin != NULL);
#ifdef CONFIG_DEBUG_FEATURES
if (priv == NULL || priv->epintin == NULL)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return -EINVAL;
}
#endif
usbtrace(CDCACM_CLASSAPI_FLOWCONTROL, (uint16_t)priv->serialstate);
ep = priv->epintin;
flags = spin_lock_irqsave(&priv->lock);
wrcontainer = (FAR struct cdcacm_wrreq_s *)sq_remfirst(&priv->txfree);
if (wrcontainer == NULL)
{
ret = -ENOMEM;
spin_unlock_irqrestore(&priv->lock, flags);
goto errout_with_flags;
}
priv->nwrq--;
spin_unlock_irqrestore(&priv->lock, flags);
DEBUGASSERT(wrcontainer->req != NULL);
req = wrcontainer->req;
DEBUGASSERT(req->buf != NULL);
notify = (FAR struct cdc_notification_s *)req->buf;
notify->type = (USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS |
USB_REQ_RECIPIENT_INTERFACE);
notify->notification = ACM_SERIAL_STATE;
notify->value[0] = 0;
notify->value[1] = 0;
notify->index[0] = 0;
notify->index[1] = 0;
notify->len[0] = 2;
notify->len[1] = 0;
notify->data[0] = priv->serialstate;
notify->data[1] = 0;
req->len = SIZEOF_NOTIFICATION_S(2);
req->priv = wrcontainer;
req->flags = USBDEV_REQFLAGS_NULLPKT;
ret = EP_SUBMIT(ep, req);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL), (uint16_t)-ret);
}
errout_with_flags:
priv->serialstate &= CDC_UART_CONSISTENT;
return ret;
}
#endif
* Name: cdcacm_resetconfig
*
* Description:
* Mark the device as not configured and disable all endpoints.
*
****************************************************************************/
static void cdcacm_resetconfig(FAR struct cdcacm_dev_s *priv)
{
* it needs to be push them to upper half serial drivers RX buffer.
*/
if (priv->nrdq != 0)
{
cdcacm_release_rxpending(priv);
priv->nrdq = 0;
}
if (priv->config != CDCACM_CONFIGIDNONE)
{
priv->config = CDCACM_CONFIGIDNONE;
* connection.
*/
#ifdef CONFIG_SERIAL_REMOVABLE
uart_connected(&priv->serdev, false);
#endif
* transfers.
*/
#ifdef CONFIG_CDCACM_HAVE_EPINTIN
EP_DISABLE(priv->epintin);
#endif
EP_DISABLE(priv->epbulkin);
EP_DISABLE(priv->epbulkout);
}
}
* Name: cdcacm_epconfigure
*
* Description:
* Configure one endpoint.
*
****************************************************************************/
static int cdcacm_epconfigure(FAR struct usbdev_ep_s *ep,
enum cdcacm_epdesc_e epid, bool last,
FAR struct usbdev_devinfo_s *devinfo,
uint8_t speed)
{
struct usb_ss_epdesc_s epdesc;
cdcacm_copy_epdesc(epid, &epdesc.epdesc, devinfo, speed);
return EP_CONFIGURE(ep, &epdesc.epdesc, last);
}
* Name: cdcacm_setconfig
*
* Description:
* Set the device configuration by allocating and configuring endpoints and
* by allocating and queue read and write requests.
*
****************************************************************************/
static int cdcacm_setconfig(FAR struct cdcacm_dev_s *priv, uint8_t config)
{
FAR struct usbdev_req_s *req;
int i;
int ret = 0;
#ifdef CONFIG_DEBUG_FEATURES
if (priv == NULL)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return -EINVAL;
}
#endif
if (config == priv->config)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALREADYCONFIGURED), 0);
return 0;
}
cdcacm_resetconfig(priv);
if (config == CDCACM_CONFIGIDNONE)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGNONE), 0);
return 0;
}
if (config != CDCACM_CONFIGID)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGIDBAD), 0);
return -EINVAL;
}
#ifdef CONFIG_CDCACM_HAVE_EPINTIN
ret = cdcacm_epconfigure(priv->epintin, CDCACM_EPINTIN, false,
&priv->devinfo, priv->usbdev->speed);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINCONFIGFAIL), 0);
goto errout;
}
priv->epintin->priv = priv;
#endif
ret = cdcacm_epconfigure(priv->epbulkin, CDCACM_EPBULKIN, false,
&priv->devinfo, priv->usbdev->speed);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINCONFIGFAIL), 0);
goto errout;
}
priv->epbulkin->priv = priv;
ret = cdcacm_epconfigure(priv->epbulkout, CDCACM_EPBULKOUT, true,
&priv->devinfo, priv->usbdev->speed);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTCONFIGFAIL), 0);
goto errout;
}
priv->epbulkout->priv = priv;
DEBUGASSERT(priv->nrdq == 0);
for (i = 0; i < CONFIG_CDCACM_NRDREQS; i++)
{
req = priv->rdreqs[i].req;
req->callback = cdcacm_rdcomplete;
ret = EP_SUBMIT(priv->epbulkout, req);
if (ret != OK)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT),
(uint16_t)-ret);
goto errout;
}
priv->nrdq++;
}
priv->config = config;
#ifdef CONFIG_SERIAL_REMOVABLE
uart_connected(&priv->serdev, true);
#endif
return OK;
errout:
cdcacm_resetconfig(priv);
return ret;
}
* Name: cdcacm_ep0incomplete
*
* Description:
* Handle completion of EP0 control operations
*
****************************************************************************/
static void cdcacm_ep0incomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
if (req->result || req->xfrd != req->len)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_REQRESULT),
(uint16_t)-req->result);
}
}
* Name: cdcacm_rdcomplete
*
* Description:
* Handle completion of read request on the bulk OUT endpoint. This
* is handled like the receipt of serial data on the "UART"
*
****************************************************************************/
static void cdcacm_rdcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
FAR struct cdcacm_rdreq_s *rdcontainer;
FAR struct cdcacm_dev_s *priv;
irqstate_t flags;
#ifdef CONFIG_DEBUG_FEATURES
if (!ep || !ep->priv || !req)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
priv = (FAR struct cdcacm_dev_s *)ep->priv;
rdcontainer = (FAR struct cdcacm_rdreq_s *)req->priv;
DEBUGASSERT(rdcontainer != NULL);
switch (req->result)
{
case 0:
{
usbtrace(TRACE_CLASSRDCOMPLETE, priv->nrdq);
flags = spin_lock_irqsave(&priv->lock);
rdcontainer->offset = 0;
sq_addlast((FAR sq_entry_t *)rdcontainer, &priv->rxpending);
spin_unlock_irqrestore(&priv->lock, flags);
* list
*/
cdcacm_release_rxpending(priv);
}
break;
case -ESHUTDOWN:
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSHUTDOWN), 0);
flags = spin_lock_irqsave(&priv->lock);
if (priv->nrdq != 0)
{
priv->nrdq--;
}
spin_unlock_irqrestore(&priv->lock, flags);
}
break;
default:
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDUNEXPECTED),
(uint16_t)-req->result);
cdcacm_requeue_rdrequest(priv, rdcontainer);
break;
}
}
}
* Name: cdcacm_wrcomplete
*
* Description:
* Handle completion of write request. This function probably executes
* in the context of an interrupt handler.
*
****************************************************************************/
static void cdcacm_wrcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
FAR struct cdcacm_dev_s *priv;
FAR struct cdcacm_wrreq_s *wrcontainer;
irqstate_t flags;
#ifdef CONFIG_DEBUG_FEATURES
if (!ep || !ep->priv || !req || !req->priv)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
priv = (FAR struct cdcacm_dev_s *)ep->priv;
wrcontainer = (FAR struct cdcacm_wrreq_s *)req->priv;
flags = spin_lock_irqsave(&priv->lock);
sq_addlast((FAR sq_entry_t *)wrcontainer, &priv->txfree);
priv->nwrq++;
spin_unlock_irqrestore(&priv->lock, flags);
* condition
*/
switch (req->result)
{
case OK:
{
usbtrace(TRACE_CLASSWRCOMPLETE, priv->nwrq);
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
if (priv->wrcontainer == NULL)
#endif
{
cdcacm_sndpacket(priv);
}
}
break;
case -ESHUTDOWN:
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRSHUTDOWN), priv->nwrq);
}
break;
default:
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRUNEXPECTED),
(uint16_t)-req->result);
}
break;
}
}
* USB Class Driver Methods
****************************************************************************/
* Name: cdcacm_bind
*
* Description:
* Invoked when the driver is bound to a USB device driver
*
****************************************************************************/
static int cdcacm_bind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct cdcacm_dev_s *priv =
((FAR struct cdcacm_driver_s *)driver)->dev;
FAR struct cdcacm_wrreq_s *wrcontainer;
FAR struct cdcacm_rdreq_s *rdcontainer;
irqstate_t flags;
size_t reqlen;
int ret;
int i;
usbtrace(TRACE_CLASSBIND, 0);
priv->usbdev = dev;
* can be recovered in ep0 completion events (Unless we are part of
* a composite device and, in that case, the composite device owns
* EP0).
*/
#ifndef CONFIG_CDCACM_COMPOSITE
dev->ep0->priv = priv;
#endif
priv->ctrlreq = usbdev_allocreq(dev->ep0, CDCACM_MXDESCLEN);
if (priv->ctrlreq == NULL)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCCTRLREQ), 0);
ret = -ENOMEM;
goto errout;
}
priv->ctrlreq->callback = cdcacm_ep0incomplete;
* until the SET CONFIGURATION request is processed in cdcacm_setconfig.
* This is done here because there may be calls to kmm_malloc and the SET
* CONFIGURATION processing probably occurs within interrupt handling
* logic where kmm_malloc calls will fail.
*/
#ifdef CONFIG_CDCACM_HAVE_EPINTIN
priv->epintin = DEV_ALLOCEP(dev, CDCACM_MKEPINTIN(&priv->devinfo),
true, USB_EP_ATTR_XFER_INT);
if (!priv->epintin)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINALLOCFAIL), 0);
ret = -ENODEV;
goto errout;
}
priv->epintin->priv = priv;
#endif
priv->epbulkin = DEV_ALLOCEP(dev, CDCACM_MKEPBULKIN(&priv->devinfo),
true, USB_EP_ATTR_XFER_BULK);
if (!priv->epbulkin)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINALLOCFAIL), 0);
ret = -ENODEV;
goto errout;
}
priv->epbulkin->priv = priv;
priv->epbulkout = DEV_ALLOCEP(dev, CDCACM_MKEPBULKOUT(&priv->devinfo),
false, USB_EP_ATTR_XFER_BULK);
if (!priv->epbulkout)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTALLOCFAIL), 0);
ret = -ENODEV;
goto errout;
}
priv->epbulkout->priv = priv;
#if defined(CONFIG_USBDEV_SUPERSPEED)
if (dev->speed == USB_SPEED_SUPER ||
dev->speed == USB_SPEED_SUPER_PLUS)
{
if (CONFIG_CDCACM_EPBULKOUT_MAXBURST < USB_SS_BULK_EP_MAXBURST)
{
reqlen = CONFIG_CDCACM_EPBULKOUT_SSSIZE *
(CONFIG_CDCACM_EPBULKOUT_MAXBURST + 1);
}
else
{
reqlen = CONFIG_CDCACM_EPBULKOUT_SSSIZE *
USB_SS_BULK_EP_MAXBURST;
}
}
else
#endif
#if defined(CONFIG_USBDEV_DUALSPEED)
if (dev->speed == USB_SPEED_HIGH)
{
reqlen = CONFIG_CDCACM_EPBULKOUT_HSSIZE;
}
else
#endif
{
reqlen = CONFIG_CDCACM_EPBULKOUT_FSSIZE;
}
if (CONFIG_CDCACM_BULKOUT_REQLEN > reqlen)
{
reqlen = CONFIG_CDCACM_BULKOUT_REQLEN;
}
for (i = 0; i < CONFIG_CDCACM_NRDREQS; i++)
{
rdcontainer = &priv->rdreqs[i];
rdcontainer->req = usbdev_allocreq(priv->epbulkout, reqlen);
if (rdcontainer->req == NULL)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), -ENOMEM);
ret = -ENOMEM;
goto errout;
}
rdcontainer->offset = 0;
rdcontainer->req->priv = rdcontainer;
rdcontainer->req->callback = cdcacm_rdcomplete;
}
* buffer size should be larger than a full build IN packet. Otherwise,
* we will send a bogus null packet at the end of each packet.
*
* Pick the larger of the max packet size and the configured request size.
*
* NOTE: These write requests are sized for the bulk IN endpoint but are
* shared with interrupt IN endpoint which does not need a large buffer.
*/
#if defined(CONFIG_USBDEV_SUPERSPEED)
if (dev->speed == USB_SPEED_SUPER ||
dev->speed == USB_SPEED_SUPER_PLUS)
{
if (CONFIG_CDCACM_EPBULKIN_MAXBURST < USB_SS_BULK_EP_MAXBURST)
{
reqlen = CONFIG_CDCACM_EPBULKOUT_SSSIZE *
(CONFIG_CDCACM_EPBULKIN_MAXBURST + 1);
}
else
{
reqlen = CONFIG_CDCACM_EPBULKOUT_SSSIZE *
USB_SS_BULK_EP_MAXBURST;
}
}
else
#endif
#if defined(CONFIG_USBDEV_DUALSPEED)
if (dev->speed == USB_SPEED_HIGH)
{
reqlen = CONFIG_CDCACM_EPBULKIN_HSSIZE;
}
else
#endif
{
reqlen = CONFIG_CDCACM_EPBULKIN_FSSIZE;
}
if (CONFIG_CDCACM_BULKIN_REQLEN > reqlen)
{
reqlen = CONFIG_CDCACM_BULKIN_REQLEN;
}
for (i = 0; i < CONFIG_CDCACM_NWRREQS; i++)
{
wrcontainer = &priv->wrreqs[i];
wrcontainer->req = usbdev_allocreq(priv->epbulkin, reqlen);
if (wrcontainer->req == NULL)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRALLOCREQ), -ENOMEM);
ret = -ENOMEM;
goto errout;
}
wrcontainer->req->priv = wrcontainer;
wrcontainer->req->callback = cdcacm_wrcomplete;
flags = spin_lock_irqsave(&priv->lock);
sq_addlast((FAR sq_entry_t *)wrcontainer, &priv->txfree);
priv->nwrq++;
spin_unlock_irqrestore(&priv->lock, flags);
}
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
flags = spin_lock_irqsave(&priv->lock);
priv->wrcontainer = (FAR struct cdcacm_wrreq_s *)
sq_remfirst(&priv->txfree);
priv->serdev.xmit.buffer = (FAR char *)priv->wrcontainer->req->buf;
priv->serdev.xmit.size = reqlen + 1;
priv->nwrq--;
spin_unlock_irqrestore(&priv->lock, flags);
#endif
* composite device)
*/
#ifndef CONFIG_CDCACM_COMPOSITE
#ifdef CONFIG_USBDEV_SELFPOWERED
DEV_SETSELFPOWERED(dev);
#endif
* part of a composite device)
*/
DEV_CONNECT(dev);
#endif
return OK;
errout:
cdcacm_unbind(driver, dev);
return ret;
}
* Name: cdcacm_unbind
*
* Description:
* Invoked when the driver is unbound from a USB device driver
*
****************************************************************************/
static void cdcacm_unbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct cdcacm_dev_s *priv;
FAR struct cdcacm_wrreq_s *wrcontainer;
FAR struct cdcacm_rdreq_s *rdcontainer;
irqstate_t flags;
int i;
usbtrace(TRACE_CLASSUNBIND, 0);
#ifdef CONFIG_DEBUG_FEATURES
if (!driver || !dev)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
priv = ((FAR struct cdcacm_driver_s *)driver)->dev;
#ifdef CONFIG_DEBUG_FEATURES
if (!priv)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
return;
}
#endif
if (priv != NULL)
{
* we were terminated gracefully, then the configuration should
* already have been reset. If not, then calling cdcacm_resetconfig
* should cause the endpoints to immediately terminate all
* transfers and return the requests to us (with result == -ESHUTDOWN)
*/
cdcacm_resetconfig(priv);
if (priv->ctrlreq != NULL)
{
usbdev_freereq(dev->ep0, priv->ctrlreq);
priv->ctrlreq = NULL;
}
* been returned to the free list at this time -- we don't check)
*/
DEBUGASSERT(priv->nrdq == 0);
for (i = 0; i < CONFIG_CDCACM_NRDREQS; i++)
{
rdcontainer = &priv->rdreqs[i];
if (rdcontainer->req)
{
usbdev_freereq(priv->epbulkout, rdcontainer->req);
rdcontainer->req = NULL;
}
}
* of them)
*/
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
nxmutex_lock(&priv->serdev.xmit.lock);
#endif
flags = spin_lock_irqsave(&priv->lock);
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
DEBUGASSERT(priv->nwrq >= CONFIG_CDCACM_NWRREQS - 1);
if (priv->wrcontainer)
{
sq_addlast((FAR sq_entry_t *)priv->wrcontainer, &priv->txfree);
priv->wrcontainer = NULL;
priv->nwrq++;
}
#else
DEBUGASSERT(priv->nwrq == CONFIG_CDCACM_NWRREQS);
#endif
while (!sq_empty(&priv->txfree))
{
wrcontainer = (struct cdcacm_wrreq_s *)sq_remfirst(&priv->txfree);
if (wrcontainer->req != NULL)
{
usbdev_freereq(priv->epbulkin, wrcontainer->req);
priv->nwrq--;
}
}
DEBUGASSERT(priv->nwrq == 0);
spin_unlock_irqrestore(&priv->lock, flags);
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
nxmutex_unlock(&priv->serdev.xmit.lock);
#endif
#ifdef CONFIG_CDCACM_HAVE_EPINTIN
if (priv->epintin)
{
DEV_FREEEP(dev, priv->epintin);
priv->epintin = NULL;
}
#endif
if (priv->epbulkout)
{
DEV_FREEEP(dev, priv->epbulkout);
priv->epbulkout = NULL;
}
if (priv->epbulkin)
{
DEV_FREEEP(dev, priv->epbulkin);
priv->epbulkin = NULL;
}
priv->serdev.xmit.head = 0;
priv->serdev.xmit.tail = 0;
}
}
* Name: cdcacm_setup
*
* Description:
* Invoked for ep0 control requests. This function probably executes
* in the context of an interrupt handler.
*
****************************************************************************/
static int cdcacm_setup(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 cdcacm_dev_s *priv;
FAR struct usbdev_req_s *ctrlreq;
uint16_t value;
uint16_t index;
uint16_t len;
int ret = -EOPNOTSUPP;
#ifdef CONFIG_DEBUG_FEATURES
if (!driver || !dev || !ctrl)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return -EINVAL;
}
#endif
usbtrace(TRACE_CLASSSETUP, ctrl->req);
priv = ((FAR struct cdcacm_driver_s *)driver)->dev;
#ifdef CONFIG_DEBUG_FEATURES
if (!priv || !priv->ctrlreq)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
return -ENODEV;
}
#endif
ctrlreq = priv->ctrlreq;
value = GETUINT16(ctrl->value);
index = GETUINT16(ctrl->index);
len = GETUINT16(ctrl->len);
uinfo("type=%02x req=%02x value=%04x index=%04x len=%04x\n",
ctrl->type, ctrl->req, value, index, len);
if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD)
{
* Standard Requests
**********************************************************************/
switch (ctrl->req)
{
case USB_REQ_GETDESCRIPTOR:
{
* and the descriptor index in the LS byte (order is little
* endian)
*/
switch (ctrl->value[1])
{
* device, then the device descriptor is provided by logic in
* the composite device implementation.
*/
#ifndef CONFIG_CDCACM_COMPOSITE
case USB_DESC_TYPE_DEVICE:
{
ret = usbdev_copy_devdesc(ctrlreq->buf,
cdcacm_getdevdesc(),
dev->speed);
}
break;
#endif
* device, then the device qualifier descriptor is provided by
* logic in the composite device implementation.
*/
#if !defined(CONFIG_CDCACM_COMPOSITE) && defined(CONFIG_USBDEV_DUALSPEED)
case USB_DESC_TYPE_DEVICEQUALIFIER:
{
ret = USB_SIZEOF_QUALDESC;
memcpy(ctrlreq->buf, cdcacm_getqualdesc(), ret);
}
break;
case USB_DESC_TYPE_OTHERSPEEDCONFIG:
#endif
* device, then the configuration descriptor is provided by
* logic in the composite device implementation.
*/
#ifndef CONFIG_CDCACM_COMPOSITE
case USB_DESC_TYPE_CONFIG:
{
ret = cdcacm_mkcfgdesc(ctrlreq->buf, &priv->devinfo,
dev->speed, ctrl->value[1]);
}
break;
#endif
* device, then the language string descriptor is provided by
* logic in the composite device implementation.
*/
#ifndef CONFIG_CDCACM_COMPOSITE
case USB_DESC_TYPE_STRING:
{
ret =
cdcacm_mkstrdesc(ctrl->value[0],
(FAR struct usb_strdesc_s *)
ctrlreq->buf);
}
break;
#endif
default:
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_GETUNKNOWNDESC),
value);
}
break;
}
}
break;
case USB_REQ_SETCONFIGURATION:
{
if (ctrl->type == 0)
{
ret = cdcacm_setconfig(priv, value);
}
}
break;
* then the overall composite class configuration is managed by logic
* in the composite device implementation.
*/
#ifndef CONFIG_CDCACM_COMPOSITE
case USB_REQ_GETCONFIGURATION:
{
if (ctrl->type == USB_DIR_IN)
{
*(FAR uint8_t *)ctrlreq->buf = priv->config;
ret = 1;
}
}
break;
#endif
case USB_REQ_SETINTERFACE:
{
if (ctrl->type == USB_REQ_RECIPIENT_INTERFACE &&
priv->config == CDCACM_CONFIGID)
{
if ((index == priv->devinfo.ifnobase &&
value == CDCACM_NOTALTIFID) ||
(index == (priv->devinfo.ifnobase + 1) &&
value == CDCACM_DATAALTIFID))
{
cdcacm_resetconfig(priv);
cdcacm_setconfig(priv, CDCACM_CONFIGID);
ret = 0;
}
}
}
break;
case USB_REQ_GETINTERFACE:
{
if (ctrl->type == (USB_DIR_IN | USB_REQ_RECIPIENT_INTERFACE) &&
priv->config == CDCACM_CONFIGIDNONE)
{
if ((index == priv->devinfo.ifnobase &&
value == CDCACM_NOTALTIFID) ||
(index == (priv->devinfo.ifnobase + 1) &&
value == CDCACM_DATAALTIFID))
{
*(FAR uint8_t *) ctrlreq->buf = value;
ret = 1;
}
else
{
ret = -EDOM;
}
}
}
break;
default:
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDSTDREQ),
ctrl->req);
break;
}
}
else if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_CLASS)
{
* CDC ACM-Specific Requests
**********************************************************************/
switch (ctrl->req)
{
* and number-of-character bits. (Optional)
*/
case ACM_GET_LINE_CODING:
{
if (ctrl->type == (USB_DIR_IN | USB_REQ_TYPE_CLASS |
USB_REQ_RECIPIENT_INTERFACE) &&
index == priv->devinfo.ifnobase)
{
* structure.
*/
memcpy(ctrlreq->buf, &priv->linecoding,
SIZEOF_CDC_LINECODING);
ret = SIZEOF_CDC_LINECODING;
}
else
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ),
ctrl->type);
}
}
break;
* number-of-character bits. (Optional)
*/
case ACM_SET_LINE_CODING:
{
if (ctrl->type == (USB_DIR_OUT | USB_REQ_TYPE_CLASS |
USB_REQ_RECIPIENT_INTERFACE) &&
len == SIZEOF_CDC_LINECODING &&
index == priv->devinfo.ifnobase)
{
* NOTE: that this is conditional now because not all device
* controller drivers supported provision of EP0 OUT data
* with the setup command.
*/
if (dataout && len <= SIZEOF_CDC_LINECODING)
{
memcpy(&priv->linecoding,
dataout, SIZEOF_CDC_LINECODING);
}
ret = 0;
* info, then callout now.
*/
if (priv->callback)
{
priv->callback(CDCACM_EVENT_LINECODING);
}
}
else
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ),
ctrl->type);
}
}
break;
* device the DTE device is now present. (Optional)
*/
case ACM_SET_CTRL_LINE_STATE:
{
if (ctrl->type == (USB_DIR_OUT | USB_REQ_TYPE_CLASS |
USB_REQ_RECIPIENT_INTERFACE) &&
index == priv->devinfo.ifnobase)
{
* structure. Only bits 0 and 1 have meaning. Respond with
* a zero length packet.
*/
priv->ctrlline = value & 3;
ret = 0;
* status info, then call out now.
*/
if (priv->callback)
{
priv->callback(CDCACM_EVENT_CTRLLINE);
}
}
else
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ),
ctrl->type);
}
}
break;
case ACM_SEND_BREAK:
{
if (ctrl->type == (USB_DIR_OUT | USB_REQ_TYPE_CLASS |
USB_REQ_RECIPIENT_INTERFACE) &&
index == priv->devinfo.ifnobase)
{
* request, then call out now. Respond with a zero length
* packet.
*/
ret = 0;
if (priv->callback)
{
priv->callback(CDCACM_EVENT_SENDBREAK);
}
}
else
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ),
ctrl->type);
}
}
break;
default:
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ),
ctrl->req);
break;
}
}
else
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDTYPE), ctrl->type);
}
* value (ret < 0), the USB driver will stall.
*/
if (ret >= 0)
{
ctrlreq->len = MIN(len, ret);
ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT;
* indirectly in the case where this class is a member of a composite
* device.
*/
#ifndef CONFIG_CDCACM_COMPOSITE
ret = EP_SUBMIT(dev->ep0, ctrlreq);
#else
ret = composite_ep0submit(driver, dev, ctrlreq, ctrl);
#endif
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPRESPQ), (uint16_t)-ret);
ctrlreq->result = OK;
cdcacm_ep0incomplete(dev->ep0, ctrlreq);
}
}
return ret;
}
* Name: cdcacm_disconnect
*
* 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 cdcacm_disconnect(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct cdcacm_dev_s *priv;
irqstate_t flags;
usbtrace(TRACE_CLASSDISCONNECT, 0);
#ifdef CONFIG_DEBUG_FEATURES
if (!driver || !dev || !dev->ep0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
priv = ((FAR struct cdcacm_driver_s *)driver)->dev;
#ifdef CONFIG_DEBUG_FEATURES
if (!priv)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
return;
}
#endif
* connection.
*/
#ifdef CONFIG_SERIAL_REMOVABLE
uart_connected(&priv->serdev, false);
#endif
cdcacm_resetconfig(priv);
flags = spin_lock_irqsave(&priv->lock);
priv->serdev.xmit.head = 0;
priv->serdev.xmit.tail = 0;
spin_unlock_irqrestore(&priv->lock, flags);
* re-enumerated (unless we are part of a composite device)
*/
#ifndef CONFIG_CDCACM_COMPOSITE
DEV_CONNECT(dev);
#endif
}
* Name: cdcacm_suspend
*
* Description:
* Handle the USB suspend event.
*
****************************************************************************/
#ifdef CONFIG_SERIAL_REMOVABLE
static void cdcacm_suspend(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct cdcacm_dev_s *priv;
usbtrace(TRACE_CLASSSUSPEND, 0);
#ifdef CONFIG_DEBUG_FEATURES
if (!driver || !dev)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
priv = ((FAR struct cdcacm_driver_s *)driver)->dev;
uart_connected(&priv->serdev, false);
}
#endif
* Name: cdcacm_resume
*
* Description:
* Handle the USB resume event.
*
****************************************************************************/
#ifdef CONFIG_SERIAL_REMOVABLE
static void cdcacm_resume(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct cdcacm_dev_s *priv;
usbtrace(TRACE_CLASSRESUME, 0);
#ifdef CONFIG_DEBUG_FEATURES
if (!driver || !dev)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
priv = ((FAR struct cdcacm_driver_s *)driver)->dev;
if (priv->config != CDCACM_CONFIGIDNONE)
{
uart_connected(&priv->serdev, true);
}
}
#endif
* Serial Device Methods
****************************************************************************/
* Name: cdcuart_setup
*
* Description:
* This method is called the first time that the serial port is opened.
*
****************************************************************************/
static int cdcuart_setup(FAR struct uart_dev_s *dev)
{
FAR struct cdcacm_dev_s *priv;
usbtrace(CDCACM_CLASSAPI_SETUP, 0);
#ifdef CONFIG_DEBUG_FEATURES
if (!dev || !dev->priv)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return -EINVAL;
}
#endif
priv = (FAR struct cdcacm_dev_s *)dev->priv;
if (priv->config == CDCACM_CONFIGIDNONE)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SETUPNOTCONNECTED), 0);
return -ENOTCONN;
}
return OK;
}
* Name: cdcuart_shutdown
*
* Description:
* This method is called when the serial port is closed. This operation
* is very simple for the USB serial back-end because the serial driver
* has already assured that the TX data has full drained -- it calls
* cdcuart_txempty() until that function returns true before calling this
* function.
*
****************************************************************************/
static void cdcuart_shutdown(FAR struct uart_dev_s *dev)
{
usbtrace(CDCACM_CLASSAPI_SHUTDOWN, 0);
#ifdef CONFIG_DEBUG_FEATURES
if (!dev || !dev->priv)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
}
#endif
}
* Name: cdcuart_attach
*
* Description:
* Does not apply to the USB serial class device
*
****************************************************************************/
static int cdcuart_attach(FAR struct uart_dev_s *dev)
{
usbtrace(CDCACM_CLASSAPI_ATTACH, 0);
return OK;
}
* Name: cdcuart_detach
*
* Description:
* Does not apply to the USB serial class device
*
****************************************************************************/
static void cdcuart_detach(FAR struct uart_dev_s *dev)
{
usbtrace(CDCACM_CLASSAPI_DETACH, 0);
}
* Name: cdcuart_ioctl
*
* Description:
* All ioctl calls will be routed through this method
*
****************************************************************************/
static int cdcuart_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR struct cdcacm_dev_s *priv = inode->i_private;
#if defined(CONFIG_CDCACM_DISABLE_TXBUF) || defined(CONFIG_CDCACM_DISABLE_RXBUF)
FAR struct uart_dev_s *serdev = &priv->serdev;
#endif
int ret = OK;
switch (cmd)
{
#ifdef CONFIG_CDCACM_DISABLE_RXBUF
* (without waiting)
*/
case FIONREAD:
{
FAR struct cdcacm_rdreq_s *rdcontainer;
FAR sq_entry_t *entry;
int count;
irqstate_t flags = spin_lock_irqsave(&priv->lock);
count = serdev->recv.head - serdev->recv.tail;
sq_for_every(&priv->rxpending, entry)
{
rdcontainer = (FAR struct cdcacm_rdreq_s *)entry;
count += rdcontainer->req->xfrd;
}
spin_unlock_irqrestore(&priv->lock, flags);
*(FAR int *)((uintptr_t)arg) = count;
}
break;
#endif
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
* buffer.
*/
case FIONWRITE:
{
FAR struct cdcacm_wrreq_s *wrcontainer;
FAR sq_entry_t *entry;
int count;
int i;
irqstate_t flags = spin_lock_irqsave(&priv->lock);
count = serdev->xmit.head - serdev->xmit.tail;
if (priv->nwrq < (CONFIG_CDCACM_NWRREQS - 1))
{
for (i = 0; i < CONFIG_CDCACM_NWRREQS; i++)
{
sq_for_every(&priv->txfree, entry)
{
wrcontainer = (FAR struct cdcacm_wrreq_s *)entry;
if (&priv->wrreqs[i] == wrcontainer)
{
continue;
}
else if (&priv->wrreqs[i] != priv->wrcontainer)
{
count += priv->wrreqs[i].req->len;
}
}
}
}
spin_unlock_irqrestore(&priv->lock, flags);
*(FAR int *)((uintptr_t)arg) = count;
}
break;
case FIONSPACE:
{
FAR sq_entry_t *entry;
int count = 0;
irqstate_t flags = spin_lock_irqsave(&priv->lock);
if (serdev->xmit.head == 0)
{
count = serdev->xmit.size - 1;
}
sq_for_every(&priv->txfree, entry)
{
count += serdev->xmit.size - 1;
}
spin_unlock_irqrestore(&priv->lock, flags);
*(FAR int *)((uintptr_t)arg) = count;
}
break;
#endif
case TCFLSH:
{
ret = -ENOTTY;
#ifdef CONFIG_CDCACM_DISABLE_RXBUF
if (arg == TCIFLUSH || arg == TCIOFLUSH)
{
FAR struct cdcacm_rdreq_s *rdcontainer;
ret = OK;
irqstate_t flags = spin_lock_irqsave(&priv->lock);
if (priv->rdcontainer)
{
sq_addlast((FAR sq_entry_t *)priv->rdcontainer,
&priv->rxpending);
priv->rdcontainer = NULL;
}
while (!sq_empty(&priv->rxpending))
{
rdcontainer = (FAR struct cdcacm_rdreq_s *)
sq_remfirst(&priv->rxpending);
ret = cdcacm_requeue_rdrequest(priv, rdcontainer);
}
serdev->recv.head = 0;
serdev->recv.tail = 0;
spin_unlock_irqrestore(&priv->lock, flags);
#ifdef CONFIG_SERIAL_IFLOWCONTROL
uart_rxflowcontrol(serdev, 0, false);
#endif
}
#endif
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
if (arg == TCOFLUSH || arg == TCIOFLUSH)
{
irqstate_t flags = spin_lock_irqsave_nopreempt(&priv->lock);
ret = OK;
if (priv->wrcontainer)
{
serdev->xmit.head = 0;
serdev->xmit.tail = 0;
uart_datasent(serdev);
}
else if(priv->nwrq > 0)
{
priv->wrcontainer = (FAR struct cdcacm_wrreq_s *)
sq_remfirst(&priv->txfree);
serdev->xmit.buffer =
(FAR char *)priv->wrcontainer->req->buf;
priv->nwrq--;
serdev->xmit.head = 0;
serdev->xmit.tail = 0;
uart_datasent(serdev);
}
else
{
ret = -EBUSY;
}
spin_unlock_irqrestore_nopreempt(&priv->lock, flags);
}
#endif
}
break;
* Register a callback for serial event notification. Argument:
* cdcacm_callback_t. See cdcacm_callback_t type definition below.
* NOTE: The callback will most likely invoked at the interrupt level.
* The called back function should, therefore, limit its operations to
* invoking some kind of IPC to handle the serial event in some normal
* task environment.
*/
case CAIOC_REGISTERCB:
{
priv->callback = (cdcacm_callback_t)((uintptr_t)arg);
}
break;
* Get current line coding. Argument: struct cdc_linecoding_s*.
* See include/nuttx/usb/cdc.h for structure definition. This IOCTL
* should be called to get the data associated with the
* CDCACM_EVENT_LINECODING event).
*/
case CAIOC_GETLINECODING:
{
FAR struct cdc_linecoding_s *ptr =
(FAR struct cdc_linecoding_s *)((uintptr_t)arg);
if (ptr != NULL)
{
memcpy(ptr, &priv->linecoding, sizeof(struct cdc_linecoding_s));
}
else
{
ret = -EINVAL;
}
}
break;
* Get control line status bits. Argument FAR int*. See
* include/nuttx/usb/cdc.h for bit definitions. This IOCTL should be
* called to get the data associated CDCACM_EVENT_CTRLLINE event.
*/
case CAIOC_GETCTRLLINE:
{
FAR int *ptr = (FAR int *)((uintptr_t)arg);
if (ptr != NULL)
{
*ptr = priv->ctrlline;
}
else
{
ret = -EINVAL;
}
}
break;
#ifdef CONFIG_CDCACM_IFLOWCONTROL
* Send a serial state to the host via the Interrupt IN endpoint.
* Argument: int. This includes the current state of the carrier
* detect, DSR, break, and ring signal. See "Table 69: UART State
* Bitmap Values" and CDC_UART_definitions in include/nuttx/usb/cdc.h.
*/
case CAIOC_NOTIFY:
{
DEBUGASSERT(arg < UINT8_MAX);
priv->serialstate = (uint8_t)arg;
ret = cdcacm_serialstate(priv);
}
break;
#endif
#ifdef CONFIG_SERIAL_TERMIOS
case TCGETS:
{
struct termios *termiosp = (FAR struct termios *)arg;
if (!termiosp)
{
ret = -EINVAL;
break;
}
termiosp->c_cflag =
((priv->linecoding.parity != CDC_PARITY_NONE) ? PARENB : 0) |
((priv->linecoding.parity == CDC_PARITY_ODD) ? PARODD : 0) |
((priv->linecoding.stop == CDC_CHFMT_STOP2) ? CSTOPB : 0) |
CS8;
#ifdef CONFIG_CDCACM_OFLOWCONTROL
# warning Missing logic
#endif
#ifdef CONFIG_CDCACM_IFLOWCONTROL
termiosp->c_cflag |= (priv->iflow) ? CRTS_IFLOW : 0;
#endif
cfsetispeed(termiosp, (speed_t)priv->linecoding.baud[3] << 24 |
(speed_t)priv->linecoding.baud[2] << 16 |
(speed_t)priv->linecoding.baud[1] << 8 |
(speed_t)priv->linecoding.baud[0]);
}
break;
case TCSETS:
{
struct termios *termiosp = (FAR struct termios *)arg;
#ifdef CONFIG_CDCACM_IFLOWCONTROL
bool iflow;
#endif
if (!termiosp)
{
ret = -EINVAL;
break;
}
#ifdef CONFIG_CDCACM_OFLOWCONTROL
# warning Missing logic
#endif
#ifdef CONFIG_CDCACM_IFLOWCONTROL
iflow = ((termiosp->c_cflag & CRTS_IFLOW) != 0);
if (iflow != priv->iflow)
{
if (!iflow)
{
* that DSR is set unconditionally.
*/
if ((priv->serialstate & CDCACM_UART_DSR) == 0)
{
priv->serialstate |= (CDCACM_UART_DSR | CDCACM_UART_DCD);
ret = cdcacm_serialstate(priv);
}
priv->iflow = false;
priv->iactive = false;
* packets were queued in priv->rxpending. We must now
* process all of them (unless RX interrupts are also
* disabled)
*/
cdcacm_release_rxpending(priv);
}
else
{
priv->iflow = true;
priv->iactive = false;
* make sure the DSR is clear.
*
* NOTE: Here we assume that DSR is set so we don't check its
* current value nor to we handle the case where we would set
* DSR because the RX buffer is (nearly) empty!
*/
if (priv->upper)
{
priv->serialstate &= ~CDCACM_UART_DSR;
priv->serialstate |= CDCACM_UART_DCD;
ret = cdcacm_serialstate(priv);
priv->iactive = true;
}
}
priv->rxenabled = true;
}
#endif
}
break;
#endif
default:
ret = -ENOTTY;
break;
}
return ret;
}
* Name: cdcuart_rxint
*
* Description:
* Called by the serial driver to enable or disable RX interrupts. We, of
* course, have no RX interrupts but must behave consistently. This method
* is called under the conditions:
*
* 1. With enable==true when the port is opened (just after cdcuart_setup
* and cdcuart_attach are called called)
* 2. With enable==false while transferring data from the RX buffer
* 2. With enable==true while waiting for more incoming data
* 3. With enable==false when the port is closed (just before
* cdcuart_detach and cdcuart_shutdown are called).
*
* Assumptions:
* Called from the serial upper-half driver running on the thread of
* execution of the caller of the driver or, possibly, on from the
* USB interrupt handler (at least for the case where the RX interrupt
* is disabled)
*
****************************************************************************/
static void cdcuart_rxint(FAR struct uart_dev_s *dev, bool enable)
{
FAR struct cdcacm_dev_s *priv;
irqstate_t flags;
usbtrace(CDCACM_CLASSAPI_RXINT, (uint16_t)enable);
#ifdef CONFIG_DEBUG_FEATURES
if (!dev || !dev->priv)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
priv = (FAR struct cdcacm_dev_s *)dev->priv;
* in the following.
*/
if (enable)
{
* to enabled state?
*/
flags = spin_lock_irqsave(&priv->lock);
if (!priv->rxenabled)
{
priv->rxenabled = true;
}
spin_unlock_irqrestore(&priv->lock, flags);
* packets were queued in priv->rxpending. We must now process
* all of them (unless flow control is enabled)
*
* NOTE: This action may cause this function to be re-entered
* with enable == false , anyway the pend-list should be flushed
*/
cdcacm_release_rxpending(priv);
}
* transition from the enabled to the disabled state.
*/
else
{
flags = spin_lock_irqsave(&priv->lock);
priv->rxenabled = false;
spin_unlock_irqrestore(&priv->lock, flags);
}
}
* Name: cdcuart_rxflowcontrol
*
* Description:
* Called when Rx buffer is full (or exceeds configured watermark levels
* if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is defined).
* Return true if UART activated RX flow control to block more incoming
* data
*
* Input Parameters:
* dev - UART device instance
* nbuffered - the number of characters currently buffered
* (if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is
* not defined the value will be 0 for an empty buffer or the
* defined buffer size for a full buffer)
* upper - true indicates the upper watermark was crossed where
* false indicates the lower watermark has been crossed
*
* Returned Value:
* true if RX flow control activated.
*
****************************************************************************/
#ifdef CONFIG_SERIAL_IFLOWCONTROL
static bool cdcuart_rxflowcontrol(FAR struct uart_dev_s *dev,
unsigned int nbuffered, bool upper)
{
#ifdef CONFIG_CDCACM_IFLOWCONTROL
FAR struct cdcacm_dev_s *priv;
#ifdef CONFIG_DEBUG_FEATURES
if (dev == NULL || dev->priv == NULL)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return false;
}
#endif
priv = (FAR struct cdcacm_dev_s *)dev->priv;
priv->upper = upper;
if (priv->iflow)
{
* or clear it if the upper water mark has been crossed.
*/
if (upper)
{
* setting of DSR.
*/
if ((priv->serialstate & CDCACM_UART_DSR) != 0)
{
priv->serialstate &= ~CDCACM_UART_DSR;
priv->serialstate |= CDCACM_UART_DCD;
* REVISIT: Error return case. Would an error mean DSR is not
* set?
*/
cdcacm_serialstate(priv);
}
priv->iactive = true;
}
* a change in the setting of DSR.
*/
else
{
* cdcacm_release_rxpending())
*/
priv->iactive = false;
if ((priv->serialstate & CDCACM_UART_DSR) == 0)
{
priv->serialstate |= (CDCACM_UART_DSR | CDCACM_UART_DCD);
* REVISIT: Error return case. Would an error mean DSR is
* still clear?
*/
cdcacm_serialstate(priv);
}
* were queued in priv->rxpending. We must now process all of
* them (unless RX interrupts becomes enabled)
*
* NOTE: This action may cause this function to be re-entered with
* upper == false.
*/
cdcacm_release_rxpending(priv);
}
}
else
{
if ((priv->serialstate & CDCACM_UART_DSR) == 0)
{
priv->serialstate |= (CDCACM_UART_DSR | CDCACM_UART_DCD);
* REVISIT: Error return case. Would an error mean DSR is still
* not set?
*/
cdcacm_serialstate(priv);
priv->iactive = false;
}
}
return priv->iactive;
#else
return false;
#endif
}
#endif
* Name: cdcuart_txint
*
* Description:
* Called by the serial driver to enable or disable TX interrupts. We, of
* course, have no TX interrupts but must behave consistently. Initially,
* TX interrupts are disabled. This method is called under the conditions:
*
* 1. With enable==false while transferring data into the TX buffer
* 2. With enable==true when data may be taken from the buffer.
* 3. With enable==false when the TX buffer is empty
*
****************************************************************************/
static void cdcuart_txint(FAR struct uart_dev_s *dev, bool enable)
{
FAR struct cdcacm_dev_s *priv;
usbtrace(CDCACM_CLASSAPI_TXINT, (uint16_t)enable);
#ifdef CONFIG_DEBUG_FEATURES
if (!dev || !dev->priv)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
priv = (FAR struct cdcacm_dev_s *)dev->priv;
* send the next packet now.
*/
uinfo("enable=%d head=%d tail=%d\n",
enable, priv->serdev.xmit.head, priv->serdev.xmit.tail);
if (enable && priv->serdev.xmit.head != priv->serdev.xmit.tail)
{
cdcacm_sndpacket(priv);
}
}
* Name: cdcuart_txempty
*
* Description:
* Return true when all data has been sent. This is called from the
* serial driver when the driver is closed. It will call this API
* periodically until it reports true. NOTE that the serial driver takes
* all responsibility for flushing TX data through the hardware so we can
* be a bit sloppy about that.
*
****************************************************************************/
static bool cdcuart_txempty(FAR struct uart_dev_s *dev)
{
FAR struct cdcacm_dev_s *priv = (FAR struct cdcacm_dev_s *)dev->priv;
FAR struct usbdev_ep_s *ep = priv->epbulkin;
irqstate_t flags;
bool empty;
usbtrace(CDCACM_CLASSAPI_TXEMPTY, 0);
#ifdef CONFIG_DEBUG_FEATURES
if (!priv)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return true;
}
#endif
priv->ispolling = true;
EP_POLL(ep);
priv->ispolling = false;
* txfree, then there is no longer any TX data in flight.
*/
flags = spin_lock_irqsave(&priv->lock);
#ifdef CONFIG_CDCACM_DISABLE_TXBUF
* CONFIG_CDCACM_NWRREQS - 1.
*/
empty = priv->nwrq >= (CONFIG_CDCACM_NWRREQS - 1);
#else
empty = priv->nwrq >= CONFIG_CDCACM_NWRREQS;
#endif
spin_unlock_irqrestore(&priv->lock, flags);
return empty;
}
* Name: cdcuart_release
*
* Description:
* This is called to release some resource about the device when device
* was close and unregistered.
*
****************************************************************************/
static int cdcuart_release(FAR struct uart_dev_s *dev)
{
FAR struct cdcacm_dev_s *priv = (FAR struct cdcacm_dev_s *)dev->priv;
usbtrace(CDCACM_CLASSAPI_RELEASE, 0);
wd_cancel(&priv->rxfailsafe);
kmm_free(priv);
return OK;
}
#ifndef CONFIG_CDCACM_DISABLE_TXBUF
* Name: cdcuart_dmasend
*
* Description:
* Set up to transfer bytes from the TX circular buffer.
*
****************************************************************************/
static void cdcuart_dmasend(FAR struct uart_dev_s *dev)
{
FAR struct uart_dmaxfer_s *xfer = &dev->dmatx;
FAR struct cdcacm_dev_s *priv = dev->priv;
FAR struct usbdev_ep_s *ep = priv->epbulkin;
FAR struct cdcacm_wrreq_s *wrcontainer;
FAR struct usbdev_req_s *req;
irqstate_t flags;
size_t nbytes;
size_t reqlen;
int ret;
reqlen = MIN(CONFIG_CDCACM_BULKIN_REQLEN, ep->maxpacket);
flags = spin_lock_irqsave(&priv->lock);
wrcontainer = (FAR struct cdcacm_wrreq_s *)sq_remfirst(&priv->txfree);
req = wrcontainer->req;
priv->nwrq--;
spin_unlock_irqrestore(&priv->lock, flags);
nbytes = MIN(reqlen, xfer->length);
memcpy(req->buf, xfer->buffer, nbytes);
req->len = nbytes;
nbytes = MIN(reqlen - nbytes, xfer->nlength);
if (nbytes)
{
memcpy(req->buf + req->len, xfer->nbuffer, nbytes);
req->len += nbytes;
}
xfer->nbytes = req->len;
uart_xmitchars_done(dev);
req->priv = wrcontainer;
req->flags = USBDEV_REQFLAGS_NULLPKT;
ret = EP_SUBMIT(ep, req);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL),
(uint16_t)-ret);
}
}
#endif
#ifndef CONFIG_CDCACM_DISABLE_RXBUF
* Name: cdcuart_dmareceive
*
* Description:
* Set up to receive bytes into the RX circular buffer.
*
****************************************************************************/
static void cdcuart_dmareceive(FAR struct uart_dev_s *dev)
{
FAR struct uart_dmaxfer_s *xfer = &dev->dmarx;
FAR struct cdcacm_dev_s *priv = dev->priv;
FAR struct cdcacm_rdreq_s *rdcontainer;
FAR struct usbdev_req_s *req;
FAR uint8_t *reqbuf;
size_t nbytes = 0;
size_t reqlen;
rdcontainer = (FAR struct cdcacm_rdreq_s *)
sq_peek(&priv->rxpending);
DEBUGASSERT(rdcontainer != NULL);
req = rdcontainer->req;
DEBUGASSERT(req != NULL);
reqbuf = &req->buf[rdcontainer->offset];
reqlen = req->xfrd - rdcontainer->offset;
nbytes = MIN(reqlen, xfer->length);
memcpy(xfer->buffer, reqbuf, nbytes);
rdcontainer->offset += nbytes;
xfer->nbytes = nbytes;
if (xfer->nbuffer)
{
nbytes = MIN(reqlen - nbytes, xfer->nlength);
memcpy(xfer->nbuffer, reqbuf + xfer->nbytes, nbytes);
rdcontainer->offset += nbytes;
xfer->nbytes += nbytes;
}
uart_recvchars_done(dev);
* pending RX list.
*/
if (rdcontainer->offset >= rdcontainer->req->xfrd)
{
sq_remfirst(&priv->rxpending);
cdcacm_requeue_rdrequest(priv, rdcontainer);
}
}
#else
* Name: cdcacm_rcvpacket
*
* Description:
* Set up to receive bytes into the RX container.
*
****************************************************************************/
static void cdcacm_rcvpacket(FAR struct cdcacm_dev_s *priv)
{
FAR struct uart_dev_s *dev = &priv->serdev;
FAR struct cdcacm_rdreq_s *rdcontainer;
if (dev->recv.head != dev->recv.tail)
{
return;
}
if (priv->rdcontainer)
{
* If there is a pending req, cdcacm_rdcomplete may be called at
* requeue time, which causes this function to be called again,
* so priv->rxcontainer must be set to NULL before requeue.
*/
rdcontainer = priv->rdcontainer;
priv->rdcontainer = NULL;
cdcacm_requeue_rdrequest(priv, rdcontainer);
}
if (!priv->rdcontainer && !sq_empty(&priv->rxpending))
{
priv->rdcontainer = (FAR struct cdcacm_rdreq_s *)
sq_remfirst(&priv->rxpending);
dev->recv.buffer = (FAR char *)priv->rdcontainer->req->buf;
dev->recv.head = priv->rdcontainer->req->xfrd;
dev->recv.size = dev->recv.head + 1;
dev->recv.tail = 0;
uart_datareceived(dev);
}
}
#endif
* Public Functions
****************************************************************************/
#ifdef CONFIG_SYSLOG_CDCACM
* Name: cdcacm_write
*
* Description:
* This provides a cdcacm write method for syslog devices that support
* multiple byte writes
*
* Input Parameters:
* buffer - The buffer containing the data to be output
* buflen - The number of bytes in the buffer
*
* Returned Value:
* On success, the number of characters written is returned. A negated
* errno value is returned on any failure.
*
****************************************************************************/
ssize_t cdcacm_write(FAR const char *buffer, size_t buflen)
{
FAR struct cdcacm_dev_s *priv = g_syslog_cdcacm;
size_t len = 0;
while (len < buflen)
{
if (!priv || !(priv->ctrlline & CDC_DTE_PRESENT))
{
return -EINVAL;
}
if (cdcuart_txready(&priv->serdev))
{
ssize_t ret = cdcuart_sendbuf(&priv->serdev,
buffer + len,
buflen - len);
if (ret < 0)
{
return ret;
}
len += ret;
}
}
return buflen;
}
* Name: cdcacm_disable_syslog
*
* Description:
* Disable CDCACM syslog channel by clearing the globle pointer.
* This function is used in specific situation, such as must disable
* cdcacm log printing when usb re-enumeration.
*
****************************************************************************/
void cdcacm_disable_syslog(void)
{
g_syslog_cdcacm = NULL;
}
#endif
* Name: cdcacm_classobject
*
* Description:
* Register USB serial port (and USB serial console if so configured) and
* return the class object.
*
* Input Parameters:
* minor - Device minor number. E.g., minor 0 would correspond to
* /dev/ttyACM0.
* classdev - The location to return the CDC serial class' device
* instance.
*
* Returned Value:
* A pointer to the allocated class object (NULL on failure).
*
****************************************************************************/
#ifndef CONFIG_CDCACM_COMPOSITE
static
#endif
int cdcacm_classobject(int minor, FAR struct usbdev_devinfo_s *devinfo,
FAR struct usbdevclass_driver_s **classdev)
{
FAR struct cdcacm_alloc_s *alloc;
FAR struct cdcacm_dev_s *priv;
FAR struct cdcacm_driver_s *drvr;
char devname[CDCACM_DEVNAME_SIZE];
int ret;
alloc = (FAR struct cdcacm_alloc_s *)
kmm_malloc(sizeof(struct cdcacm_alloc_s));
if (!alloc)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCDEVSTRUCT), 0);
return -ENOMEM;
}
priv = &alloc->dev;
drvr = &alloc->drvr;
memset(priv, 0, sizeof(struct cdcacm_dev_s));
sq_init(&priv->txfree);
sq_init(&priv->rxpending);
spin_lock_init(&priv->lock);
priv->minor = minor;
memcpy(&priv->devinfo, devinfo,
sizeof(struct usbdev_devinfo_s));
#ifdef CONFIG_CDCACM_IFLOWCONTROL
priv->serialstate = (CDCACM_UART_DCD | CDCACM_UART_DSR);
#endif
priv->linecoding.baud[0] = (115200) & 0xff;
priv->linecoding.baud[1] = (115200 >> 8) & 0xff;
priv->linecoding.baud[2] = (115200 >> 16) & 0xff;
priv->linecoding.baud[3] = (115200 >> 24) & 0xff;
priv->linecoding.stop = CDC_CHFMT_STOP1;
priv->linecoding.parity = CDC_PARITY_NONE;
priv->linecoding.nbits = 8;
#ifdef CONFIG_SERIAL_REMOVABLE
priv->serdev.disconnected = true;
#endif
#ifndef CONFIG_CDCACM_DISABLE_RXBUF
priv->serdev.recv.size = CONFIG_CDCACM_RXBUFSIZE;
priv->serdev.recv.buffer = priv->rxbuffer;
#endif
#ifndef CONFIG_CDCACM_DISABLE_TXBUF
priv->serdev.xmit.size = CONFIG_CDCACM_TXBUFSIZE;
priv->serdev.xmit.buffer = priv->txbuffer;
#endif
priv->serdev.ops = &g_uartops;
priv->serdev.priv = priv;
#if defined(CONFIG_USBDEV_SUPERSPEED)
drvr->drvr.speed = USB_SPEED_SUPER;
#elif defined(CONFIG_USBDEV_DUALSPEED)
drvr->drvr.speed = USB_SPEED_HIGH;
#else
drvr->drvr.speed = USB_SPEED_FULL;
#endif
drvr->drvr.ops = &g_driverops;
drvr->dev = priv;
#ifdef CONFIG_CDCACM_CONSOLE
if (minor == 0)
{
priv->serdev.isconsole = true;
ret = uart_register("/dev/console", &priv->serdev);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONSOLEREGISTER),
(uint16_t)-ret);
goto errout_with_class;
}
}
#endif
snprintf(devname, sizeof(devname), CDCACM_DEVNAME_FORMAT, minor);
ret = uart_register(devname, &priv->serdev);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UARTREGISTER),
(uint16_t)-ret);
goto errout_with_class;
}
*classdev = &drvr->drvr;
#ifdef CONFIG_SYSLOG_CDCACM
if (minor == CONFIG_SYSLOG_CDCACM_MINOR)
{
g_syslog_cdcacm = priv;
}
#endif
return OK;
errout_with_class:
kmm_free(alloc);
return ret;
}
* Name: cdcacm_initialize
*
* Description:
* Register USB serial port (and USB serial console if so configured).
*
* Input Parameters:
* minor - Device minor number. E.g., minor 0 would correspond to
* /dev/ttyACM0.
* handle - An optional opaque reference to the CDC/ACM class object that
* may subsequently be used with cdcacm_uninitialize().
*
* Returned Value:
* Zero (OK) means that the driver was successfully registered. On any
* failure, a negated errno value is returned.
*
****************************************************************************/
#ifndef CONFIG_CDCACM_COMPOSITE
int cdcacm_initialize(int minor, FAR void **handle)
{
FAR struct usbdevclass_driver_s *drvr = NULL;
struct usbdev_devinfo_s devinfo;
int ret;
memset(&devinfo, 0, sizeof(struct usbdev_devinfo_s));
*
* ifnobase must be provided by board-specific logic
*/
devinfo.ninterfaces = CDCACM_NINTERFACES;
*
* strbase must be provided by board-specific logic
*/
devinfo.nstrings = CDCACM_NSTRIDS;
*
* Endpoint numbers must be provided by board-specific logic when
* CDC/ACM is used in a composite device.
*/
devinfo.nendpoints = CDCACM_NUM_EPS;
devinfo.epno[CDCACM_EP_INTIN_IDX] = CONFIG_CDCACM_EPINTIN;
devinfo.epno[CDCACM_EP_BULKIN_IDX] = CONFIG_CDCACM_EPBULKIN;
devinfo.epno[CDCACM_EP_BULKOUT_IDX] = CONFIG_CDCACM_EPBULKOUT;
ret = cdcacm_classobject(minor, &devinfo, &drvr);
if (ret == OK)
{
ret = usbdev_register(drvr);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_DEVREGISTER),
(uint16_t)-ret);
}
}
* by provided a pointer to the location to return it.
*/
if (handle)
{
*handle = (FAR void *)drvr;
}
return ret;
}
#endif
* Name: cdcacm_uninitialize
*
* Description:
* Un-initialize the USB storage class driver. This function is used
* internally by the USB composite driver to uninitialize the CDC/ACM
* driver. This same interface is available (with an untyped input
* parameter) when the CDC/ACM driver is used standalone.
*
* Input Parameters:
* There is one parameter, it differs in typing depending upon whether the
* CDC/ACM driver is an internal part of a composite device, or a
* standalone USB driver:
*
* classdev - The class object returned by cdcacm_classobject()
*
* Returned Value:
* None
*
****************************************************************************/
void cdcacm_uninitialize(FAR struct usbdevclass_driver_s *classdev)
{
FAR struct cdcacm_driver_s *drvr = (FAR struct cdcacm_driver_s *)classdev;
FAR struct cdcacm_dev_s *priv = drvr->dev;
char devname[CDCACM_DEVNAME_SIZE];
int ret;
#ifdef CONFIG_SYSLOG_CDCACM
if (g_syslog_cdcacm == priv)
{
g_syslog_cdcacm = NULL;
}
#endif
#ifndef CONFIG_CDCACM_COMPOSITE
usbdev_unregister(&drvr->drvr);
#endif
snprintf(devname, sizeof(devname), CDCACM_DEVNAME_FORMAT, priv->minor);
ret = unregister_driver(devname);
if (ret < 0)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UARTUNREGISTER),
(uint16_t)-ret);
}
}
* Name: cdcacm_get_composite_devdesc
*
* Description:
* Helper function to fill in some constants into the composite
* configuration struct.
*
* Input Parameters:
* dev - Pointer to the configuration struct we should fill
*
* Returned Value:
* None
*
****************************************************************************/
#if defined(CONFIG_USBDEV_COMPOSITE) && defined(CONFIG_CDCACM_COMPOSITE)
void cdcacm_get_composite_devdesc(struct composite_devdesc_s *dev)
{
memset(dev, 0, sizeof(struct composite_devdesc_s));
*
* classobject() and uninitialize() must be provided by board-specific
* logic
*/
dev->mkconfdesc = cdcacm_mkcfgdesc;
dev->mkstrdesc = cdcacm_mkstrdesc;
dev->nconfigs = CDCACM_NCONFIGS;
dev->configid = CDCACM_CONFIGID;
dev->cfgdescsize = cdcacm_mkcfgdesc(NULL, NULL, USB_SPEED_UNKNOWN, 0);
*
* ifnobase must be provided by board-specific logic
*/
dev->devinfo.ninterfaces = CDCACM_NINTERFACES;
*
* strbase must be provided by board-specific logic
*/
dev->devinfo.nstrings = CDCACM_NSTRIDS;
*
* Endpoint numbers must be provided by board-specific logic.
*/
dev->devinfo.nendpoints = CDCACM_NUM_EPS;
}
#endif