2005-04-16 15:20:36 -07:00
/* -*- linux-c -*-
*
* drivers / char / viocons . c
*
* iSeries Virtual Terminal
*
* Authors : Dave Boutcher < boutcher @ us . ibm . com >
* Ryan Arnold < ryanarn @ us . ibm . com >
* Colin Devilbiss < devilbis @ us . ibm . com >
* Stephen Rothwell < sfr @ au1 . ibm . com >
*
* ( C ) Copyright 2000 , 2001 , 2002 , 2003 , 2004 IBM Corporation
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation ; either version 2 of the
* License , or ( at your option ) anyu later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software Foundation ,
* Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/config.h>
# include <linux/version.h>
# include <linux/kernel.h>
# include <linux/proc_fs.h>
# include <linux/errno.h>
# include <linux/vmalloc.h>
# include <linux/mm.h>
# include <linux/console.h>
# include <linux/module.h>
# include <asm/uaccess.h>
# include <linux/init.h>
# include <linux/wait.h>
# include <linux/spinlock.h>
# include <asm/ioctls.h>
# include <linux/kd.h>
# include <linux/tty.h>
# include <linux/tty_flip.h>
# include <linux/sysrq.h>
# include <asm/iSeries/vio.h>
# include <asm/iSeries/HvLpEvent.h>
2005-11-02 11:11:11 +11:00
# include <asm/iseries/hv_call_event.h>
2005-11-02 11:55:28 +11:00
# include <asm/iseries/hv_lp_config.h>
2005-11-01 16:59:20 +11:00
# include <asm/iseries/hv_call.h>
2005-04-16 15:20:36 -07:00
# ifdef CONFIG_VT
# error You must turn off CONFIG_VT to use CONFIG_VIOCONS
# endif
# define VIOTTY_MAGIC (0x0DCB)
# define VTTY_PORTS 10
# define VIOCONS_KERN_WARN KERN_WARNING "viocons: "
# define VIOCONS_KERN_INFO KERN_INFO "viocons: "
static DEFINE_SPINLOCK ( consolelock ) ;
static DEFINE_SPINLOCK ( consoleloglock ) ;
# ifdef CONFIG_MAGIC_SYSRQ
static int vio_sysrq_pressed ;
extern int sysrq_enabled ;
# endif
/*
* The structure of the events that flow between us and OS / 400. You can ' t
* mess with this unless the OS / 400 side changes too
*/
struct viocharlpevent {
struct HvLpEvent event ;
u32 reserved ;
u16 version ;
u16 subtype_result_code ;
u8 virtual_device ;
u8 len ;
u8 data [ VIOCHAR_MAX_DATA ] ;
} ;
# define VIOCHAR_WINDOW 10
# define VIOCHAR_HIGHWATERMARK 3
enum viocharsubtype {
viocharopen = 0x0001 ,
viocharclose = 0x0002 ,
viochardata = 0x0003 ,
viocharack = 0x0004 ,
viocharconfig = 0x0005
} ;
enum viochar_rc {
viochar_rc_ebusy = 1
} ;
# define VIOCHAR_NUM_BUF 16
/*
* Our port information . We store a pointer to one entry in the
* tty_driver_data
*/
static struct port_info {
int magic ;
struct tty_struct * tty ;
HvLpIndex lp ;
u8 vcons ;
u64 seq ; /* sequence number of last HV send */
u64 ack ; /* last ack from HV */
/*
* When we get writes faster than we can send it to the partition ,
* buffer the data here . Note that used is a bit map of used buffers .
* It had better have enough bits to hold VIOCHAR_NUM_BUF the bitops assume
* it is a multiple of unsigned long
*/
unsigned long used ;
u8 * buffer [ VIOCHAR_NUM_BUF ] ;
int bufferBytes [ VIOCHAR_NUM_BUF ] ;
int curbuf ;
int bufferOverflow ;
int overflowMessage ;
} port_info [ VTTY_PORTS ] ;
# define viochar_is_console(pi) ((pi) == &port_info[0])
# define viochar_port(pi) ((pi) - &port_info[0])
static void initDataEvent ( struct viocharlpevent * viochar , HvLpIndex lp ) ;
static struct tty_driver * viotty_driver ;
void hvlog ( char * fmt , . . . )
{
int i ;
unsigned long flags ;
va_list args ;
static char buf [ 256 ] ;
spin_lock_irqsave ( & consoleloglock , flags ) ;
va_start ( args , fmt ) ;
i = vscnprintf ( buf , sizeof ( buf ) - 1 , fmt , args ) ;
va_end ( args ) ;
buf [ i + + ] = ' \r ' ;
HvCall_writeLogBuffer ( buf , i ) ;
spin_unlock_irqrestore ( & consoleloglock , flags ) ;
}
void hvlogOutput ( const char * buf , int count )
{
unsigned long flags ;
int begin ;
int index ;
static const char cr = ' \r ' ;
begin = 0 ;
spin_lock_irqsave ( & consoleloglock , flags ) ;
for ( index = 0 ; index < count ; index + + ) {
if ( buf [ index ] = = ' \n ' ) {
/*
* Start right after the last ' \n ' or at the zeroth
* array position and output the number of characters
* including the newline .
*/
HvCall_writeLogBuffer ( & buf [ begin ] , index - begin + 1 ) ;
begin = index + 1 ;
HvCall_writeLogBuffer ( & cr , 1 ) ;
}
}
if ( ( index - begin ) > 0 )
HvCall_writeLogBuffer ( & buf [ begin ] , index - begin ) ;
spin_unlock_irqrestore ( & consoleloglock , flags ) ;
}
/*
* Make sure we ' re pointing to a valid port_info structure . Shamelessly
* plagerized from serial . c
*/
static inline int viotty_paranoia_check ( struct port_info * pi ,
char * name , const char * routine )
{
static const char * bad_pi_addr = VIOCONS_KERN_WARN
" warning: bad address for port_info struct (%s) in %s \n " ;
static const char * badmagic = VIOCONS_KERN_WARN
" warning: bad magic number for port_info struct (%s) in %s \n " ;
if ( ( pi < & port_info [ 0 ] ) | | ( viochar_port ( pi ) > VTTY_PORTS ) ) {
printk ( bad_pi_addr , name , routine ) ;
return 1 ;
}
if ( pi - > magic ! = VIOTTY_MAGIC ) {
printk ( badmagic , name , routine ) ;
return 1 ;
}
return 0 ;
}
/*
* Add data to our pending - send buffers .
*
* NOTE : Don ' t use printk in here because it gets nastily recursive .
* hvlog can be used to log to the hypervisor buffer
*/
static int buffer_add ( struct port_info * pi , const char * buf , size_t len )
{
size_t bleft ;
size_t curlen ;
const char * curbuf ;
int nextbuf ;
curbuf = buf ;
bleft = len ;
while ( bleft > 0 ) {
/*
* If there is no space left in the current buffer , we have
* filled everything up , so return . If we filled the previous
* buffer we would already have moved to the next one .
*/
if ( pi - > bufferBytes [ pi - > curbuf ] = = VIOCHAR_MAX_DATA ) {
hvlog ( " \n \r viocons: No overflow buffer available for memcpy(). \n " ) ;
pi - > bufferOverflow + + ;
pi - > overflowMessage = 1 ;
break ;
}
/*
* Turn on the " used " bit for this buffer . If it ' s already on ,
* that ' s fine .
*/
set_bit ( pi - > curbuf , & pi - > used ) ;
/*
* See if this buffer has been allocated . If not , allocate it .
*/
if ( pi - > buffer [ pi - > curbuf ] = = NULL ) {
pi - > buffer [ pi - > curbuf ] =
kmalloc ( VIOCHAR_MAX_DATA , GFP_ATOMIC ) ;
if ( pi - > buffer [ pi - > curbuf ] = = NULL ) {
hvlog ( " \n \r viocons: kmalloc failed allocating spaces for buffer %d. " ,
pi - > curbuf ) ;
break ;
}
}
/* Figure out how much we can copy into this buffer. */
if ( bleft < ( VIOCHAR_MAX_DATA - pi - > bufferBytes [ pi - > curbuf ] ) )
curlen = bleft ;
else
curlen = VIOCHAR_MAX_DATA - pi - > bufferBytes [ pi - > curbuf ] ;
/* Copy the data into the buffer. */
memcpy ( pi - > buffer [ pi - > curbuf ] + pi - > bufferBytes [ pi - > curbuf ] ,
curbuf , curlen ) ;
pi - > bufferBytes [ pi - > curbuf ] + = curlen ;
curbuf + = curlen ;
bleft - = curlen ;
/*
* Now see if we ' ve filled this buffer . If not then
* we ' ll try to use it again later . If we ' ve filled it
* up then we ' ll advance the curbuf to the next in the
* circular queue .
*/
if ( pi - > bufferBytes [ pi - > curbuf ] = = VIOCHAR_MAX_DATA ) {
nextbuf = ( pi - > curbuf + 1 ) % VIOCHAR_NUM_BUF ;
/*
* Move to the next buffer if it hasn ' t been used yet
*/
if ( test_bit ( nextbuf , & pi - > used ) = = 0 )
pi - > curbuf = nextbuf ;
}
}
return len - bleft ;
}
/*
* Send pending data
*
* NOTE : Don ' t use printk in here because it gets nastily recursive .
* hvlog can be used to log to the hypervisor buffer
*/
static void send_buffers ( struct port_info * pi )
{
HvLpEvent_Rc hvrc ;
int nextbuf ;
struct viocharlpevent * viochar ;
unsigned long flags ;
spin_lock_irqsave ( & consolelock , flags ) ;
viochar = ( struct viocharlpevent * )
vio_get_event_buffer ( viomajorsubtype_chario ) ;
/* Make sure we got a buffer */
if ( viochar = = NULL ) {
hvlog ( " \n \r viocons: Can't get viochar buffer in sendBuffers(). " ) ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
return ;
}
if ( pi - > used = = 0 ) {
hvlog ( " \n \r viocons: in sendbuffers(), but no buffers used. \n " ) ;
vio_free_event_buffer ( viomajorsubtype_chario , viochar ) ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
return ;
}
/*
* curbuf points to the buffer we ' re filling . We want to
* start sending AFTER this one .
*/
nextbuf = ( pi - > curbuf + 1 ) % VIOCHAR_NUM_BUF ;
/*
* Loop until we find a buffer with the used bit on
*/
while ( test_bit ( nextbuf , & pi - > used ) = = 0 )
nextbuf = ( nextbuf + 1 ) % VIOCHAR_NUM_BUF ;
initDataEvent ( viochar , pi - > lp ) ;
/*
* While we have buffers with data , and our send window
* is open , send them
*/
while ( ( test_bit ( nextbuf , & pi - > used ) ) & &
( ( pi - > seq - pi - > ack ) < VIOCHAR_WINDOW ) ) {
viochar - > len = pi - > bufferBytes [ nextbuf ] ;
viochar - > event . xCorrelationToken = pi - > seq + + ;
viochar - > event . xSizeMinus1 =
offsetof ( struct viocharlpevent , data ) + viochar - > len ;
memcpy ( viochar - > data , pi - > buffer [ nextbuf ] , viochar - > len ) ;
hvrc = HvCallEvent_signalLpEvent ( & viochar - > event ) ;
if ( hvrc ) {
/*
* MUST unlock the spinlock before doing a printk
*/
vio_free_event_buffer ( viomajorsubtype_chario , viochar ) ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
printk ( VIOCONS_KERN_WARN
" error sending event! return code %d \n " ,
( int ) hvrc ) ;
return ;
}
/*
* clear the used bit , zero the number of bytes in
* this buffer , and move to the next buffer
*/
clear_bit ( nextbuf , & pi - > used ) ;
pi - > bufferBytes [ nextbuf ] = 0 ;
nextbuf = ( nextbuf + 1 ) % VIOCHAR_NUM_BUF ;
}
/*
* If we have emptied all the buffers , start at 0 again .
* this will re - use any allocated buffers
*/
if ( pi - > used = = 0 ) {
pi - > curbuf = 0 ;
if ( pi - > overflowMessage )
pi - > overflowMessage = 0 ;
if ( pi - > tty ) {
tty_wakeup ( pi - > tty ) ;
}
}
vio_free_event_buffer ( viomajorsubtype_chario , viochar ) ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
}
/*
* Our internal writer . Gets called both from the console device and
* the tty device . the tty pointer will be NULL if called from the console .
* Return total number of bytes " written " .
*
* NOTE : Don ' t use printk in here because it gets nastily recursive . hvlog
* can be used to log to the hypervisor buffer
*/
static int internal_write ( struct port_info * pi , const char * buf , size_t len )
{
HvLpEvent_Rc hvrc ;
size_t bleft ;
size_t curlen ;
const char * curbuf ;
unsigned long flags ;
struct viocharlpevent * viochar ;
/*
* Write to the hvlog of inbound data are now done prior to
* calling internal_write ( ) since internal_write ( ) is only called in
* the event that an lp event path is active , which isn ' t the case for
* logging attempts prior to console initialization .
*
* If there is already data queued for this port , send it prior to
* attempting to send any new data .
*/
if ( pi - > used )
send_buffers ( pi ) ;
spin_lock_irqsave ( & consolelock , flags ) ;
viochar = vio_get_event_buffer ( viomajorsubtype_chario ) ;
if ( viochar = = NULL ) {
spin_unlock_irqrestore ( & consolelock , flags ) ;
hvlog ( " \n \r viocons: Can't get vio buffer in internal_write(). " ) ;
return - EAGAIN ;
}
initDataEvent ( viochar , pi - > lp ) ;
curbuf = buf ;
bleft = len ;
while ( ( bleft > 0 ) & & ( pi - > used = = 0 ) & &
( ( pi - > seq - pi - > ack ) < VIOCHAR_WINDOW ) ) {
if ( bleft > VIOCHAR_MAX_DATA )
curlen = VIOCHAR_MAX_DATA ;
else
curlen = bleft ;
viochar - > event . xCorrelationToken = pi - > seq + + ;
memcpy ( viochar - > data , curbuf , curlen ) ;
viochar - > len = curlen ;
viochar - > event . xSizeMinus1 =
offsetof ( struct viocharlpevent , data ) + curlen ;
hvrc = HvCallEvent_signalLpEvent ( & viochar - > event ) ;
if ( hvrc ) {
hvlog ( " viocons: error sending event! %d \n " , ( int ) hvrc ) ;
goto out ;
}
curbuf + = curlen ;
bleft - = curlen ;
}
/* If we didn't send it all, buffer as much of it as we can. */
if ( bleft > 0 )
bleft - = buffer_add ( pi , curbuf , bleft ) ;
out :
vio_free_event_buffer ( viomajorsubtype_chario , viochar ) ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
return len - bleft ;
}
static struct port_info * get_port_data ( struct tty_struct * tty )
{
unsigned long flags ;
struct port_info * pi ;
spin_lock_irqsave ( & consolelock , flags ) ;
if ( tty ) {
pi = ( struct port_info * ) tty - > driver_data ;
if ( ! pi | | viotty_paranoia_check ( pi , tty - > name ,
" get_port_data " ) ) {
pi = NULL ;
}
} else
/*
* If this is the console device , use the lp from
* the first port entry
*/
pi = & port_info [ 0 ] ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
return pi ;
}
/*
* Initialize the common fields in a charLpEvent
*/
static void initDataEvent ( struct viocharlpevent * viochar , HvLpIndex lp )
{
memset ( viochar , 0 , sizeof ( struct viocharlpevent ) ) ;
viochar - > event . xFlags . xValid = 1 ;
viochar - > event . xFlags . xFunction = HvLpEvent_Function_Int ;
viochar - > event . xFlags . xAckInd = HvLpEvent_AckInd_NoAck ;
viochar - > event . xFlags . xAckType = HvLpEvent_AckType_DeferredAck ;
viochar - > event . xType = HvLpEvent_Type_VirtualIo ;
viochar - > event . xSubtype = viomajorsubtype_chario | viochardata ;
viochar - > event . xSourceLp = HvLpConfig_getLpIndex ( ) ;
viochar - > event . xTargetLp = lp ;
viochar - > event . xSizeMinus1 = sizeof ( struct viocharlpevent ) ;
viochar - > event . xSourceInstanceId = viopath_sourceinst ( lp ) ;
viochar - > event . xTargetInstanceId = viopath_targetinst ( lp ) ;
}
/*
* early console device write
*/
static void viocons_write_early ( struct console * co , const char * s , unsigned count )
{
hvlogOutput ( s , count ) ;
}
/*
* console device write
*/
static void viocons_write ( struct console * co , const char * s , unsigned count )
{
int index ;
int begin ;
struct port_info * pi ;
static const char cr = ' \r ' ;
/*
* Check port data first because the target LP might be valid but
* simply not active , in which case we want to hvlog the output .
*/
pi = get_port_data ( NULL ) ;
if ( pi = = NULL ) {
hvlog ( " \n \r viocons_write: unable to get port data. " ) ;
return ;
}
hvlogOutput ( s , count ) ;
if ( ! viopath_isactive ( pi - > lp ) )
return ;
/*
* Any newline character found will cause a
* carriage return character to be emitted as well .
*/
begin = 0 ;
for ( index = 0 ; index < count ; index + + ) {
if ( s [ index ] = = ' \n ' ) {
/*
* Newline found . Print everything up to and
* including the newline
*/
internal_write ( pi , & s [ begin ] , index - begin + 1 ) ;
begin = index + 1 ;
/* Emit a carriage return as well */
internal_write ( pi , & cr , 1 ) ;
}
}
/* If any characters left to write, write them now */
if ( ( index - begin ) > 0 )
internal_write ( pi , & s [ begin ] , index - begin ) ;
}
/*
* Work out the device associate with this console
*/
static struct tty_driver * viocons_device ( struct console * c , int * index )
{
* index = c - > index ;
return viotty_driver ;
}
/*
* console device I / O methods
*/
static struct console viocons_early = {
. name = " viocons " ,
. write = viocons_write_early ,
. flags = CON_PRINTBUFFER ,
. index = - 1 ,
} ;
static struct console viocons = {
. name = " viocons " ,
. write = viocons_write ,
. device = viocons_device ,
. flags = CON_PRINTBUFFER ,
. index = - 1 ,
} ;
/*
* TTY Open method
*/
static int viotty_open ( struct tty_struct * tty , struct file * filp )
{
int port ;
unsigned long flags ;
struct port_info * pi ;
port = tty - > index ;
if ( ( port < 0 ) | | ( port > = VTTY_PORTS ) )
return - ENODEV ;
spin_lock_irqsave ( & consolelock , flags ) ;
pi = & port_info [ port ] ;
/* If some other TTY is already connected here, reject the open */
if ( ( pi - > tty ) & & ( pi - > tty ! = tty ) ) {
spin_unlock_irqrestore ( & consolelock , flags ) ;
printk ( VIOCONS_KERN_WARN
" attempt to open device twice from different ttys \n " ) ;
return - EBUSY ;
}
tty - > driver_data = pi ;
pi - > tty = tty ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
return 0 ;
}
/*
* TTY Close method
*/
static void viotty_close ( struct tty_struct * tty , struct file * filp )
{
unsigned long flags ;
struct port_info * pi ;
spin_lock_irqsave ( & consolelock , flags ) ;
pi = ( struct port_info * ) tty - > driver_data ;
if ( ! pi | | viotty_paranoia_check ( pi , tty - > name , " viotty_close " ) ) {
spin_unlock_irqrestore ( & consolelock , flags ) ;
return ;
}
if ( tty - > count = = 1 )
pi - > tty = NULL ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
}
/*
* TTY Write method
*/
static int viotty_write ( struct tty_struct * tty , const unsigned char * buf ,
int count )
{
struct port_info * pi ;
pi = get_port_data ( tty ) ;
if ( pi = = NULL ) {
hvlog ( " \n \r viotty_write: no port data. " ) ;
return - ENODEV ;
}
if ( viochar_is_console ( pi ) )
hvlogOutput ( buf , count ) ;
/*
* If the path to this LP is closed , don ' t bother doing anything more .
* just dump the data on the floor and return count . For some reason
* some user level programs will attempt to probe available tty ' s and
* they ' ll attempt a viotty_write on an invalid port which maps to an
* invalid target lp . If this is the case then ignore the
* viotty_write call and , since the viopath isn ' t active to this
* partition , return count .
*/
if ( ! viopath_isactive ( pi - > lp ) )
return count ;
return internal_write ( pi , buf , count ) ;
}
/*
* TTY put_char method
*/
static void viotty_put_char ( struct tty_struct * tty , unsigned char ch )
{
struct port_info * pi ;
pi = get_port_data ( tty ) ;
if ( pi = = NULL )
return ;
/* This will append '\r' as well if the char is '\n' */
if ( viochar_is_console ( pi ) )
hvlogOutput ( & ch , 1 ) ;
if ( viopath_isactive ( pi - > lp ) )
internal_write ( pi , & ch , 1 ) ;
}
/*
* TTY write_room method
*/
static int viotty_write_room ( struct tty_struct * tty )
{
int i ;
int room = 0 ;
struct port_info * pi ;
unsigned long flags ;
spin_lock_irqsave ( & consolelock , flags ) ;
pi = ( struct port_info * ) tty - > driver_data ;
if ( ! pi | | viotty_paranoia_check ( pi , tty - > name , " viotty_write_room " ) ) {
spin_unlock_irqrestore ( & consolelock , flags ) ;
return 0 ;
}
/* If no buffers are used, return the max size. */
if ( pi - > used = = 0 ) {
spin_unlock_irqrestore ( & consolelock , flags ) ;
return VIOCHAR_MAX_DATA * VIOCHAR_NUM_BUF ;
}
/*
* We retain the spinlock because we want to get an accurate
* count and it can change on us between each operation if we
* don ' t hold the spinlock .
*/
for ( i = 0 ; ( ( i < VIOCHAR_NUM_BUF ) & & ( room < VIOCHAR_MAX_DATA ) ) ; i + + )
room + = ( VIOCHAR_MAX_DATA - pi - > bufferBytes [ i ] ) ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
if ( room > VIOCHAR_MAX_DATA )
room = VIOCHAR_MAX_DATA ;
return room ;
}
/*
* TTY chars_in_buffer method
*/
static int viotty_chars_in_buffer ( struct tty_struct * tty )
{
return 0 ;
}
static int viotty_ioctl ( struct tty_struct * tty , struct file * file ,
unsigned int cmd , unsigned long arg )
{
switch ( cmd ) {
/*
* the ioctls below read / set the flags usually shown in the leds
* don ' t use them - they will go away without warning
*/
case KDGETLED :
case KDGKBLED :
return put_user ( 0 , ( char * ) arg ) ;
case KDSKBLED :
return 0 ;
}
return n_tty_ioctl ( tty , file , cmd , arg ) ;
}
/*
* Handle an open charLpEvent . Could be either interrupt or ack
*/
static void vioHandleOpenEvent ( struct HvLpEvent * event )
{
unsigned long flags ;
struct viocharlpevent * cevent = ( struct viocharlpevent * ) event ;
u8 port = cevent - > virtual_device ;
struct port_info * pi ;
int reject = 0 ;
if ( event - > xFlags . xFunction = = HvLpEvent_Function_Ack ) {
if ( port > = VTTY_PORTS )
return ;
spin_lock_irqsave ( & consolelock , flags ) ;
/* Got the lock, don't cause console output */
pi = & port_info [ port ] ;
if ( event - > xRc = = HvLpEvent_Rc_Good ) {
pi - > seq = pi - > ack = 0 ;
/*
* This line allows connections from the primary
* partition but once one is connected from the
* primary partition nothing short of a reboot
* of linux will allow access from the hosting
* partition again without a required iSeries fix .
*/
pi - > lp = event - > xTargetLp ;
}
spin_unlock_irqrestore ( & consolelock , flags ) ;
if ( event - > xRc ! = HvLpEvent_Rc_Good )
printk ( VIOCONS_KERN_WARN
" handle_open_event: event->xRc == (%d). \n " ,
event - > xRc ) ;
if ( event - > xCorrelationToken ! = 0 ) {
atomic_t * aptr = ( atomic_t * ) event - > xCorrelationToken ;
atomic_set ( aptr , 1 ) ;
} else
printk ( VIOCONS_KERN_WARN
" weird...got open ack without atomic \n " ) ;
return ;
}
/* This had better require an ack, otherwise complain */
if ( event - > xFlags . xAckInd ! = HvLpEvent_AckInd_DoAck ) {
printk ( VIOCONS_KERN_WARN " viocharopen without ack bit! \n " ) ;
return ;
}
spin_lock_irqsave ( & consolelock , flags ) ;
/* Got the lock, don't cause console output */
/* Make sure this is a good virtual tty */
if ( port > = VTTY_PORTS ) {
event - > xRc = HvLpEvent_Rc_SubtypeError ;
cevent - > subtype_result_code = viorc_openRejected ;
/*
* Flag state here since we can ' t printk while holding
* a spinlock .
*/
reject = 1 ;
} else {
pi = & port_info [ port ] ;
if ( ( pi - > lp ! = HvLpIndexInvalid ) & &
( pi - > lp ! = event - > xSourceLp ) ) {
/*
* If this is tty is already connected to a different
* partition , fail .
*/
event - > xRc = HvLpEvent_Rc_SubtypeError ;
cevent - > subtype_result_code = viorc_openRejected ;
reject = 2 ;
} else {
pi - > lp = event - > xSourceLp ;
event - > xRc = HvLpEvent_Rc_Good ;
cevent - > subtype_result_code = viorc_good ;
pi - > seq = pi - > ack = 0 ;
reject = 0 ;
}
}
spin_unlock_irqrestore ( & consolelock , flags ) ;
if ( reject = = 1 )
printk ( VIOCONS_KERN_WARN " open rejected: bad virtual tty. \n " ) ;
else if ( reject = = 2 )
printk ( VIOCONS_KERN_WARN
" open rejected: console in exclusive use by another partition. \n " ) ;
/* Return the acknowledgement */
HvCallEvent_ackLpEvent ( event ) ;
}
/*
* Handle a close charLpEvent . This should ONLY be an Interrupt because the
* virtual console should never actually issue a close event to the hypervisor
* because the virtual console never goes away . A close event coming from the
* hypervisor simply means that there are no client consoles connected to the
* virtual console .
*
* Regardless of the number of connections masqueraded on the other side of
* the hypervisor ONLY ONE close event should be called to accompany the ONE
* open event that is called . The close event should ONLY be called when NO
* MORE connections ( masqueraded or not ) exist on the other side of the
* hypervisor .
*/
static void vioHandleCloseEvent ( struct HvLpEvent * event )
{
unsigned long flags ;
struct viocharlpevent * cevent = ( struct viocharlpevent * ) event ;
u8 port = cevent - > virtual_device ;
if ( event - > xFlags . xFunction = = HvLpEvent_Function_Int ) {
if ( port > = VTTY_PORTS ) {
printk ( VIOCONS_KERN_WARN
" close message from invalid virtual device. \n " ) ;
return ;
}
/* For closes, just mark the console partition invalid */
spin_lock_irqsave ( & consolelock , flags ) ;
/* Got the lock, don't cause console output */
if ( port_info [ port ] . lp = = event - > xSourceLp )
port_info [ port ] . lp = HvLpIndexInvalid ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
printk ( VIOCONS_KERN_INFO " close from %d \n " , event - > xSourceLp ) ;
} else
printk ( VIOCONS_KERN_WARN
" got unexpected close acknowlegement \n " ) ;
}
/*
* Handle a config charLpEvent . Could be either interrupt or ack
*/
static void vioHandleConfig ( struct HvLpEvent * event )
{
struct viocharlpevent * cevent = ( struct viocharlpevent * ) event ;
HvCall_writeLogBuffer ( cevent - > data , cevent - > len ) ;
if ( cevent - > data [ 0 ] = = 0x01 )
printk ( VIOCONS_KERN_INFO " window resized to %d: %d: %d: %d \n " ,
cevent - > data [ 1 ] , cevent - > data [ 2 ] ,
cevent - > data [ 3 ] , cevent - > data [ 4 ] ) ;
else
printk ( VIOCONS_KERN_WARN " unknown config event \n " ) ;
}
/*
* Handle a data charLpEvent .
*/
static void vioHandleData ( struct HvLpEvent * event )
{
struct tty_struct * tty ;
unsigned long flags ;
struct viocharlpevent * cevent = ( struct viocharlpevent * ) event ;
struct port_info * pi ;
int index ;
u8 port = cevent - > virtual_device ;
if ( port > = VTTY_PORTS ) {
printk ( VIOCONS_KERN_WARN " data on invalid virtual device %d \n " ,
port ) ;
return ;
}
/*
* Hold the spinlock so that we don ' t take an interrupt that
* changes tty between the time we fetch the port_info
* pointer and the time we paranoia check .
*/
spin_lock_irqsave ( & consolelock , flags ) ;
pi = & port_info [ port ] ;
/*
* Change 05 / 01 / 2003 - Ryan Arnold : If a partition other than
* the current exclusive partition tries to send us data
* events then just drop them on the floor because we don ' t
* want his stinking data . He isn ' t authorized to receive
* data because he wasn ' t the first one to get the console ,
* therefore he shouldn ' t be allowed to send data either .
* This will work without an iSeries fix .
*/
if ( pi - > lp ! = event - > xSourceLp ) {
spin_unlock_irqrestore ( & consolelock , flags ) ;
return ;
}
tty = pi - > tty ;
if ( tty = = NULL ) {
spin_unlock_irqrestore ( & consolelock , flags ) ;
printk ( VIOCONS_KERN_WARN " no tty for virtual device %d \n " ,
port ) ;
return ;
}
if ( tty - > magic ! = TTY_MAGIC ) {
spin_unlock_irqrestore ( & consolelock , flags ) ;
printk ( VIOCONS_KERN_WARN " tty bad magic \n " ) ;
return ;
}
/*
* Just to be paranoid , make sure the tty points back to this port
*/
pi = ( struct port_info * ) tty - > driver_data ;
if ( ! pi | | viotty_paranoia_check ( pi , tty - > name , " vioHandleData " ) ) {
spin_unlock_irqrestore ( & consolelock , flags ) ;
return ;
}
spin_unlock_irqrestore ( & consolelock , flags ) ;
/*
* Change 07 / 21 / 2003 - Ryan Arnold : functionality added to
* support sysrq utilizing ^ O as the sysrq key . The sysrq
* functionality will only work if built into the kernel and
* then only if sysrq is enabled through the proc filesystem .
*/
for ( index = 0 ; index < cevent - > len ; index + + ) {
# ifdef CONFIG_MAGIC_SYSRQ
if ( sysrq_enabled ) {
/* 0x0f is the ascii character for ^O */
if ( cevent - > data [ index ] = = ' \x0f ' ) {
vio_sysrq_pressed = 1 ;
/*
* continue because we don ' t want to add
* the sysrq key into the data string .
*/
continue ;
} else if ( vio_sysrq_pressed ) {
handle_sysrq ( cevent - > data [ index ] , NULL , tty ) ;
vio_sysrq_pressed = 0 ;
/*
* continue because we don ' t want to add
* the sysrq sequence into the data string .
*/
continue ;
}
}
# endif
/*
* The sysrq sequence isn ' t included in this check if
* sysrq is enabled and compiled into the kernel because
* the sequence will never get inserted into the buffer .
* Don ' t attempt to copy more data into the buffer than we
* have room for because it would fail without indication .
*/
if ( ( tty - > flip . count + 1 ) > TTY_FLIPBUF_SIZE ) {
printk ( VIOCONS_KERN_WARN " input buffer overflow! \n " ) ;
break ;
}
tty_insert_flip_char ( tty , cevent - > data [ index ] , TTY_NORMAL ) ;
}
/* if cevent->len == 0 then no data was added to the buffer and flip.count == 0 */
if ( tty - > flip . count )
/* The next call resets flip.count when the data is flushed. */
tty_flip_buffer_push ( tty ) ;
}
/*
* Handle an ack charLpEvent .
*/
static void vioHandleAck ( struct HvLpEvent * event )
{
struct viocharlpevent * cevent = ( struct viocharlpevent * ) event ;
unsigned long flags ;
u8 port = cevent - > virtual_device ;
if ( port > = VTTY_PORTS ) {
printk ( VIOCONS_KERN_WARN " data on invalid virtual device \n " ) ;
return ;
}
spin_lock_irqsave ( & consolelock , flags ) ;
port_info [ port ] . ack = event - > xCorrelationToken ;
spin_unlock_irqrestore ( & consolelock , flags ) ;
if ( port_info [ port ] . used )
send_buffers ( & port_info [ port ] ) ;
}
/*
* Handle charLpEvents and route to the appropriate routine
*/
static void vioHandleCharEvent ( struct HvLpEvent * event )
{
int charminor ;
if ( event = = NULL )
return ;
charminor = event - > xSubtype & VIOMINOR_SUBTYPE_MASK ;
switch ( charminor ) {
case viocharopen :
vioHandleOpenEvent ( event ) ;
break ;
case viocharclose :
vioHandleCloseEvent ( event ) ;
break ;
case viochardata :
vioHandleData ( event ) ;
break ;
case viocharack :
vioHandleAck ( event ) ;
break ;
case viocharconfig :
vioHandleConfig ( event ) ;
break ;
default :
if ( ( event - > xFlags . xFunction = = HvLpEvent_Function_Int ) & &
( event - > xFlags . xAckInd = = HvLpEvent_AckInd_DoAck ) ) {
event - > xRc = HvLpEvent_Rc_InvalidSubtype ;
HvCallEvent_ackLpEvent ( event ) ;
}
}
}
/*
* Send an open event
*/
static int send_open ( HvLpIndex remoteLp , void * sem )
{
return HvCallEvent_signalLpEventFast ( remoteLp ,
HvLpEvent_Type_VirtualIo ,
viomajorsubtype_chario | viocharopen ,
HvLpEvent_AckInd_DoAck , HvLpEvent_AckType_ImmediateAck ,
viopath_sourceinst ( remoteLp ) ,
viopath_targetinst ( remoteLp ) ,
( u64 ) ( unsigned long ) sem , VIOVERSION < < 16 ,
0 , 0 , 0 , 0 ) ;
}
static struct tty_operations serial_ops = {
. open = viotty_open ,
. close = viotty_close ,
. write = viotty_write ,
. put_char = viotty_put_char ,
. write_room = viotty_write_room ,
. chars_in_buffer = viotty_chars_in_buffer ,
. ioctl = viotty_ioctl ,
} ;
static int __init viocons_init2 ( void )
{
atomic_t wait_flag ;
int rc ;
/* +2 for fudge */
rc = viopath_open ( HvLpConfig_getPrimaryLpIndex ( ) ,
viomajorsubtype_chario , VIOCHAR_WINDOW + 2 ) ;
if ( rc )
printk ( VIOCONS_KERN_WARN " error opening to primary %d \n " , rc ) ;
if ( viopath_hostLp = = HvLpIndexInvalid )
vio_set_hostlp ( ) ;
/*
* And if the primary is not the same as the hosting LP , open to the
* hosting lp
*/
if ( ( viopath_hostLp ! = HvLpIndexInvalid ) & &
( viopath_hostLp ! = HvLpConfig_getPrimaryLpIndex ( ) ) ) {
printk ( VIOCONS_KERN_INFO " open path to hosting (%d) \n " ,
viopath_hostLp ) ;
rc = viopath_open ( viopath_hostLp , viomajorsubtype_chario ,
VIOCHAR_WINDOW + 2 ) ; /* +2 for fudge */
if ( rc )
printk ( VIOCONS_KERN_WARN
" error opening to partition %d: %d \n " ,
viopath_hostLp , rc ) ;
}
if ( vio_setHandler ( viomajorsubtype_chario , vioHandleCharEvent ) < 0 )
printk ( VIOCONS_KERN_WARN
" error seting handler for console events! \n " ) ;
/*
* First , try to open the console to the hosting lp .
* Wait on a semaphore for the response .
*/
atomic_set ( & wait_flag , 0 ) ;
if ( ( viopath_isactive ( viopath_hostLp ) ) & &
( send_open ( viopath_hostLp , ( void * ) & wait_flag ) = = 0 ) ) {
printk ( VIOCONS_KERN_INFO " hosting partition %d \n " ,
viopath_hostLp ) ;
while ( atomic_read ( & wait_flag ) = = 0 )
mb ( ) ;
atomic_set ( & wait_flag , 0 ) ;
}
/*
* If we don ' t have an active console , try the primary
*/
if ( ( ! viopath_isactive ( port_info [ 0 ] . lp ) ) & &
( viopath_isactive ( HvLpConfig_getPrimaryLpIndex ( ) ) ) & &
( send_open ( HvLpConfig_getPrimaryLpIndex ( ) , ( void * ) & wait_flag )
= = 0 ) ) {
printk ( VIOCONS_KERN_INFO " opening console to primary partition \n " ) ;
while ( atomic_read ( & wait_flag ) = = 0 )
mb ( ) ;
}
/* Initialize the tty_driver structure */
viotty_driver = alloc_tty_driver ( VTTY_PORTS ) ;
viotty_driver - > owner = THIS_MODULE ;
viotty_driver - > driver_name = " vioconsole " ;
viotty_driver - > devfs_name = " vcs/ " ;
viotty_driver - > name = " tty " ;
viotty_driver - > name_base = 1 ;
viotty_driver - > major = TTY_MAJOR ;
viotty_driver - > minor_start = 1 ;
viotty_driver - > type = TTY_DRIVER_TYPE_CONSOLE ;
viotty_driver - > subtype = 1 ;
viotty_driver - > init_termios = tty_std_termios ;
viotty_driver - > flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS ;
tty_set_operations ( viotty_driver , & serial_ops ) ;
if ( tty_register_driver ( viotty_driver ) ) {
printk ( VIOCONS_KERN_WARN " couldn't register console driver \n " ) ;
put_tty_driver ( viotty_driver ) ;
viotty_driver = NULL ;
}
unregister_console ( & viocons_early ) ;
register_console ( & viocons ) ;
return 0 ;
}
static int __init viocons_init ( void )
{
int i ;
printk ( VIOCONS_KERN_INFO " registering console \n " ) ;
for ( i = 0 ; i < VTTY_PORTS ; i + + ) {
port_info [ i ] . lp = HvLpIndexInvalid ;
port_info [ i ] . magic = VIOTTY_MAGIC ;
}
HvCall_setLogBufferFormatAndCodepage ( HvCall_LogBuffer_ASCII , 437 ) ;
register_console ( & viocons_early ) ;
return 0 ;
}
console_initcall ( viocons_init ) ;
module_init ( viocons_init2 ) ;