2005-04-17 02:20:36 +04:00
/*
* drivers / s390 / cio / chsc . c
* S / 390 common I / O routines - - channel subsystem call
*
2008-07-14 11:58:45 +04:00
* Copyright IBM Corp . 1999 , 2008
2005-04-17 02:20:36 +04:00
* Author ( s ) : Ingo Adlung ( adlung @ de . ibm . com )
2006-01-15 00:21:04 +03:00
* Cornelia Huck ( cornelia . huck @ de . ibm . com )
2005-04-17 02:20:36 +04:00
* Arnd Bergmann ( arndb @ de . ibm . com )
*/
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/init.h>
# include <linux/device.h>
# include <asm/cio.h>
2007-04-27 18:01:31 +04:00
# include <asm/chpid.h>
2008-07-14 11:59:05 +04:00
# include <asm/chsc.h>
2005-04-17 02:20:36 +04:00
2008-07-14 11:58:46 +04:00
# include "../s390mach.h"
2005-04-17 02:20:36 +04:00
# include "css.h"
# include "cio.h"
# include "cio_debug.h"
# include "ioasm.h"
2007-04-27 18:01:28 +04:00
# include "chp.h"
2005-04-17 02:20:36 +04:00
# include "chsc.h"
static void * sei_page ;
2008-07-17 19:16:47 +04:00
/**
* chsc_error_from_response ( ) - convert a chsc response to an error
* @ response : chsc response code
*
* Returns an appropriate Linux error code for @ response .
*/
int chsc_error_from_response ( int response )
2008-02-05 18:50:34 +03:00
{
switch ( response ) {
case 0x0001 :
return 0 ;
case 0x0002 :
case 0x0003 :
case 0x0006 :
case 0x0007 :
case 0x0008 :
case 0x000a :
return - EINVAL ;
case 0x0004 :
return - EOPNOTSUPP ;
default :
return - EIO ;
}
}
2008-07-17 19:16:47 +04:00
EXPORT_SYMBOL_GPL ( chsc_error_from_response ) ;
2008-02-05 18:50:34 +03:00
2007-04-27 18:01:35 +04:00
struct chsc_ssd_area {
struct chsc_header request ;
u16 : 10 ;
u16 ssid : 2 ;
u16 : 4 ;
u16 f_sch ; /* first subchannel */
u16 : 16 ;
u16 l_sch ; /* last subchannel */
u32 : 32 ;
struct chsc_header response ;
u32 : 32 ;
u8 sch_valid : 1 ;
u8 dev_valid : 1 ;
u8 st : 3 ; /* subchannel type */
u8 zeroes : 3 ;
u8 unit_addr ; /* unit address */
u16 devno ; /* device number */
u8 path_mask ;
u8 fla_valid_mask ;
u16 sch ; /* subchannel */
u8 chpid [ 8 ] ; /* chpids 0-7 */
u16 fla [ 8 ] ; /* full link addresses 0-7 */
} __attribute__ ( ( packed ) ) ;
int chsc_get_ssd_info ( struct subchannel_id schid , struct chsc_ssd_info * ssd )
2005-04-17 02:20:36 +04:00
{
2007-04-27 18:01:35 +04:00
unsigned long page ;
struct chsc_ssd_area * ssd_area ;
int ccode ;
int ret ;
int i ;
int mask ;
2005-04-17 02:20:36 +04:00
2007-04-27 18:01:35 +04:00
page = get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
if ( ! page )
return - ENOMEM ;
ssd_area = ( struct chsc_ssd_area * ) page ;
2006-03-24 14:15:14 +03:00
ssd_area - > request . length = 0x0010 ;
ssd_area - > request . code = 0x0004 ;
2007-04-27 18:01:35 +04:00
ssd_area - > ssid = schid . ssid ;
ssd_area - > f_sch = schid . sch_no ;
ssd_area - > l_sch = schid . sch_no ;
2005-04-17 02:20:36 +04:00
ccode = chsc ( ssd_area ) ;
2007-04-27 18:01:35 +04:00
/* Check response. */
2005-04-17 02:20:36 +04:00
if ( ccode > 0 ) {
2007-04-27 18:01:35 +04:00
ret = ( ccode = = 3 ) ? - ENODEV : - EBUSY ;
goto out_free ;
2005-04-17 02:20:36 +04:00
}
2008-02-05 18:50:34 +03:00
ret = chsc_error_from_response ( ssd_area - > response . code ) ;
if ( ret ! = 0 ) {
2007-04-27 18:01:35 +04:00
CIO_MSG_EVENT ( 2 , " chsc: ssd failed for 0.%x.%04x (rc=%04x) \n " ,
schid . ssid , schid . sch_no ,
2005-04-17 02:20:36 +04:00
ssd_area - > response . code ) ;
2007-04-27 18:01:35 +04:00
goto out_free ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 18:01:35 +04:00
if ( ! ssd_area - > sch_valid ) {
ret = - ENODEV ;
goto out_free ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 18:01:35 +04:00
/* Copy data */
ret = 0 ;
memset ( ssd , 0 , sizeof ( struct chsc_ssd_info ) ) ;
2008-01-26 16:10:45 +03:00
if ( ( ssd_area - > st ! = SUBCHANNEL_TYPE_IO ) & &
( ssd_area - > st ! = SUBCHANNEL_TYPE_MSG ) )
2007-04-27 18:01:35 +04:00
goto out_free ;
ssd - > path_mask = ssd_area - > path_mask ;
ssd - > fla_valid_mask = ssd_area - > fla_valid_mask ;
for ( i = 0 ; i < 8 ; i + + ) {
mask = 0x80 > > i ;
if ( ssd_area - > path_mask & mask ) {
chp_id_init ( & ssd - > chpid [ i ] ) ;
ssd - > chpid [ i ] . id = ssd_area - > chpid [ i ] ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 18:01:35 +04:00
if ( ssd_area - > fla_valid_mask & mask )
ssd - > fla [ i ] = ssd_area - > fla [ i ] ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 18:01:35 +04:00
out_free :
free_page ( page ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
2008-01-26 16:10:48 +03:00
static int s390_subchannel_remove_chpid ( struct subchannel * sch , void * data )
2005-04-17 02:20:36 +04:00
{
2006-12-08 17:54:26 +03:00
spin_lock_irq ( sch - > lock ) ;
2008-07-14 11:58:45 +04:00
if ( sch - > driver & & sch - > driver - > chp_event )
if ( sch - > driver - > chp_event ( sch , data , CHP_OFFLINE ) ! = 0 )
2005-04-17 02:20:36 +04:00
goto out_unreg ;
2006-12-08 17:54:26 +03:00
spin_unlock_irq ( sch - > lock ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
2007-04-27 18:01:33 +04:00
2005-04-17 02:20:36 +04:00
out_unreg :
sch - > lpm = 0 ;
2007-04-27 18:01:33 +04:00
spin_unlock_irq ( sch - > lock ) ;
2007-04-27 18:01:34 +04:00
css_schedule_eval ( sch - > schid ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2007-04-27 18:01:28 +04:00
void chsc_chp_offline ( struct chp_id chpid )
2005-04-17 02:20:36 +04:00
{
char dbf_txt [ 15 ] ;
2008-07-14 11:59:02 +04:00
struct chp_link link ;
2005-04-17 02:20:36 +04:00
2007-04-27 18:01:26 +04:00
sprintf ( dbf_txt , " chpr%x.%02x " , chpid . cssid , chpid . id ) ;
2005-04-17 02:20:36 +04:00
CIO_TRACE_EVENT ( 2 , dbf_txt ) ;
2007-04-27 18:01:28 +04:00
if ( chp_get_status ( chpid ) < = 0 )
2005-04-17 02:20:36 +04:00
return ;
2008-07-14 11:59:02 +04:00
memset ( & link , 0 , sizeof ( struct chp_link ) ) ;
link . chpid = chpid ;
2008-04-17 09:45:59 +04:00
/* Wait until previous actions have settled. */
css_wait_for_slow_path ( ) ;
2008-07-14 11:59:02 +04:00
for_each_subchannel_staged ( s390_subchannel_remove_chpid , NULL , & link ) ;
2005-04-17 02:20:36 +04:00
}
2008-01-26 16:10:48 +03:00
static int s390_process_res_acc_new_sch ( struct subchannel_id schid , void * data )
2006-01-06 11:19:22 +03:00
{
struct schib schib ;
/*
* We don ' t know the device yet , but since a path
* may be available now to the device we ' ll have
* to do recognition again .
* Since we don ' t have any idea about which chpid
* that beast may be on we ' ll have to do a stsch
* on all devices , grr . . .
*/
2006-01-06 11:19:25 +03:00
if ( stsch_err ( schid , & schib ) )
2006-01-06 11:19:22 +03:00
/* We're through */
2007-04-27 18:01:34 +04:00
return - ENXIO ;
2006-01-06 11:19:22 +03:00
/* Put it on the slow path. */
2007-04-27 18:01:34 +04:00
css_schedule_eval ( schid ) ;
2006-01-06 11:19:22 +03:00
return 0 ;
}
2008-01-26 16:10:48 +03:00
static int __s390_process_res_acc ( struct subchannel * sch , void * data )
2005-04-17 02:20:36 +04:00
{
2006-12-08 17:54:26 +03:00
spin_lock_irq ( sch - > lock ) ;
2008-07-14 11:58:45 +04:00
if ( sch - > driver & & sch - > driver - > chp_event )
sch - > driver - > chp_event ( sch , data , CHP_ONLINE ) ;
2006-12-08 17:54:26 +03:00
spin_unlock_irq ( sch - > lock ) ;
2008-01-26 16:10:48 +03:00
2006-09-20 17:59:54 +04:00
return 0 ;
2006-01-06 11:19:22 +03:00
}
2008-07-14 11:59:02 +04:00
static void s390_process_res_acc ( struct chp_link * link )
2006-01-06 11:19:22 +03:00
{
2005-04-17 02:20:36 +04:00
char dbf_txt [ 15 ] ;
2008-07-14 11:59:02 +04:00
sprintf ( dbf_txt , " accpr%x.%02x " , link - > chpid . cssid ,
link - > chpid . id ) ;
2005-04-17 02:20:36 +04:00
CIO_TRACE_EVENT ( 2 , dbf_txt ) ;
2008-07-14 11:59:02 +04:00
if ( link - > fla ! = 0 ) {
sprintf ( dbf_txt , " fla%x " , link - > fla ) ;
2005-04-17 02:20:36 +04:00
CIO_TRACE_EVENT ( 2 , dbf_txt ) ;
}
2008-04-17 09:45:59 +04:00
/* Wait until previous actions have settled. */
css_wait_for_slow_path ( ) ;
2005-04-17 02:20:36 +04:00
/*
* I / O resources may have become accessible .
* Scan through all subchannels that may be concerned and
* do a validation on those .
* The more information we have ( info ) , the less scanning
* will we have to do .
*/
2008-01-26 16:10:48 +03:00
for_each_subchannel_staged ( __s390_process_res_acc ,
2008-07-14 11:59:02 +04:00
s390_process_res_acc_new_sch , link ) ;
2005-04-17 02:20:36 +04:00
}
static int
__get_chpid_from_lir ( void * data )
{
struct lir {
u8 iq ;
u8 ic ;
u16 sci ;
/* incident-node descriptor */
u32 indesc [ 28 ] ;
/* attached-node descriptor */
u32 andesc [ 28 ] ;
/* incident-specific information */
u32 isinfo [ 28 ] ;
2007-02-05 23:17:40 +03:00
} __attribute__ ( ( packed ) ) * lir ;
2005-04-17 02:20:36 +04:00
2006-10-11 17:31:47 +04:00
lir = data ;
2005-04-17 02:20:36 +04:00
if ( ! ( lir - > iq & 0x80 ) )
/* NULL link incident record */
return - EINVAL ;
if ( ! ( lir - > indesc [ 0 ] & 0xc0000000 ) )
/* node descriptor not valid */
return - EINVAL ;
if ( ! ( lir - > indesc [ 0 ] & 0x10000000 ) )
/* don't handle device-type nodes - FIXME */
return - EINVAL ;
/* Byte 3 contains the chpid. Could also be CTCA, but we don't care */
return ( u16 ) ( lir - > indesc [ 0 ] & 0x000000ff ) ;
}
2007-02-05 23:17:42 +03:00
struct chsc_sei_area {
struct chsc_header request ;
u32 reserved1 ;
u32 reserved2 ;
u32 reserved3 ;
struct chsc_header response ;
u32 reserved4 ;
u8 flags ;
u8 vf ; /* validity flags */
u8 rs ; /* reporting source */
u8 cc ; /* content code */
u16 fla ; /* full link address */
u16 rsid ; /* reporting source id */
u32 reserved5 ;
u32 reserved6 ;
u8 ccdf [ 4096 - 16 - 24 ] ; /* content-code dependent field */
/* ccdf has to be big enough for a link-incident record */
} __attribute__ ( ( packed ) ) ;
2007-04-27 18:01:34 +04:00
static void chsc_process_sei_link_incident ( struct chsc_sei_area * sei_area )
2007-02-05 23:17:42 +03:00
{
2007-04-27 18:01:26 +04:00
struct chp_id chpid ;
int id ;
2007-02-05 23:17:42 +03:00
CIO_CRW_EVENT ( 4 , " chsc: link incident (rs=%02x, rs_id=%04x) \n " ,
sei_area - > rs , sei_area - > rsid ) ;
if ( sei_area - > rs ! = 4 )
2007-04-27 18:01:34 +04:00
return ;
2007-04-27 18:01:26 +04:00
id = __get_chpid_from_lir ( sei_area - > ccdf ) ;
if ( id < 0 )
2007-02-05 23:17:42 +03:00
CIO_CRW_EVENT ( 4 , " chsc: link incident - invalid LIR \n " ) ;
2007-04-27 18:01:26 +04:00
else {
chp_id_init ( & chpid ) ;
chpid . id = id ;
2007-04-27 18:01:28 +04:00
chsc_chp_offline ( chpid ) ;
2007-04-27 18:01:26 +04:00
}
2007-02-05 23:17:42 +03:00
}
2007-04-27 18:01:34 +04:00
static void chsc_process_sei_res_acc ( struct chsc_sei_area * sei_area )
2005-04-17 02:20:36 +04:00
{
2008-07-14 11:59:02 +04:00
struct chp_link link ;
2007-04-27 18:01:26 +04:00
struct chp_id chpid ;
2007-02-05 23:17:42 +03:00
int status ;
CIO_CRW_EVENT ( 4 , " chsc: resource accessibility event (rs=%02x, "
" rs_id=%04x) \n " , sei_area - > rs , sei_area - > rsid ) ;
if ( sei_area - > rs ! = 4 )
2007-04-27 18:01:34 +04:00
return ;
2007-04-27 18:01:26 +04:00
chp_id_init ( & chpid ) ;
chpid . id = sei_area - > rsid ;
2007-02-05 23:17:42 +03:00
/* allocate a new channel path structure, if needed */
2007-04-27 18:01:28 +04:00
status = chp_get_status ( chpid ) ;
2007-02-05 23:17:42 +03:00
if ( status < 0 )
2007-04-27 18:01:28 +04:00
chp_new ( chpid ) ;
2007-02-05 23:17:42 +03:00
else if ( ! status )
2007-04-27 18:01:34 +04:00
return ;
2008-07-14 11:59:02 +04:00
memset ( & link , 0 , sizeof ( struct chp_link ) ) ;
link . chpid = chpid ;
2007-02-05 23:17:42 +03:00
if ( ( sei_area - > vf & 0xc0 ) ! = 0 ) {
2008-07-14 11:59:02 +04:00
link . fla = sei_area - > fla ;
2007-02-05 23:17:42 +03:00
if ( ( sei_area - > vf & 0xc0 ) = = 0xc0 )
/* full link address */
2008-07-14 11:59:02 +04:00
link . fla_mask = 0xffff ;
2007-02-05 23:17:42 +03:00
else
/* link address */
2008-07-14 11:59:02 +04:00
link . fla_mask = 0xff00 ;
2007-02-05 23:17:42 +03:00
}
2008-07-14 11:59:02 +04:00
s390_process_res_acc ( & link ) ;
2007-02-05 23:17:42 +03:00
}
2007-04-27 18:01:31 +04:00
struct chp_config_data {
u8 map [ 32 ] ;
u8 op ;
u8 pc ;
} ;
2007-04-27 18:01:34 +04:00
static void chsc_process_sei_chp_config ( struct chsc_sei_area * sei_area )
2007-04-27 18:01:31 +04:00
{
struct chp_config_data * data ;
struct chp_id chpid ;
int num ;
CIO_CRW_EVENT ( 4 , " chsc: channel-path-configuration notification \n " ) ;
if ( sei_area - > rs ! = 0 )
2007-04-27 18:01:34 +04:00
return ;
2007-04-27 18:01:31 +04:00
data = ( struct chp_config_data * ) & ( sei_area - > ccdf ) ;
chp_id_init ( & chpid ) ;
for ( num = 0 ; num < = __MAX_CHPID ; num + + ) {
if ( ! chp_test_bit ( data - > map , num ) )
continue ;
chpid . id = num ;
printk ( KERN_WARNING " cio: processing configure event %d for "
" chpid %x.%02x \n " , data - > op , chpid . cssid , chpid . id ) ;
switch ( data - > op ) {
case 0 :
chp_cfg_schedule ( chpid , 1 ) ;
break ;
case 1 :
chp_cfg_schedule ( chpid , 0 ) ;
break ;
case 2 :
chp_cfg_cancel_deconfigure ( chpid ) ;
break ;
}
}
}
2007-04-27 18:01:34 +04:00
static void chsc_process_sei ( struct chsc_sei_area * sei_area )
2007-02-05 23:17:42 +03:00
{
/* Check if we might have lost some information. */
2007-04-27 18:01:34 +04:00
if ( sei_area - > flags & 0x40 ) {
2007-02-05 23:17:42 +03:00
CIO_CRW_EVENT ( 2 , " chsc: event overflow \n " ) ;
2007-04-27 18:01:34 +04:00
css_schedule_eval_all ( ) ;
}
2007-02-05 23:17:42 +03:00
/* which kind of information was stored? */
switch ( sei_area - > cc ) {
case 1 : /* link incident*/
2007-04-27 18:01:34 +04:00
chsc_process_sei_link_incident ( sei_area ) ;
2007-02-05 23:17:42 +03:00
break ;
case 2 : /* i/o resource accessibiliy */
2007-04-27 18:01:34 +04:00
chsc_process_sei_res_acc ( sei_area ) ;
2007-02-05 23:17:42 +03:00
break ;
2007-04-27 18:01:31 +04:00
case 8 : /* channel-path-configuration notification */
2007-04-27 18:01:34 +04:00
chsc_process_sei_chp_config ( sei_area ) ;
2007-04-27 18:01:31 +04:00
break ;
2007-02-05 23:17:42 +03:00
default : /* other stuff */
CIO_CRW_EVENT ( 4 , " chsc: unhandled sei content code %d \n " ,
sei_area - > cc ) ;
break ;
}
}
2008-07-14 11:58:46 +04:00
static void chsc_process_crw ( struct crw * crw0 , struct crw * crw1 , int overflow )
2007-02-05 23:17:42 +03:00
{
struct chsc_sei_area * sei_area ;
2005-04-17 02:20:36 +04:00
2008-07-14 11:58:46 +04:00
if ( overflow ) {
css_schedule_eval_all ( ) ;
return ;
}
CIO_CRW_EVENT ( 2 , " CRW reports slct=%d, oflw=%d, "
" chn=%d, rsc=%X, anc=%d, erc=%X, rsid=%X \n " ,
crw0 - > slct , crw0 - > oflw , crw0 - > chn , crw0 - > rsc , crw0 - > anc ,
crw0 - > erc , crw0 - > rsid ) ;
2005-04-17 02:20:36 +04:00
if ( ! sei_page )
2007-04-27 18:01:34 +04:00
return ;
2007-02-05 23:17:42 +03:00
/* Access to sei_page is serialized through machine check handler
* thread , so no need for locking . */
2005-04-17 02:20:36 +04:00
sei_area = sei_page ;
2008-07-14 11:58:46 +04:00
CIO_TRACE_EVENT ( 2 , " prcss " ) ;
2005-04-17 02:20:36 +04:00
do {
memset ( sei_area , 0 , sizeof ( * sei_area ) ) ;
2006-03-24 14:15:14 +03:00
sei_area - > request . length = 0x0010 ;
sei_area - > request . code = 0x000e ;
2007-02-05 23:17:42 +03:00
if ( chsc ( sei_area ) )
break ;
2005-04-17 02:20:36 +04:00
2007-02-05 23:17:42 +03:00
if ( sei_area - > response . code = = 0x0001 ) {
CIO_CRW_EVENT ( 4 , " chsc: sei successful \n " ) ;
2007-04-27 18:01:34 +04:00
chsc_process_sei ( sei_area ) ;
2007-02-05 23:17:42 +03:00
} else {
CIO_CRW_EVENT ( 2 , " chsc: sei failed (rc=%04x) \n " ,
2005-04-17 02:20:36 +04:00
sei_area - > response . code ) ;
break ;
}
} while ( sei_area - > flags & 0x80 ) ;
}
2007-04-27 18:01:34 +04:00
void chsc_chp_online ( struct chp_id chpid )
2006-01-06 11:19:22 +03:00
{
2005-04-17 02:20:36 +04:00
char dbf_txt [ 15 ] ;
2008-07-14 11:59:02 +04:00
struct chp_link link ;
2005-04-17 02:20:36 +04:00
2007-04-27 18:01:26 +04:00
sprintf ( dbf_txt , " cadd%x.%02x " , chpid . cssid , chpid . id ) ;
2005-04-17 02:20:36 +04:00
CIO_TRACE_EVENT ( 2 , dbf_txt ) ;
2008-04-17 09:45:59 +04:00
if ( chp_get_status ( chpid ) ! = 0 ) {
2008-07-14 11:59:02 +04:00
memset ( & link , 0 , sizeof ( struct chp_link ) ) ;
link . chpid = chpid ;
2008-04-17 09:45:59 +04:00
/* Wait until previous actions have settled. */
css_wait_for_slow_path ( ) ;
2008-07-14 11:58:45 +04:00
for_each_subchannel_staged ( __s390_process_res_acc , NULL ,
2008-07-14 11:59:02 +04:00
& link ) ;
2008-04-17 09:45:59 +04:00
}
2005-04-17 02:20:36 +04:00
}
2007-04-27 18:01:26 +04:00
static void __s390_subchannel_vary_chpid ( struct subchannel * sch ,
struct chp_id chpid , int on )
2005-04-17 02:20:36 +04:00
{
unsigned long flags ;
2008-07-14 11:59:02 +04:00
struct chp_link link ;
2005-04-17 02:20:36 +04:00
2008-07-14 11:59:02 +04:00
memset ( & link , 0 , sizeof ( struct chp_link ) ) ;
link . chpid = chpid ;
2006-12-08 17:54:26 +03:00
spin_lock_irqsave ( sch - > lock , flags ) ;
2008-07-14 11:58:45 +04:00
if ( sch - > driver & & sch - > driver - > chp_event )
2008-07-14 11:59:02 +04:00
sch - > driver - > chp_event ( sch , & link ,
2008-07-14 11:58:45 +04:00
on ? CHP_VARY_ON : CHP_VARY_OFF ) ;
2006-12-08 17:54:26 +03:00
spin_unlock_irqrestore ( sch - > lock , flags ) ;
2005-04-17 02:20:36 +04:00
}
2008-01-26 16:10:48 +03:00
static int s390_subchannel_vary_chpid_off ( struct subchannel * sch , void * data )
2005-04-17 02:20:36 +04:00
{
2008-01-26 16:10:48 +03:00
struct chp_id * chpid = data ;
2005-04-17 02:20:36 +04:00
__s390_subchannel_vary_chpid ( sch , * chpid , 0 ) ;
return 0 ;
}
2008-01-26 16:10:48 +03:00
static int s390_subchannel_vary_chpid_on ( struct subchannel * sch , void * data )
2005-04-17 02:20:36 +04:00
{
2008-01-26 16:10:48 +03:00
struct chp_id * chpid = data ;
2005-04-17 02:20:36 +04:00
__s390_subchannel_vary_chpid ( sch , * chpid , 1 ) ;
return 0 ;
}
2006-01-06 11:19:22 +03:00
static int
__s390_vary_chpid_on ( struct subchannel_id schid , void * data )
{
struct schib schib ;
2006-01-06 11:19:25 +03:00
if ( stsch_err ( schid , & schib ) )
2006-01-06 11:19:22 +03:00
/* We're through */
return - ENXIO ;
/* Put it on the slow path. */
2007-04-27 18:01:34 +04:00
css_schedule_eval ( schid ) ;
2006-01-06 11:19:22 +03:00
return 0 ;
}
2007-04-27 18:01:28 +04:00
/**
* chsc_chp_vary - propagate channel - path vary operation to subchannels
* @ chpid : channl - path ID
* @ on : non - zero for vary online , zero for vary offline
2005-04-17 02:20:36 +04:00
*/
2007-04-27 18:01:28 +04:00
int chsc_chp_vary ( struct chp_id chpid , int on )
2005-04-17 02:20:36 +04:00
{
2008-07-14 11:59:02 +04:00
struct chp_link link ;
memset ( & link , 0 , sizeof ( struct chp_link ) ) ;
link . chpid = chpid ;
2008-04-17 09:45:59 +04:00
/* Wait until previous actions have settled. */
css_wait_for_slow_path ( ) ;
2005-04-17 02:20:36 +04:00
/*
* Redo PathVerification on the devices the chpid connects to
*/
2006-01-06 11:19:22 +03:00
if ( on )
2008-01-26 16:10:48 +03:00
for_each_subchannel_staged ( s390_subchannel_vary_chpid_on ,
2008-07-14 11:59:02 +04:00
__s390_vary_chpid_on , & link ) ;
2008-01-26 16:10:48 +03:00
else
for_each_subchannel_staged ( s390_subchannel_vary_chpid_off ,
2008-07-14 11:59:02 +04:00
NULL , & link ) ;
2008-01-26 16:10:48 +03:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
2006-03-24 14:15:14 +03:00
static void
chsc_remove_cmg_attr ( struct channel_subsystem * css )
{
int i ;
for ( i = 0 ; i < = __MAX_CHPID ; i + + ) {
if ( ! css - > chps [ i ] )
continue ;
2007-04-27 18:01:28 +04:00
chp_remove_cmg_attr ( css - > chps [ i ] ) ;
2006-03-24 14:15:14 +03:00
}
}
static int
chsc_add_cmg_attr ( struct channel_subsystem * css )
{
int i , ret ;
ret = 0 ;
for ( i = 0 ; i < = __MAX_CHPID ; i + + ) {
if ( ! css - > chps [ i ] )
continue ;
2007-04-27 18:01:28 +04:00
ret = chp_add_cmg_attr ( css - > chps [ i ] ) ;
2006-03-24 14:15:14 +03:00
if ( ret )
goto cleanup ;
}
return ret ;
cleanup :
for ( - - i ; i > = 0 ; i - - ) {
if ( ! css - > chps [ i ] )
continue ;
2007-04-27 18:01:28 +04:00
chp_remove_cmg_attr ( css - > chps [ i ] ) ;
2006-03-24 14:15:14 +03:00
}
return ret ;
}
static int
__chsc_do_secm ( struct channel_subsystem * css , int enable , void * page )
{
struct {
struct chsc_header request ;
u32 operation_code : 2 ;
u32 : 30 ;
u32 key : 4 ;
u32 : 28 ;
u32 zeroes1 ;
u32 cub_addr1 ;
u32 zeroes2 ;
u32 cub_addr2 ;
u32 reserved [ 13 ] ;
struct chsc_header response ;
u32 status : 8 ;
u32 : 4 ;
u32 fmt : 4 ;
u32 : 16 ;
2007-02-05 23:17:40 +03:00
} __attribute__ ( ( packed ) ) * secm_area ;
2006-03-24 14:15:14 +03:00
int ret , ccode ;
secm_area = page ;
secm_area - > request . length = 0x0050 ;
secm_area - > request . code = 0x0016 ;
secm_area - > key = PAGE_DEFAULT_KEY ;
secm_area - > cub_addr1 = ( u64 ) ( unsigned long ) css - > cub_addr1 ;
secm_area - > cub_addr2 = ( u64 ) ( unsigned long ) css - > cub_addr2 ;
secm_area - > operation_code = enable ? 0 : 1 ;
ccode = chsc ( secm_area ) ;
if ( ccode > 0 )
return ( ccode = = 3 ) ? - ENODEV : - EBUSY ;
switch ( secm_area - > response . code ) {
2008-02-05 18:50:34 +03:00
case 0x0102 :
case 0x0103 :
2006-03-24 14:15:14 +03:00
ret = - EINVAL ;
default :
2008-02-05 18:50:34 +03:00
ret = chsc_error_from_response ( secm_area - > response . code ) ;
2006-03-24 14:15:14 +03:00
}
2008-02-05 18:50:34 +03:00
if ( ret ! = 0 )
CIO_CRW_EVENT ( 2 , " chsc: secm failed (rc=%04x) \n " ,
secm_area - > response . code ) ;
2006-03-24 14:15:14 +03:00
return ret ;
}
int
chsc_secm ( struct channel_subsystem * css , int enable )
{
void * secm_area ;
int ret ;
secm_area = ( void * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
if ( ! secm_area )
return - ENOMEM ;
if ( enable & & ! css - > cm_enabled ) {
css - > cub_addr1 = ( void * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
css - > cub_addr2 = ( void * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
if ( ! css - > cub_addr1 | | ! css - > cub_addr2 ) {
free_page ( ( unsigned long ) css - > cub_addr1 ) ;
free_page ( ( unsigned long ) css - > cub_addr2 ) ;
free_page ( ( unsigned long ) secm_area ) ;
return - ENOMEM ;
}
}
ret = __chsc_do_secm ( css , enable , secm_area ) ;
if ( ! ret ) {
css - > cm_enabled = enable ;
if ( css - > cm_enabled ) {
ret = chsc_add_cmg_attr ( css ) ;
if ( ret ) {
memset ( secm_area , 0 , PAGE_SIZE ) ;
__chsc_do_secm ( css , 0 , secm_area ) ;
css - > cm_enabled = 0 ;
}
} else
chsc_remove_cmg_attr ( css ) ;
}
2007-04-27 18:01:38 +04:00
if ( ! css - > cm_enabled ) {
2006-03-24 14:15:14 +03:00
free_page ( ( unsigned long ) css - > cub_addr1 ) ;
free_page ( ( unsigned long ) css - > cub_addr2 ) ;
}
free_page ( ( unsigned long ) secm_area ) ;
return ret ;
}
2008-07-14 11:59:05 +04:00
int chsc_determine_channel_path_desc ( struct chp_id chpid , int fmt , int rfmt ,
int c , int m ,
struct chsc_response_struct * resp )
2005-04-17 02:20:36 +04:00
{
int ccode , ret ;
struct {
struct chsc_header request ;
2008-07-14 11:59:05 +04:00
u32 : 2 ;
u32 m : 1 ;
u32 c : 1 ;
u32 fmt : 4 ;
u32 cssid : 8 ;
u32 : 4 ;
u32 rfmt : 4 ;
2005-04-17 02:20:36 +04:00
u32 first_chpid : 8 ;
u32 : 24 ;
u32 last_chpid : 8 ;
u32 zeroes1 ;
struct chsc_header response ;
2008-07-14 11:59:05 +04:00
u8 data [ PAGE_SIZE - 20 ] ;
2007-02-05 23:17:40 +03:00
} __attribute__ ( ( packed ) ) * scpd_area ;
2005-04-17 02:20:36 +04:00
2008-07-14 11:59:05 +04:00
if ( ( rfmt = = 1 ) & & ! css_general_characteristics . fcs )
return - EINVAL ;
if ( ( rfmt = = 2 ) & & ! css_general_characteristics . cib )
return - EINVAL ;
2005-04-17 02:20:36 +04:00
scpd_area = ( void * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
if ( ! scpd_area )
return - ENOMEM ;
2006-03-24 14:15:14 +03:00
scpd_area - > request . length = 0x0010 ;
scpd_area - > request . code = 0x0002 ;
2005-04-17 02:20:36 +04:00
2008-07-14 11:59:05 +04:00
scpd_area - > cssid = chpid . cssid ;
2007-04-27 18:01:26 +04:00
scpd_area - > first_chpid = chpid . id ;
scpd_area - > last_chpid = chpid . id ;
2008-07-14 11:59:05 +04:00
scpd_area - > m = m ;
scpd_area - > c = c ;
scpd_area - > fmt = fmt ;
scpd_area - > rfmt = rfmt ;
2005-04-17 02:20:36 +04:00
ccode = chsc ( scpd_area ) ;
if ( ccode > 0 ) {
ret = ( ccode = = 3 ) ? - ENODEV : - EBUSY ;
goto out ;
}
2008-02-05 18:50:34 +03:00
ret = chsc_error_from_response ( scpd_area - > response . code ) ;
if ( ret = = 0 )
/* Success. */
2008-07-14 11:59:05 +04:00
memcpy ( resp , & scpd_area - > response , scpd_area - > response . length ) ;
2008-02-05 18:50:34 +03:00
else
CIO_CRW_EVENT ( 2 , " chsc: scpd failed (rc=%04x) \n " ,
2005-04-17 02:20:36 +04:00
scpd_area - > response . code ) ;
out :
free_page ( ( unsigned long ) scpd_area ) ;
return ret ;
}
2008-07-14 11:59:05 +04:00
EXPORT_SYMBOL_GPL ( chsc_determine_channel_path_desc ) ;
int chsc_determine_base_channel_path_desc ( struct chp_id chpid ,
struct channel_path_desc * desc )
{
struct chsc_response_struct * chsc_resp ;
int ret ;
chsc_resp = kzalloc ( sizeof ( * chsc_resp ) , GFP_KERNEL ) ;
if ( ! chsc_resp )
return - ENOMEM ;
ret = chsc_determine_channel_path_desc ( chpid , 0 , 0 , 0 , 0 , chsc_resp ) ;
if ( ret )
goto out_free ;
memcpy ( desc , & chsc_resp - > data , chsc_resp - > length ) ;
out_free :
kfree ( chsc_resp ) ;
return ret ;
}
2005-04-17 02:20:36 +04:00
2006-03-24 14:15:14 +03:00
static void
chsc_initialize_cmg_chars ( struct channel_path * chp , u8 cmcv ,
struct cmg_chars * chars )
{
switch ( chp - > cmg ) {
case 2 :
case 3 :
chp - > cmg_chars = kmalloc ( sizeof ( struct cmg_chars ) ,
GFP_KERNEL ) ;
if ( chp - > cmg_chars ) {
int i , mask ;
struct cmg_chars * cmg_chars ;
cmg_chars = chp - > cmg_chars ;
for ( i = 0 ; i < NR_MEASUREMENT_CHARS ; i + + ) {
mask = 0x80 > > ( i + 3 ) ;
if ( cmcv & mask )
cmg_chars - > values [ i ] = chars - > values [ i ] ;
else
cmg_chars - > values [ i ] = 0 ;
}
}
break ;
default :
/* No cmg-dependent data. */
break ;
}
}
2007-04-27 18:01:28 +04:00
int chsc_get_channel_measurement_chars ( struct channel_path * chp )
2006-03-24 14:15:14 +03:00
{
int ccode , ret ;
struct {
struct chsc_header request ;
u32 : 24 ;
u32 first_chpid : 8 ;
u32 : 24 ;
u32 last_chpid : 8 ;
u32 zeroes1 ;
struct chsc_header response ;
u32 zeroes2 ;
u32 not_valid : 1 ;
u32 shared : 1 ;
u32 : 22 ;
u32 chpid : 8 ;
u32 cmcv : 5 ;
u32 : 11 ;
u32 cmgq : 8 ;
u32 cmg : 8 ;
u32 zeroes3 ;
u32 data [ NR_MEASUREMENT_CHARS ] ;
2007-02-05 23:17:40 +03:00
} __attribute__ ( ( packed ) ) * scmc_area ;
2006-03-24 14:15:14 +03:00
scmc_area = ( void * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
if ( ! scmc_area )
return - ENOMEM ;
scmc_area - > request . length = 0x0010 ;
scmc_area - > request . code = 0x0022 ;
2007-04-27 18:01:26 +04:00
scmc_area - > first_chpid = chp - > chpid . id ;
scmc_area - > last_chpid = chp - > chpid . id ;
2006-03-24 14:15:14 +03:00
ccode = chsc ( scmc_area ) ;
if ( ccode > 0 ) {
ret = ( ccode = = 3 ) ? - ENODEV : - EBUSY ;
goto out ;
}
2008-02-05 18:50:34 +03:00
ret = chsc_error_from_response ( scmc_area - > response . code ) ;
if ( ret = = 0 ) {
/* Success. */
2006-03-24 14:15:14 +03:00
if ( ! scmc_area - > not_valid ) {
chp - > cmg = scmc_area - > cmg ;
chp - > shared = scmc_area - > shared ;
chsc_initialize_cmg_chars ( chp , scmc_area - > cmcv ,
( struct cmg_chars * )
& scmc_area - > data ) ;
} else {
chp - > cmg = - 1 ;
chp - > shared = - 1 ;
}
2008-02-05 18:50:34 +03:00
} else {
CIO_CRW_EVENT ( 2 , " chsc: scmc failed (rc=%04x) \n " ,
2006-03-24 14:15:14 +03:00
scmc_area - > response . code ) ;
}
out :
free_page ( ( unsigned long ) scmc_area ) ;
return ret ;
}
2007-07-27 14:29:21 +04:00
int __init chsc_alloc_sei_area ( void )
2005-04-17 02:20:36 +04:00
{
2008-07-14 11:58:46 +04:00
int ret ;
2005-04-17 02:20:36 +04:00
sei_page = ( void * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
2008-07-14 11:58:46 +04:00
if ( ! sei_page ) {
2007-07-27 14:29:19 +04:00
CIO_MSG_EVENT ( 0 , " Can't allocate page for processing of "
" chsc machine checks! \n " ) ;
2008-07-14 11:58:46 +04:00
return - ENOMEM ;
}
ret = s390_register_crw_handler ( CRW_RSC_CSS , chsc_process_crw ) ;
if ( ret )
kfree ( sei_page ) ;
return ret ;
2005-04-17 02:20:36 +04:00
}
2007-07-27 14:29:21 +04:00
void __init chsc_free_sei_area ( void )
{
2008-07-14 11:58:46 +04:00
s390_unregister_crw_handler ( CRW_RSC_CSS ) ;
2007-07-27 14:29:21 +04:00
kfree ( sei_page ) ;
}
2006-01-06 11:19:25 +03:00
int __init
chsc_enable_facility ( int operation_code )
{
int ret ;
struct {
struct chsc_header request ;
u8 reserved1 : 4 ;
u8 format : 4 ;
u8 reserved2 ;
u16 operation_code ;
u32 reserved3 ;
u32 reserved4 ;
u32 operation_data_area [ 252 ] ;
struct chsc_header response ;
u32 reserved5 : 4 ;
u32 format2 : 4 ;
u32 reserved6 : 24 ;
2007-02-05 23:17:40 +03:00
} __attribute__ ( ( packed ) ) * sda_area ;
2006-01-06 11:19:25 +03:00
sda_area = ( void * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
if ( ! sda_area )
return - ENOMEM ;
2006-03-24 14:15:14 +03:00
sda_area - > request . length = 0x0400 ;
sda_area - > request . code = 0x0031 ;
2006-01-06 11:19:25 +03:00
sda_area - > operation_code = operation_code ;
ret = chsc ( sda_area ) ;
if ( ret > 0 ) {
ret = ( ret = = 3 ) ? - ENODEV : - EBUSY ;
goto out ;
}
2008-02-05 18:50:34 +03:00
2006-01-06 11:19:25 +03:00
switch ( sda_area - > response . code ) {
2008-02-05 18:50:34 +03:00
case 0x0101 :
2006-01-06 11:19:25 +03:00
ret = - EOPNOTSUPP ;
break ;
2008-02-05 18:50:34 +03:00
default :
ret = chsc_error_from_response ( sda_area - > response . code ) ;
2006-01-06 11:19:25 +03:00
}
2008-02-05 18:50:34 +03:00
if ( ret ! = 0 )
CIO_CRW_EVENT ( 2 , " chsc: sda (oc=%x) failed (rc=%04x) \n " ,
operation_code , sda_area - > response . code ) ;
2006-01-06 11:19:25 +03:00
out :
free_page ( ( unsigned long ) sda_area ) ;
return ret ;
}
2005-04-17 02:20:36 +04:00
struct css_general_char css_general_characteristics ;
struct css_chsc_char css_chsc_characteristics ;
int __init
chsc_determine_css_characteristics ( void )
{
int result ;
struct {
struct chsc_header request ;
u32 reserved1 ;
u32 reserved2 ;
u32 reserved3 ;
struct chsc_header response ;
u32 reserved4 ;
u32 general_char [ 510 ] ;
u32 chsc_char [ 518 ] ;
2007-02-05 23:17:40 +03:00
} __attribute__ ( ( packed ) ) * scsc_area ;
2005-04-17 02:20:36 +04:00
scsc_area = ( void * ) get_zeroed_page ( GFP_KERNEL | GFP_DMA ) ;
2008-02-05 18:50:34 +03:00
if ( ! scsc_area )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2006-03-24 14:15:14 +03:00
scsc_area - > request . length = 0x0010 ;
scsc_area - > request . code = 0x0010 ;
2005-04-17 02:20:36 +04:00
result = chsc ( scsc_area ) ;
if ( result ) {
2008-02-05 18:50:34 +03:00
result = ( result = = 3 ) ? - ENODEV : - EBUSY ;
2005-04-17 02:20:36 +04:00
goto exit ;
}
2008-02-05 18:50:34 +03:00
result = chsc_error_from_response ( scsc_area - > response . code ) ;
if ( result = = 0 ) {
memcpy ( & css_general_characteristics , scsc_area - > general_char ,
sizeof ( css_general_characteristics ) ) ;
memcpy ( & css_chsc_characteristics , scsc_area - > chsc_char ,
sizeof ( css_chsc_characteristics ) ) ;
} else
CIO_CRW_EVENT ( 2 , " chsc: scsc failed (rc=%04x) \n " ,
scsc_area - > response . code ) ;
2005-04-17 02:20:36 +04:00
exit :
free_page ( ( unsigned long ) scsc_area ) ;
return result ;
}
EXPORT_SYMBOL_GPL ( css_general_characteristics ) ;
EXPORT_SYMBOL_GPL ( css_chsc_characteristics ) ;
2008-07-14 11:58:56 +04:00
int chsc_sstpc ( void * page , unsigned int op , u16 ctrl )
{
struct {
struct chsc_header request ;
unsigned int rsvd0 ;
unsigned int op : 8 ;
unsigned int rsvd1 : 8 ;
unsigned int ctrl : 16 ;
unsigned int rsvd2 [ 5 ] ;
struct chsc_header response ;
unsigned int rsvd3 [ 7 ] ;
} __attribute__ ( ( packed ) ) * rr ;
int rc ;
memset ( page , 0 , PAGE_SIZE ) ;
rr = page ;
rr - > request . length = 0x0020 ;
rr - > request . code = 0x0033 ;
rr - > op = op ;
rr - > ctrl = ctrl ;
rc = chsc ( rr ) ;
if ( rc )
return - EIO ;
rc = ( rr - > response . code = = 0x0001 ) ? 0 : - EIO ;
return rc ;
}
int chsc_sstpi ( void * page , void * result , size_t size )
{
struct {
struct chsc_header request ;
unsigned int rsvd0 [ 3 ] ;
struct chsc_header response ;
char data [ size ] ;
} __attribute__ ( ( packed ) ) * rr ;
int rc ;
memset ( page , 0 , PAGE_SIZE ) ;
rr = page ;
rr - > request . length = 0x0010 ;
rr - > request . code = 0x0038 ;
rc = chsc ( rr ) ;
if ( rc )
return - EIO ;
memcpy ( result , & rr - > data , size ) ;
return ( rr - > response . code = = 0x0001 ) ? 0 : - EIO ;
}