2005-04-17 02:20:36 +04:00
/*
* USB Serial Console driver
*
* Copyright ( C ) 2001 - 2002 Greg Kroah - Hartman ( greg @ kroah . com )
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation .
*
* Thanks to Randy Dunlap for the original version of this code .
*
*/
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/tty.h>
# include <linux/console.h>
# include <linux/usb.h>
2006-07-12 08:22:58 +04:00
# include <linux/usb/serial.h>
2005-04-17 02:20:36 +04:00
static int debug ;
struct usbcons_info {
int magic ;
int break_flag ;
struct usb_serial_port * port ;
} ;
static struct usbcons_info usbcons_info ;
static struct console usbcons ;
/*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* USB Serial console driver
*
* Much of the code here is copied from drivers / char / serial . c
* and implements a phony serial console in the same way that
* serial . c does so that in case some software queries it ,
* it will get the same results .
*
* Things that are different from the way the serial port code
* does things , is that we call the lower level usb - serial
* driver code to initialize the device , and we set the initial
* console speeds based on the command line arguments .
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
/*
* The parsing of the command line works exactly like the
* serial . c code , except that the specifier is " ttyUSB " instead
* of " ttyS " .
*/
2006-04-13 01:41:59 +04:00
static int usb_console_setup ( struct console * co , char * options )
2005-04-17 02:20:36 +04:00
{
struct usbcons_info * info = & usbcons_info ;
int baud = 9600 ;
int bits = 8 ;
int parity = ' n ' ;
int doflow = 0 ;
int cflag = CREAD | HUPCL | CLOCAL ;
char * s ;
struct usb_serial * serial ;
struct usb_serial_port * port ;
int retval = 0 ;
2007-11-14 01:22:07 +03:00
struct tty_struct * tty = NULL ;
struct ktermios * termios = NULL , dummy ;
2005-04-17 02:20:36 +04:00
2008-03-04 03:08:34 +03:00
dbg ( " %s " , __func__ ) ;
2005-04-17 02:20:36 +04:00
if ( options ) {
baud = simple_strtoul ( options , NULL , 10 ) ;
s = options ;
while ( * s > = ' 0 ' & & * s < = ' 9 ' )
s + + ;
if ( * s )
parity = * s + + ;
if ( * s )
bits = * s + + - ' 0 ' ;
if ( * s )
doflow = ( * s + + = = ' r ' ) ;
}
/* build a cflag setting */
switch ( baud ) {
case 1200 :
cflag | = B1200 ;
break ;
case 2400 :
cflag | = B2400 ;
break ;
case 4800 :
cflag | = B4800 ;
break ;
case 19200 :
cflag | = B19200 ;
break ;
case 38400 :
cflag | = B38400 ;
break ;
case 57600 :
cflag | = B57600 ;
break ;
case 115200 :
cflag | = B115200 ;
break ;
case 9600 :
default :
cflag | = B9600 ;
/*
* Set this to a sane value to prevent a divide error
*/
baud = 9600 ;
break ;
}
switch ( bits ) {
case 7 :
cflag | = CS7 ;
break ;
default :
case 8 :
cflag | = CS8 ;
break ;
}
switch ( parity ) {
case ' o ' : case ' O ' :
cflag | = PARODD ;
break ;
case ' e ' : case ' E ' :
cflag | = PARENB ;
break ;
}
co - > cflag = cflag ;
2007-11-12 23:14:49 +03:00
/*
* no need to check the index here : if the index is wrong , console
* code won ' t call us
*/
serial = usb_serial_get_by_index ( co - > index ) ;
2005-04-17 02:20:36 +04:00
if ( serial = = NULL ) {
/* no device is connected yet, sorry :( */
2007-11-12 23:14:49 +03:00
err ( " No USB device connected to ttyUSB%i " , co - > index ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
}
port = serial - > port [ 0 ] ;
port - > tty = NULL ;
info - > port = port ;
+ + port - > open_count ;
if ( port - > open_count = = 1 ) {
2007-11-14 01:22:07 +03:00
if ( serial - > type - > set_termios ) {
/*
* allocate a fake tty so the driver can initialize
* the termios structure , then later call set_termios to
* configure according to command line arguments
*/
tty = kzalloc ( sizeof ( * tty ) , GFP_KERNEL ) ;
if ( ! tty ) {
retval = - ENOMEM ;
err ( " no more memory " ) ;
goto reset_open_count ;
}
termios = kzalloc ( sizeof ( * termios ) , GFP_KERNEL ) ;
if ( ! termios ) {
retval = - ENOMEM ;
err ( " no more memory " ) ;
goto free_tty ;
}
memset ( & dummy , 0 , sizeof ( struct ktermios ) ) ;
tty - > termios = termios ;
port - > tty = tty ;
}
2005-04-17 02:20:36 +04:00
/* only call the device specific open if this
* is the first time the port is opened */
if ( serial - > type - > open )
retval = serial - > type - > open ( port , NULL ) ;
else
retval = usb_serial_generic_open ( port , NULL ) ;
2007-11-14 01:22:07 +03:00
if ( retval ) {
err ( " could not open USB console port " ) ;
goto free_termios ;
2005-04-17 02:20:36 +04:00
}
2007-11-14 01:22:07 +03:00
if ( serial - > type - > set_termios ) {
termios - > c_cflag = cflag ;
serial - > type - > set_termios ( port , & dummy ) ;
port - > tty = NULL ;
kfree ( termios ) ;
kfree ( tty ) ;
2005-04-17 02:20:36 +04:00
}
}
2007-11-14 01:22:07 +03:00
USB: usb_serial: clean tty reference in the last close
When a usb serial adapter is used as console, the usb serial console
driver bumps the open_count on the port struct used but doesn't attach
a real tty to it (only a fake one temporaly). If this port is opened later
using the regular character device interface, the open method won't
initialize the port, which is the expected, and will receive a brand new
tty struct created by tty layer, which will be stored in port->tty.
When the last close is issued, open_count won't be 0 because of the
console usage and the port->tty will still contain the old tty value. This
is the last ttyUSB<n> close so the allocated tty will be freed by the
tty layer. The usb_serial and usb_serial_port are still in use by the
console, so port_free() won't be called (serial_close() ->
usb_serial_put() -> destroy_serial() -> port_free()), so the scheduled
work (port->work, usb_serial_port_work()) will still run. And
usb_serial_port_work() does:
(...)
tty = port->tty;
if (!tty)
return;
tty_wakeup(tty);
which causes (manually copied):
Faulting instruction address: 0x6b6b6b68
Oops: Kernel access of bad area, sig: 11 [#1]
PREEMPT PowerMac
Modules linked in: binfmt_misc ipv6 nfs lockd nfs_acl sunrpc dm_snapshot dm_mirror dm_mod hfsplus uinput ams input_polldev genrtc cpufreq_powersave i2c_powermac therm_adt746x snd_aoa_codec_tas snd_aoa_fabric_layout snd_aoa joydev snd_aoa_i2sbus snd_pcm_oss snd_mixer_oss snd_pcm snd_timer snd_page_alloc pmac_zilog serial_core evdev ide_cd cdrom snd appletouch soundcore snd_aoa_soundbus bcm43xx firmware_class usbhid ieee80211softmac ff_memless firewire_ohci firewire_core ieee80211 ieee80211_crypt crc_itu_t sungem sungem_phy uninorth_agp agpart ssb
NIP: 6b6b6b68 LR: c01b2108 CTR: 6b6b6b6b
REGS: c106de80 TRAP: 0400 Not tainted (2.6.24-rc2)
MSR: 40009032 <EE,ME,IR,DR> CR: 82004024 XER: 00000000
TASK = c106b4c0[5] 'events/0' THREAD: c106c000
GPR00: 6b6b6b6b c106df30 c106b4c0 c2d613a0 00009032 00000001 00001a00 00000001
GPR08: 00000008 00000000 00000000 c106c000 42004028 00000000 016ffbe0 0171a724
GPR16: 016ffcf4 00240e24 00240e70 016fee68 016ff9a4 c03046c4 c0327f50 c03046fc
GPR24: c106b6b9 c106b4c0 c101d610 c106c000 c02160fc c1eac1dc c2d613ac c2d613a0
NIP [6b6b6b68] 0x6b6b6b68
LR [c01b2108] tty_wakeup+0x6c/0x9c
Call Trace:
[c106df30] [c01b20e8] tty_wakeup+0x4c/0x9c (unreliable)
[c106df40] [c0216138] usb_serial_port_work+0x3c/0x78
[c106df50] [c00432e8] run_workqueue+0xc4/0x15c
[c106df90] [c0043798] worker_thread+0xa0/0x124
[c106dfd0] [c0048224] kthread+0x48/0x84
[c106dff0] [c00129bc] kernel_thread+0x44/0x60
Instruction dump:
XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX
XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX
Slab corruption: size-2048 start=c2d613a0, len=2048
Redzone: 0x9f911029d74e35b/0x9f911029d74e35b.
Last user: [<c01b16d8>](release_one_tty+0xbc/0xf4)
050: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b
Prev obj: start=c2d60b88, len=2048
Redzone: 0x9f911029d74e35b/0x9f911029d74e35b.
Last user: [<c00f30ec>](show_stat+0x410/0x428)
000: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b
010: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b
This patch avoids this, clearing port->tty considering if the port is
used as serial console or not
Signed-off-by: Aristeu Rozanski <arozansk@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2007-11-12 23:15:02 +03:00
port - > console = 1 ;
2007-11-14 01:22:07 +03:00
retval = 0 ;
2005-04-17 02:20:36 +04:00
2007-11-14 01:22:07 +03:00
out :
return retval ;
free_termios :
kfree ( termios ) ;
port - > tty = NULL ;
free_tty :
kfree ( tty ) ;
reset_open_count :
port - > open_count = 0 ;
goto out ;
2005-04-17 02:20:36 +04:00
}
static void usb_console_write ( struct console * co , const char * buf , unsigned count )
{
static struct usbcons_info * info = & usbcons_info ;
struct usb_serial_port * port = info - > port ;
struct usb_serial * serial ;
int retval = - ENODEV ;
2006-04-25 09:46:17 +04:00
if ( ! port | | port - > serial - > dev - > state = = USB_STATE_NOTATTACHED )
2005-04-17 02:20:36 +04:00
return ;
serial = port - > serial ;
if ( count = = 0 )
return ;
2008-03-04 03:08:34 +03:00
dbg ( " %s - port %d, %d byte(s) " , __func__ , port - > number , count ) ;
2005-04-17 02:20:36 +04:00
if ( ! port - > open_count ) {
2008-03-04 03:08:34 +03:00
dbg ( " %s - port not opened " , __func__ ) ;
2006-04-14 00:26:35 +04:00
return ;
2005-04-17 02:20:36 +04:00
}
2006-04-14 00:26:35 +04:00
while ( count ) {
unsigned int i ;
unsigned int lf ;
/* search for LF so we can insert CR if necessary */
for ( i = 0 , lf = 0 ; i < count ; i + + ) {
if ( * ( buf + i ) = = 10 ) {
lf = 1 ;
i + + ;
break ;
}
}
/* pass on to the driver specific version of this function if it is available */
if ( serial - > type - > write )
retval = serial - > type - > write ( port , buf , i ) ;
else
retval = usb_serial_generic_write ( port , buf , i ) ;
2008-03-04 03:08:34 +03:00
dbg ( " %s - return value : %d " , __func__ , retval ) ;
2006-04-14 00:26:35 +04:00
if ( lf ) {
/* append CR after LF */
unsigned char cr = 13 ;
if ( serial - > type - > write )
retval = serial - > type - > write ( port , & cr , 1 ) ;
else
retval = usb_serial_generic_write ( port , & cr , 1 ) ;
2008-03-04 03:08:34 +03:00
dbg ( " %s - return value : %d " , __func__ , retval ) ;
2006-04-14 00:26:35 +04:00
}
buf + = i ;
count - = i ;
}
2005-04-17 02:20:36 +04:00
}
static struct console usbcons = {
. name = " ttyUSB " ,
. write = usb_console_write ,
. setup = usb_console_setup ,
. flags = CON_PRINTBUFFER ,
. index = - 1 ,
} ;
2006-04-25 09:46:17 +04:00
void usb_serial_console_disconnect ( struct usb_serial * serial )
{
if ( serial & & serial - > port & & serial - > port [ 0 ] & & serial - > port [ 0 ] = = usbcons_info . port ) {
usb_serial_console_exit ( ) ;
usb_serial_put ( serial ) ;
}
}
2005-04-17 02:20:36 +04:00
void usb_serial_console_init ( int serial_debug , int minor )
{
debug = serial_debug ;
if ( minor = = 0 ) {
/*
* Call register_console ( ) if this is the first device plugged
* in . If we call it earlier , then the callback to
* console_setup ( ) will fail , as there is not a device seen by
* the USB subsystem yet .
*/
/*
* Register console .
* NOTES :
* console_setup ( ) is called ( back ) immediately ( from register_console ) .
* console_write ( ) is called immediately from register_console iff
* CON_PRINTBUFFER is set in flags .
*/
dbg ( " registering the USB serial console. " ) ;
register_console ( & usbcons ) ;
}
}
void usb_serial_console_exit ( void )
{
2006-04-25 09:46:17 +04:00
if ( usbcons_info . port ) {
unregister_console ( & usbcons ) ;
if ( usbcons_info . port - > open_count )
usbcons_info . port - > open_count - - ;
usbcons_info . port = NULL ;
}
2005-04-17 02:20:36 +04:00
}