2005-04-16 15:20:36 -07:00
/*
* drivers / s390 / char / con3270 . c
* IBM / 3270 Driver - console view .
*
* Author ( s ) :
* Original 3270 Code for 2.4 written by Richard Hitt ( UTS Global )
* Rewritten for 2.5 by Martin Schwidefsky < schwidefsky @ de . ibm . com >
* - - Copyright ( C ) 2003 IBM Deutschland Entwicklung GmbH , IBM Corporation
*/
# include <linux/bootmem.h>
# include <linux/console.h>
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/list.h>
# include <linux/types.h>
2006-02-01 03:06:35 -08:00
# include <linux/err.h>
2005-04-16 15:20:36 -07:00
# include <asm/ccwdev.h>
# include <asm/cio.h>
# include <asm/cpcmd.h>
# include <asm/ebcdic.h>
# include "raw3270.h"
2007-10-12 16:11:35 +02:00
# include "tty3270.h"
2005-04-16 15:20:36 -07:00
# include "ctrlchar.h"
# define CON3270_OUTPUT_BUFFER_SIZE 1024
# define CON3270_STRING_PAGES 4
static struct raw3270_fn con3270_fn ;
/*
* Main 3270 console view data structure .
*/
struct con3270 {
struct raw3270_view view ;
spinlock_t lock ;
struct list_head freemem ; /* list of free memory for strings. */
/* Output stuff. */
struct list_head lines ; /* list of lines. */
struct list_head update ; /* list of lines to update. */
int line_nr ; /* line number for next update. */
int nr_lines ; /* # lines in list. */
int nr_up ; /* # lines up in history. */
unsigned long update_flags ; /* Update indication bits. */
struct string * cline ; /* current output line. */
struct string * status ; /* last line of display. */
struct raw3270_request * write ; /* single write request. */
struct timer_list timer ;
/* Input stuff. */
struct string * input ; /* input string for read request. */
struct raw3270_request * read ; /* single read request. */
struct raw3270_request * kreset ; /* single keyboard reset request. */
struct tasklet_struct readlet ; /* tasklet to issue read request. */
} ;
static struct con3270 * condev ;
/* con3270->update_flags. See con3270_update for details. */
# define CON_UPDATE_ERASE 1 /* Use EWRITEA instead of WRITE. */
# define CON_UPDATE_LIST 2 /* Update lines in tty3270->update. */
# define CON_UPDATE_STATUS 4 /* Update status line. */
# define CON_UPDATE_ALL 7
static void con3270_update ( struct con3270 * ) ;
/*
* Setup timeout for a device . On timeout trigger an update .
*/
2007-02-05 21:16:47 +01:00
static void con3270_set_timer ( struct con3270 * cp , int expires )
2005-04-16 15:20:36 -07:00
{
if ( expires = = 0 ) {
if ( timer_pending ( & cp - > timer ) )
del_timer ( & cp - > timer ) ;
return ;
}
if ( timer_pending ( & cp - > timer ) & &
mod_timer ( & cp - > timer , jiffies + expires ) )
return ;
cp - > timer . function = ( void ( * ) ( unsigned long ) ) con3270_update ;
cp - > timer . data = ( unsigned long ) cp ;
cp - > timer . expires = jiffies + expires ;
add_timer ( & cp - > timer ) ;
}
/*
* The status line is the last line of the screen . It shows the string
* " console view " in the lower left corner and " Running " / " More... " / " Holding "
* in the lower right corner of the screen .
*/
static void
con3270_update_status ( struct con3270 * cp )
{
char * str ;
str = ( cp - > nr_up ! = 0 ) ? " History " : " Running " ;
memcpy ( cp - > status - > string + 24 , str , 7 ) ;
codepage_convert ( cp - > view . ascebc , cp - > status - > string + 24 , 7 ) ;
cp - > update_flags | = CON_UPDATE_STATUS ;
}
static void
con3270_create_status ( struct con3270 * cp )
{
static const unsigned char blueprint [ ] =
{ TO_SBA , 0 , 0 , TO_SF , TF_LOG , TO_SA , TAT_COLOR , TAC_GREEN ,
' c ' , ' o ' , ' n ' , ' s ' , ' o ' , ' l ' , ' e ' , ' ' , ' v ' , ' i ' , ' e ' , ' w ' ,
TO_RA , 0 , 0 , 0 , ' R ' , ' u ' , ' n ' , ' n ' , ' i ' , ' n ' , ' g ' , TO_SF , TF_LOG } ;
cp - > status = alloc_string ( & cp - > freemem , sizeof ( blueprint ) ) ;
/* Copy blueprint to status line */
memcpy ( cp - > status - > string , blueprint , sizeof ( blueprint ) ) ;
/* Set TO_RA addresses. */
raw3270_buffer_address ( cp - > view . dev , cp - > status - > string + 1 ,
cp - > view . cols * ( cp - > view . rows - 1 ) ) ;
raw3270_buffer_address ( cp - > view . dev , cp - > status - > string + 21 ,
cp - > view . cols * cp - > view . rows - 8 ) ;
/* Convert strings to ebcdic. */
codepage_convert ( cp - > view . ascebc , cp - > status - > string + 8 , 12 ) ;
codepage_convert ( cp - > view . ascebc , cp - > status - > string + 24 , 7 ) ;
}
/*
* Set output offsets to 3270 datastream fragment of a console string .
*/
static void
con3270_update_string ( struct con3270 * cp , struct string * s , int nr )
{
if ( s - > len > = cp - > view . cols - 5 )
return ;
raw3270_buffer_address ( cp - > view . dev , s - > string + s - > len - 3 ,
cp - > view . cols * ( nr + 1 ) ) ;
}
/*
* Rebuild update list to print all lines .
*/
static void
con3270_rebuild_update ( struct con3270 * cp )
{
struct string * s , * n ;
int nr ;
/*
* Throw away update list and create a new one ,
* containing all lines that will fit on the screen .
*/
list_for_each_entry_safe ( s , n , & cp - > update , update )
list_del_init ( & s - > update ) ;
nr = cp - > view . rows - 2 + cp - > nr_up ;
list_for_each_entry_reverse ( s , & cp - > lines , list ) {
if ( nr < cp - > view . rows - 1 )
list_add ( & s - > update , & cp - > update ) ;
if ( - - nr < 0 )
break ;
}
cp - > line_nr = 0 ;
cp - > update_flags | = CON_UPDATE_LIST ;
}
/*
* Alloc string for size bytes . Free strings from history if necessary .
*/
static struct string *
con3270_alloc_string ( struct con3270 * cp , size_t size )
{
struct string * s , * n ;
s = alloc_string ( & cp - > freemem , size ) ;
if ( s )
return s ;
list_for_each_entry_safe ( s , n , & cp - > lines , list ) {
list_del ( & s - > list ) ;
if ( ! list_empty ( & s - > update ) )
list_del ( & s - > update ) ;
cp - > nr_lines - - ;
if ( free_string ( & cp - > freemem , s ) > = size )
break ;
}
s = alloc_string ( & cp - > freemem , size ) ;
BUG_ON ( ! s ) ;
if ( cp - > nr_up ! = 0 & & cp - > nr_up + cp - > view . rows > cp - > nr_lines ) {
cp - > nr_up = cp - > nr_lines - cp - > view . rows + 1 ;
con3270_rebuild_update ( cp ) ;
con3270_update_status ( cp ) ;
}
return s ;
}
/*
* Write completion callback .
*/
static void
con3270_write_callback ( struct raw3270_request * rq , void * data )
{
raw3270_request_reset ( rq ) ;
xchg ( & ( ( struct con3270 * ) rq - > view ) - > write , rq ) ;
}
/*
* Update console display .
*/
static void
con3270_update ( struct con3270 * cp )
{
struct raw3270_request * wrq ;
char wcc , prolog [ 6 ] ;
unsigned long flags ;
unsigned long updated ;
struct string * s , * n ;
int rc ;
2005-10-30 15:00:10 -08:00
if ( cp - > view . dev )
raw3270_activate_view ( & cp - > view ) ;
2005-04-16 15:20:36 -07:00
wrq = xchg ( & cp - > write , 0 ) ;
if ( ! wrq ) {
con3270_set_timer ( cp , 1 ) ;
return ;
}
spin_lock_irqsave ( & cp - > view . lock , flags ) ;
updated = 0 ;
if ( cp - > update_flags & CON_UPDATE_ERASE ) {
/* Use erase write alternate to initialize display. */
raw3270_request_set_cmd ( wrq , TC_EWRITEA ) ;
updated | = CON_UPDATE_ERASE ;
} else
raw3270_request_set_cmd ( wrq , TC_WRITE ) ;
wcc = TW_NONE ;
raw3270_request_add_data ( wrq , & wcc , 1 ) ;
/*
* Update status line .
*/
if ( cp - > update_flags & CON_UPDATE_STATUS )
if ( raw3270_request_add_data ( wrq , cp - > status - > string ,
cp - > status - > len ) = = 0 )
updated | = CON_UPDATE_STATUS ;
if ( cp - > update_flags & CON_UPDATE_LIST ) {
prolog [ 0 ] = TO_SBA ;
prolog [ 3 ] = TO_SA ;
prolog [ 4 ] = TAT_COLOR ;
prolog [ 5 ] = TAC_TURQ ;
raw3270_buffer_address ( cp - > view . dev , prolog + 1 ,
cp - > view . cols * cp - > line_nr ) ;
raw3270_request_add_data ( wrq , prolog , 6 ) ;
/* Write strings in the update list to the screen. */
list_for_each_entry_safe ( s , n , & cp - > update , update ) {
if ( s ! = cp - > cline )
con3270_update_string ( cp , s , cp - > line_nr ) ;
if ( raw3270_request_add_data ( wrq , s - > string ,
s - > len ) ! = 0 )
break ;
list_del_init ( & s - > update ) ;
if ( s ! = cp - > cline )
cp - > line_nr + + ;
}
if ( list_empty ( & cp - > update ) )
updated | = CON_UPDATE_LIST ;
}
wrq - > callback = con3270_write_callback ;
rc = raw3270_start ( & cp - > view , wrq ) ;
if ( rc = = 0 ) {
cp - > update_flags & = ~ updated ;
if ( cp - > update_flags )
con3270_set_timer ( cp , 1 ) ;
} else {
raw3270_request_reset ( wrq ) ;
xchg ( & cp - > write , wrq ) ;
}
spin_unlock_irqrestore ( & cp - > view . lock , flags ) ;
}
/*
* Read tasklet .
*/
static void
con3270_read_tasklet ( struct raw3270_request * rrq )
{
static char kreset_data = TW_KR ;
struct con3270 * cp ;
unsigned long flags ;
int nr_up , deactivate ;
cp = ( struct con3270 * ) rrq - > view ;
spin_lock_irqsave ( & cp - > view . lock , flags ) ;
nr_up = cp - > nr_up ;
deactivate = 0 ;
/* Check aid byte. */
switch ( cp - > input - > string [ 0 ] ) {
case 0x7d : /* enter: jump to bottom. */
nr_up = 0 ;
break ;
case 0xf3 : /* PF3: deactivate the console view. */
deactivate = 1 ;
break ;
case 0x6d : /* clear: start from scratch. */
con3270_rebuild_update ( cp ) ;
cp - > update_flags = CON_UPDATE_ALL ;
con3270_set_timer ( cp , 1 ) ;
break ;
case 0xf7 : /* PF7: do a page up in the console log. */
nr_up + = cp - > view . rows - 2 ;
if ( nr_up + cp - > view . rows - 1 > cp - > nr_lines ) {
nr_up = cp - > nr_lines - cp - > view . rows + 1 ;
if ( nr_up < 0 )
nr_up = 0 ;
}
break ;
case 0xf8 : /* PF8: do a page down in the console log. */
nr_up - = cp - > view . rows - 2 ;
if ( nr_up < 0 )
nr_up = 0 ;
break ;
}
if ( nr_up ! = cp - > nr_up ) {
cp - > nr_up = nr_up ;
con3270_rebuild_update ( cp ) ;
con3270_update_status ( cp ) ;
con3270_set_timer ( cp , 1 ) ;
}
spin_unlock_irqrestore ( & cp - > view . lock , flags ) ;
/* Start keyboard reset command. */
raw3270_request_reset ( cp - > kreset ) ;
raw3270_request_set_cmd ( cp - > kreset , TC_WRITE ) ;
raw3270_request_add_data ( cp - > kreset , & kreset_data , 1 ) ;
raw3270_start ( & cp - > view , cp - > kreset ) ;
if ( deactivate )
raw3270_deactivate_view ( & cp - > view ) ;
raw3270_request_reset ( rrq ) ;
xchg ( & cp - > read , rrq ) ;
raw3270_put_view ( & cp - > view ) ;
}
/*
* Read request completion callback .
*/
static void
con3270_read_callback ( struct raw3270_request * rq , void * data )
{
raw3270_get_view ( rq - > view ) ;
/* Schedule tasklet to pass input to tty. */
tasklet_schedule ( & ( ( struct con3270 * ) rq - > view ) - > readlet ) ;
}
/*
* Issue a read request . Called only from interrupt function .
*/
static void
con3270_issue_read ( struct con3270 * cp )
{
struct raw3270_request * rrq ;
int rc ;
rrq = xchg ( & cp - > read , 0 ) ;
if ( ! rrq )
/* Read already scheduled. */
return ;
rrq - > callback = con3270_read_callback ;
rrq - > callback_data = cp ;
raw3270_request_set_cmd ( rrq , TC_READMOD ) ;
raw3270_request_set_data ( rrq , cp - > input - > string , cp - > input - > len ) ;
/* Issue the read modified request. */
rc = raw3270_start_irq ( & cp - > view , rrq ) ;
if ( rc )
raw3270_request_reset ( rrq ) ;
}
/*
* Switch to the console view .
*/
static int
con3270_activate ( struct raw3270_view * view )
{
unsigned long flags ;
struct con3270 * cp ;
cp = ( struct con3270 * ) view ;
spin_lock_irqsave ( & cp - > view . lock , flags ) ;
cp - > nr_up = 0 ;
con3270_rebuild_update ( cp ) ;
con3270_update_status ( cp ) ;
cp - > update_flags = CON_UPDATE_ALL ;
con3270_set_timer ( cp , 1 ) ;
spin_unlock_irqrestore ( & cp - > view . lock , flags ) ;
return 0 ;
}
static void
con3270_deactivate ( struct raw3270_view * view )
{
unsigned long flags ;
struct con3270 * cp ;
cp = ( struct con3270 * ) view ;
spin_lock_irqsave ( & cp - > view . lock , flags ) ;
del_timer ( & cp - > timer ) ;
spin_unlock_irqrestore ( & cp - > view . lock , flags ) ;
}
static int
con3270_irq ( struct con3270 * cp , struct raw3270_request * rq , struct irb * irb )
{
/* Handle ATTN. Schedule tasklet to read aid. */
if ( irb - > scsw . dstat & DEV_STAT_ATTENTION )
con3270_issue_read ( cp ) ;
if ( rq ) {
if ( irb - > scsw . dstat & DEV_STAT_UNIT_CHECK )
rq - > rc = - EIO ;
else
/* Normal end. Copy residual count. */
rq - > rescnt = irb - > scsw . count ;
}
return RAW3270_IO_DONE ;
}
/* Console view to a 3270 device. */
static struct raw3270_fn con3270_fn = {
. activate = con3270_activate ,
. deactivate = con3270_deactivate ,
. intv = ( void * ) con3270_irq
} ;
static inline void
con3270_cline_add ( struct con3270 * cp )
{
if ( ! list_empty ( & cp - > cline - > list ) )
/* Already added. */
return ;
list_add_tail ( & cp - > cline - > list , & cp - > lines ) ;
cp - > nr_lines + + ;
con3270_rebuild_update ( cp ) ;
}
static inline void
con3270_cline_insert ( struct con3270 * cp , unsigned char c )
{
cp - > cline - > string [ cp - > cline - > len + + ] =
cp - > view . ascebc [ ( c < ' ' ) ? ' ' : c ] ;
if ( list_empty ( & cp - > cline - > update ) ) {
list_add_tail ( & cp - > cline - > update , & cp - > update ) ;
cp - > update_flags | = CON_UPDATE_LIST ;
}
}
static inline void
con3270_cline_end ( struct con3270 * cp )
{
struct string * s ;
unsigned int size ;
/* Copy cline. */
size = ( cp - > cline - > len < cp - > view . cols - 5 ) ?
cp - > cline - > len + 4 : cp - > view . cols ;
s = con3270_alloc_string ( cp , size ) ;
memcpy ( s - > string , cp - > cline - > string , cp - > cline - > len ) ;
if ( s - > len < cp - > view . cols - 5 ) {
s - > string [ s - > len - 4 ] = TO_RA ;
s - > string [ s - > len - 1 ] = 0 ;
} else {
while ( - - size > cp - > cline - > len )
s - > string [ size ] = cp - > view . ascebc [ ' ' ] ;
}
/* Replace cline with allocated line s and reset cline. */
list_add ( & s - > list , & cp - > cline - > list ) ;
list_del_init ( & cp - > cline - > list ) ;
if ( ! list_empty ( & cp - > cline - > update ) ) {
list_add ( & s - > update , & cp - > cline - > update ) ;
list_del_init ( & cp - > cline - > update ) ;
}
cp - > cline - > len = 0 ;
}
/*
* Write a string to the 3270 console
*/
static void
con3270_write ( struct console * co , const char * str , unsigned int count )
{
struct con3270 * cp ;
unsigned long flags ;
unsigned char c ;
cp = condev ;
spin_lock_irqsave ( & cp - > view . lock , flags ) ;
while ( count - - > 0 ) {
c = * str + + ;
if ( cp - > cline - > len = = 0 )
con3270_cline_add ( cp ) ;
if ( c ! = ' \n ' )
con3270_cline_insert ( cp , c ) ;
if ( c = = ' \n ' | | cp - > cline - > len > = cp - > view . cols )
con3270_cline_end ( cp ) ;
}
/* Setup timer to output current console buffer after 1/10 second */
if ( cp - > view . dev & & ! timer_pending ( & cp - > timer ) )
con3270_set_timer ( cp , HZ / 10 ) ;
spin_unlock_irqrestore ( & cp - > view . lock , flags ) ;
}
static struct tty_driver *
con3270_device ( struct console * c , int * index )
{
* index = c - > index ;
return tty3270_driver ;
}
/*
* Wait for end of write request .
*/
static void
con3270_wait_write ( struct con3270 * cp )
{
while ( ! cp - > write ) {
raw3270_wait_cons_dev ( cp - > view . dev ) ;
barrier ( ) ;
}
}
/*
* panic ( ) calls console_unblank before the system enters a
* disabled , endless loop .
*/
static void
con3270_unblank ( void )
{
struct con3270 * cp ;
unsigned long flags ;
cp = condev ;
if ( ! cp - > view . dev )
return ;
spin_lock_irqsave ( & cp - > view . lock , flags ) ;
con3270_wait_write ( cp ) ;
cp - > nr_up = 0 ;
con3270_rebuild_update ( cp ) ;
con3270_update_status ( cp ) ;
while ( cp - > update_flags ! = 0 ) {
spin_unlock_irqrestore ( & cp - > view . lock , flags ) ;
con3270_update ( cp ) ;
spin_lock_irqsave ( & cp - > view . lock , flags ) ;
con3270_wait_write ( cp ) ;
}
spin_unlock_irqrestore ( & cp - > view . lock , flags ) ;
}
/*
* The console structure for the 3270 console
*/
static struct console con3270 = {
. name = " tty3270 " ,
. write = con3270_write ,
. device = con3270_device ,
. unblank = con3270_unblank ,
. flags = CON_PRINTBUFFER ,
} ;
/*
* 3270 console initialization code called from console_init ( ) .
* NOTE : This is called before kmalloc is available .
*/
static int __init
con3270_init ( void )
{
struct ccw_device * cdev ;
struct raw3270 * rp ;
void * cbuf ;
int i ;
/* Check if 3270 is to be the console */
if ( ! CONSOLE_IS_3270 )
return - ENODEV ;
/* Set the console mode for VM */
if ( MACHINE_IS_VM ) {
2005-06-25 14:55:32 -07:00
cpcmd ( " TERM CONMODE 3270 " , NULL , 0 , NULL ) ;
cpcmd ( " TERM AUTOCR OFF " , NULL , 0 , NULL ) ;
2005-04-16 15:20:36 -07:00
}
cdev = ccw_device_probe_console ( ) ;
2006-02-01 03:06:35 -08:00
if ( IS_ERR ( cdev ) )
2005-04-16 15:20:36 -07:00
return - ENODEV ;
rp = raw3270_setup_console ( cdev ) ;
if ( IS_ERR ( rp ) )
return PTR_ERR ( rp ) ;
condev = ( struct con3270 * ) alloc_bootmem_low ( sizeof ( struct con3270 ) ) ;
memset ( condev , 0 , sizeof ( struct con3270 ) ) ;
condev - > view . dev = rp ;
condev - > read = raw3270_request_alloc_bootmem ( 0 ) ;
condev - > read - > callback = con3270_read_callback ;
condev - > read - > callback_data = condev ;
condev - > write =
raw3270_request_alloc_bootmem ( CON3270_OUTPUT_BUFFER_SIZE ) ;
condev - > kreset = raw3270_request_alloc_bootmem ( 1 ) ;
INIT_LIST_HEAD ( & condev - > lines ) ;
INIT_LIST_HEAD ( & condev - > update ) ;
init_timer ( & condev - > timer ) ;
tasklet_init ( & condev - > readlet ,
( void ( * ) ( unsigned long ) ) con3270_read_tasklet ,
( unsigned long ) condev - > read ) ;
2005-10-30 15:00:10 -08:00
raw3270_add_view ( & condev - > view , & con3270_fn , 1 ) ;
2005-04-16 15:20:36 -07:00
INIT_LIST_HEAD ( & condev - > freemem ) ;
for ( i = 0 ; i < CON3270_STRING_PAGES ; i + + ) {
cbuf = ( void * ) alloc_bootmem_low_pages ( PAGE_SIZE ) ;
add_string_memory ( & condev - > freemem , cbuf , PAGE_SIZE ) ;
}
condev - > cline = alloc_string ( & condev - > freemem , condev - > view . cols ) ;
condev - > cline - > len = 0 ;
con3270_create_status ( condev ) ;
condev - > input = alloc_string ( & condev - > freemem , 80 ) ;
register_console ( & con3270 ) ;
return 0 ;
}
console_initcall ( con3270_init ) ;