| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * USB Serial Console driver | 
|  | 3 | * | 
|  | 4 | * Copyright (C) 2001 - 2002 Greg Kroah-Hartman (greg@kroah.com) | 
|  | 5 | * | 
|  | 6 | *	This program is free software; you can redistribute it and/or | 
|  | 7 | *	modify it under the terms of the GNU General Public License version | 
|  | 8 | *	2 as published by the Free Software Foundation. | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 9 | * | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 10 | * Thanks to Randy Dunlap for the original version of this code. | 
|  | 11 | * | 
|  | 12 | */ | 
|  | 13 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 14 | #include <linux/kernel.h> | 
|  | 15 | #include <linux/init.h> | 
|  | 16 | #include <linux/slab.h> | 
|  | 17 | #include <linux/tty.h> | 
|  | 18 | #include <linux/console.h> | 
|  | 19 | #include <linux/usb.h> | 
| Greg Kroah-Hartman | a969888 | 2006-07-11 21:22:58 -0700 | [diff] [blame] | 20 | #include <linux/usb/serial.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 21 |  | 
|  | 22 | static int debug; | 
|  | 23 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 24 | struct usbcons_info { | 
|  | 25 | int			magic; | 
|  | 26 | int			break_flag; | 
|  | 27 | struct usb_serial_port	*port; | 
|  | 28 | }; | 
|  | 29 |  | 
|  | 30 | static struct usbcons_info usbcons_info; | 
|  | 31 | static struct console usbcons; | 
|  | 32 |  | 
|  | 33 | /* | 
|  | 34 | * ------------------------------------------------------------ | 
|  | 35 | * USB Serial console driver | 
|  | 36 | * | 
|  | 37 | * Much of the code here is copied from drivers/char/serial.c | 
|  | 38 | * and implements a phony serial console in the same way that | 
|  | 39 | * serial.c does so that in case some software queries it, | 
|  | 40 | * it will get the same results. | 
|  | 41 | * | 
|  | 42 | * Things that are different from the way the serial port code | 
|  | 43 | * does things, is that we call the lower level usb-serial | 
|  | 44 | * driver code to initialize the device, and we set the initial | 
|  | 45 | * console speeds based on the command line arguments. | 
|  | 46 | * ------------------------------------------------------------ | 
|  | 47 | */ | 
|  | 48 |  | 
|  | 49 |  | 
|  | 50 | /* | 
|  | 51 | * The parsing of the command line works exactly like the | 
|  | 52 | * serial.c code, except that the specifier is "ttyUSB" instead | 
|  | 53 | * of "ttyS". | 
|  | 54 | */ | 
| Paul Fulghum | 69a4bf7 | 2006-04-12 23:41:59 +0200 | [diff] [blame] | 55 | static int usb_console_setup(struct console *co, char *options) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 56 | { | 
|  | 57 | struct usbcons_info *info = &usbcons_info; | 
|  | 58 | int baud = 9600; | 
|  | 59 | int bits = 8; | 
|  | 60 | int parity = 'n'; | 
|  | 61 | int doflow = 0; | 
|  | 62 | int cflag = CREAD | HUPCL | CLOCAL; | 
|  | 63 | char *s; | 
|  | 64 | struct usb_serial *serial; | 
|  | 65 | struct usb_serial_port *port; | 
|  | 66 | int retval = 0; | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 67 | struct tty_struct *tty = NULL; | 
|  | 68 | struct ktermios *termios = NULL, dummy; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 69 |  | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 70 | dbg("%s", __func__); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 71 |  | 
|  | 72 | if (options) { | 
|  | 73 | baud = simple_strtoul(options, NULL, 10); | 
|  | 74 | s = options; | 
|  | 75 | while (*s >= '0' && *s <= '9') | 
|  | 76 | s++; | 
|  | 77 | if (*s) | 
|  | 78 | parity = *s++; | 
|  | 79 | if (*s) | 
|  | 80 | bits   = *s++ - '0'; | 
|  | 81 | if (*s) | 
|  | 82 | doflow = (*s++ == 'r'); | 
|  | 83 | } | 
| Alan Cox | c17ee88 | 2008-07-22 11:10:08 +0100 | [diff] [blame] | 84 |  | 
|  | 85 | /* Sane default */ | 
|  | 86 | if (baud == 0) | 
|  | 87 | baud = 9600; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 88 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 89 | switch (bits) { | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 90 | case 7: | 
|  | 91 | cflag |= CS7; | 
|  | 92 | break; | 
|  | 93 | default: | 
|  | 94 | case 8: | 
|  | 95 | cflag |= CS8; | 
|  | 96 | break; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 97 | } | 
|  | 98 | switch (parity) { | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 99 | case 'o': case 'O': | 
|  | 100 | cflag |= PARODD; | 
|  | 101 | break; | 
|  | 102 | case 'e': case 'E': | 
|  | 103 | cflag |= PARENB; | 
|  | 104 | break; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 105 | } | 
|  | 106 | co->cflag = cflag; | 
|  | 107 |  | 
| Aristeu Rozanski | 27680d2 | 2007-11-12 15:14:49 -0500 | [diff] [blame] | 108 | /* | 
|  | 109 | * no need to check the index here: if the index is wrong, console | 
|  | 110 | * code won't call us | 
|  | 111 | */ | 
|  | 112 | serial = usb_serial_get_by_index(co->index); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 113 | if (serial == NULL) { | 
|  | 114 | /* no device is connected yet, sorry :( */ | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 115 | err("No USB device connected to ttyUSB%i", co->index); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 116 | return -ENODEV; | 
|  | 117 | } | 
|  | 118 |  | 
|  | 119 | port = serial->port[0]; | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 120 | port->port.tty = NULL; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 121 |  | 
|  | 122 | info->port = port; | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 123 |  | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 124 | ++port->port.count; | 
|  | 125 | if (port->port.count == 1) { | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 126 | if (serial->type->set_termios) { | 
|  | 127 | /* | 
|  | 128 | * allocate a fake tty so the driver can initialize | 
|  | 129 | * the termios structure, then later call set_termios to | 
|  | 130 | * configure according to command line arguments | 
|  | 131 | */ | 
|  | 132 | tty = kzalloc(sizeof(*tty), GFP_KERNEL); | 
|  | 133 | if (!tty) { | 
|  | 134 | retval = -ENOMEM; | 
|  | 135 | err("no more memory"); | 
|  | 136 | goto reset_open_count; | 
|  | 137 | } | 
|  | 138 | termios = kzalloc(sizeof(*termios), GFP_KERNEL); | 
|  | 139 | if (!termios) { | 
|  | 140 | retval = -ENOMEM; | 
|  | 141 | err("no more memory"); | 
|  | 142 | goto free_tty; | 
|  | 143 | } | 
|  | 144 | memset(&dummy, 0, sizeof(struct ktermios)); | 
|  | 145 | tty->termios = termios; | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 146 | port->port.tty = tty; | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 147 | } | 
|  | 148 |  | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 149 | /* only call the device specific open if this | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 150 | * is the first time the port is opened */ | 
|  | 151 | if (serial->type->open) | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 152 | retval = serial->type->open(NULL, port, NULL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 153 | else | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 154 | retval = usb_serial_generic_open(NULL, port, NULL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 155 |  | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 156 | if (retval) { | 
|  | 157 | err("could not open USB console port"); | 
|  | 158 | goto free_termios; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 159 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 160 |  | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 161 | if (serial->type->set_termios) { | 
|  | 162 | termios->c_cflag = cflag; | 
| Alan Cox | c17ee88 | 2008-07-22 11:10:08 +0100 | [diff] [blame] | 163 | tty_termios_encode_baud_rate(termios, baud, baud); | 
| Jason Wessel | 06dd881 | 2008-09-08 14:53:37 +0100 | [diff] [blame] | 164 | serial->type->set_termios(tty, port, &dummy); | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 165 |  | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 166 | port->port.tty = NULL; | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 167 | kfree(termios); | 
|  | 168 | kfree(tty); | 
|  | 169 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 170 | } | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 171 |  | 
| Aristeu Rozanski | 9a6b1ef | 2007-11-12 15:15:02 -0500 | [diff] [blame] | 172 | port->console = 1; | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 173 | retval = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 174 |  | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 175 | out: | 
|  | 176 | return retval; | 
|  | 177 | free_termios: | 
|  | 178 | kfree(termios); | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 179 | port->port.tty = NULL; | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 180 | free_tty: | 
|  | 181 | kfree(tty); | 
|  | 182 | reset_open_count: | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 183 | port->port.count = 0; | 
| Aristeu Rozanski | c87d6a4 | 2007-11-13 17:22:07 -0500 | [diff] [blame] | 184 | goto out; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 185 | } | 
|  | 186 |  | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 187 | static void usb_console_write(struct console *co, | 
|  | 188 | const char *buf, unsigned count) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 189 | { | 
|  | 190 | static struct usbcons_info *info = &usbcons_info; | 
|  | 191 | struct usb_serial_port *port = info->port; | 
|  | 192 | struct usb_serial *serial; | 
|  | 193 | int retval = -ENODEV; | 
|  | 194 |  | 
| Guennadi Liakhovetski | 73e487f | 2006-04-25 07:46:17 +0200 | [diff] [blame] | 195 | if (!port || port->serial->dev->state == USB_STATE_NOTATTACHED) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 196 | return; | 
|  | 197 | serial = port->serial; | 
|  | 198 |  | 
|  | 199 | if (count == 0) | 
|  | 200 | return; | 
|  | 201 |  | 
| Harvey Harrison | 441b62c | 2008-03-03 16:08:34 -0800 | [diff] [blame] | 202 | dbg("%s - port %d, %d byte(s)", __func__, port->number, count); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 203 |  | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 204 | if (!port->port.count) { | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 205 | dbg("%s - port not opened", __func__); | 
| Paul Fulghum | c10746d | 2006-04-13 22:26:35 +0200 | [diff] [blame] | 206 | return; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 207 | } | 
|  | 208 |  | 
| Paul Fulghum | c10746d | 2006-04-13 22:26:35 +0200 | [diff] [blame] | 209 | while (count) { | 
|  | 210 | unsigned int i; | 
|  | 211 | unsigned int lf; | 
|  | 212 | /* search for LF so we can insert CR if necessary */ | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 213 | for (i = 0, lf = 0 ; i < count ; i++) { | 
| Paul Fulghum | c10746d | 2006-04-13 22:26:35 +0200 | [diff] [blame] | 214 | if (*(buf + i) == 10) { | 
|  | 215 | lf = 1; | 
|  | 216 | i++; | 
|  | 217 | break; | 
|  | 218 | } | 
|  | 219 | } | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 220 | /* pass on to the driver specific version of this function if | 
|  | 221 | it is available */ | 
| Paul Fulghum | c10746d | 2006-04-13 22:26:35 +0200 | [diff] [blame] | 222 | if (serial->type->write) | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 223 | retval = serial->type->write(NULL, port, buf, i); | 
| Paul Fulghum | c10746d | 2006-04-13 22:26:35 +0200 | [diff] [blame] | 224 | else | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 225 | retval = usb_serial_generic_write(NULL, port, buf, i); | 
| Harvey Harrison | 441b62c | 2008-03-03 16:08:34 -0800 | [diff] [blame] | 226 | dbg("%s - return value : %d", __func__, retval); | 
| Paul Fulghum | c10746d | 2006-04-13 22:26:35 +0200 | [diff] [blame] | 227 | if (lf) { | 
|  | 228 | /* append CR after LF */ | 
|  | 229 | unsigned char cr = 13; | 
|  | 230 | if (serial->type->write) | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 231 | retval = serial->type->write(NULL, | 
|  | 232 | port, &cr, 1); | 
| Paul Fulghum | c10746d | 2006-04-13 22:26:35 +0200 | [diff] [blame] | 233 | else | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 234 | retval = usb_serial_generic_write(NULL, | 
|  | 235 | port, &cr, 1); | 
| Harvey Harrison | 441b62c | 2008-03-03 16:08:34 -0800 | [diff] [blame] | 236 | dbg("%s - return value : %d", __func__, retval); | 
| Paul Fulghum | c10746d | 2006-04-13 22:26:35 +0200 | [diff] [blame] | 237 | } | 
|  | 238 | buf += i; | 
|  | 239 | count -= i; | 
|  | 240 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 241 | } | 
|  | 242 |  | 
|  | 243 | static struct console usbcons = { | 
|  | 244 | .name =		"ttyUSB", | 
|  | 245 | .write =	usb_console_write, | 
|  | 246 | .setup =	usb_console_setup, | 
|  | 247 | .flags =	CON_PRINTBUFFER, | 
|  | 248 | .index =	-1, | 
|  | 249 | }; | 
|  | 250 |  | 
| Guennadi Liakhovetski | 73e487f | 2006-04-25 07:46:17 +0200 | [diff] [blame] | 251 | void usb_serial_console_disconnect(struct usb_serial *serial) | 
|  | 252 | { | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 253 | if (serial && serial->port && serial->port[0] | 
|  | 254 | && serial->port[0] == usbcons_info.port) { | 
| Guennadi Liakhovetski | 73e487f | 2006-04-25 07:46:17 +0200 | [diff] [blame] | 255 | usb_serial_console_exit(); | 
|  | 256 | usb_serial_put(serial); | 
|  | 257 | } | 
|  | 258 | } | 
|  | 259 |  | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 260 | void usb_serial_console_init(int serial_debug, int minor) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 261 | { | 
|  | 262 | debug = serial_debug; | 
|  | 263 |  | 
|  | 264 | if (minor == 0) { | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 265 | /* | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 266 | * Call register_console() if this is the first device plugged | 
|  | 267 | * in.  If we call it earlier, then the callback to | 
|  | 268 | * console_setup() will fail, as there is not a device seen by | 
|  | 269 | * the USB subsystem yet. | 
|  | 270 | */ | 
|  | 271 | /* | 
|  | 272 | * Register console. | 
|  | 273 | * NOTES: | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 274 | * console_setup() is called (back) immediately (from | 
|  | 275 | * register_console). console_write() is called immediately | 
|  | 276 | * from register_console iff CON_PRINTBUFFER is set in flags. | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 277 | */ | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 278 | dbg("registering the USB serial console."); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 279 | register_console(&usbcons); | 
|  | 280 | } | 
|  | 281 | } | 
|  | 282 |  | 
| Alan Cox | 4dbd5a0 | 2008-07-22 11:09:57 +0100 | [diff] [blame] | 283 | void usb_serial_console_exit(void) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 284 | { | 
| Guennadi Liakhovetski | 73e487f | 2006-04-25 07:46:17 +0200 | [diff] [blame] | 285 | if (usbcons_info.port) { | 
|  | 286 | unregister_console(&usbcons); | 
| Alan Cox | 95da310 | 2008-07-22 11:09:07 +0100 | [diff] [blame] | 287 | if (usbcons_info.port->port.count) | 
|  | 288 | usbcons_info.port->port.count--; | 
| Guennadi Liakhovetski | 73e487f | 2006-04-25 07:46:17 +0200 | [diff] [blame] | 289 | usbcons_info.port = NULL; | 
|  | 290 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 291 | } | 
|  | 292 |  |