This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit 3a0fb4b9bfa61058c472eb577e914835f083e468
Author: Lingao Meng <[email protected]>
AuthorDate: Tue Jun 2 11:58:58 2026 +0800

    drivers/serial: bound uart_tcdrain xmit-buffer wait by caller timeout
    
    uart_tcdrain() takes a caller-supplied timeout (e.g. 10s from the
    TCDRN ioctl path), but the timeout was only applied to the final
    TX-FIFO polling loop.  The earlier xmit-buffer drain loop called
    nxsem_wait(&dev->xmitsem) with no timeout, so any condition that
    prevents the lower half from posting xmitsem (e.g. a stuck DMA
    completion path, a wedged hardware-flow-control stall) would block
    tcdrain() indefinitely, regardless of the timeout the caller asked
    for.  The pre-existing comment ("NOTE: There is no timeout on the
    following loop. ... the caller should call tcflush() first") openly
    acknowledged this hang.
    
    Move the start timestamp before both phases and replace the bare
    nxsem_wait() with nxsem_tickwait() using the remaining time, so the
    total time spent in tcdrain() honors the caller's timeout regardless
    of which phase stalls.  When the remaining time is already exhausted,
    short-circuit to -ETIMEDOUT without calling into the scheduler.  The
    existing exit path (drop critical section, skip the FIFO polling
    loop, unlock the xmit mutex, leave the cancellation point) handles
    the new -ETIMEDOUT propagation correctly without further changes.
    
    Also fold the "Set up for the timeout" comment into the kludge
    REVISIT comment, since the timestamp is no longer set up at that
    point.
    
    Signed-off-by: Lingao Meng <[email protected]>
---
 drivers/serial/serial.c | 39 +++++++++++++++++++++++++--------------
 1 file changed, 25 insertions(+), 14 deletions(-)

diff --git a/drivers/serial/serial.c b/drivers/serial/serial.c
index cc734e0a84b..a30fdcaa2fe 100644
--- a/drivers/serial/serial.c
+++ b/drivers/serial/serial.c
@@ -525,6 +525,14 @@ static int uart_tcdrain(FAR uart_dev_t *dev,
     {
       irqstate_t flags;
       clock_t start;
+      clock_t elapsed;
+
+      /* Take a single timestamp.  The caller-supplied timeout bounds the
+       * total time spent in this function, covering both the xmit-buffer
+       * drain wait below and the FIFO-empty polling loop further down.
+       */
+
+      start = clock_systime_ticks();
 
       /* Trigger emission to flush the contents of the tx buffer */
 
@@ -544,12 +552,11 @@ static int uart_tcdrain(FAR uart_dev_t *dev,
       else
 #endif
         {
-          /* 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.
+          /* Continue waiting while the TX buffer is not empty.  The wait is
+           * bounded by the caller-supplied timeout so this loop cannot hang
+           * indefinitely (e.g. on hardware-flow-control stalls).  The caller
+           * may still call tcflush() to discard the buffered Tx data on
+           * timeout.
            */
 
           ret = OK;
@@ -569,7 +576,17 @@ static int uart_tcdrain(FAR uart_dev_t *dev,
               uart_dmatxavail(dev);
 #endif
               uart_enabletxint(dev);
-              ret = nxsem_wait(&dev->xmitsem);
+
+              elapsed = clock_systime_ticks() - start;
+              if (elapsed >= timeout)
+                {
+                  ret = -ETIMEDOUT;
+                }
+              else
+                {
+                  ret = nxsem_tickwait(&dev->xmitsem, timeout - elapsed);
+                }
+
               uart_disabletxint(dev);
             }
         }
@@ -581,21 +598,15 @@ static int uart_tcdrain(FAR uart_dev_t *dev,
        * this event, so we have to do a busy wait poll.
        */
 
-      /* Set up for the timeout
-       *
-       * REVISIT:  This is a kludge.  The correct fix would be add an
+      /* REVISIT: This is a kludge.  The correct fix would be add an
        * interface to the lower half driver so that the tcflush() operation
        * all also cause the lower half driver to clear and reset the Tx FIFO.
        */
 
-      start = clock_systime_ticks();
-
       if (ret >= 0)
         {
           while (!uart_txempty(dev))
             {
-              clock_t elapsed;
-
               nxsched_usleep(POLL_DELAY_USEC);
 
               /* Check for a timeout */

Reply via email to