Bug 197109

Summary: Hardware flow control (both CTS/RTS and XON/XOFF) not respected
Product: Drivers Reporter: ejtagle
Component: SerialAssignee: Russell King (rmk)
Status: NEW ---    
Severity: high CC: kernel, m.cavallini, o-schindler, olof.tangrot, sven.koehler
Priority: P1    
Hardware: All   
OS: Linux   
Kernel Version: latest Subsystem:
Regression: No Bisected commit-id:

Description ejtagle 2017-10-02 18:15:56 UTC
On USB to serial devices, if I set cooked mode, hardware flow control (either XON/XOFF or RTS/CTS), even if the device sends an XOFF char, the USB to serial converter will still continue sending characters.

I have traced the problem: 

If you look at n_tty.c: (https://github.com/torvalds/linux/blob/master/drivers/tty/n_tty.c) , take a look at n_tty_receive_char_special() . There you will see

"...
if (c == STOP_CHAR(tty)) {
stop_tty(tty);"...

This should stop the tty sending bytes when an XOFF char is sent.

stop_tty(tty) is defined in tty_io.c: (https://github.com/torvalds/linux/blob/master/drivers/tty/tty_io.c) , and that calls :

if (tty->ops->stop)
tty->ops->stop(tty);

ops is a pointer to the tty operations. In the case of USB to serial adapters, that structure is defined on (usb-serial.c): https://github.com/torvalds/linux/blob/master/drivers/usb/serial/usb-serial.c

Take a look at the definition of tty_operations (line 1180). There is no definition for either start() or stop() methods, so essentially, the USB to serial driver will never stop sending characters. If there is something waiting to be sent, it will be sent.

(original analysis was done by TG9541 (https://github.com/neundorf/CuteCom/issues/22, last comment), and I verified that it still applies to the latest kernel version
Comment 1 Olof Tångrot 2017-12-25 15:12:42 UTC
I can confirm this problem using the ch341 driver. However using FTDI-driver detecting a FTDI232RL chip flow control with XON/XOFF works (verifed using gtkterm and an own POSIX compliant program). Both drivers provides a /dev/ttyUSBx device so I can't confirm that the problem affects all USB-2-serial drivers and for the FTDI driver the kernel seems to provide working XON/XOFF flow control.
Comment 2 Sven Köhler 2018-10-01 00:50:26 UTC
To enable RTS/CTS hardware flow control, the following code needs to be added to ch341_set_termios:

  if (C_CRTSCTS(tty)) {
    ch341_control_out(port->serial->dev, CH341_REQ_WRITE_REG, 0x2727, 0x0101);
  } else {
    ch341_control_out(port->serial->dev, CH341_REQ_WRITE_REG, 0x2727, 0x0000);
  }

Not sure if I got this right.

I have an CH340G adapter from ebay with RTS/CTS pins. I will test RTS/CTS flow control with Windows and a patched Linux soon.
Comment 3 MarukoBG 2019-03-18 15:33:07 UTC
I can confirm this issue.
Using a kernel 4.9 Xon/Xoff flow control is ignored where using 3.10.17 it works.
Comment 4 Oliver Schindler 2020-10-08 16:15:01 UTC
Bug (or missing feature ) is still there on 4.15.17 ( Ubuntu 18.04 latest ) and on  latest kernel master.
There is already a patchset available, which fixes even some more things :
https://www.spinics.net/lists/linux-serial/msg21947.html 
but this has to be reworked. Picking and adapting patch 8/10 , solved the issue :


```
--- a/drivers/usb/serial/ch341.c
+++ b/drivers/usb/serial/ch341.c
@@ -66,6 +66,7 @@
 
 #define CH341_REG_BREAK        0x05
 #define CH341_REG_LCR          0x18
+#define CH341_REG_RTSCTS       0x27
 #define CH341_NBREAK_BITS      0x01
 
 #define CH341_LCR_ENABLE_RX    0x80
@@ -467,6 +468,7 @@ static int ch341_tiocmset(struct tty_struct *tty,
        struct ch341_private *priv = usb_get_serial_port_data(port);
        unsigned long flags;
        u8 control;
+        int r;
 
        spin_lock_irqsave(&priv->lock, flags);
        if (set & TIOCM_RTS)
@@ -480,6 +482,18 @@ static int ch341_tiocmset(struct tty_struct *tty,
        control = priv->mcr;
        spin_unlock_irqrestore(&priv->lock, flags);
                                                                                                                                                                                                                                                                               
+        if (tty->termios.c_cflag & CRTSCTS) {                                                                                                                                                                                                                                 
+               r = ch341_control_out(port->serial->dev, CH341_REQ_WRITE_REG,                                                                                                                                                                                                  
+                               CH341_REG_RTSCTS | ((uint16_t)CH341_REG_RTSCTS << 8),                                                                                                                                                                                          
+                               0x0101);                                                                                                                                                                                                                                       
+               if (r < 0) {                                                                                                                                                                                                                                                   
+                       dev_err(&port->dev, "%s - USB control write error (%d)\n",                                                                                                                                                                                             
+                                       __func__, r);                                                                                                                                                                                                                          
+                       tty->termios.c_cflag &= ~CRTSCTS;                                                                                                                                                                                                                      
+               }                                                                                                                                                                                                                                                              
+       }                                                                                                                                                                                                                                                                      
+                                                                                                                                                                                                                                                                              
+                                                                                                                                                                                                                                                                              
        return ch341_set_handshake(port->serial->dev, control);                                                                                                                                                                                                                
 }                                                                                                                                                                                                                                                                         ```