diff --git a/drivers/usbdev/cdcacm.c b/drivers/usbdev/cdcacm.c index efffdd1a490..7772619320e 100644 --- a/drivers/usbdev/cdcacm.c +++ b/drivers/usbdev/cdcacm.c @@ -103,6 +103,7 @@ struct cdcacm_dev_s bool upper; /* True: RX buffer is (nearly) full */ #endif bool rxenabled; /* true: UART RX "interrupts" enabled */ + bool issending; struct cdc_linecoding_s linecoding; /* Buffered line status */ cdcacm_callback_t callback; /* Serial event callback function */ @@ -153,11 +154,7 @@ struct cdcacm_alloc_s /* Transfer helpers *********************************************************/ -static uint16_t cdcacm_fillrequest(FAR struct cdcacm_dev_s *priv, - uint8_t *reqbuf, uint16_t reqlen); static int cdcacm_sndpacket(FAR struct cdcacm_dev_s *priv); -static int cdcacm_recvpacket(FAR struct cdcacm_dev_s *priv, - FAR struct cdcacm_rdreq_s *rdcontainer); 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); @@ -223,6 +220,12 @@ static bool cdcuart_rxflowcontrol(FAR struct uart_dev_s *dev, 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); /**************************************************************************** * Private Data @@ -256,7 +259,7 @@ static const struct uart_ops_s g_uartops = cdcuart_ioctl, /* ioctl */ NULL, /* receive */ cdcuart_rxint, /* rxinit */ - NULL, /* rxavailable */ + cdcuart_rxavailable, /* rxavailable */ #ifdef CONFIG_SERIAL_IFLOWCONTROL cdcuart_rxflowcontrol, /* rxflowcontrol */ #endif @@ -272,9 +275,11 @@ static const struct uart_ops_s g_uartops = #endif NULL, /* send */ cdcuart_txint, /* txinit */ - NULL, /* txready */ + cdcuart_txready, /* txready */ cdcuart_txempty, /* txempty */ - cdcuart_release /* release */ + cdcuart_release, /* release */ + cdcuart_recvbuf, /* recvbuf */ + cdcuart_sendbuf /* sendbuf */ }; /**************************************************************************** @@ -282,70 +287,68 @@ static const struct uart_ops_s g_uartops = ****************************************************************************/ /**************************************************************************** - * Name: cdcacm_fillrequest + * Name: cdcuart_txready * * Description: - * If there is data to send it is copied to the given buffer. Called - * either to initiate the first write operation, or from the completion - * interrupt handler service consecutive write operations. - * - * NOTE: The USB serial driver does not use the serial drivers - * uart_xmitchars() API. That logic is essentially duplicated here because - * unlike UART hardware, we need to be able to handle writes not byte-by- - * byte, but packet-by-packet. Unfortunately, that decision also exposes - * some internals of the serial driver in the following. + * Check if tx buf is ready or not. * ****************************************************************************/ -static uint16_t cdcacm_fillrequest(FAR struct cdcacm_dev_s *priv, - FAR uint8_t *reqbuf, - uint16_t reqlen) +static bool cdcuart_txready(FAR struct uart_dev_s *dev) { - FAR uart_dev_t *serdev = &priv->serdev; - FAR struct uart_buffer_s *xmit = &serdev->xmit; - irqstate_t flags; - uint16_t nbytes = 0; + FAR struct cdcacm_dev_s *priv = dev->priv; - /* Disable interrupts */ + return !sq_empty(&priv->txfree); +} - flags = enter_critical_section(); +/**************************************************************************** + * Name: cdcuart_sendbuf + * + * Description: + * This function transfers the TX data into the request, and submits the + * requests to the USB controller. + * + ****************************************************************************/ - /* Transfer bytes while we have bytes available and there is room in the - * request. - */ +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; + size_t reqlen; + size_t nbytes; + int ret; - while (xmit->head != xmit->tail && nbytes < reqlen) + /* Get the maximum number of bytes that will fit into one bulk IN request */ + + reqlen = MAX(CONFIG_CDCACM_BULKIN_REQLEN, ep->maxpacket); + + /* Peek at the request in the container at the head of the list */ + + wrcontainer = (FAR struct cdcacm_wrreq_s *)sq_remfirst(&priv->txfree); + req = wrcontainer->req; + priv->nwrq--; + + /* Fill the request with serial TX data */ + + nbytes = MIN(reqlen, len); + memcpy(req->buf, buf, nbytes); + + /* Submit the request to the endpoint */ + + req->len = nbytes; + req->priv = wrcontainer; + req->flags = USBDEV_REQFLAGS_NULLPKT; + ret = EP_SUBMIT(ep, req); + if (ret < 0) { - *reqbuf++ = xmit->buffer[xmit->tail]; - nbytes++; - - /* Increment the tail pointer */ - - if (++(xmit->tail) >= xmit->size) - { - xmit->tail = 0; - } + usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL), + (uint16_t)-ret); + return ret; } - /* When all of the characters have been sent from the buffer disable the - * "TX interrupt". - */ - - if (xmit->head == xmit->tail) - { - uart_disabletxint(serdev); - } - - /* If any bytes were removed from the buffer, inform any waiters that - * there is space available. - */ - - if (nbytes) - { - uart_datasent(serdev); - } - - leave_critical_section(flags); return nbytes; } @@ -362,13 +365,7 @@ static uint16_t cdcacm_fillrequest(FAR struct cdcacm_dev_s *priv, static int cdcacm_sndpacket(FAR struct cdcacm_dev_s *priv) { - FAR struct usbdev_ep_s *ep; - FAR struct usbdev_req_s *req; - FAR struct cdcacm_wrreq_s *wrcontainer; - uint16_t reqlen; irqstate_t flags; - int len; - int ret = OK; #ifdef CONFIG_DEBUG_FEATURES if (priv == NULL) @@ -379,227 +376,93 @@ static int cdcacm_sndpacket(FAR struct cdcacm_dev_s *priv) #endif flags = enter_critical_section(); - - /* Use our bulk IN endpoint for the transfer */ - - ep = priv->epbulkin; - - /* Loop until either (1) we run out or write requests, or (2) - * cdcacm_fillrequest() is unable to fill the request with data (i.e., - * until there is no more data to be sent). - */ + if (priv->issending) + { + goto out; + } + else + { + priv->issending = true; + } uinfo("head=%d tail=%d nwrq=%d empty=%d\n", priv->serdev.xmit.head, priv->serdev.xmit.tail, priv->nwrq, sq_empty(&priv->txfree)); - /* Get the maximum number of bytes that will fit into one bulk IN request */ + uart_xmitchars(&priv->serdev); - reqlen = MAX(CONFIG_CDCACM_BULKIN_REQLEN, ep->maxpacket); - - while (!sq_empty(&priv->txfree)) - { - /* Peek at the request in the container at the head of the list */ - - wrcontainer = (FAR struct cdcacm_wrreq_s *)sq_peek(&priv->txfree); - req = wrcontainer->req; - - /* Fill the request with serial TX data */ - - len = cdcacm_fillrequest(priv, req->buf, reqlen); - if (len > 0) - { - /* Remove the empty container from the request list */ - - sq_remfirst(&priv->txfree); - priv->nwrq--; - - /* Then submit the request to the endpoint */ - - req->len = len; - req->priv = wrcontainer; - req->flags = USBDEV_REQFLAGS_NULLPKT; - ret = EP_SUBMIT(ep, req); - if (ret != OK) - { - usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL), - (uint16_t)-ret); - break; - } - } - else - { - break; - } - } + priv->issending = false; +out: leave_critical_section(flags); - return ret; + return OK; } /**************************************************************************** - * Name: cdcacm_recvpacket + * Name: cdcuart_rxavailable * * Description: - * A normal completion event was received by the read completion handler - * at the interrupt level (with interrupts disabled). This function handles - * the USB packet and provides the received data to the uart RX buffer. - * - * Assumptions: - * Called from the USB interrupt handler with interrupts disabled. + * Check if data has been saved in rx buf. * ****************************************************************************/ -static int cdcacm_recvpacket(FAR struct cdcacm_dev_s *priv, - FAR struct cdcacm_rdreq_s *rdcontainer) +static bool cdcuart_rxavailable(FAR struct uart_dev_s *dev) { - FAR uart_dev_t *serdev; - FAR struct uart_buffer_s *recv; + FAR struct cdcacm_dev_s *priv = dev->priv; + + 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; -#ifdef CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS - unsigned int watermark; -#endif - uint16_t reqlen; - uint16_t nexthead; - uint16_t nbytes = 0; + size_t reqlen; + size_t nbytes; + int ret; - DEBUGASSERT(priv != NULL && rdcontainer != NULL); + /* Process each packet in the priv->rxpending list */ -#ifdef CONFIG_CDCACM_IFLOWCONTROL - DEBUGASSERT(priv->rxenabled && !priv->iactive); -#else - DEBUGASSERT(priv->rxenabled); -#endif + rdcontainer = (FAR struct cdcacm_rdreq_s *)sq_peek(&priv->rxpending); + DEBUGASSERT(rdcontainer != NULL); - req = rdcontainer->req; + req = rdcontainer->req; DEBUGASSERT(req != NULL); reqbuf = &req->buf[rdcontainer->offset]; reqlen = req->xfrd - rdcontainer->offset; - uinfo("head=%d tail=%d nrdq=%d reqlen=%d\n", - priv->serdev.recv.head, priv->serdev.recv.tail, priv->nrdq, reqlen); + nbytes = MIN(reqlen, len); + memcpy(buf, reqbuf, nbytes); + rdcontainer->offset += nbytes; - serdev = &priv->serdev; - recv = &serdev->recv; - - /* Pre-calculate the head index and check for wrap around. We need to do - * this so that we can determine if the circular buffer will overrun - * BEFORE we overrun the buffer! + /* The entire packet was processed and may be removed from the + * pending RX list. */ - nexthead = recv->head + 1; - if (nexthead >= recv->size) + if (rdcontainer->offset >= req->xfrd) { - nexthead = 0; - } - -#ifdef CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS - /* Pre-calculate the watermark level that we will need to test against. - * Note that the range of the the upper watermark is from 1 to 99 percent - * and that the actual capacity of the RX buffer is (recv->size - 1). - */ - - watermark = CONFIG_SERIAL_IFLOWCONTROL_UPPER_WATERMARK * recv->size / 100; - DEBUGASSERT(watermark > 0 && watermark < (recv->size - 1)); -#endif - - /* Then copy data into the RX buffer until either: (1) all of the data has - * been copied, or (2) the RX buffer is full. - * - * NOTE: If the RX buffer becomes full, then we have overrun the serial - * driver and data will be lost. This is the correct behavior for a - * proper emulation of a serial link. It should not NAK, it should drop - * data like a physical serial port. - * - * If you don't like that behavior. DO NOT change it here. Instead, you - * should finish the implementation of RX flow control which is the only - * proper way to throttle a serial device. - */ - - while (nexthead != recv->tail && nbytes < reqlen) - { -#if defined(CONFIG_SERIAL_IFLOWCONTROL) && \ - defined(CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS) - unsigned int nbuffered; - - /* How many bytes are buffered */ - - if (recv->head >= recv->tail) + sq_remfirst(&priv->rxpending); + ret = cdcacm_requeue_rdrequest(priv, rdcontainer); + if (ret < 0) { - nbuffered = recv->head - recv->tail; - } - else - { - nbuffered = recv->size - recv->tail + recv->head; - } - - /* Is the level now above the watermark level that we need to report? */ - - if (nbuffered >= watermark) - { - /* Let the lower level driver know that the watermark level has - * been crossed. It will probably activate RX flow control. - */ - - if (cdcuart_rxflowcontrol(&priv->serdev, nbuffered, true)) - { - /* Low-level driver activated RX flow control, exit loop now. */ - - break; - } - } -#endif - - /* Copy one byte to the head of the circular RX buffer */ - - recv->buffer[recv->head] = *reqbuf++; - nbytes++; - - /* Increment the head index and check for wrap around */ - - recv->head = nexthead; - if (++nexthead >= recv->size) - { - nexthead = 0; + return ret; } } -#if defined(CONFIG_SERIAL_IFLOWCONTROL) && \ - !defined(CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS) - /* Check if RX buffer became full and allow serial low-level driver to - * pause processing. This allows proper utilization of hardware flow - * control when there are no watermarks. - */ - - if (nexthead == recv->tail) - { - cdcuart_rxflowcontrol(&priv->serdev, recv->size - 1, true); - } -#endif - - /* If data was added to the incoming serial buffer, then wake up any - * threads is waiting for incoming data. If we are running in an interrupt - * handler, then the serial driver will not run until the interrupt - * handler returns. - */ - - if (nbytes > 0) - { - uart_datareceived(serdev); - } - - /* Return an overrun error if the entire packet could not be transferred. */ - - if (nbytes < reqlen) - { - usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RXOVERRUN), 0); - rdcontainer->offset += nbytes; - return -ENOSPC; - } - - return OK; + return nbytes; } /**************************************************************************** @@ -647,7 +510,6 @@ static int cdcacm_requeue_rdrequest(FAR struct cdcacm_dev_s *priv, static int cdcacm_release_rxpending(FAR struct cdcacm_dev_s *priv) { - FAR struct cdcacm_rdreq_s *rdcontainer; irqstate_t flags; int ret = -EBUSY; @@ -683,34 +545,7 @@ static int cdcacm_release_rxpending(FAR struct cdcacm_dev_s *priv) ret = OK; - while (!sq_empty(&priv->rxpending)) - { - /* Process each packet in the priv->rxpending list */ - - rdcontainer = (FAR struct cdcacm_rdreq_s *) - sq_peek(&priv->rxpending); - DEBUGASSERT(rdcontainer != NULL); - - /* cdcacm_recvpacket() will return OK if the entire packet was - * successful buffered. In the case of RX buffer overrun, - * cdcacm_recvpacket() will return a failure (-ENOSPC) and will - * set the req->offset field. - */ - - ret = cdcacm_recvpacket(priv, rdcontainer); - if (ret < 0) - { - uwarn("WARNING: RX buffer full\n"); - break; - } - - /* The entire packet was processed and may be removed from the - * pending RX list and returned to the DCD. - */ - - sq_remfirst(&priv->rxpending); - ret = cdcacm_requeue_rdrequest(priv, rdcontainer); - } + uart_recvchars(&priv->serdev); } /* Restart the RX failsafe timer if there are RX packets in