2005-04-16 15:20:36 -07:00
/*
2009-06-16 10:30:40 +02:00
* SCLP line mode console driver
2005-04-16 15:20:36 -07:00
*
2009-06-16 10:30:40 +02:00
* Copyright IBM Corp . 1999 , 2009
* Author ( s ) : Martin Peschke < mpeschke @ de . ibm . com >
* Martin Schwidefsky < schwidefsky @ de . ibm . com >
2005-04-16 15:20:36 -07:00
*/
# include <linux/kmod.h>
# include <linux/console.h>
# include <linux/init.h>
# include <linux/timer.h>
# include <linux/jiffies.h>
2008-07-14 09:59:45 +02:00
# include <linux/termios.h>
2005-04-16 15:20:36 -07:00
# include <linux/err.h>
2008-10-10 21:33:27 +02:00
# include <linux/reboot.h>
2005-04-16 15:20:36 -07:00
# include "sclp.h"
# include "sclp_rw.h"
# include "sclp_tty.h"
# define sclp_console_major 4 /* TTYAUX_MAJOR */
# define sclp_console_minor 64
# define sclp_console_name "ttyS"
/* Lock to guard over changes to global variables */
static spinlock_t sclp_con_lock ;
/* List of free pages that can be used for console output buffering */
static struct list_head sclp_con_pages ;
/* List of full struct sclp_buffer structures ready for output */
static struct list_head sclp_con_outqueue ;
/* Pointer to current console buffer */
static struct sclp_buffer * sclp_conbuf ;
/* Timer for delayed output of console messages */
static struct timer_list sclp_con_timer ;
2009-06-16 10:30:40 +02:00
/* Suspend mode flag */
static int sclp_con_suspended ;
/* Flag that output queue is currently running */
static int sclp_con_queue_running ;
2005-04-16 15:20:36 -07:00
/* Output format for console messages */
static unsigned short sclp_con_columns ;
static unsigned short sclp_con_width_htab ;
static void
sclp_conbuf_callback ( struct sclp_buffer * buffer , int rc )
{
unsigned long flags ;
void * page ;
do {
page = sclp_unmake_buffer ( buffer ) ;
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
2009-06-16 10:30:40 +02:00
2005-04-16 15:20:36 -07:00
/* Remove buffer from outqueue */
list_del ( & buffer - > list ) ;
list_add_tail ( ( struct list_head * ) page , & sclp_con_pages ) ;
2009-06-16 10:30:40 +02:00
2005-04-16 15:20:36 -07:00
/* Check if there is a pending buffer on the out queue. */
buffer = NULL ;
if ( ! list_empty ( & sclp_con_outqueue ) )
2009-06-16 10:30:40 +02:00
buffer = list_first_entry ( & sclp_con_outqueue ,
struct sclp_buffer , list ) ;
if ( ! buffer | | sclp_con_suspended ) {
sclp_con_queue_running = 0 ;
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
break ;
}
2005-04-16 15:20:36 -07:00
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
2009-06-16 10:30:40 +02:00
} while ( sclp_emit_buffer ( buffer , sclp_conbuf_callback ) ) ;
2005-04-16 15:20:36 -07:00
}
2009-06-16 10:30:40 +02:00
/*
* Finalize and emit first pending buffer .
*/
static void sclp_conbuf_emit ( void )
2005-04-16 15:20:36 -07:00
{
struct sclp_buffer * buffer ;
unsigned long flags ;
int rc ;
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
2009-06-16 10:30:40 +02:00
if ( sclp_conbuf )
list_add_tail ( & sclp_conbuf - > list , & sclp_con_outqueue ) ;
2005-04-16 15:20:36 -07:00
sclp_conbuf = NULL ;
2009-06-16 10:30:40 +02:00
if ( sclp_con_queue_running | | sclp_con_suspended )
goto out_unlock ;
if ( list_empty ( & sclp_con_outqueue ) )
goto out_unlock ;
buffer = list_first_entry ( & sclp_con_outqueue , struct sclp_buffer ,
list ) ;
sclp_con_queue_running = 1 ;
2005-04-16 15:20:36 -07:00
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
2009-06-16 10:30:40 +02:00
2005-04-16 15:20:36 -07:00
rc = sclp_emit_buffer ( buffer , sclp_conbuf_callback ) ;
if ( rc )
sclp_conbuf_callback ( buffer , rc ) ;
2009-06-16 10:30:40 +02:00
return ;
out_unlock :
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
}
/*
* Wait until out queue is empty
*/
static void sclp_console_sync_queue ( void )
{
unsigned long flags ;
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
if ( timer_pending ( & sclp_con_timer ) )
2009-06-22 12:08:09 +02:00
del_timer ( & sclp_con_timer ) ;
2009-06-16 10:30:40 +02:00
while ( sclp_con_queue_running ) {
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
sclp_sync_wait ( ) ;
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
}
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
2005-04-16 15:20:36 -07:00
}
/*
* When this routine is called from the timer then we flush the
* temporary write buffer without further waiting on a final new line .
*/
static void
sclp_console_timeout ( unsigned long data )
{
sclp_conbuf_emit ( ) ;
}
/*
* Writes the given message to S390 system console
*/
static void
sclp_console_write ( struct console * console , const char * message ,
unsigned int count )
{
unsigned long flags ;
void * page ;
int written ;
if ( count = = 0 )
return ;
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
/*
* process escape characters , write message into buffer ,
* send buffer to SCLP
*/
do {
/* make sure we have a console output buffer */
if ( sclp_conbuf = = NULL ) {
while ( list_empty ( & sclp_con_pages ) ) {
2009-06-16 10:30:40 +02:00
if ( sclp_con_suspended )
goto out ;
2005-04-16 15:20:36 -07:00
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
sclp_sync_wait ( ) ;
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
}
page = sclp_con_pages . next ;
list_del ( ( struct list_head * ) page ) ;
sclp_conbuf = sclp_make_buffer ( page , sclp_con_columns ,
sclp_con_width_htab ) ;
}
/* try to write the string to the current output buffer */
written = sclp_write ( sclp_conbuf , ( const unsigned char * )
message , count ) ;
if ( written = = count )
break ;
/*
* Not all characters could be written to the current
* output buffer . Emit the buffer , create a new buffer
* and then output the rest of the string .
*/
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
sclp_conbuf_emit ( ) ;
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
message + = written ;
count - = written ;
} while ( count > 0 ) ;
/* Setup timer to output current console buffer after 1/10 second */
if ( sclp_conbuf ! = NULL & & sclp_chars_in_buffer ( sclp_conbuf ) ! = 0 & &
! timer_pending ( & sclp_con_timer ) ) {
init_timer ( & sclp_con_timer ) ;
sclp_con_timer . function = sclp_console_timeout ;
sclp_con_timer . data = 0UL ;
sclp_con_timer . expires = jiffies + HZ / 10 ;
add_timer ( & sclp_con_timer ) ;
}
2009-06-16 10:30:40 +02:00
out :
2005-04-16 15:20:36 -07:00
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
}
static struct tty_driver *
sclp_console_device ( struct console * c , int * index )
{
* index = c - > index ;
return sclp_tty_driver ;
}
/*
2009-06-16 10:30:40 +02:00
* Make sure that all buffers will be flushed to the SCLP .
2005-04-16 15:20:36 -07:00
*/
static void
2008-10-10 21:33:27 +02:00
sclp_console_flush ( void )
2009-06-16 10:30:40 +02:00
{
sclp_conbuf_emit ( ) ;
sclp_console_sync_queue ( ) ;
}
/*
* Resume console : If there are cached messages , emit them .
*/
static void sclp_console_resume ( void )
2005-04-16 15:20:36 -07:00
{
unsigned long flags ;
2009-06-16 10:30:40 +02:00
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
sclp_con_suspended = 0 ;
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
2005-04-16 15:20:36 -07:00
sclp_conbuf_emit ( ) ;
2009-06-16 10:30:40 +02:00
}
/*
* Suspend console : Set suspend flag and flush console
*/
static void sclp_console_suspend ( void )
{
unsigned long flags ;
2005-04-16 15:20:36 -07:00
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
2009-06-16 10:30:40 +02:00
sclp_con_suspended = 1 ;
2005-04-16 15:20:36 -07:00
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
2009-06-16 10:30:40 +02:00
sclp_console_flush ( ) ;
2005-04-16 15:20:36 -07:00
}
2009-06-16 10:30:40 +02:00
static int sclp_console_notify ( struct notifier_block * self ,
unsigned long event , void * data )
2008-10-10 21:33:27 +02:00
{
sclp_console_flush ( ) ;
return NOTIFY_OK ;
}
static struct notifier_block on_panic_nb = {
. notifier_call = sclp_console_notify ,
2009-06-16 10:30:40 +02:00
. priority = SCLP_PANIC_PRIO_CLIENT ,
2008-10-10 21:33:27 +02:00
} ;
static struct notifier_block on_reboot_nb = {
. notifier_call = sclp_console_notify ,
. priority = 1 ,
} ;
2005-04-16 15:20:36 -07:00
/*
* used to register the SCLP console to the kernel and to
* give printk necessary information
*/
static struct console sclp_console =
{
. name = sclp_console_name ,
. write = sclp_console_write ,
. device = sclp_console_device ,
. flags = CON_PRINTBUFFER ,
. index = 0 /* ttyS0 */
} ;
2009-06-16 10:30:40 +02:00
/*
* This function is called for SCLP suspend and resume events .
*/
void sclp_console_pm_event ( enum sclp_pm_event sclp_pm_event )
{
switch ( sclp_pm_event ) {
case SCLP_PM_EVENT_FREEZE :
sclp_console_suspend ( ) ;
break ;
case SCLP_PM_EVENT_RESTORE :
case SCLP_PM_EVENT_THAW :
sclp_console_resume ( ) ;
break ;
}
}
2005-04-16 15:20:36 -07:00
/*
* called by console_init ( ) in drivers / char / tty_io . c at boot - time .
*/
static int __init
sclp_console_init ( void )
{
void * page ;
int i ;
int rc ;
if ( ! CONSOLE_IS_SCLP )
return 0 ;
rc = sclp_rw_init ( ) ;
if ( rc )
return rc ;
/* Allocate pages for output buffering */
INIT_LIST_HEAD ( & sclp_con_pages ) ;
for ( i = 0 ; i < MAX_CONSOLE_PAGES ; i + + ) {
2009-06-22 12:08:06 +02:00
page = ( void * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
list_add_tail ( page , & sclp_con_pages ) ;
2005-04-16 15:20:36 -07:00
}
INIT_LIST_HEAD ( & sclp_con_outqueue ) ;
spin_lock_init ( & sclp_con_lock ) ;
sclp_conbuf = NULL ;
init_timer ( & sclp_con_timer ) ;
/* Set output format */
if ( MACHINE_IS_VM )
/*
* save 4 characters for the CPU number
* written at start of each line by VM / CP
*/
sclp_con_columns = 76 ;
else
sclp_con_columns = 80 ;
sclp_con_width_htab = 8 ;
/* enable printk-access to this driver */
2008-10-10 21:33:27 +02:00
atomic_notifier_chain_register ( & panic_notifier_list , & on_panic_nb ) ;
register_reboot_notifier ( & on_reboot_nb ) ;
2005-04-16 15:20:36 -07:00
register_console ( & sclp_console ) ;
return 0 ;
}
console_initcall ( sclp_console_init ) ;