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 */
