Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serial port hangs on close()

I developed this simple kernel module, which emulates a serial port by using a FIFO queue and a timer (read from hardware : out from the queue, write to hardware : insert in the queue).
Source code is shown next.

  #include <linux/kernel.h>
  #include <linux/errno.h>
  #include <linux/init.h>
  #include <linux/slab.h>
  #include <linux/tty.h>
  #include <linux/tty_flip.h>
  #include <linux/serial.h>
  #include <linux/serial_core.h>
  #include <linux/module.h>


  #define TINY_SERIAL_DEBUG
  #define pr_fmt(fmt) "tiny_serial: " fmt

  #if defined(TINY_SERIAL_DEBUG)
  #define DBG(fmt, ...)  printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
  #else 
  #define DBG(fmt, ...)  no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)   
  #endif

  #define DRIVER_AUTHOR "Me Me <[email protected]>"
  #define DRIVER_DESC "Tiny serial driver"

  /* Module information */
  MODULE_AUTHOR( DRIVER_AUTHOR );
  MODULE_DESCRIPTION( DRIVER_DESC );
  MODULE_LICENSE("GPL");

  #define DELAY_TIME        100// HZ * 2    /* 2 seconds per character */
  #define TINY_DATA_CHARACTER    't'

  #define TINY_SERIAL_MAJOR    240 //240    /* experimental range */
  #define TINY_SERIAL_MINORS    1 //1    /* only have one minor */
  #define UART_NR            1    /* only use one port */

  #define TINY_SERIAL_NAME    "ttytiny"

  #define MY_NAME            TINY_SERIAL_NAME

  #define BUF_SIZE 4096

  static char buf[BUF_SIZE];
  static char *read_ptr;
  static char *write_ptr;

  static struct timer_list *timer;


  static void serial_out(char data) 
  {
      *write_ptr = data;
      write_ptr++;
      if (write_ptr >= buf + BUF_SIZE)
          write_ptr = buf;
  }

  static void serial_in(char *data)
  {
      if (read_ptr == NULL) {
          DBG("pointer is null !\n");
      }

      if (read_ptr && (read_ptr != write_ptr)) {
          *data = *read_ptr;
            read_ptr++;
         if(read_ptr >= buf + BUF_SIZE)
              read_ptr = buf;
      }
  }


  static void tiny_stop_tx(struct uart_port *port)
  {
      DBG("tiny_stop_tx()\n");
  }

  static void tiny_stop_rx(struct uart_port *port)
  {
      DBG("tiny_stop_rx()\n");
  }

  static void tiny_enable_ms(struct uart_port *port)
  {
      DBG("tiny_enable_ms()\n");
  }

  static void tiny_rx_chars(struct uart_port *port, int size)
  {
      int i = 0;
      char byte;
      char flag;
      struct tty_port *tty = &port->state->port;

      if (size <= 0) {
          return;
      }

      while (size--) {
         serial_in(&byte);
         DBG("read: 0x%2x\n", byte);
         flag = TTY_NORMAL;
         port->icount.rx++;

         if (uart_handle_sysrq_char(port, byte)) {
             DBG("found ignore char !\n");
             goto ignore_char;
         }

         uart_insert_char(port, 0, 0, byte, flag);

 ignore_char:
         i ++;
     }

     tty_flip_buffer_push(tty);
     DBG("push to user space !\n");
 }

 static int tiny_tx_chars(struct uart_port *port)
 {
     struct circ_buf *xmit = &port->state->xmit;
     int count;

     DBG("tiny_tx_chars()\n"); 

     if (port->x_char) {
         DBG("wrote 0x%2x\r\n", port->x_char);
         port->icount.tx++;
         port->x_char = 0;
         return 0;
     }
     if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
         tiny_stop_tx(port);
         return 0;
     }

     count = port->fifosize >> 1;

     do {
         DBG("wrote 0x%2x\r\n", xmit->buf[xmit->tail]);
         serial_out(xmit->buf[xmit->tail]);
         xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
         port->icount.tx++;
         if (uart_circ_empty(xmit))
             break;
     } while (--count > 0);

     if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
         uart_write_wakeup(port);

     if (uart_circ_empty(xmit))
         tiny_stop_tx(port);

     return ((port->fifosize >> 1) - count + 1);
 }

 static void tiny_start_tx(struct uart_port *port)
{
     DBG("tiny_start_tx()\n");
 }

 static void tiny_timer(unsigned long data)
 {
     struct uart_port *port;
     struct tty_port *tport;
     int ret = 0;

     DBG("tiny_timer()\n"); 
     port = (struct uart_port *)data;
     if (!port)
         return;
     if (!port->state)
         return;
     tport = &port->state->port;

     /* resubmit the timer again */
     timer->expires = jiffies + DELAY_TIME;
     add_timer(timer);

     /* see if we have any data to transmit */
     ret = tiny_tx_chars(port);
     tiny_rx_chars(port, ret);
}

 static unsigned int tiny_tx_empty(struct uart_port *port)
 {
     DBG("tiny_tx_empty()\n");
     return 0;
 }

 static unsigned int tiny_get_mctrl(struct uart_port *port)
 {
    DBG("tiny_get_mctrl()\n");
     return 0;
 }

 static void tiny_set_mctrl(struct uart_port *port, unsigned int mctrl)
 {
     DBG("tiny_set_mctrl()\n"); 
 }

 static void tiny_break_ctl(struct uart_port *port, int break_state)
 {
     DBG("tiny_set_mctrl()\n"); 
 }

 static void tiny_set_termios(struct uart_port *port,
                  struct ktermios *new, struct ktermios *old)
 {
     int baud, quot, cflag = new->c_cflag;

     DBG("tiny_set_termios()\n"); 

     /* get the byte size */
     switch (cflag & CSIZE) {
     case CS5:
         DBG(" - data bits = 5\n");
         break;
     case CS6:
         DBG(" - data bits = 6\n");
         break;
     case CS7:
         DBG(" - data bits = 7\n");
         break;
     default: // CS8
         DBG(" - data bits = 8\n");
         break;
     }

     /* determine the parity */
     if (cflag & PARENB)
         if (cflag & PARODD)
             DBG(" - parity = odd\n");
         else
             DBG(" - parity = even\n");
     else
         DBG(" - parity = none\n");

     /* figure out the stop bits requested */
     if (cflag & CSTOPB)
         DBG(" - stop bits = 2\n");
     else
         DBG(" - stop bits = 1\n");

     /* figure out the flow control settings */
     if (cflag & CRTSCTS)
         DBG(" - RTS/CTS is enabled\n");
     else
         DBG(" - RTS/CTS is disabled\n");

     /* Set baud rate */
     baud = uart_get_baud_rate(port, new, old, 9600, 115200);
     quot = uart_get_divisor(port, baud);


 }

 static int tiny_startup(struct uart_port *port)
 {
     /* this is the first time this port is opened */
     /* do any hardware initialization needed here */

     DBG("tiny_startup()\n");

     /* create our timer and submit it */
     if (!timer) {
         timer = kmalloc(sizeof(*timer), GFP_KERNEL);
         if (!timer)
             return -ENOMEM;
         init_timer(timer);
     }
     timer->data = (unsigned long)port;
     timer->expires = jiffies + DELAY_TIME;
     timer->function = tiny_timer;
     add_timer(timer);
     return 0;
 }

 static void tiny_shutdown(struct uart_port *port)
 {
     /* The port is being closed by the last user. */
     /* Do any hardware specific stuff here */

     DBG("tiny_shutdown()\n");

     /* shut down our timer */
     del_timer(timer);
 }

 static const char *tiny_type(struct uart_port *port)
 {
     DBG("tiny_type()\n");
     return "tinytty";
 }

 static void tiny_release_port(struct uart_port *port)
 {
     DBG("tiny_release_port()\n");
 }

 static int tiny_request_port(struct uart_port *port)
 {
     DBG("tiny_request_port()\n");
    return 0;
 }

 static void tiny_config_port(struct uart_port *port, int flags)
 {
     DBG("tiny_config_port()\n");
 }

 static int tiny_verify_port(struct uart_port *port, struct serial_struct *ser)
 {
     DBG("tiny_verify_port()\n");
     return 0;
 }

 static struct uart_ops tiny_ops = {
     .tx_empty    = tiny_tx_empty,
     .set_mctrl    = tiny_set_mctrl,
     .get_mctrl    = tiny_get_mctrl,
     .stop_tx    = tiny_stop_tx,
     .start_tx    = tiny_start_tx,
     .stop_rx    = tiny_stop_rx,
     .enable_ms    = tiny_enable_ms,
     .break_ctl    = tiny_break_ctl,
     .startup    = tiny_startup,
     .shutdown    = tiny_shutdown,
     .set_termios    = tiny_set_termios,
     .type        = tiny_type,
     .release_port    = tiny_release_port,
     .request_port    = tiny_request_port,
     .config_port    = tiny_config_port,
     .verify_port    = tiny_verify_port,
 };

 static struct uart_port tiny_port = {
     .ops        = &tiny_ops,
     .line       = 0,
     .type       = 104,
     .iotype     = SERIAL_IO_PORT,
     .fifosize   = 128,
     .flags      = ASYNC_BOOT_AUTOCONF,
     .irq        = 0,
 };

 static struct uart_driver tiny_reg = {
     .owner        = THIS_MODULE,
     .driver_name    = TINY_SERIAL_NAME,
     .dev_name    = TINY_SERIAL_NAME,
     .major        = TINY_SERIAL_MAJOR,
     .minor        = TINY_SERIAL_MINORS,
     .nr        = UART_NR,
 };

 static int __init tiny_init(void)
 {
     int result;

     DBG(KERN_INFO "Tiny serial driver loaded\n");

     result = uart_register_driver(&tiny_reg);
     if (result) {
         DBG("tiny_init() error!\n");
         return result;
     }

     result = uart_add_one_port(&tiny_reg, &tiny_port);
     if (result) {
         DBG("uart_add_one_port() error!\n");
         uart_unregister_driver(&tiny_reg);
     }

     read_ptr = buf;
     write_ptr = buf;

     return result;
 }

 module_init(tiny_init);

Then, I wrote a simple test-application which configures the port settings (baud rate, parity, stop bits, etc) and starts a write + read transaction, reading the previously written string.
Source code is shown next.

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h> 
  #include <strings.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <unistd.h>
  #include <termios.h>
  #include <sys/stat.h>

  #include <sys/select.h>
  #include <sys/time.h>

  #define PAR_NONE   0
  #define PAR_EVEN   1
  #define PAR_ODD    2

  #define DATA_8BIT  1
  #define DATA_7BIT  2

  #define STOP_1BIT  1
  #define STOP_2BIT  2


  struct tiny_op {
      int max_fd;
      int fd;
      fd_set rfds;
      fd_set wfds;
      fd_set efds;
      struct timeval r_timeout;
      struct timeval w_timeout; 
  };

  static struct tiny_op tops;


  static void set_termios(struct termios *termios, int baudrate, int parity, int bits, int stop) 
  {
      termios->c_cflag |= CLOCAL | CREAD;

      termios->c_cflag &= ~CSIZE;
      switch (bits) {
          case DATA_7BIT:
              termios->c_cflag |= CS7;
              break;

          case DATA_8BIT:
              termios->c_cflag |= CS8; 
              break;

          default:

              termios->c_cflag |= CS8;
              break;
      }

      switch (parity) {
          case PAR_NONE:
              termios->c_cflag &= ~PARENB;
              termios->c_cflag &= ~PARODD;
              break;

          case PAR_EVEN:
              termios->c_cflag |= PARENB;
              termios->c_cflag &= ~PARODD;
              break;

          case PAR_ODD:
              termios->c_cflag |= PARENB;
              termios->c_cflag |= PARODD;
              break;

          default:
              termios->c_cflag &= ~PARENB;
              termios->c_cflag &= ~PARODD;
              break;
      }

      switch (stop) {
          case STOP_1BIT:
              termios->c_cflag &= ~CSTOPB;
              break;

          case STOP_2BIT:
              termios->c_cflag |= CSTOPB;
              break;

          default:
              termios->c_cflag &= ~CSTOPB;
              break;
      }

      termios->c_iflag |= INPCK | ISTRIP;
      termios->c_lflag = 0;
      termios->c_oflag = 0;

      termios->c_cc[VTIME] = 5;
      termios->c_cc[VMIN]  = 0;

      cfsetspeed(termios, baudrate);
 }


 static int tiny_write(struct tiny_op *op, char *buff, int size)
 {
     int ret = -1;
     int len = 0;

     op->w_timeout.tv_sec  = 5;
     op->w_timeout.tv_usec = 0;

     ret = select(op->max_fd, NULL, &op->wfds, &op->efds, &op->w_timeout);
     if (ret < 0) {
         printf("[TINY SERIAL] Select fallita (write) !\n");
         return -1;
     } if (0 == ret) {
         printf("[TINY SERIAL] Select time-out (write) !\n");
     } else {
             if (FD_ISSET(op->fd, &op->efds)) {
             printf("[TINY SERIAL] Attenzione: errore in scrittura!\n");
             return -1;
         }

         if (FD_ISSET(op->fd, &op->wfds)) {
             printf("[TINY SERIAL] Scrittura pronta, procedo !\n");
             len = write(op->fd, (const void *)buff, size);

             if (len == size) {
                 printf("[TINY SERIAL] Scrittura completata!\n");
             } else {
                 printf("[TINY SERIAL] Scrittura parzialmente completata, scritti : = %d\n", len);
             }
        }
     }

     return 0;
 }


 static int tiny_read(struct tiny_op *op, char *buff, int size)
 {
     int ret = -1;
     int len = 0;

     op->r_timeout.tv_sec = 5;
     op->r_timeout.tv_usec = 0;

     ret = select(op->max_fd, &op->rfds, NULL, &op->efds, &op->r_timeout);
     if (ret < 0) {
         printf("[TINY SERIAL] Select fallita (read) !\n");
         return -1;
     } if (0 == ret) {
         printf("[TINY SERIAL] Select time-out (read)!\n");
     } else {
         if (FD_ISSET(op->fd, &op->efds)) {
             printf("[TINY SERIAL] Attenzione: errore in lettura!\n");
             return -1;
         }

         if (FD_ISSET(op->fd, &op->rfds)) {
             printf("[TINY SERIAL] Dati da leggere disponibili!\n");

             len = read(op->fd, (void *)buff, size);
             printf("[TINY SERIAL] Lettura da seriale: %s, len: %d\n", buff, len);
         }
     }

     return 0;
 }


 int main(int argc, char **argv)
 {
     int fd = -1, ret = -1;
     struct termios oldtio, newtio;

     char *str = {"Hello world!!\0"};
     char buff[strlen(str)];

     if(argc == 1){

       printf("[TINY DRIVER] Inserisci il device seriale da testare.\n");
       return -1;
     }

     char * device = argv[1];
     struct stat file_infos;

     if(strlen(device) == 0){
        printf("[TINY DRIVER] Seleziona un device corretto.\n");
        return -1;
     }

     printf("[TINY DRIVER] Hai selezionato il device: %s\n", device);

     if(stat(device, &file_infos) < 0){
        printf("[TINY DRIVER] Attenzione: errore nell'accesso al device selezionato: %s, riprova con un altro dispositivo.\n", device);
        return -1;
     }

     bzero((void *)&oldtio, sizeof(oldtio));
     bzero((void *)&newtio, sizeof(newtio)); 

     fd = open(device, O_RDWR | O_NONBLOCK | O_SYNC);
     if (fd == -1) {
         printf("[TINY SERIAL] failed to open /dev/ttytiny0 !\n");
         return -1;
     }

     printf("[TINY SERIAL] succeed to open /dev/ttytiny0 !\n");

     ret = tcgetattr(fd, &oldtio);
     if (ret != 0) {
         printf("[TINY SERIAL] failed to get attr !\n");
         close(fd);
         return -1;
     }

     set_termios(&newtio, B38400, PAR_EVEN, DATA_8BIT, STOP_2BIT);

     tcflush(fd, TCIOFLUSH);

     ret = tcsetattr(fd, TCSANOW, &newtio);
     if (ret != 0) {
         printf("[TINY SERIAL] failed to set termios !\n");
         close(fd);
         return -1;
     }

     tops.fd = fd;

     tops.max_fd = tops.fd + 1;

     FD_ZERO(&tops.rfds);
     FD_ZERO(&tops.wfds);
     FD_ZERO(&tops.efds);

     FD_SET(tops.fd, &tops.rfds);
     FD_SET(tops.fd, &tops.wfds);
     FD_SET(tops.fd, &tops.efds);

     if (tiny_write(&tops, str, strlen(str)) != 0) {
         close(fd);
         return -1;
     }

     if (tiny_read(&tops, buff, sizeof(buff)) != 0) {
         close(fd);
         return -1;
     }

     ret = tcsetattr(fd, TCSANOW, &oldtio);
     if (ret != 0) {
         printf("[TINY SERIAL] Errore nel reset delle impostazioni.\n");
     }else{
         printf("[TINY SERIAL] Impostazioni resettate correttamente.\n");
     }

     ret = tcflush(fd, TCIOFLUSH);

     if(ret != 0){
        printf("[TINY SERIAL] Errore nel flushing dei dati (finale) !\n");
     }else{
        printf("[TINY SERIAL] Dati flushati correttamente!\n");
     }

     //Program hangs on this call
     ret = close(fd);

     if (ret != 0) {   
         printf("[TINY SERIAL] Errore nella Chiusura del file !\n");
         return -1;
     }else{
         printf("[TINY SERIAL] Chiusura del file avvenuta correttamente !\n");
     }

     return 0;
 }

My problem is: the test application works fine (i can write and read my own string) until it reaches the close(2) function: there it hangs forever, without reaching the end of the program (I must close it with CTRL+C, and then it closes properly as the kernel module calls the tiny_shutdown() function).
I also tried to write a simple program to open and close the /dev/ttytiny0 device, but the result is the same (although I noticed that, if i remove the close(2) operation, the program still hangs without terminating, but this time I couldn't shut it down with CTRL+C).
Any link/reference to books or material on the subject will be highly appreciated.
Thanks in advance!

Edit : Reporting here the kernel log messages (while running test application)

[  446.862221] tiny_ser: module verification failed: signature and/or required key missing - tainting kernel
[  446.864143] tiny_serial: 6Tiny serial driver loaded
[  486.715801] tiny_serial: tiny_startup()
[  486.715812] tiny_serial: tiny_set_termios()
[  486.715814] tiny_serial:  - data bits = 8
[  486.715816] tiny_serial:  - parity = none
[  486.715818] tiny_serial:  - stop bits = 1
[  486.715820] tiny_serial:  - RTS/CTS is disabled
[  486.715824] tiny_serial: tiny_set_mctrl()
[  486.715853] tiny_serial: tiny_set_termios()
[  486.715856] tiny_serial:  - data bits = 8
[  486.715857] tiny_serial:  - parity = even
[  486.715859] tiny_serial:  - stop bits = 2
[  486.715861] tiny_serial:  - RTS/CTS is disabled
[  486.715943] tiny_serial: tiny_start_tx()
[  487.116105] tiny_serial: tiny_timer()
[  487.116171] tiny_serial: tiny_tx_chars()
[  487.116183] tiny_serial: wrote 0x42
[  487.116189] tiny_serial: wrote 0x75
[  487.116196] tiny_serial: wrote 0x6f
[  487.116202] tiny_serial: wrote 0x6e
[  487.116208] tiny_serial: wrote 0x61
[  487.116214] tiny_serial: wrote 0x73
[  487.116220] tiny_serial: wrote 0x65
[  487.116226] tiny_serial: wrote 0x72
[  487.116232] tiny_serial: wrote 0x61
[  487.116238] tiny_serial: wrote 0x20
[  487.116245] tiny_serial: wrote 0x64
[  487.116251] tiny_serial: wrote 0x72
[  487.116257] tiny_serial: wrote 0x69
[  487.116263] tiny_serial: wrote 0x76
[  487.116269] tiny_serial: wrote 0x65
[  487.116275] tiny_serial: wrote 0x72
[  487.116281] tiny_serial: wrote 0x21
[  487.116287] tiny_serial: wrote 0x21
[  487.116295] tiny_serial: tiny_stop_tx()
[  487.116303] tiny_serial: read: 0x42
[  487.116312] tiny_serial: read: 0x75
[  487.116318] tiny_serial: read: 0x6f
[  487.116324] tiny_serial: read: 0x6e
[  487.116330] tiny_serial: read: 0x61
[  487.116337] tiny_serial: read: 0x73
[  487.116343] tiny_serial: read: 0x65
[  487.116349] tiny_serial: read: 0x72
[  487.116355] tiny_serial: read: 0x61
[  487.116361] tiny_serial: read: 0x20
[  487.116367] tiny_serial: read: 0x64
[  487.116373] tiny_serial: read: 0x72
[  487.116379] tiny_serial: read: 0x69
[  487.116385] tiny_serial: read: 0x76
[  487.116391] tiny_serial: read: 0x65
[  487.116397] tiny_serial: read: 0x72
[  487.116403] tiny_serial: read: 0x21
[  487.116409] tiny_serial: read: 0x21
[  487.116444] tiny_serial: push to user space !
[  487.116565] tiny_serial: tiny_start_tx()
[  487.116709] tiny_serial: tiny_set_termios()
[  487.116713] tiny_serial:  - data bits = 8
[  487.116715] tiny_serial:  - parity = none
[  487.116717] tiny_serial:  - stop bits = 1
[  487.116719] tiny_serial:  - RTS/CTS is disabled
[  487.116746] tiny_serial: tiny_tx_empty()
[  487.516021] tiny_serial: tiny_timer()
[  487.516094] tiny_serial: tiny_tx_chars()
[  487.516104] tiny_serial: tiny_stop_tx()
[  487.915917] tiny_serial: tiny_timer()
[  487.915960] tiny_serial: tiny_tx_chars()
[  487.915971] tiny_serial: tiny_stop_tx()
[  505.910955] tiny_serial: tiny_timer()
[  505.910968] tiny_serial: tiny_tx_chars()
[  505.910971] tiny_serial: tiny_stop_tx()
[  506.455320] tiny_serial: tiny_timer()
[  506.455332] tiny_serial: tiny_tx_chars()
[  506.455335] tiny_serial: tiny_stop_tx()
[  506.855344] tiny_serial: tiny_timer()
[  506.855428] tiny_serial: tiny_tx_chars()
[  506.855437] tiny_serial: tiny_stop_tx()
[  507.255499] tiny_serial: tiny_timer()
[  507.255563] tiny_serial: tiny_tx_chars()
[  507.255572] tiny_serial: tiny_stop_tx()
[  507.655342] tiny_serial: tiny_timer()
[  507.655401] tiny_serial: tiny_tx_chars()
[  507.655411] tiny_serial: tiny_stop_tx()
[  507.755090] tiny_serial: tiny_stop_rx()
[  507.755100] tiny_serial: tiny_tx_empty()
[  507.755103] tiny_serial: tiny_set_mctrl()
[  507.755105] tiny_serial: tiny_shutdown()
[  507.755108] tiny_serial: tiny_shutdown() - after del_timer()

Note that the sequence tiny_stop_rx() - tiny_tx_empty() - tiny_set_mctrl() - tiny_shutdown() gets called when I terminate the test-application execution with CTRL+C.
Other repeated messages (the sequence tiny_timer() - tiny_tx_chars() - tiny_stop_tx()) are generated by the timer which checks if something has to be transmitted (but the queue is empty, so it sleeps again).
Of course, the test-application logs show a correct behavior, until reaching the close() function.

like image 915
aldoalpha Avatar asked Jul 01 '18 13:07

aldoalpha


1 Answers

Your implementation returns 0 from tx_empty() which means "not empty".

From kernel.org/doc/Documentation/serial/driver:

tx_empty(port)

This function tests whether the transmitter fifo and shifter for the port described by 'port' is empty. If it is empty, this function should return TIOCSER_TEMT, otherwise return 0. If the port does not support this operation, then it should return TIOCSER_TEMT.

like image 112
alk Avatar answered Nov 15 '22 08:11

alk