Serial port hangs on close()


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 <me@me.com>"
#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%2xn", 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%2xrn", 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%2xrn", 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 = 5n");
break;
case CS6:
DBG(" - data bits = 6n");
break;
case CS7:
DBG(" - data bits = 7n");
break;
default: // CS8
DBG(" - data bits = 8n");
break;
}

/* determine the parity */
if (cflag & PARENB)
if (cflag & PARODD)
DBG(" - parity = oddn");
else
DBG(" - parity = evenn");
else
DBG(" - parity = nonen");

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

/* figure out the flow control settings */
if (cflag & CRTSCTS)
DBG(" - RTS/CTS is enabledn");
else
DBG(" - RTS/CTS is disabledn");

/* 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 loadedn");

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 : = %dn", 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: %dn", buff, len);
}
}

return 0;
}


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

char *str = {"Hello world!!"};
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: %sn", 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.





Mind showing the logs written by the driver when being used by the test program?
– alk
Jul 1 at 14:13





Sure, I added them to the question.
– aldoalpha
Jul 1 at 14:29





So does the driver stop logging after close() started blocking? If yes, which is the last entry?
– alk
Jul 1 at 14:34


close()





Also would you mind putting another log-statement right after del_timer() and retest?
– alk
Jul 1 at 14:34



del_timer()





Your implementation returns 0 from tx_empty() which means "not empty".
– alk
Jul 1 at 15:25


0


tx_empty()




1 Answer
1



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


0


tx_empty()



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



tx_empty(port)


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.


TIOCSER_TEMT


TIOCSER_TEMT






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

How to make file upload 'Required' in Contact Form 7?

Rothschild family

amazon EC2 - How to make wp-config.php to writable?