SAM4E USP: Re-vamp read request queue handling. Add logic to handle RX overrun errors

This commit is contained in:
Gregory Nutt 2014-03-26 11:38:47 -06:00
parent f6dedaee30
commit 3f4593dda8
3 changed files with 200 additions and 139 deletions

View File

@ -7046,4 +7046,3 @@
startup (2014-3-25).
* tools/astyle.sh: A code formatting tool from Lorenz Meier. This
tool should do a better job than the old tools/indent.sh tool.

View File

@ -159,7 +159,7 @@
#define SAM_TRACEERR_IRQREGISTRATION 0x0018
#define SAM_TRACEERR_NOTCONFIGURED 0x0019
#define SAM_TRACEERR_REQABORTED 0x001a
#define SAM_TRACEERR_RXDATABK0ERR 0x001b
#define SAM_TRACEERR_RXDATABKERR 0x001b
#define SAM_TRACEERR_TXCOMPERR 0x001c
#define SAM_TRACEERR_UNSUPPEPTYPE 0x001d
@ -230,7 +230,7 @@ enum sam_epstate_e
UDP_EPSTATE_STALLED, /* Endpoint is stalled */
UDP_EPSTATE_IDLE, /* Endpoint is idle (i.e. ready for transmission) */
UDP_EPSTATE_SENDING, /* Endpoint is sending data */
UDP_EPSTATE_RECEIVING, /* Endpoint is receiving data */
UDP_EPSTATE_RXSTOPPED, /* OUT dndpoint is stopped waiting for a read request */
/* --- Endpoint 0 Only --- */
UDP_EPSTATE_EP0DATAOUT, /* Endpoint 0 is receiving SETUP OUT data */
UDP_EPSTATE_EP0STATUSIN, /* Endpoint 0 is sending SETUP status */
@ -303,7 +303,7 @@ struct sam_ep_s
uint8_t halted:1; /* true: Endpoint feature halted */
uint8_t zlpneeded:1; /* Zero length packet needed at end of transfer */
uint8_t zlpsent:1; /* Zero length packet has been sent */
uint8_t wqbusy:1; /* Write request queue is busy (recursion avoidance kludge) */
uint8_t txbusy:1; /* Write request queue is busy (recursion avoidance kludge) */
};
struct sam_usbdev_s
@ -381,7 +381,8 @@ static void sam_req_wrsetup(struct sam_usbdev_s *priv,
static int sam_req_write(struct sam_usbdev_s *priv,
struct sam_ep_s *privep);
static int sam_req_read(struct sam_usbdev_s *priv,
struct sam_ep_s *privep, uint16_t recvsize);
struct sam_ep_s *privep, uint16_t recvsize,
int bank);
static void sam_req_cancel(struct sam_ep_s *privep, int16_t status);
/* Interrupt level processing ***********************************************/
@ -532,7 +533,7 @@ const struct trace_msg_t g_usb_trace_strings_deverror[] =
TRACE_STR(SAM_TRACEERR_IRQREGISTRATION),
TRACE_STR(SAM_TRACEERR_NOTCONFIGURED),
TRACE_STR(SAM_TRACEERR_REQABORTED),
TRACE_STR(SAM_TRACEERR_RXDATABK0ERR),
TRACE_STR(SAM_TRACEERR_RXDATABKERR),
TRACE_STR(SAM_TRACEERR_TXCOMPERR),
TRACE_STR(SAM_TRACEERR_UNSUPPEPTYPE),
TRACE_STR_END
@ -1050,7 +1051,7 @@ static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep)
if (privep->epstate == UDP_EPSTATE_IDLE)
{
/* Return the write request to the class driver. Set the wqbusy
/* Return the write request to the class driver. Set the txbusy
* bit to prevent being called recursively from any new submission
* generated by returning the write request.
*/
@ -1058,9 +1059,9 @@ static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep)
usbtrace(TRACE_COMPLETE(epno), privreq->req.xfrd);
DEBUGASSERT(privreq->req.len == privreq->req.xfrd);
privep->wqbusy = true;
privep->txbusy = true;
sam_req_complete(privep, OK);
privep->wqbusy = false;
privep->txbusy = false;
}
}
@ -1073,12 +1074,12 @@ static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep)
* Description:
* Complete the last read request by transferring the data from the RX FIFO
* to the request buffer, return the completed read request to the class
* implementation, and try to started the next queued read request.
* implementation, and try to start the next queued read request.
*
* This function is called in one of three contexts: (1) When the endpoint
* is IDLE and a new read request is submitted (with interrupts disabled),
* (2) from interrupt handling when the current FIFO transfer completes,
* or (3) when resuming a stalled OUT or control endpoint.
* This function is called in one of two contexts: The normal case is (1)
* from interrupt handling when the current RX FIFO transfer completes.
* But there is also a special case (2) when the OUT endpoint is stopped
* because there are no available read requests.
*
* Calling rules:
*
@ -1093,10 +1094,13 @@ static int sam_req_write(struct sam_usbdev_s *priv, struct sam_ep_s *privep)
* When the transfer completes, the 'recvsize' is the number of bytes
* waiting in the FIFO to be read.
*
* bank indicates the bit in the CSR register that must be cleared
* after the data has been read from the RX FIFO
*
****************************************************************************/
static int sam_req_read(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
uint16_t recvsize)
uint16_t recvsize, int bank)
{
struct sam_req_s *privreq;
volatile const uint32_t *fifo;
@ -1107,19 +1111,30 @@ static int sam_req_read(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
DEBUGASSERT(priv && privep && privep->epstate == UDP_EPSTATE_IDLE);
/* Loop in case we need to handle multiple read requests */
/* Check the request from the head of the endpoint request queue */
while (privep->epstate == UDP_EPSTATE_IDLE)
epno = USB_EPNO(privep->ep.eplog);
do
{
/* Check the request from the head of the endpoint request queue */
/* Peek at the next read request in the requeust queue */
epno = USB_EPNO(privep->ep.eplog);
privreq = sam_rqpeek(&privep->reqq);
if (!privreq)
{
/* No packet to receive data */
/* No read request to receive data */
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_EPOUTQEMPTY), epno);
/* Disable further interrupts from this endpoint. The RXDATABK0/1
* interrupt will pend until either another read request is received
* from the class driver or until the endpoint is reset because of
* no response. Set a flag so that we know that we are in this
* perverse state and can re-enable endpoint interrupts when the
* next read request is received.
*/
sam_putreg(UDP_INT_EP(epno), SAM_UDP_IDR);
privep->epstate = UDP_EPSTATE_RXSTOPPED;
return -ENOENT;
}
@ -1132,70 +1147,58 @@ static int sam_req_read(struct sam_usbdev_s *priv, struct sam_ep_s *privep,
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EPOUTNULLPACKET), 0);
sam_req_complete(privep, OK);
recvsize = 0;
continue;
}
usbtrace(TRACE_READ(USB_EPNO(privep->ep.eplog)), recvsize);
/* Get the number of bytes that can be received. This is the size
* of the user-provided request buffer, minus the number of bytes
* already transferred to the user-buffer.
*/
remaining = privreq->req.len - privreq->req.xfrd;
/* Read the smaller of the number of bytes available in FIFO and the
* size remaining in the request buffer provided by the caller.
*/
readlen = MIN(remaining, recvsize);
recvsize = 0;
/* Get the source and destination transfer addresses */
fifo = (volatile const uint32_t *)SAM_UDPEP_FDR(epno);
dest = privreq->req.buf + privreq->req.xfrd;
/* Update the total number of bytes transferred */
privreq->req.xfrd += readlen;
privreq->inflight = 0;
/* Retrieve packet from the endpoint FIFO */
for (; readlen > 0; readlen--)
{
*dest++ = (uint8_t)(*fifo);
}
/* If nothing has yet be transferred into the read request, then
* indicate that we are in the RECEIVING state.
*/
if (privreq->req.xfrd == 0)
{
/* Set the RECEIVING state */
privep->epstate = UDP_EPSTATE_RECEIVING;
}
/* We will not try to accumulate packet data here. If anything
* has been received, we will complete the transfer immediately and
* give the data to the class driver. The idea is that we will let the
* receiving be in-charge if incoming buffer.
*/
else
{
/* Return the read request to the class driver. */
usbtrace(TRACE_COMPLETE(epno), privreq->req.xfrd);
privep->epstate = UDP_EPSTATE_IDLE;
sam_req_complete(privep, OK);
privreq = NULL;
}
}
while (privreq == NULL);
usbtrace(TRACE_READ(USB_EPNO(privep->ep.eplog)), recvsize);
/* Get the number of bytes that can be received. This is the size
* of the user-provided request buffer, minus the number of bytes
* already transferred to the user-buffer.
*/
remaining = privreq->req.len - privreq->req.xfrd;
/* Read the smaller of the number of bytes available in FIFO and the
* size remaining in the request buffer provided by the caller.
*/
readlen = MIN(remaining, recvsize);
recvsize = 0;
/* Get the source and destination transfer addresses */
fifo = (volatile const uint32_t *)SAM_UDPEP_FDR(epno);
dest = privreq->req.buf + privreq->req.xfrd;
/* Update the total number of bytes transferred */
privreq->req.xfrd += readlen;
privreq->inflight = 0;
/* Retrieve packet from the endpoint FIFO */
for (; readlen > 0; readlen--)
{
*dest++ = (uint8_t)(*fifo);
}
/* We get here when an RXDATABK0/1 interrupt occurs. That interrupt
* cannot be cleared until all of the data has been taken from the RX
* FIFO. But we can
*/
sam_csr_clrbits(epno, bank ? UDPEP_CSR_RXDATABK1 : UDPEP_CSR_RXDATABK0);
/* Complete the transfer immediately and give the data to the class
* driver. The idea is that we will let the receiving be in-charge of
* re-assembling data fragments.
*/
usbtrace(TRACE_COMPLETE(epno), privreq->req.xfrd);
sam_req_complete(privep, OK);
return OK;
}
@ -1846,24 +1849,31 @@ static void sam_ep_bankinterrupt(struct sam_usbdev_s *priv,
{
uint32_t eptype;
uint16_t pktsize;
uint8_t epno;
/* Get the endpoint type */
eptype = csr & UDPEP_CSR_EPTYPE_MASK;
epno = USB_EPNO(privep->ep.eplog);
/* Are we receiving data for a read request? */
/* Are we receiving data for a read request? EP0 does not receive data
* using read requests.
*/
if (privep->epstate == UDP_EPSTATE_RECEIVING)
if (privep->epstate == UDP_EPSTATE_IDLE && epno != 0)
{
/* Yes, get the size of the packet that we just received */
pktsize = (uint16_t)
((csr & UDPEP_CSR_RXBYTECNT_MASK) >> UDPEP_CSR_RXBYTECNT_SHIFT);
/* And continue processing the read request */
/* And continue processing the read request. sam_req_read will
* clear the RXDATABK1 interrupt once that data has been
* transferred from the FIFO.
*/
privep->epstate = UDP_EPSTATE_IDLE;
sam_req_read(priv, privep, pktsize);
(void)sam_req_read(priv, privep, pktsize, bank);
}
/* Did we just receive the data associated with an OUT SETUP command? */
@ -1872,6 +1882,8 @@ static void sam_ep_bankinterrupt(struct sam_usbdev_s *priv,
{
uint16_t len;
DEBUGASSERT(epno == EP0 && bank == 0);
/* Yes.. back to the IDLE state */
privep->epstate = UDP_EPSTATE_IDLE;
@ -1890,23 +1902,46 @@ static void sam_ep_bankinterrupt(struct sam_usbdev_s *priv,
sam_ep0_read(priv->ep0out, len);
/* Clear the RX Data Bank 0 interrupt (should not be bank 1!). */
sam_csr_clrbits(EP0, UDPEP_CSR_RXDATABK0);
/* And handle the EP0 SETUP now. */
sam_ep0_setup(priv);
}
else
{
/* Clear the RX Data Bank 0 interrupt (should not be bank 1!).
* Then stall.
*/
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_EP0SETUPOUTSIZE), pktsize);
sam_csr_clrbits(EP0, UDPEP_CSR_RXDATABK0);
(void)sam_ep_stall(privep);
}
}
/* Check if ACK received on a Control EP */
/* Check for a EP0 STATUS packet returned by the host at the end of a
* SETUP status phase
*/
else if (eptype != UDPEP_CSR_EPTYPE_CTRL ||
(csr & UDPEP_CSR_RXBYTECNT_MASK) != 0)
else if (eptype == UDPEP_CSR_EPTYPE_CTRL &&
(csr & UDPEP_CSR_RXBYTECNT_MASK) == 0)
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_RXDATABK0ERR), privep->epstate);
DEBUGASSERT(epno == EP0 && bank == 0);
/* Clear the RX Data Bank 0 interrupt */
sam_csr_clrbits(EP0, UDPEP_CSR_RXDATABK0);
}
/* Otherwise there is a problem. Complain an clear the interrupt */
else
{
usbtrace(TRACE_DEVERROR(SAM_TRACEERR_RXDATABKERR), privep->epstate);
sam_csr_clrbits(epno, bank ? UDPEP_CSR_RXDATABK1 : UDPEP_CSR_RXDATABK0);
}
}
@ -1990,13 +2025,12 @@ static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_RXDATABK0), (uint16_t)csr);
/* Handle data received on Bank 0 */
/* Handle data received on Bank 0. sam_ep_bankinterrupt will
* clear the RXDATABK0 interrupt once that data has been
* transferred from the FIFO.
*/
sam_ep_bankinterrupt(priv, privep, csr, 0);
/* Acknowledge the RX Data Bank 0 interrupt */
sam_csr_clrbits(epno, UDPEP_CSR_RXDATABK0);
}
/* OUT packet received in data bank 1 */
@ -2004,14 +2038,14 @@ static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno)
else if ((csr & UDPEP_CSR_RXDATABK1) != 0)
{
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_RXDATABK1), (uint16_t)csr);
DEBUGASSERT(SAM_UDP_NBANKS(epno) > 1);
/* Handle data received on Bank 1 */
/* Handle data received on Bank 1. sam_ep_bankinterrupt will
* clear the RXDATABK1 interrupt once that data has been
* transferred from the FIFO.
*/
sam_ep_bankinterrupt(priv, privep, csr, 1);
/* Acknowledge the RX Data Bank 1 interrupt */
sam_csr_clrbits(epno, UDPEP_CSR_RXDATABK1);
}
/* STALL sent */
@ -2024,7 +2058,7 @@ static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno)
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_STALLSNT), (uint16_t)csr);
/* Acknowledge the interrupt */
/* Clear the STALLSENT interrupt */
sam_csr_clrbits(epno, UDPEP_CSR_STALLSENT);
@ -2059,10 +2093,9 @@ static void sam_ep_interrupt(struct sam_usbdev_s *priv, int epno)
usbtrace(TRACE_INTDECODE(SAM_TRACEINTID_RXSETUP), (uint16_t)csr);
/* If a request transfer was pending, complete it. */
/* If a write request transfer was pending, complete it. */
if (privep->epstate == UDP_EPSTATE_RECEIVING ||
privep->epstate == UDP_EPSTATE_SENDING)
if (privep->epstate == UDP_EPSTATE_SENDING)
{
sam_req_complete(privep, -EPROTO);
}
@ -2475,7 +2508,7 @@ static void sam_ep_reset(struct sam_usbdev_s *priv, uint8_t epno)
privep->halted = false;
privep->zlpneeded = false;
privep->zlpsent = false;
privep->wqbusy = false;
privep->txbusy = false;
}
/****************************************************************************
@ -2531,8 +2564,8 @@ static int sam_ep_stall(struct sam_ep_s *privep)
epno = USB_EPNO(privep->ep.eplog);
usbtrace(TRACE_EPSTALL, epno);
/* If this is an IN endpoint (or endpoint 0), then cancel all
* of the pending write requests.
/* If this is an IN endpoint (or endpoint 0), then cancel any
* write requests in progress.
*/
if (epno == 0 || USB_ISEPIN(privep->ep.eplog))
@ -2540,15 +2573,6 @@ static int sam_ep_stall(struct sam_ep_s *privep)
sam_req_cancel(privep, -EPERM);
}
/* Otherwise, it is an OUT endpoint. Complete any read request
* currently in progress (they will get requeued immediately).
*/
else if (privep->epstate == UDP_EPSTATE_RECEIVING)
{
sam_req_complete(privep, -EPERM);
}
/* Put endpoint into stalled state */
privep->epstate = UDP_EPSTATE_STALLED;
@ -2620,12 +2644,6 @@ static int sam_ep_resume(struct sam_ep_s *privep)
(void)sam_req_write(priv, privep);
}
else
{
/* OUT endpoint. Restart any queued read requests. */
(void)sam_req_read(priv, privep, 0);
}
}
irqrestore(flags);
@ -3117,7 +3135,7 @@ static int sam_ep_submit(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
* processing in progress, then transfer the data now.
*/
if (privep->epstate == UDP_EPSTATE_IDLE && !privep->wqbusy)
if (privep->epstate == UDP_EPSTATE_IDLE && !privep->txbusy)
{
ret = sam_req_write(priv, privep);
}
@ -3133,11 +3151,21 @@ static int sam_ep_submit(struct usbdev_ep_s *ep, struct usbdev_req_s *req)
sam_req_enqueue(&privep->reqq, privreq);
usbtrace(TRACE_OUTREQQUEUED(epno), req->len);
/* If the OUT endpoint IDLE, then setup the read */
/* Check if we have stopped RX receipt due to lack of read
* read requests. If that is the case for this endpoint, then
* re-enable endpoint interrupts now.
*/
if (privep->epstate == UDP_EPSTATE_IDLE)
if (privep->epstate == UDP_EPSTATE_RXSTOPPED)
{
ret = sam_req_read(priv, privep, 0);
/* Un-stop the OUT endpoint be re-enabling endpoint interrupts.
* There should be a pending RXDATABK0/1 interrupt or, if a long
* time has elapsed since the endpoint was stopped, an ENDBUSRES
* interrupt.
*/
privep->epstate = UDP_EPSTATE_IDLE;
sam_putreg(UDP_INT_EP(epno), SAM_UDP_IER);
}
}
@ -3565,7 +3593,7 @@ static void sam_reset(struct sam_usbdev_s *priv)
privep->halted = false;
privep->zlpneeded = false;
privep->zlpsent = false;
privep->wqbusy = false;
privep->txbusy = false;
}
/* Re-configure the USB controller in its initial, unconnected state */

View File

@ -674,9 +674,8 @@ USB Full-Speed Device
2014-3-25: Marginally functional. Very slow to come up. USB analyzer
shows several resets before the host decides that it is
happy with the device. There are no obvious errors in the
USB data capture.
2014-3-25: There also seem to be issues about writing files. This
needs more investigation.
USB data capture. Testing is insufficient. This needs to
be revisited.
CDC/ACM Serial Device Class
---------------------------
@ -714,6 +713,38 @@ USB Full-Speed Device
than requires that you first install a serial driver (a .inf file). There
are example .inf files for NuttX in the nuttx/configs/spark directories.
3. There is hand-shaking to pace incoming serial data. As a result, you may
experience data loss due to RX overrun errors. The overrun errors occur
when more data is received than can be buffered in memory on the target.
At present, the only workaround is to increase the amount of buffering
in the target. That allow the target to accept short bursts of larger
volumes of data (but would still fail on sustained, high speed incoming
data. The following configuration options can be changed to increase
the buffering.
1. RX buffer size. All incoming data is buffered by the serial driver
until it can be read by the application. The default size of this
RX buffer is only 256 but can be increased as you see fit:
CONFIG_CDCACM_RXBUFSIZE=256 : Default RX buffer size is only 256 bytes
2. Upstream from the RX buffers are USB read request buffers. Each
buffer is the maximum size of one USB packet (64 byte) and that cannot
really be changed. But if you want to increase this upstream buffering
capability, you can increase the number of available read requests.
The default is four, providing an additional buffering capability of
of 4*64=256 bytes.
Each read request receives data from USB, copies the data into the
serial RX buffer, and then is available to receive more data. This
recycling of read requests stalls as soon as the serial RX buffer is
full. Data loss occurs when there are no available read requests to
accept the next packet from the host. So increasing the number of
read requests can also help to minimize RX overrun:
CONFIG_CDCACM_NRDREQS=4 : Default is only 4 read requests
STATUS:
2013-2-23: Checks out OK. See discussion of the usbnsh configuration
@ -1174,15 +1205,13 @@ Configurations
above under "USB Full-Speed Device."
STATUS:
2014-3-21: USB support is under development and USB MSC support is
only partially functional. Additional test and integration
is required.
2014-3-21: USB support is partially functional. Additional test and
integration is required. See STATUS in the "USB Full-Speed
Device" for further information
2014-3-22: USB seems to work properly (there are not obvious errors
in a USB bus capture. However, the AT25 does not mount
on either the Linux or Windows host. Since there are no
USB errors, this could only be an issue with the USB MSC
protocol (not likely) or with the FAT format on the AT25
serial FLASH (likely).
in a USB bus capture. However, as of this data the AT25
does not mount on either the Linux or Windows host. This
needs to be retested.
7. This configuration can be used to verify the touchscreen on on the
SAM4E-EK LCD. See the instructions above in the paragraph entitled
@ -1201,7 +1230,8 @@ Configurations
to data overrun problems. The current HSMCI driver
supports DMA via the DMAC. However, the data sheet
only discusses PDC-based HSMCI DMA (although there is
a DMA channel interface definition for HSMCI).
a DMA channel interface definition for HSMCI). So
this is effort is dead-in-the-water for now.
usbnsh:
@ -1224,7 +1254,7 @@ Configurations
entitled "USB Full-Speed Device",
b. The CDC/ACM serial class is enabled as described in the paragraph
"CDC/ACM Serial Device Class"
"CDC/ACM Serial Device Class".
c. The serial console is disabled:
@ -1241,6 +1271,10 @@ Configurations
d. Support for debug output on UART0 is provided as described in the
next note.
3. If you send large amounts of data to the target, you may see data
loss due to RX overrun errors. See the NOTES in the section entitle
"CDC/ACM Serial Device Class" for some possible work-arounds.
3. This configuration does have UART0 output enabled and set up as
the system logging device: