Skip to content
  • Lawrence Rust's avatar
    8250: Fix tcsetattr to avoid ioctl(TIOCMIWAIT) hang · 47d3904f
    Lawrence Rust authored
    
    
    Calling tcsetattr prevents any thread(s) currently suspended in ioctl
    TIOCMIWAIT for the same device from ever resuming.
    
    If a thread is suspended inside a call to ioctl TIOCMIWAIT, waiting for
    a modem status change, then the 8250 driver enables modem status
    interrupts (MSI).  The device interrupt service routine resumes the
    suspended thread(s) on the next MSI.
    
    If while the thread(s) are suspended, another thread calls tcsetattr
    then the 8250 driver disables MSI (unless CTS/RTS handshaking is
    enabled) thus preventing the suspended thread(s) from ever being
    resumed.
    
    This patch only disables MSI in tcsetattr handling if there are no
    suspended threads.
    
    Program to demonstrate bug & fix:
    
    /* gcc miwait.c -o miwait -l pthread */
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <pthread.h>
    #include <termios.h>
    #include <sys/ioctl.h>
    #include <linux/serial.h>
    
    static void* monitor( void* pv);
    static int s_fd;
    
    int main( void)
      {
      const char kszDev[] = "/dev/ttyS0";
      pthread_t t;
      struct termios tio;
    
      s_fd = open( kszDev, O_RDWR | O_NONBLOCK);
      if ( s_fd < 0)
        return fprintf( stderr, "Error(%d) opening %s: %s\n", errno, kszDev, strerror( errno)), 1;
    
      pthread_create( &t, NULL, &monitor, NULL);
    
      /* Modem status changes seen here */
      puts( "Main: awaiting status changes");
      sleep( 5);
    
      tcgetattr( s_fd, &tio);
      tio.c_cflag ^= CSTOPB;
    
      /* But not after here */
      puts( "Main: tcsetattr called");
      tcsetattr( s_fd, TCSANOW, &tio);
    
      for (;;)
        sleep( 1);
      }
    
    static void* monitor( void* pv)
      {
      (void)pv;
      for(;;)
        {
        unsigned uModem;
        struct serial_icounter_struct cnt;
    
        if ( ioctl( s_fd, TIOCMGET, &uModem) < 0)
          fprintf( stderr, "Error(%d) in TIOCMGET: %s\n", errno, strerror( errno));
        printf( "Modem status:%s%s%s%s%s%s\n",
          (uModem & TIOCM_RTS) ? " RTS" : "",
          (uModem & TIOCM_DTR) ? " DTR" : "",
          (uModem & TIOCM_CTS) ? " CTS" : "",
          (uModem & TIOCM_DSR) ? " DSR" : "",
          (uModem & TIOCM_CD) ? " CD" : "",
          (uModem & TIOCM_RI) ? " RI" : ""
        );
    
        if ( ioctl( s_fd, TIOCGICOUNT, &cnt) < 0)
          fprintf( stderr, "Error(%d) in TIOCGICOUNT: %s\n", errno, strerror( errno));
        printf( "Irqs: CTS:%d DSR:%d RNG:%d DCD:%d Rx:%d Tx:%d Frame:%d Orun:%d Par:%d Brk:%d Oflow:%d\n",
          cnt.cts, cnt.dsr, cnt.rng, cnt.dcd,
          cnt.rx, cnt.tx, cnt.frame, cnt.overrun, cnt.parity,
          cnt.brk, cnt.buf_overrun
        );
    
        fputs( "Waiting...", stdout), fflush( stdout);
        if ( 0 > ioctl( s_fd, TIOCMIWAIT, (unsigned long)(TIOCM_CAR | TIOCM_RNG | TIOCM_DSR | TIOCM_CTS)))
          fprintf( stderr, "\nError(%d) in TIOCMIWAIT: %s\n", errno, strerror( errno));
        fputs( "\n", stdout);
        }
      return NULL;
      }
    
    Signed-off by Lawrence Rust <lawrence@softsystem.co.uk>
    
    Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
    47d3904f