2005-04-16 15:20:36 -07:00
/*
* drivers / s390 / char / sclp_con . c
* SCLP line mode console driver
*
* S390 version
* Copyright ( C ) 1999 IBM Deutschland Entwicklung GmbH , IBM Corporation
* Author ( s ) : Martin Peschke < mpeschke @ de . ibm . com >
* Martin Schwidefsky < schwidefsky @ de . ibm . com >
*/
# include <linux/kmod.h>
# include <linux/console.h>
# include <linux/init.h>
# include <linux/timer.h>
# include <linux/jiffies.h>
# include <linux/bootmem.h>
# include <linux/err.h>
# include "sclp.h"
# include "sclp_rw.h"
# include "sclp_tty.h"
# define SCLP_CON_PRINT_HEADER "sclp console driver: "
# 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 ;
/* Counter how many buffers are emitted (max 1) and how many */
/* are on the output queue. */
static int sclp_con_buffer_count ;
/* 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 ;
/* 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 ) ;
/* Remove buffer from outqueue */
list_del ( & buffer - > list ) ;
sclp_con_buffer_count - - ;
list_add_tail ( ( struct list_head * ) page , & sclp_con_pages ) ;
/* Check if there is a pending buffer on the out queue. */
buffer = NULL ;
if ( ! list_empty ( & sclp_con_outqueue ) )
buffer = list_entry ( sclp_con_outqueue . next ,
struct sclp_buffer , list ) ;
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
} while ( buffer & & sclp_emit_buffer ( buffer , sclp_conbuf_callback ) ) ;
}
2007-02-05 21:18:53 +01:00
static void
2005-04-16 15:20:36 -07:00
sclp_conbuf_emit ( void )
{
struct sclp_buffer * buffer ;
unsigned long flags ;
int count ;
int rc ;
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
buffer = sclp_conbuf ;
sclp_conbuf = NULL ;
if ( buffer = = NULL ) {
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
return ;
}
list_add_tail ( & buffer - > list , & sclp_con_outqueue ) ;
count = sclp_con_buffer_count + + ;
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
if ( count )
return ;
rc = sclp_emit_buffer ( buffer , sclp_conbuf_callback ) ;
if ( rc )
sclp_conbuf_callback ( buffer , rc ) ;
}
/*
* 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 ) ) {
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 ) ;
}
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 ;
}
/*
* This routine is called from panic when the kernel
* is going to give up . We have to make sure that all buffers
* will be flushed to the SCLP .
*/
static void
sclp_console_unblank ( void )
{
unsigned long flags ;
sclp_conbuf_emit ( ) ;
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
if ( timer_pending ( & sclp_con_timer ) )
del_timer ( & sclp_con_timer ) ;
while ( sclp_con_buffer_count > 0 ) {
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
sclp_sync_wait ( ) ;
spin_lock_irqsave ( & sclp_con_lock , flags ) ;
}
spin_unlock_irqrestore ( & sclp_con_lock , flags ) ;
}
/*
* 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 ,
. unblank = sclp_console_unblank ,
. flags = CON_PRINTBUFFER ,
. index = 0 /* ttyS0 */
} ;
/*
* 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 + + ) {
page = alloc_bootmem_low_pages ( PAGE_SIZE ) ;
if ( page = = NULL )
return - ENOMEM ;
list_add_tail ( ( struct list_head * ) page , & sclp_con_pages ) ;
}
INIT_LIST_HEAD ( & sclp_con_outqueue ) ;
spin_lock_init ( & sclp_con_lock ) ;
sclp_con_buffer_count = 0 ;
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 */
register_console ( & sclp_console ) ;
return 0 ;
}
console_initcall ( sclp_console_init ) ;