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.
Advertisement
Answer
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 returnTIOCSER_TEMT
.