cdcacm: use serial public APIs

Signed-off-by: yangsong8 <yangsong8@xiaomi.com>
This commit is contained in:
yangsong8 2024-08-14 22:05:11 +08:00 committed by Xiang Xiao
parent b319c27f03
commit e3fcabaec2

View file

@ -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