drivers/serial: hold xmit.lock around echo in uart_readv()

uart_putxmitchar() manipulates dev->xmit.head/buffer directly with
no internal locking - every other caller (uart_write()) takes
dev->xmit.lock first. uart_readv()'s ECHO handling (both the
backspace/delete erase sequence and the normal character echo)
calls uart_putxmitchar() without taking that lock, so a concurrent
uart_write() and a local echo can race on the same circular buffer
state, corrupting it.

Take dev->xmit.lock around each echo's uart_putxmitchar() calls,
matching what uart_write() already does. uart_readv() holds
dev->recv.lock for its own duration, but no other code path ever
acquires recv.lock while holding xmit.lock, so nesting xmit.lock
inside the existing recv.lock scope here doesn't introduce a new
lock-ordering cycle.

Fixes #14845

Signed-off-by: yi chen <94xhn1@gmail.com>
This commit is contained in:
yi chen 2026-07-10 06:12:11 +08:00 committed by Alan C. Assis
parent 3ffe5a9358
commit 6fdc2dde41

View file

@ -1031,9 +1031,11 @@ static ssize_t uart_readv(FAR struct file *filep, FAR struct uio *uio)
recvd--;
if (dev->tc_lflag & ECHO)
{
nxmutex_lock(&dev->xmit.lock);
uart_putxmitchar(dev, '\b', true);
uart_putxmitchar(dev, ' ', true);
uart_putxmitchar(dev, '\b', true);
nxmutex_unlock(&dev->xmit.lock);
#ifdef CONFIG_SERIAL_TXDMA
uart_dmatxavail(dev);
@ -1088,12 +1090,14 @@ static ssize_t uart_readv(FAR struct file *filep, FAR struct uio *uio)
if (!iscntrl(ch & 0xff) || ch == '\n')
{
nxmutex_lock(&dev->xmit.lock);
if (ch == '\n')
{
uart_putxmitchar(dev, '\r', true);
}
uart_putxmitchar(dev, ch, true);
nxmutex_unlock(&dev->xmit.lock);
/* Mark the tx buffer have echoed content here,
* to avoid the tx buffer is empty such as special escape