├── reload ├── Makefile ├── README.md └── nullmodem.c /reload: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sudo rmmod nullmodem 3 | sudo insmod nullmodem.ko 4 | sudo chmod a+rw /dev/nmp* 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Comment/uncomment the following line to disable/enable debugging 2 | #DEBUG = y 3 | 4 | 5 | # Add your debugging flag (or not) to CFLAGS 6 | ifeq ($(DEBUG),y) 7 | DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines 8 | else 9 | DEBFLAGS = -O2 10 | endif 11 | 12 | EXTRA_CFLAGS += $(DEBFLAGS) -I.. -Wno-declaration-after-statement 13 | 14 | ifneq ($(KERNELRELEASE),) 15 | # call from kernel build system 16 | 17 | obj-m := nullmodem.o 18 | 19 | else 20 | 21 | KERNELDIR ?= /lib/modules/$(shell uname -r)/build 22 | PWD := $(shell pwd) 23 | 24 | default: 25 | $(MAKE) -C $(KERNELDIR) M=$(PWD) modules 26 | 27 | endif 28 | 29 | 30 | 31 | clean: 32 | rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers 33 | 34 | depend .depend dep: 35 | $(CC) $(CFLAGS) -M *.c > .depend 36 | 37 | 38 | ifeq (.depend,$(wildcard .depend)) 39 | include .depend 40 | endif 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This implements a virtual nullmodem driver for linux as a kernel module. 2 | 3 | I wrote this when I needed a real virtual nullmodem emulator that could emulate the control lines and 4 | handle ioctl() calls for TIOCMIWAIT like a real hardware device. 5 | 6 | I found the tty0tty project on sourceforge but it did not do what I needed, 7 | so I took it as a basis and started hacking. 8 | 9 | I also posted a response on stackoverflow where someone asked for a nullmodem emulator: 10 | http://stackoverflow.com/questions/52187/virtual-serial-port-for-linux 11 | 12 | Introduction: 13 | 14 | When the module is loaded, pairs of virtual COM ports are created that are connected to each other. 15 | The name of the devices are /dev/nmpX, where X is the number of the COM port. "nmp" stands for "null modem port". 16 | 17 | Two consecutive devices are linked with each other: 18 | 19 | /dev/nmp0 <-> /dev/nmp1 20 | /dev/nmp2 <-> /dev/nmp3 21 | etc. 22 | 23 | 24 | Features: 25 | - line speed emulation 26 | 27 | Unlike pseudo terminals, this driver emulates serial line speed by using a timer. 28 | So if you set the line speed to 9600 baud, the througput will be about 960 cps. 29 | The driver takes mismatching configuration of the two ends of the virtual line into account. 30 | So if one end is configured for a different baud rate than the other end, each end will not 31 | receive any data from the other end. 32 | Likewise, if startbits/stopbits/databits don't match, no data will be received. 33 | Charsizes less than 8 are handled by clearing the appropriate upper bits. 34 | 35 | - emulation of control lines 36 | - Supports TIOCMGET/TIOCMSET. 37 | This allows setting and reading the control lines (RTS/DTR etc.) via ioctl(). 38 | When changed on one end of the virtual line, it affects the other end. 39 | - Supports TIOCMIWAIT. 40 | With this ioctl a user mode program can perform a blocking wait for control line changes. 41 | 42 | 43 | Known problems / limitations: 44 | - Flow control via XON/XOFF is not implemented. 45 | - a 4k buffer is allocated for each virtual COM port (when opened). This may be a bit large. 46 | - The timer that is used to emulate the line speed fires 20 times per second, even when no port is open 47 | (although it does not do much apart from checking every port whether it is open). 48 | This may put an unnecessary load on the system. However, I have found this not to be a problem. 49 | 50 | 51 | Installation: 52 | 53 | Just unpack the tarball somewhere and run make in the nullmodem directory. 54 | You will need to have the "kernel-headers" package installed to compile the module. 55 | I included a small shell script, called "reload", that (re-)loads the module and sets permissions 56 | of the /dev/nmp* devices. 57 | 58 | Tweaking: 59 | 60 | There are a few defines that can be tweaked: 61 | - NULLMODEM_PAIRS: This defines how many virtual com port pairs will be created when the module is loaded. 62 | Currently, there will be 4 pairs created (8 devices). 63 | - TIMER_INTERVAL: This determines the interval at which the timer fires. 64 | If you slow the timer down, more data will need to be transfered at each timer tick. 65 | Make sure the buffers are large enough. The timer rate and the highest baud rate determine the needed buffer size. 66 | Currently, the timer is set to 20Hz, i.e. every 50 milliseconds. 67 | - TX_BUF_SIZE: This determines the size of the send buffers for the serial ports. 68 | These get filled by writes to the port and are drained by the timer. 69 | The timer also uses a buffer of this size to transfer data from one end of the virtual line to the other. 70 | - NULLMODEM_MAJOR: this is the major device number. This is from the experimental range. 71 | Maybe someday it will be an officially assigned number :) 72 | 73 | In the makefile, you can turn on debug mode by un-commenting the "DEBUG = y" line. 74 | The driver then prints diagnostic messages to the kernel log. You can watch this with a "tail -f /var/log/kern.log". 75 | Some messages that produce a high volume of logging output are commented out in the code. 76 | You can uncomment them if you want. 77 | 78 | Notes/disclaimer: 79 | 80 | This was my first attempt at kernel hacking. Likewise I did not do any extensive stability testing. 81 | If it fails for you - bad luck :) 82 | Well, you can contact me, and I might try and find a fix. 83 | But, I wrote this quite some time ago, and I don't remember everything about writing kernel drivers, 84 | or which features this driver may be lacking. 85 | I apologize if the code style does not conform to any linux kernel coding rules. 86 | 87 | If anyone of the kernel staff wants to include this in the kernel distribution as an experimental driver, 88 | please go ahead. Maybe this thing will come to life and/or find a new maintainer. 89 | 90 | 91 | That said, may it be useful to you! 92 | 93 | Peter Remmers 94 | pitti98@googlemail.com 95 | -------------------------------------------------------------------------------- /nullmodem.c: -------------------------------------------------------------------------------- 1 | /* ######################################################################## 2 | 3 | nullmodem - linux null modem emulator (module) 4 | 5 | ######################################################################## 6 | 7 | Copyright (c) : 2012 Peter Remmers 8 | 9 | Based on tty0tty driver - Copyright (c) : 2010 Luis Claudio Gambôa Lopes 10 | Based on Tiny TTY driver - Copyright (C) 2002-2004 Greg Kroah-Hartman (greg@kroah.com) 11 | 12 | This program is free software; you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation; either version 2, or (at your option) 15 | any later version. 16 | 17 | This program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 25 | 26 | For e-mail suggestions : pitti98@googlemail.com 27 | ######################################################################## */ 28 | 29 | 30 | #include 31 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,0) 32 | #include 33 | #endif 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #define DRIVER_VERSION "v1.1" 49 | #define DRIVER_AUTHOR "Peter Remmers " 50 | #define DRIVER_DESC "nullmodem driver" 51 | 52 | /* Module information */ 53 | MODULE_AUTHOR( DRIVER_AUTHOR ); 54 | MODULE_DESCRIPTION( DRIVER_DESC ); 55 | MODULE_LICENSE("GPL"); 56 | 57 | #ifdef SCULL_DEBUG 58 | #define dprintf(fmt, args...) printk(KERN_DEBUG fmt, ##args) 59 | #else 60 | #define dprintf(fmt, args...) 61 | #endif 62 | 63 | #define NULLMODEM_MAJOR 240 /* experimental range */ 64 | #define NULLMODEM_PAIRS 4 65 | 66 | #define TIMER_INTERVAL (HZ/20) 67 | //#define TIMER_INTERVAL HZ 68 | #define TX_BUF_SIZE 4096 69 | #define WAKEUP_CHARS 256 70 | 71 | struct nullmodem_pair; 72 | struct nullmodem_end 73 | { 74 | struct tty_struct *tty; /* pointer to the tty for this device */ 75 | struct nullmodem_end *other; 76 | struct nullmodem_pair *pair; 77 | struct async_icount icount; 78 | struct serial_struct serial; 79 | unsigned char xchar; 80 | unsigned char char_length; 81 | unsigned nominal_bit_count; 82 | unsigned actual_bit_count; 83 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) 84 | struct kfifo fifo; 85 | #else 86 | struct kfifo *fifo; 87 | #endif 88 | }; 89 | struct nullmodem_pair 90 | { 91 | spinlock_t spin; /* locks this structure */ 92 | struct nullmodem_end a; 93 | struct nullmodem_end b; 94 | int control_lines; /* control lines as seen from end a */ 95 | wait_queue_head_t control_lines_wait; 96 | }; 97 | static struct nullmodem_pair pair_table[NULLMODEM_PAIRS]; 98 | 99 | static struct timer_list nullmodem_timer; 100 | 101 | static struct ktermios* get_termios(struct tty_struct *tty) 102 | { 103 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0) 104 | return &(tty->termios); 105 | #else 106 | return tty->termios; 107 | #endif 108 | } 109 | 110 | static int switch_pin_view(int pins) 111 | { 112 | int out = 0; 113 | if (pins & TIOCM_RTS) out |= TIOCM_CTS; 114 | if (pins & TIOCM_DTR) out |= TIOCM_DSR; 115 | if (pins & TIOCM_CTS) out |= TIOCM_RTS; 116 | if (pins & TIOCM_DSR) out |= TIOCM_DTR; 117 | return out; 118 | } 119 | static int get_pins(struct nullmodem_end *end) 120 | { 121 | int pins = end->pair->control_lines; 122 | if (end == &end->pair->b) 123 | pins = switch_pin_view(pins); 124 | if (pins&TIOCM_DSR) 125 | pins |= TIOCM_CD; 126 | return pins; 127 | } 128 | static void change_pins(struct nullmodem_end *end, unsigned int set, unsigned int clear) 129 | { 130 | int is_end_b = (end == &end->pair->b); 131 | int old_pins = end->pair->control_lines; 132 | if (is_end_b) 133 | old_pins = switch_pin_view(old_pins); 134 | 135 | int new_pins = (old_pins & ~clear) | set; 136 | int change = old_pins ^ new_pins; 137 | 138 | if (is_end_b) 139 | new_pins = switch_pin_view(new_pins); 140 | 141 | end->pair->control_lines = new_pins; 142 | 143 | if (change & TIOCM_RTS) 144 | { 145 | end->other->icount.cts++; 146 | } 147 | if (change & TIOCM_DTR) 148 | { 149 | end->other->icount.dsr++; 150 | end->other->icount.dcd++; 151 | } 152 | 153 | if (end->other->tty 154 | && (get_termios(end->other->tty)->c_cflag & CRTSCTS) 155 | && (change&TIOCM_RTS)) 156 | { 157 | if (!(new_pins&TIOCM_RTS)) 158 | end->other->tty->hw_stopped = 1; 159 | else 160 | { 161 | end->other->tty->hw_stopped = 0; 162 | tty_wakeup(end->other->tty); 163 | } 164 | } 165 | 166 | if (change) 167 | wake_up_interruptible(&end->pair->control_lines_wait); 168 | } 169 | 170 | static unsigned char drain[TX_BUF_SIZE]; 171 | static unsigned long last_timer_jiffies; 172 | static unsigned long delta_jiffies; 173 | #define FACTOR 10 174 | static inline void handle_end(struct nullmodem_end *end) 175 | { 176 | if (!end->tty) 177 | return; 178 | if (end->tty->hw_stopped) 179 | { 180 | //dprintf("%s - #%d: hw_stopped\n", __FUNCTION__, end->tty->index); 181 | return; 182 | } 183 | unsigned nominal_bits = get_termios(end->tty)->c_ospeed * FACTOR * delta_jiffies / HZ; 184 | unsigned add_bits = end->nominal_bit_count - end->actual_bit_count; 185 | unsigned chars = (nominal_bits+add_bits) / end->char_length; 186 | unsigned actual_bits = chars * end->char_length; 187 | 188 | end->nominal_bit_count += nominal_bits; 189 | end->actual_bit_count += actual_bits; 190 | 191 | // dprintf("%s - #%d: nb %u add %u ab %u ch %u nbc %u abc %u\n", __FUNCTION__, 192 | // end->tty->index, nominal_bits, add_bits, actual_bits, chars, 193 | // end->nominal_bit_count, end->actual_bit_count); 194 | 195 | if (chars == 0) 196 | return; 197 | 198 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) 199 | int cnt = kfifo_out(&end->fifo, drain, chars); 200 | #else 201 | int cnt = __kfifo_get(end->fifo, drain, chars); 202 | #endif 203 | if (cnt < chars) 204 | { 205 | end->nominal_bit_count = 0; 206 | end->actual_bit_count = 0; 207 | } 208 | if (cnt <= 0) 209 | { 210 | //dprintf("%s - #%d: fifo empty\n", __FUNCTION__, end->tty->index); 211 | return; 212 | } 213 | 214 | // dprintf("%s - #%d: drained %d bytes\n", __FUNCTION__, end->tty->index, cnt); 215 | 216 | if (end->other->tty) 217 | { 218 | if (get_termios(end->tty)->c_ospeed == end->other->tty->termios.c_ispeed 219 | && (get_termios(end->tty)->c_cflag & (CSIZE|PARENB|CSTOPB)) 220 | ==(get_termios(end->other->tty)->c_cflag & (CSIZE|PARENB|CSTOPB))) 221 | { 222 | tcflag_t csize = (get_termios(end->tty)->c_cflag&CSIZE); 223 | if (csize != CS8) 224 | { 225 | int i; 226 | unsigned char mask = 0xFF; 227 | switch (csize) 228 | { 229 | case CS7: mask = 0x7F; break; 230 | case CS6: mask = 0x3F; break; 231 | case CS5: mask = 0x1F; break; 232 | } 233 | for (i=0; i= KERNEL_VERSION(3,10,0) 237 | int written = tty_insert_flip_string(end->other->tty->port, drain, cnt); 238 | #else 239 | int written = tty_insert_flip_string(end->other->tty, drain, cnt); 240 | #endif 241 | if (written > 0) 242 | { 243 | //dprintf("%s - #%d -> #%d: copied %d bytes\n", __FUNCTION__, end->tty->index, end->other->tty->index, written); 244 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) 245 | tty_flip_buffer_push(end->other->tty->port); 246 | #else 247 | tty_flip_buffer_push(end->other->tty); 248 | #endif 249 | } 250 | } 251 | } 252 | 253 | // if (kfifo_len(&end->fifo) < WAKEUP_CHARS) 254 | tty_wakeup(end->tty); 255 | } 256 | 257 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) 258 | static void nullmodem_timer_proc(struct timer_list *data) 259 | #else 260 | static void nullmodem_timer_proc(unsigned long data) 261 | #endif 262 | { 263 | int i; 264 | unsigned long flags; 265 | //dprint("%s jiffies: %lu\n", __FUNCTION__, jiffies); 266 | 267 | unsigned long current_jiffies = jiffies; 268 | delta_jiffies = current_jiffies - last_timer_jiffies; 269 | last_timer_jiffies = current_jiffies; 270 | 271 | for (i=0; ispin, flags); 276 | handle_end(&pair->a); 277 | handle_end(&pair->b); 278 | spin_unlock_irqrestore(&pair->spin, flags); 279 | } 280 | 281 | nullmodem_timer.expires += TIMER_INTERVAL; 282 | add_timer(&nullmodem_timer); 283 | } 284 | 285 | static void handle_termios(struct tty_struct *tty) 286 | { 287 | struct nullmodem_end *end = tty->driver_data; 288 | 289 | speed_t speed = tty_get_baud_rate(tty); 290 | if (speed == 0) 291 | change_pins(end, 0, TIOCM_DTR|TIOCM_RTS); 292 | else 293 | change_pins(end, TIOCM_DTR|TIOCM_RTS, 0); 294 | 295 | unsigned int cflag = get_termios(tty)->c_cflag; 296 | end->char_length = 2; 297 | switch (cflag & CSIZE) 298 | { 299 | case CS5: end->char_length +=5; break; 300 | case CS6: end->char_length +=6; break; 301 | case CS7: end->char_length +=7; break; 302 | default: 303 | case CS8: end->char_length +=8; break; 304 | } 305 | if (cflag & PARENB) end->char_length += 1; 306 | if (cflag & CSTOPB) end->char_length += 1; 307 | end->char_length *= FACTOR; 308 | 309 | tty->hw_stopped = (get_termios(tty)->c_cflag&CRTSCTS) 310 | && !(get_pins(end) & TIOCM_CTS); 311 | } 312 | static int nullmodem_open(struct tty_struct *tty, struct file *file) 313 | { 314 | struct nullmodem_pair *pair = &pair_table[tty->index/2]; 315 | struct nullmodem_end *end = ((tty->index&1) ? &pair->b : &pair->a); 316 | unsigned long flags; 317 | int err = -ENOMEM; 318 | 319 | dprintf("%s - #%d c:%d\n", __FUNCTION__, tty->index, tty->count); 320 | 321 | if (tty->count > 1) 322 | return 0; 323 | 324 | spin_lock_irqsave(&end->pair->spin, flags); 325 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) 326 | if (kfifo_alloc(&end->fifo, TX_BUF_SIZE, GFP_KERNEL)) 327 | goto exit; 328 | #else 329 | end->fifo = kfifo_alloc(TX_BUF_SIZE, GFP_KERNEL, NULL); 330 | if (!end->fifo) 331 | goto exit; 332 | #endif 333 | tty->driver_data = end; 334 | end->tty = tty; 335 | end->nominal_bit_count = 0; 336 | end->actual_bit_count = 0; 337 | handle_termios(tty); 338 | err = 0; 339 | exit: 340 | spin_unlock_irqrestore(&end->pair->spin, flags); 341 | return err; 342 | } 343 | 344 | static void nullmodem_close(struct tty_struct *tty, struct file *file) 345 | { 346 | struct nullmodem_end *end = tty->driver_data; 347 | unsigned long flags; 348 | 349 | dprintf("%s - #%d c:%d\n", __FUNCTION__, tty->index, tty->count); 350 | 351 | if (tty->count > 1) 352 | return; 353 | 354 | spin_lock_irqsave(&end->pair->spin, flags); 355 | end->tty = NULL; 356 | change_pins(end, 0, TIOCM_RTS|TIOCM_DTR); 357 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) 358 | kfifo_free(&end->fifo); 359 | #else 360 | kfifo_free(end->fifo); 361 | end->fifo = NULL; 362 | #endif 363 | tty->hw_stopped = 1; 364 | spin_unlock_irqrestore(&end->pair->spin, flags); 365 | 366 | wake_up_interruptible(&tty->read_wait); 367 | wake_up_interruptible(&tty->write_wait); 368 | } 369 | 370 | static int nullmodem_write(struct tty_struct *tty, const unsigned char *buffer, int count) 371 | { 372 | struct nullmodem_end *end = tty->driver_data; 373 | int written = 0; 374 | 375 | if (tty->stopped) 376 | { 377 | dprintf("%s - #%d %d bytes --> 0 (tty stopped)\n", __FUNCTION__, tty->index, count); 378 | return 0; 379 | } 380 | 381 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) 382 | written = kfifo_in(&end->fifo, buffer, count); 383 | #else 384 | written = __kfifo_put(end->fifo, buffer, count); 385 | #endif 386 | //dprintf("%s - #%d %d bytes --> %d written\n", __FUNCTION__, tty->index, count, written); 387 | return written; 388 | } 389 | 390 | static int nullmodem_write_room(struct tty_struct *tty) 391 | { 392 | struct nullmodem_end *end = tty->driver_data; 393 | int room = 0; 394 | 395 | if (tty->stopped) 396 | { 397 | dprintf("%s - #%d --> %d (tty stopped)\n", __FUNCTION__, tty->index, room); 398 | return 0; 399 | } 400 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) 401 | room = kfifo_avail(&end->fifo); 402 | #else 403 | room = TX_BUF_SIZE - __kfifo_len(end->fifo); 404 | #endif 405 | //dprintf("%s - #%d --> %d\n", __FUNCTION__, tty->index, room); 406 | return room; 407 | } 408 | 409 | #define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) 410 | 411 | static void nullmodem_set_termios(struct tty_struct *tty, struct ktermios *old_termios) 412 | { 413 | struct nullmodem_end *end = tty->driver_data; 414 | unsigned long flags; 415 | unsigned int cflag; 416 | 417 | dprintf("%s - #%d\n", __FUNCTION__, tty->index); 418 | 419 | cflag = get_termios(tty)->c_cflag; 420 | 421 | /* check that they really want us to change something */ 422 | if (old_termios) 423 | { 424 | if (cflag == old_termios->c_cflag 425 | && RELEVANT_IFLAG(get_termios(tty)->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag)) 426 | { 427 | dprintf(" - nothing to change...\n"); 428 | return; 429 | } 430 | } 431 | spin_lock_irqsave(&end->pair->spin, flags); 432 | handle_termios(tty); 433 | spin_unlock_irqrestore(&end->pair->spin, flags); 434 | 435 | #ifdef SCULL_DEBUG 436 | speed_t speed = tty_get_baud_rate(tty); 437 | dprintf(" - baud = %u", speed); 438 | dprintf(" - ispeed = %u", get_termios(tty)->c_ispeed); 439 | dprintf(" - ospeed = %u", get_termios(tty)->c_ospeed); 440 | 441 | /* get the byte size */ 442 | switch (cflag & CSIZE) 443 | { 444 | case CS5: dprintf(" - data bits = 5\n"); break; 445 | case CS6: dprintf(" - data bits = 6\n"); break; 446 | case CS7: dprintf(" - data bits = 7\n"); break; 447 | default: 448 | case CS8: dprintf(" - data bits = 8\n"); break; 449 | } 450 | 451 | /* determine the parity */ 452 | if (cflag & PARENB) 453 | if (cflag & PARODD) 454 | dprintf(" - parity = odd\n"); 455 | else 456 | dprintf(" - parity = even\n"); 457 | else 458 | dprintf(" - parity = none\n"); 459 | 460 | /* figure out the stop bits requested */ 461 | if (cflag & CSTOPB) 462 | dprintf(" - stop bits = 2\n"); 463 | else 464 | dprintf(" - stop bits = 1\n"); 465 | 466 | /* figure out the hardware flow control settings */ 467 | if (cflag & CRTSCTS) 468 | dprintf(" - RTS/CTS is enabled\n"); 469 | else 470 | dprintf(" - RTS/CTS is disabled\n"); 471 | 472 | /* determine software flow control */ 473 | /* if we are implementing XON/XOFF, set the start and 474 | * stop character in the device */ 475 | /* if we are implementing INBOUND XON/XOFF */ 476 | if (I_IXOFF(tty)) 477 | dprintf(" - INBOUND XON/XOFF is enabled, " 478 | "XON = %2x, XOFF = %2x\n", START_CHAR(tty), STOP_CHAR(tty)); 479 | else 480 | dprintf(" - INBOUND XON/XOFF is disabled\n"); 481 | 482 | /* if we are implementing OUTBOUND XON/XOFF */ 483 | if (I_IXON(tty)) 484 | dprintf(" - OUTBOUND XON/XOFF is enabled, " 485 | "XON = %2x, XOFF = %2x\n", START_CHAR(tty), STOP_CHAR(tty)); 486 | else 487 | dprintf(" - OUTBOUND XON/XOFF is disabled\n"); 488 | #endif 489 | } 490 | 491 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) 492 | static int nullmodem_tiocmget(struct tty_struct *tty) 493 | #else 494 | static int nullmodem_tiocmget(struct tty_struct *tty, struct file *filp) 495 | #endif 496 | { 497 | struct nullmodem_end *end = tty->driver_data; 498 | unsigned long flags; 499 | int retval = -EINVAL; 500 | 501 | spin_lock_irqsave(&end->pair->spin, flags); 502 | retval = get_pins(end); 503 | spin_unlock_irqrestore(&end->pair->spin, flags); 504 | 505 | //dprintf("%s - #%d --> 0x%x\n", __FUNCTION__, tty->index, retval); 506 | return retval; 507 | } 508 | 509 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) 510 | static int nullmodem_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) 511 | #else 512 | static int nullmodem_tiocmset(struct tty_struct *tty, struct file *filp, unsigned int set, unsigned int clear) 513 | #endif 514 | { 515 | struct nullmodem_end *end = tty->driver_data; 516 | unsigned long flags; 517 | 518 | dprintf("%s - #%d set:0x%x clear:0x%x\n", __FUNCTION__, 519 | tty->index, set, clear); 520 | 521 | spin_lock_irqsave(&end->pair->spin, flags); 522 | change_pins(end, set, clear); 523 | spin_unlock_irqrestore(&end->pair->spin, flags); 524 | return 0; 525 | } 526 | 527 | 528 | static int nullmodem_ioctl_tiocgserial(struct tty_struct *tty, unsigned long arg) 529 | { 530 | struct nullmodem_end *end = tty->driver_data; 531 | unsigned long flags; 532 | struct serial_struct tmp; 533 | struct serial_struct *serial; 534 | 535 | dprintf("%s - #%d\n", __FUNCTION__, tty->index); 536 | 537 | if (!arg) 538 | return -EFAULT; 539 | 540 | serial = &end->serial; 541 | memset(&tmp, 0, sizeof(tmp)); 542 | 543 | spin_lock_irqsave(&end->pair->spin, flags); 544 | tmp.type = serial->type; 545 | tmp.line = serial->line; 546 | tmp.port = serial->port; 547 | tmp.irq = serial->irq; 548 | tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ; 549 | tmp.xmit_fifo_size = serial->xmit_fifo_size; 550 | tmp.baud_base = serial->baud_base; 551 | tmp.close_delay = 5*HZ; 552 | tmp.closing_wait = 30*HZ; 553 | tmp.custom_divisor = serial->custom_divisor; 554 | tmp.hub6 = serial->hub6; 555 | tmp.io_type = serial->io_type; 556 | spin_unlock_irqrestore(&end->pair->spin, flags); 557 | 558 | if (copy_to_user((void __user *)arg, &tmp, sizeof(struct serial_struct))) 559 | return -EFAULT; 560 | return 0; 561 | } 562 | 563 | static int nullmodem_ioctl_tiocmiwait(struct tty_struct *tty, unsigned long arg) 564 | { 565 | struct nullmodem_end *end = tty->driver_data; 566 | unsigned long flags; 567 | DECLARE_WAITQUEUE(wait, current); 568 | int pins; 569 | int prev; 570 | int changed; 571 | int ret; 572 | 573 | dprintf("%s - #%d\n", __FUNCTION__, tty->index); 574 | 575 | if ((tty->index&1) == 0) 576 | { 577 | if (arg & TIOCM_CD) arg |= TIOCM_DSR; 578 | } 579 | else 580 | { 581 | int t = 0; 582 | if (arg & TIOCM_CTS) t |= TIOCM_RTS; 583 | if (arg & TIOCM_DSR) t |= TIOCM_DTR; 584 | if (arg & TIOCM_CD) t |= TIOCM_DTR; 585 | arg = t; 586 | } 587 | 588 | spin_lock_irqsave(&end->pair->spin, flags); 589 | prev = end->pair->control_lines; 590 | add_wait_queue(&end->pair->control_lines_wait, &wait); 591 | set_current_state(TASK_INTERRUPTIBLE); 592 | spin_unlock_irqrestore(&end->pair->spin, flags); 593 | 594 | while (1) 595 | { 596 | schedule(); 597 | 598 | /* see if a signal woke us up */ 599 | if (signal_pending(current)) 600 | { 601 | ret = -ERESTARTSYS; 602 | break; 603 | } 604 | 605 | spin_lock_irqsave(&end->pair->spin, flags); 606 | pins = end->pair->control_lines; 607 | set_current_state(TASK_INTERRUPTIBLE); 608 | spin_unlock_irqrestore(&end->pair->spin, flags); 609 | 610 | changed = pins ^ prev; 611 | if (changed & arg) 612 | { 613 | ret = 0; 614 | break; 615 | } 616 | 617 | prev = pins; 618 | } 619 | remove_wait_queue(&end->pair->control_lines_wait, &wait); 620 | set_current_state(TASK_RUNNING); 621 | return ret; 622 | } 623 | 624 | static int nullmodem_ioctl_tiocgicount(struct tty_struct *tty, unsigned long arg) 625 | { 626 | struct nullmodem_end *end = tty->driver_data; 627 | unsigned long flags; 628 | struct serial_icounter_struct icount; 629 | 630 | dprintf("%s - #%d\n", __FUNCTION__, tty->index); 631 | 632 | spin_lock_irqsave(&end->pair->spin, flags); 633 | icount.cts = end->icount.cts; 634 | icount.dsr = end->icount.dsr; 635 | icount.rng = end->icount.rng; 636 | icount.dcd = end->icount.dcd; 637 | icount.rx = end->icount.rx; 638 | icount.tx = end->icount.tx; 639 | icount.frame = end->icount.frame; 640 | icount.overrun = end->icount.overrun; 641 | icount.parity = end->icount.parity; 642 | icount.brk = end->icount.brk; 643 | icount.buf_overrun = end->icount.buf_overrun; 644 | spin_unlock_irqrestore(&end->pair->spin, flags); 645 | 646 | if (copy_to_user((void __user *)arg, &icount, sizeof(icount))) 647 | return -EFAULT; 648 | return 0; 649 | } 650 | 651 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) 652 | static int nullmodem_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) 653 | #else 654 | static int nullmodem_ioctl(struct tty_struct *tty, struct file *filp, unsigned int cmd, unsigned long arg) 655 | #endif 656 | { 657 | if (cmd == TCGETS || cmd == TCSETS) 658 | return -ENOIOCTLCMD; 659 | 660 | dprintf("%s - #%d cmd:0x%x\n", __FUNCTION__, tty->index, cmd); 661 | 662 | switch (cmd) 663 | { 664 | case TIOCGSERIAL: 665 | return nullmodem_ioctl_tiocgserial(tty, arg); 666 | case TIOCMIWAIT: 667 | return nullmodem_ioctl_tiocmiwait(tty, arg); 668 | case TIOCGICOUNT: 669 | return nullmodem_ioctl_tiocgicount(tty, arg); 670 | } 671 | 672 | return -ENOIOCTLCMD; 673 | } 674 | 675 | static void nullmodem_send_xchar(struct tty_struct *tty, char ch) 676 | { 677 | struct nullmodem_end *end = tty->driver_data; 678 | 679 | dprintf("%s - #%d\n", __FUNCTION__, tty->index); 680 | 681 | end->xchar = ch; 682 | } 683 | 684 | static void nullmodem_throttle(struct tty_struct * tty) 685 | { 686 | struct nullmodem_end *end = tty->driver_data; 687 | unsigned long flags; 688 | 689 | dprintf("%s - #%d\n", __FUNCTION__, tty->index); 690 | 691 | if (I_IXOFF(tty)) 692 | nullmodem_send_xchar(tty, STOP_CHAR(tty)); 693 | 694 | if (get_termios(tty)->c_cflag & CRTSCTS) 695 | { 696 | spin_lock_irqsave(&end->pair->spin, flags); 697 | change_pins(end, 0, TIOCM_RTS); 698 | spin_unlock_irqrestore(&end->pair->spin, flags); 699 | } 700 | } 701 | 702 | static void nullmodem_unthrottle(struct tty_struct * tty) 703 | { 704 | struct nullmodem_end *end = tty->driver_data; 705 | unsigned long flags; 706 | 707 | dprintf("%s - #%d\n", __FUNCTION__, tty->index); 708 | 709 | if (get_termios(tty)->c_cflag & CRTSCTS) 710 | { 711 | spin_lock_irqsave(&end->pair->spin, flags); 712 | change_pins(end, TIOCM_RTS, 0); 713 | spin_unlock_irqrestore(&end->pair->spin, flags); 714 | } 715 | if (I_IXOFF(tty)) 716 | nullmodem_send_xchar(tty, START_CHAR(tty)); 717 | } 718 | 719 | static struct tty_operations serial_ops = 720 | { 721 | .open = nullmodem_open, 722 | .close = nullmodem_close, 723 | .throttle = nullmodem_throttle, 724 | .unthrottle = nullmodem_unthrottle, 725 | .write = nullmodem_write, 726 | .write_room = nullmodem_write_room, 727 | .set_termios = nullmodem_set_termios, 728 | //.send_xchar = nullmodem_send_xchar, 729 | .tiocmget = nullmodem_tiocmget, 730 | .tiocmset = nullmodem_tiocmset, 731 | .ioctl = nullmodem_ioctl, 732 | }; 733 | 734 | static struct tty_driver *nullmodem_tty_driver; 735 | 736 | static int __init nullmodem_init(void) 737 | { 738 | int retval; 739 | int i; 740 | dprintf("%s - \n", __FUNCTION__); 741 | 742 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) 743 | timer_setup(&nullmodem_timer, nullmodem_timer_proc, 0); 744 | #else 745 | init_timer(&nullmodem_timer); 746 | setup_timer(&nullmodem_timer, nullmodem_timer_proc, 0); 747 | #endif 748 | 749 | for (i = 0; i < NULLMODEM_PAIRS; ++i) 750 | { 751 | struct nullmodem_pair *pair = &pair_table[i]; 752 | memset(pair, 0, sizeof(*pair)); 753 | pair->a.other = &pair->b; 754 | pair->a.pair = pair; 755 | pair->b.other = &pair->a; 756 | pair->b.pair = pair; 757 | pair->a.char_length = 10 * FACTOR; 758 | pair->b.char_length = 10 * FACTOR; 759 | spin_lock_init(&pair->spin); 760 | init_waitqueue_head(&pair->control_lines_wait); 761 | dprintf("%s - initialized pair %d -> %p\n", __FUNCTION__, i, pair); 762 | } 763 | 764 | /* allocate the tty driver */ 765 | nullmodem_tty_driver = alloc_tty_driver(NULLMODEM_PAIRS*2); 766 | if (!nullmodem_tty_driver) 767 | return -ENOMEM; 768 | 769 | /* initialize the tty driver */ 770 | nullmodem_tty_driver->owner = THIS_MODULE; 771 | nullmodem_tty_driver->driver_name = "nullmodem"; 772 | nullmodem_tty_driver->name = "nmp"; 773 | /* no more devfs subsystem */ 774 | nullmodem_tty_driver->major = NULLMODEM_MAJOR; 775 | nullmodem_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; 776 | nullmodem_tty_driver->subtype = SERIAL_TYPE_NORMAL; 777 | nullmodem_tty_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW ; 778 | /* no more devfs subsystem */ 779 | nullmodem_tty_driver->init_termios = tty_std_termios; 780 | nullmodem_tty_driver->init_termios.c_iflag = 0; 781 | nullmodem_tty_driver->init_termios.c_oflag = 0; 782 | nullmodem_tty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD; 783 | nullmodem_tty_driver->init_termios.c_lflag = 0; 784 | nullmodem_tty_driver->init_termios.c_ispeed = 38400; 785 | nullmodem_tty_driver->init_termios.c_ospeed = 38400; 786 | 787 | tty_set_operations(nullmodem_tty_driver, &serial_ops); 788 | 789 | /* register the tty driver */ 790 | retval = tty_register_driver(nullmodem_tty_driver); 791 | if (retval) 792 | { 793 | if (retval == -EBUSY) 794 | printk(KERN_INFO "conflicting with other tty, check /proc/tty/drivers\n"); 795 | printk(KERN_ERR "failed to register nullmodem tty driver\n"); 796 | put_tty_driver(nullmodem_tty_driver); 797 | return retval; 798 | } 799 | 800 | last_timer_jiffies = jiffies; 801 | nullmodem_timer.expires = last_timer_jiffies + TIMER_INTERVAL; 802 | add_timer(&nullmodem_timer); 803 | 804 | printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION "\n"); 805 | return retval; 806 | } 807 | 808 | static void __exit nullmodem_exit(void) 809 | { 810 | int i; 811 | 812 | dprintf("%s - \n", __FUNCTION__); 813 | 814 | del_timer_sync(&nullmodem_timer); 815 | 816 | for (i = 0; i < NULLMODEM_PAIRS*2; ++i) 817 | tty_unregister_device(nullmodem_tty_driver, i); 818 | tty_unregister_driver(nullmodem_tty_driver); 819 | 820 | /* shut down all of the timers and free the memory */ 821 | for (i = 0; i < NULLMODEM_PAIRS; ++i) 822 | { 823 | pair_table[i].a.tty = NULL; 824 | pair_table[i].b.tty = NULL; 825 | } 826 | } 827 | 828 | module_init(nullmodem_init); 829 | module_exit(nullmodem_exit); 830 | --------------------------------------------------------------------------------