drivers/serial: Add a timeout to uart_tcdrain(). Even if tcflush() is used to discard buffered data, the close() can still hang if (a) hardware flow control is enabled, (b) CTS == 1, and (c) there is data stuck in the hardware Tx FIFO. tcflush() does not currently clear the MCU serial drivers' hardware Tx FIFOs. This is a workaround for that.

This commit is contained in:
Gregory Nutt
2018-05-27 11:39:34 -06:00
parent ca1a7c2b28
commit f3392e0a6c
2 changed files with 30 additions and 9 deletions
+28 -6
View File
@@ -51,6 +51,7 @@
#include <nuttx/irq.h> #include <nuttx/irq.h>
#include <nuttx/arch.h> #include <nuttx/arch.h>
#include <nuttx/clock.h>
#include <nuttx/sched.h> #include <nuttx/sched.h>
#include <nuttx/signal.h> #include <nuttx/signal.h>
#include <nuttx/semaphore.h> #include <nuttx/semaphore.h>
@@ -109,7 +110,7 @@ static void uart_pollnotify(FAR uart_dev_t *dev, pollevent_t eventset);
static int uart_putxmitchar(FAR uart_dev_t *dev, int ch, bool oktoblock); static int uart_putxmitchar(FAR uart_dev_t *dev, int ch, bool oktoblock);
static inline ssize_t uart_irqwrite(FAR uart_dev_t *dev, FAR const char *buffer, static inline ssize_t uart_irqwrite(FAR uart_dev_t *dev, FAR const char *buffer,
size_t buflen); size_t buflen);
static int uart_tcdrain(FAR uart_dev_t *dev); static int uart_tcdrain(FAR uart_dev_t *dev, systime_t timeout);
/* Character driver methods */ /* Character driver methods */
@@ -396,7 +397,7 @@ static inline ssize_t uart_irqwrite(FAR uart_dev_t *dev, FAR const char *buffer,
* *
************************************************************************************/ ************************************************************************************/
static int uart_tcdrain(FAR uart_dev_t *dev) static int uart_tcdrain(FAR uart_dev_t *dev, systime_t timeout)
{ {
int ret; int ret;
@@ -411,6 +412,7 @@ static int uart_tcdrain(FAR uart_dev_t *dev)
if (ret >= 0) if (ret >= 0)
{ {
irqstate_t flags; irqstate_t flags;
systime_t start;
/* Trigger emission to flush the contents of the tx buffer */ /* Trigger emission to flush the contents of the tx buffer */
@@ -431,7 +433,13 @@ static int uart_tcdrain(FAR uart_dev_t *dev)
else else
#endif #endif
{ {
/* Continue waiting while the TX buffer is not empty */ /* Continue waiting while the TX buffer is not empty.
*
* NOTE: There is no timeout on the following loop. In
* situations were this loop could hang (with hardware flow
* control, as an example), the caller should call
* tcflush() first to discard this buffered Tx data.
*/
ret = OK; ret = OK;
while (ret >= 0 && dev->xmit.head != dev->xmit.tail) while (ret >= 0 && dev->xmit.head != dev->xmit.tail)
@@ -443,7 +451,7 @@ static int uart_tcdrain(FAR uart_dev_t *dev)
/* Wait for some characters to be sent from the buffer with /* Wait for some characters to be sent from the buffer with
* the TX interrupt enabled. When the TX interrupt is * the TX interrupt enabled. When the TX interrupt is
* enabled, uart_xmitchars() should execute and remove some * enabled, uart_xmitchars() should execute and remove some
* of the data from the TX buffer. We mayhave to wait several * of the data from the TX buffer. We may have to wait several
* times for the TX buffer to be entirely emptied. * times for the TX buffer to be entirely emptied.
* *
* NOTE that interrupts will be re-enabled while we wait for * NOTE that interrupts will be re-enabled while we wait for
@@ -466,15 +474,29 @@ static int uart_tcdrain(FAR uart_dev_t *dev)
* this event, so we have to do a busy wait poll. * this event, so we have to do a busy wait poll.
*/ */
/* Set up for the timeout */
start = clock_systimer();
if (ret >= 0) if (ret >= 0)
{ {
while (!uart_txempty(dev)) while (!uart_txempty(dev))
{ {
systime_t elapsed;
#ifndef CONFIG_DISABLE_SIGNALS #ifndef CONFIG_DISABLE_SIGNALS
nxsig_usleep(POLL_DELAY_USEC); nxsig_usleep(POLL_DELAY_USEC);
#else #else
up_mdelay(POLL_DELAY_MSEC); up_mdelay(POLL_DELAY_MSEC);
#endif #endif
/* Check for a timeout */
elapsed = clock_systimer() - start;
if (elapsed >= timeout)
{
return -ETIMEDOUT;
}
} }
} }
@@ -657,7 +679,7 @@ static int uart_close(FAR struct file *filep)
{ {
/* Now we wait for the transmit buffer(s) to clear */ /* Now we wait for the transmit buffer(s) to clear */
(void)uart_tcdrain(dev); (void)uart_tcdrain(dev, 4 * TICK_PER_SEC);
} }
/* Free the IRQ and disable the UART */ /* Free the IRQ and disable the UART */
@@ -1345,7 +1367,7 @@ static int uart_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
case TCDRN: case TCDRN:
{ {
ret = uart_tcdrain(dev); ret = uart_tcdrain(dev, 10 * TICK_PER_SEC);
} }
break; break;
#endif #endif
+2 -3
View File
@@ -222,9 +222,8 @@
/* Line Control (used with tcflush()) */ /* Line Control (used with tcflush()) */
#define TCIFLUSH 0 /* Flush pending input. Flush untransmitted #define TCIFLUSH 0 /* Flush pending input */
* output */ #define TCIOFLUSH 1 /* Flush both pending input and untransmitted
#define TCIOFLUSH 1 /* Flush both pending input and untransmitte
* output */ * output */
#define TCOFLUSH 2 /* Flush untransmitted output */ #define TCOFLUSH 2 /* Flush untransmitted output */