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 d04f56f660ec7efeebdcf4be93d4869d7bb34aea
Author: Marco Casaroli <[email protected]>
AuthorDate: Sat Oct 31 23:07:41 2026 +0100

    drivers/serial: Discard the INTR and SUSP characters that send a signal.
    
    When ISIG is set and the terminal knows its foreground process, the
    INTR (Ctrl-C) and SUSP (Ctrl-Z) characters send SIGINT and SIGTSTP.
    The driver also put the character into the receive buffer, so the next
    read() gave it to the program: after Ctrl-C a shell got "^C" in front
    of the next command line.  POSIX says that the terminal discards such a
    character when it processes it.
    
    Now uart_check_special() removes every special character that it acts
    on: INTR and SUSP when they send a signal, and the LAUNCH and
    FORCE_PANIC characters.  Its size argument is now in and out: it
    returns the number of characters that are left.  All receive paths use
    it: receive(), recvbuf() and the DMA path, where the characters that
    are left in the second region move to follow the first region.  A
    character that sends no signal (no foreground process, or ISIG not
    set) stays as before.
    
    uart_check_special() now handles every character of the batch, not
    only the ones before the first special character, and returns the
    signal of the first INTR or SUSP character.  uart_recvchars() keeps the
    first signal of a batch: the check of each later character set it back
    to 0, so a Ctrl-C with more characters in the same interrupt sent no
    signal.
    
    Assisted-by: Claude Code:claude-opus-5-5
    Signed-off-by: Marco Casaroli <[email protected]>
---
 drivers/serial/serial.c       | 60 ++++++++++++++++++++++++++-----------------
 drivers/serial/serial_dma.c   | 59 +++++++++++++++++++++++++++++-------------
 drivers/serial/serial_io.c    | 29 ++++++++++++++++++---
 include/nuttx/serial/serial.h | 18 +++++--------
 4 files changed, 110 insertions(+), 56 deletions(-)

diff --git a/drivers/serial/serial.c b/drivers/serial/serial.c
index 46943e5382a..775c25938d5 100644
--- a/drivers/serial/serial.c
+++ b/drivers/serial/serial.c
@@ -2585,24 +2585,23 @@ void uart_reset_sem(FAR uart_dev_t *dev)
  * Name: uart_check_special
  *
  * Description:
- *   Check if the SIGINT or SIGTSTP character is in the contiguous Rx DMA
- *   buffer region.  The first signal associated with the first such
- *   character is returned.
- *
- *   If there multiple such characters in the buffer, only the signal
- *   associated with the first is returned (this a bug!)
+ *   Act on the special characters in the *size characters at buf and
+ *   remove them: the INTR and SUSP characters that send a signal (POSIX:
+ *   the terminal discards them), the launch character and the panic
+ *   character.  *size returns the number of characters that are left.
  *
  * Returned Value:
- *   0 if a signal-related character does not appear in the.  Otherwise,
- *   SIGKILL or SIGTSTP may be returned to indicate the appropriate signal
- *   action.
+ *   SIGINT or SIGTSTP for the first INTR or SUSP character, else 0.
  *
  ****************************************************************************/
 
 #if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) || \
     defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
-int uart_check_special(FAR uart_dev_t *dev, FAR const char *buf, size_t size)
+int uart_check_special(FAR uart_dev_t *dev, FAR char *buf,
+                       FAR size_t *size)
 {
+  size_t kept = 0;
+  int signo = 0;
   size_t i;
 
   if ((dev->tc_lflag & ISIG) == 0)
@@ -2610,49 +2609,62 @@ int uart_check_special(FAR uart_dev_t *dev, FAR const 
char *buf, size_t size)
       return 0;
     }
 
-  for (i = 0; i < size; i++)
+  for (i = 0; i < *size; i++)
     {
+      char ch = buf[i];
+
 #ifdef CONFIG_TTY_FORCE_PANIC
-      if (buf[i] == CONFIG_TTY_FORCE_PANIC_CHAR)
+      if (ch == CONFIG_TTY_FORCE_PANIC_CHAR)
         {
           if (++dev->panic_count >= CONFIG_TTY_FORCE_PANIC_REPEAT_COUNT)
             {
               PANIC_WITH_REGS("Force panic by user.", NULL);
             }
 
-          return 0;
-        }
-      else
-        {
-          dev->panic_count = 0;
+          continue;
         }
+
+      dev->panic_count = 0;
 #endif
 
 #ifdef CONFIG_TTY_LAUNCH
-      if (buf[i] == CONFIG_TTY_LAUNCH_CHAR)
+      if (ch == CONFIG_TTY_LAUNCH_CHAR)
         {
           uart_launch();
-          return 0;
+          continue;
         }
 #endif
 
 #ifdef CONFIG_TTY_SIGINT
       if (dev->pid > 0 && dev->tc_vintr != _POSIX_VDISABLE &&
-          (cc_t)buf[i] == dev->tc_vintr)
+          (cc_t)ch == dev->tc_vintr)
         {
-          return SIGINT;
+          if (signo == 0)
+            {
+              signo = SIGINT;
+            }
+
+          continue;
         }
 #endif
 
 #ifdef CONFIG_TTY_SIGTSTP
       if (dev->pid > 0 && dev->tc_vsusp != _POSIX_VDISABLE &&
-          (cc_t)buf[i] == dev->tc_vsusp)
+          (cc_t)ch == dev->tc_vsusp)
         {
-          return SIGTSTP;
+          if (signo == 0)
+            {
+              signo = SIGTSTP;
+            }
+
+          continue;
         }
 #endif
+
+      buf[kept++] = ch;
     }
 
-  return 0;
+  *size = kept;
+  return signo;
 }
 #endif
diff --git a/drivers/serial/serial_dma.c b/drivers/serial/serial_dma.c
index 836717f78f3..4ccad37ad9d 100644
--- a/drivers/serial/serial_dma.c
+++ b/drivers/serial/serial_dma.c
@@ -29,6 +29,7 @@
 #include <assert.h>
 #include <sys/types.h>
 #include <stdint.h>
+#include <string.h>
 
 #include <nuttx/debug.h>
 #include <nuttx/signal.h>
@@ -45,11 +46,10 @@
  * Name: uart_recvchars_check_special
  *
  * Description:
- *   Check if the SIGINT character is anywhere in the newly received DMA
- *   buffer.
- *
- *   REVISIT:  We must also remove the SIGINT/SIGTSTP character from the Rx
- *   buffer.  It should not be read as normal data by the caller.
+ *   Act on the special characters in the newly received DMA buffer, remove
+ *   them and update xfer->nbytes.  The data can be in two regions: the
+ *   characters that are left in the second region move to follow the ones
+ *   that are left in the first.
  *
  ****************************************************************************/
 
@@ -59,27 +59,49 @@
 static int uart_recvchars_check_special(FAR uart_dev_t *dev)
 {
   FAR struct uart_dmaxfer_s *xfer = &dev->dmarx;
+  size_t nfirst;
+  size_t nsecond;
+  size_t n;
   int signo;
+  int sig;
 
   /* Check if the valid DMAed data is in one or two contiguous regions */
 
   if (xfer->nbytes <= xfer->length)
     {
-      return uart_check_special(dev, xfer->buffer, xfer->nbytes);
+      nfirst = xfer->nbytes;
+      signo  = uart_check_special(dev, xfer->buffer, &nfirst);
+      xfer->nbytes = nfirst;
+      return signo;
     }
-  else
+
+  nfirst  = xfer->length;
+  nsecond = xfer->nbytes - xfer->length;
+  signo   = uart_check_special(dev, xfer->buffer, &nfirst);
+  sig     = uart_check_special(dev, xfer->nbuffer, &nsecond);
+  if (signo == 0)
     {
-      /* REVISIT:  Additional signals could be in the second region. */
+      signo = sig;
+    }
 
-      signo = uart_check_special(dev, xfer->buffer, xfer->length);
-      if (signo != 0)
-        {
-          return signo;
-        }
+  /* Close the gap at the end of the first region with the start of the
+   * second region, then move the rest of the second region to its start.
+   */
 
-      return uart_check_special(dev, xfer->nbuffer,
-                                xfer->nbytes - xfer->length);
+  n = xfer->length - nfirst;
+  if (n > nsecond)
+    {
+      n = nsecond;
     }
+
+  if (n > 0)
+    {
+      memcpy(&xfer->buffer[nfirst], xfer->nbuffer, n);
+      memmove(xfer->nbuffer, &xfer->nbuffer[n], nsecond - n);
+    }
+
+  xfer->nbytes = nfirst + nsecond;
+  return signo;
 }
 #endif
 
@@ -317,13 +339,13 @@ void uart_recvchars_done(FAR uart_dev_t *dev)
 {
   FAR struct uart_dmaxfer_s *xfer = &dev->dmarx;
   FAR struct uart_buffer_s *rxbuf = &dev->recv;
-  size_t nbytes = xfer->nbytes;
+  size_t nbytes;
 #if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) || \
     defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
   int signo = 0;
 
-  /* Check if the SIGINT character is anywhere in the newly received DMA
-   * buffer.
+  /* Act on the special characters in the newly received DMA buffer and
+   * remove them.
    */
 
   signo = uart_recvchars_check_special(dev);
@@ -331,6 +353,7 @@ void uart_recvchars_done(FAR uart_dev_t *dev)
 
   /* Move head for nbytes. */
 
+  nbytes       = xfer->nbytes;
   rxbuf->head  = (rxbuf->head + nbytes) % rxbuf->size;
   xfer->nbytes = 0;
   xfer->length = xfer->nlength = 0;
diff --git a/drivers/serial/serial_io.c b/drivers/serial/serial_io.c
index 3361c11f3f4..057d1299157 100644
--- a/drivers/serial/serial_io.c
+++ b/drivers/serial/serial_io.c
@@ -164,7 +164,7 @@ void uart_recvchars(FAR uart_dev_t *dev)
     defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
   int signo = 0;
 #endif
-  uint16_t nbytes = 0;
+  size_t nbytes = 0;
 
   /* Loop putting characters into the receive buffer until there are no
    * further characters to available.
@@ -183,6 +183,10 @@ void uart_recvchars(FAR uart_dev_t *dev)
       sbuf_size_t tail = rxbuf->tail;
       bool is_full = (nexthead == tail);
       FAR char *pbuf = NULL;
+#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) || \
+    defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
+      int sig = 0;
+#endif
       char ch;
 
 #ifdef CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS
@@ -260,6 +264,10 @@ void uart_recvchars(FAR uart_dev_t *dev)
                 }
 
               nbytes = ret;
+#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) || \
+    defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
+              sig = uart_check_special(dev, pbuf, &nbytes);
+#endif
               rxbuf->head += nbytes;
               if (rxbuf->head >= rxbuf->size)
                 {
@@ -276,6 +284,11 @@ void uart_recvchars(FAR uart_dev_t *dev)
                 {
                   continue;
                 }
+
+#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) || \
+    defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
+              sig = uart_check_special(dev, pbuf, &nbytes);
+#endif
             }
         }
       else if(dev->ops->receive)
@@ -286,6 +299,11 @@ void uart_recvchars(FAR uart_dev_t *dev)
           pbuf = &ch;
           nbytes = 1;
 
+#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) || \
+    defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
+          sig = uart_check_special(dev, pbuf, &nbytes);
+#endif
+
           /* If the RX buffer becomes full, then the serial data is
            * discarded. This is necessary because on most serial hardware,
            * you must read the data in order to clear the RX interrupt.
@@ -295,7 +313,7 @@ void uart_recvchars(FAR uart_dev_t *dev)
            * (unless it has some large internal buffering).
            */
 
-          if (!is_full)
+          if (!is_full && nbytes > 0)
             {
               /* Add the character to the buffer */
 
@@ -309,7 +327,12 @@ void uart_recvchars(FAR uart_dev_t *dev)
 
 #if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) || \
     defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
-      signo = uart_check_special(dev, pbuf, nbytes);
+      /* Keep the first signal: a later character must not clear it */
+
+      if (signo == 0)
+        {
+          signo = sig;
+        }
 #endif
     }
 
diff --git a/include/nuttx/serial/serial.h b/include/nuttx/serial/serial.h
index 66532289862..65877653176 100644
--- a/include/nuttx/serial/serial.h
+++ b/include/nuttx/serial/serial.h
@@ -571,24 +571,20 @@ void uart_reset_sem(FAR uart_dev_t *dev);
  * Name: uart_check_special
  *
  * Description:
- *   Check if the SIGINT or SIGTSTP character is in the contiguous Rx DMA
- *   buffer region.  The first signal associated with the first such
- *   character is returned.
- *
- *   If there multiple such characters in the buffer, only the signal
- *   associated with the first is returned (this a bug!)
+ *   Act on the special characters in the *size characters at buf and
+ *   remove them: the INTR and SUSP characters that send a signal (POSIX:
+ *   the terminal discards them), the launch character and the panic
+ *   character.  *size returns the number of characters that are left.
  *
  * Returned Value:
- *   0 if a signal-related character does not appear in the.  Otherwise,
- *   SIGKILL or SIGTSTP may be returned to indicate the appropriate signal
- *   action.
+ *   SIGINT or SIGTSTP for the first INTR or SUSP character, else 0.
  *
  ****************************************************************************/
 
 #if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP) || \
     defined(CONFIG_TTY_FORCE_PANIC) || defined(CONFIG_TTY_LAUNCH)
-int uart_check_special(FAR uart_dev_t *dev, FAR const char *buf,
-                       size_t size);
+int uart_check_special(FAR uart_dev_t *dev, FAR char *buf,
+                       FAR size_t *size);
 #endif
 
 /****************************************************************************

Reply via email to