This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 96c5330ea6af58aaa24270a6c5cca311a11c1461 Author: raiden00pl <[email protected]> AuthorDate: Tue Jul 21 17:07:36 2026 +0200 drivers/serial: bulk-copy raw output into the TX buffer uart_writev() queues output one byte at a time via uart_putxmitchar(). Add uart_putxmitbuf() that memcpy()s a whole run into the TX ring buffer and use it when no per-byte processing is needed (OPOST and ECHO clear, not a console). On a full buffer fall back to uart_putxmitchar(), which keeps the blocking and error handling unchanged. 8 MiB write() to /dev/ttyACM0 on nRF52840: 455 -> 573 KB/s. Guarded by CONFIG_SERIAL_TXBULK, default !DEFAULT_SMALL. Assisted-by: Claude Code Signed-off-by: raiden00pl <[email protected]> --- drivers/serial/Kconfig | 11 ++++++ drivers/serial/serial.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index ca4f7ae6494..399c398627c 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -314,6 +314,17 @@ config SERIAL_TERMIOS Note: other software settings (echo, \r\n<->\n, break, tcflush) are always supported. +config SERIAL_TXBULK + bool "Bulk TX buffer copy" + default !DEFAULT_SMALL + ---help--- + Copy whole runs of raw output into the TX buffer with memcpy() + instead of one byte at a time. Roughly doubles the write() + throughput on slow cores at the cost of about 150 bytes of + code. Only used on non-console ports with no output + post-processing and no echo; every other case keeps the + byte-wise path. + config TTY_LAUNCH bool "Enable feature TTY launch program" depends on SCHED_HPWORK diff --git a/drivers/serial/serial.c b/drivers/serial/serial.c index 8c4704cd3c7..cb0bf595a00 100644 --- a/drivers/serial/serial.c +++ b/drivers/serial/serial.c @@ -120,6 +120,10 @@ static void uart_poll_notify(FAR uart_dev_t *dev, unsigned int min, /* Write support */ +#ifdef CONFIG_SERIAL_TXBULK +static size_t uart_putxmitbuf(FAR uart_dev_t *dev, FAR const char *buf, + size_t len); +#endif static int uart_putxmitchar(FAR uart_dev_t *dev, int ch, bool oktoblock); static inline ssize_t uart_irqwrite(FAR uart_dev_t *dev, @@ -249,6 +253,71 @@ static void uart_poll_notify(FAR uart_dev_t *dev, unsigned int min, leave_critical_section(flags); } +#ifdef CONFIG_SERIAL_TXBULK +/**************************************************************************** + * Name: uart_putxmitbuf + * + * Description: + * Copy as many bytes as currently fit into the TX buffer, without + * blocking. Returns the number of bytes copied, which may be zero if + * the TX buffer is full. The caller handles a full buffer with + * uart_putxmitchar(). + * + ****************************************************************************/ + +static size_t uart_putxmitbuf(FAR uart_dev_t *dev, FAR const char *buf, + size_t len) +{ + size_t total = 0; + size_t nfree; + size_t ncopy; + int head = dev->xmit.head; + int tail = dev->xmit.tail; /* Snapshot: the drain side only frees space */ + + while (total < len) + { + /* Contiguous free space at the head, keeping one byte unused to + * distinguish a full buffer from an empty one. + */ + + if (head < tail) + { + nfree = tail - head - 1; + } + else if (tail > 0) + { + nfree = dev->xmit.size - head; + } + else + { + nfree = dev->xmit.size - head - 1; + } + + if (nfree == 0) + { + break; + } + + ncopy = MIN(len - total, nfree); + memcpy(&dev->xmit.buffer[head], buf + total, ncopy); + total += ncopy; + + /* Publish the new head only after the data is in place */ + + head += ncopy; + if (head >= dev->xmit.size) + { + head = 0; + } + + dev->xmit.head = head; + } + + return total; +} + +#endif /* CONFIG_SERIAL_TXBULK */ + /**************************************************************************** * Name: uart_putxmitchar ****************************************************************************/ @@ -1483,6 +1552,9 @@ static ssize_t uart_writev(FAR struct file *filep, FAR struct uio *uio) FAR const char *segbuf = NULL; size_t seglen = 0; size_t nseg = 0; +#ifdef CONFIG_SERIAL_TXBULK + size_t ncopy; +#endif ssize_t nwritten; ssize_t buflen; bool oktoblock; @@ -1557,7 +1629,7 @@ static ssize_t uart_writev(FAR struct file *filep, FAR struct uio *uio) */ uart_disabletxint(dev); - for (; buflen; buflen--, nseg++) + while (buflen > 0) { if (nseg >= seglen) { @@ -1572,6 +1644,29 @@ static ssize_t uart_writev(FAR struct file *filep, FAR struct uio *uio) nseg = 0; } + /* With no output processing, copy whole runs into the TX buffer at + * once. The bulk copy caches the head index, so it is only used + * when this thread is provably the sole producer: ECHO makes + * uart_readv() echo into the same buffer, and on a console + * uart_irqwrite() produces into it from interrupt context. A full + * TX buffer falls through to uart_putxmitchar() below, which keeps + * the canonical blocking, disconnect and O_NONBLOCK handling. + */ + +#ifdef CONFIG_SERIAL_TXBULK + if ((dev->tc_oflag & OPOST) == 0 && (dev->tc_lflag & ECHO) == 0 && + !dev->isconsole) + { + ncopy = uart_putxmitbuf(dev, segbuf + nseg, seglen - nseg); + if (ncopy > 0) + { + nseg += ncopy; + buflen -= ncopy; + continue; + } + } +#endif + ch = segbuf[nseg]; ret = OK; @@ -1645,6 +1740,9 @@ static ssize_t uart_writev(FAR struct file *filep, FAR struct uio *uio) break; } + + buflen--; + nseg++; } /* Consume the bytes that were successfully queued */
