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