2005-04-17 02:20:36 +04:00
/*
* drivers / s390 / cio / device_pgid . c
*
* Copyright ( C ) 2002 IBM Deutschland Entwicklung GmbH ,
* IBM Corporation
2006-01-15 00:21:04 +03:00
* Author ( s ) : Cornelia Huck ( cornelia . huck @ de . ibm . com )
2005-04-17 02:20:36 +04:00
* Martin Schwidefsky ( schwidefsky @ de . ibm . com )
*
* Path Group ID functions .
*/
# include <linux/config.h>
# include <linux/module.h>
# include <linux/init.h>
# include <asm/ccwdev.h>
# include <asm/cio.h>
# include <asm/delay.h>
# include <asm/lowcore.h>
# include "cio.h"
# include "cio_debug.h"
# include "css.h"
# include "device.h"
2006-01-06 11:19:13 +03:00
# include "ioasm.h"
2005-04-17 02:20:36 +04:00
/*
* Start Sense Path Group ID helper function . Used in ccw_device_recog
* and ccw_device_sense_pgid .
*/
static int
__ccw_device_sense_pgid_start ( struct ccw_device * cdev )
{
struct subchannel * sch ;
struct ccw1 * ccw ;
int ret ;
sch = to_subchannel ( cdev - > dev . parent ) ;
/* Setup sense path group id channel program. */
ccw = cdev - > private - > iccws ;
ccw - > cmd_code = CCW_CMD_SENSE_PGID ;
ccw - > cda = ( __u32 ) __pa ( & cdev - > private - > pgid ) ;
ccw - > count = sizeof ( struct pgid ) ;
ccw - > flags = CCW_FLAG_SLI ;
/* Reset device status. */
memset ( & cdev - > private - > irb , 0 , sizeof ( struct irb ) ) ;
/* Try on every path. */
ret = - ENODEV ;
while ( cdev - > private - > imask ! = 0 ) {
/* Try every path multiple times. */
if ( cdev - > private - > iretry > 0 ) {
cdev - > private - > iretry - - ;
ret = cio_start ( sch , cdev - > private - > iccws ,
cdev - > private - > imask ) ;
/* ret is 0, -EBUSY, -EACCES or -ENODEV */
if ( ret ! = - EACCES )
return ret ;
CIO_MSG_EVENT ( 2 , " SNID - Device %04x on Subchannel "
2006-01-06 11:19:25 +03:00
" 0.%x.%04x, lpm %02X, became 'not "
2005-04-17 02:20:36 +04:00
" operational' \n " ,
2006-01-06 11:19:25 +03:00
cdev - > private - > devno , sch - > schid . ssid ,
sch - > schid . sch_no , cdev - > private - > imask ) ;
2005-04-17 02:20:36 +04:00
}
cdev - > private - > imask > > = 1 ;
cdev - > private - > iretry = 5 ;
}
return ret ;
}
void
ccw_device_sense_pgid_start ( struct ccw_device * cdev )
{
int ret ;
cdev - > private - > state = DEV_STATE_SENSE_PGID ;
cdev - > private - > imask = 0x80 ;
cdev - > private - > iretry = 5 ;
memset ( & cdev - > private - > pgid , 0 , sizeof ( struct pgid ) ) ;
ret = __ccw_device_sense_pgid_start ( cdev ) ;
if ( ret & & ret ! = - EBUSY )
ccw_device_sense_pgid_done ( cdev , ret ) ;
}
/*
* Called from interrupt context to check if a valid answer
* to Sense Path Group ID was received .
*/
static int
__ccw_device_check_sense_pgid ( struct ccw_device * cdev )
{
struct subchannel * sch ;
struct irb * irb ;
sch = to_subchannel ( cdev - > dev . parent ) ;
irb = & cdev - > private - > irb ;
if ( irb - > scsw . fctl & ( SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC ) )
return - ETIME ;
if ( irb - > esw . esw0 . erw . cons & &
( irb - > ecw [ 0 ] & ( SNS0_CMD_REJECT | SNS0_INTERVENTION_REQ ) ) ) {
/*
* If the device doesn ' t support the Sense Path Group ID
* command further retries wouldn ' t help . . .
*/
return - EOPNOTSUPP ;
}
if ( irb - > esw . esw0 . erw . cons ) {
2006-01-06 11:19:25 +03:00
CIO_MSG_EVENT ( 2 , " SNID - device 0.%x.%04x, unit check, "
2005-04-17 02:20:36 +04:00
" lpum %02X, cnt %02d, sns : "
" %02X%02X%02X%02X %02X%02X%02X%02X ... \n " ,
2006-01-06 11:19:25 +03:00
cdev - > private - > ssid , cdev - > private - > devno ,
2005-04-17 02:20:36 +04:00
irb - > esw . esw0 . sublog . lpum ,
irb - > esw . esw0 . erw . scnt ,
irb - > ecw [ 0 ] , irb - > ecw [ 1 ] ,
irb - > ecw [ 2 ] , irb - > ecw [ 3 ] ,
irb - > ecw [ 4 ] , irb - > ecw [ 5 ] ,
irb - > ecw [ 6 ] , irb - > ecw [ 7 ] ) ;
return - EAGAIN ;
}
if ( irb - > scsw . cc = = 3 ) {
2006-01-06 11:19:25 +03:00
CIO_MSG_EVENT ( 2 , " SNID - Device %04x on Subchannel 0.%x.%04x, "
" lpm %02X, became 'not operational' \n " ,
cdev - > private - > devno , sch - > schid . ssid ,
sch - > schid . sch_no , sch - > orb . lpm ) ;
2005-04-17 02:20:36 +04:00
return - EACCES ;
}
if ( cdev - > private - > pgid . inf . ps . state2 = = SNID_STATE2_RESVD_ELSE ) {
2006-01-06 11:19:25 +03:00
CIO_MSG_EVENT ( 2 , " SNID - Device %04x on Subchannel 0.%x.%04x "
2005-04-17 02:20:36 +04:00
" is reserved by someone else \n " ,
2006-01-06 11:19:25 +03:00
cdev - > private - > devno , sch - > schid . ssid ,
sch - > schid . sch_no ) ;
2005-04-17 02:20:36 +04:00
return - EUSERS ;
}
return 0 ;
}
/*
* Got interrupt for Sense Path Group ID .
*/
void
ccw_device_sense_pgid_irq ( struct ccw_device * cdev , enum dev_event dev_event )
{
struct subchannel * sch ;
struct irb * irb ;
int ret ;
irb = ( struct irb * ) __LC_IRB ;
/* Retry sense pgid for cc=1. */
if ( irb - > scsw . stctl = =
( SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS ) ) {
if ( irb - > scsw . cc = = 1 ) {
ret = __ccw_device_sense_pgid_start ( cdev ) ;
if ( ret & & ret ! = - EBUSY )
ccw_device_sense_pgid_done ( cdev , ret ) ;
}
return ;
}
if ( ccw_device_accumulate_and_sense ( cdev , irb ) ! = 0 )
return ;
sch = to_subchannel ( cdev - > dev . parent ) ;
ret = __ccw_device_check_sense_pgid ( cdev ) ;
memset ( & cdev - > private - > irb , 0 , sizeof ( struct irb ) ) ;
switch ( ret ) {
/* 0, -ETIME, -EOPNOTSUPP, -EAGAIN, -EACCES or -EUSERS */
case 0 : /* Sense Path Group ID successful. */
if ( cdev - > private - > pgid . inf . ps . state1 = = SNID_STATE1_RESET )
2006-01-06 11:19:23 +03:00
memcpy ( & cdev - > private - > pgid , & css [ 0 ] - > global_pgid ,
2005-04-17 02:20:36 +04:00
sizeof ( struct pgid ) ) ;
ccw_device_sense_pgid_done ( cdev , 0 ) ;
break ;
case - EOPNOTSUPP : /* Sense Path Group ID not supported */
ccw_device_sense_pgid_done ( cdev , - EOPNOTSUPP ) ;
break ;
case - ETIME : /* Sense path group id stopped by timeout. */
ccw_device_sense_pgid_done ( cdev , - ETIME ) ;
break ;
case - EACCES : /* channel is not operational. */
sch - > lpm & = ~ cdev - > private - > imask ;
cdev - > private - > imask > > = 1 ;
cdev - > private - > iretry = 5 ;
/* Fall through. */
case - EAGAIN : /* Try again. */
ret = __ccw_device_sense_pgid_start ( cdev ) ;
if ( ret ! = 0 & & ret ! = - EBUSY )
ccw_device_sense_pgid_done ( cdev , - ENODEV ) ;
break ;
case - EUSERS : /* device is reserved for someone else. */
ccw_device_sense_pgid_done ( cdev , - EUSERS ) ;
break ;
}
}
/*
* Path Group ID helper function .
*/
static int
__ccw_device_do_pgid ( struct ccw_device * cdev , __u8 func )
{
struct subchannel * sch ;
struct ccw1 * ccw ;
int ret ;
sch = to_subchannel ( cdev - > dev . parent ) ;
/* Setup sense path group id channel program. */
cdev - > private - > pgid . inf . fc = func ;
ccw = cdev - > private - > iccws ;
if ( ! cdev - > private - > flags . pgid_single ) {
cdev - > private - > pgid . inf . fc | = SPID_FUNC_MULTI_PATH ;
ccw - > cmd_code = CCW_CMD_SUSPEND_RECONN ;
ccw - > cda = 0 ;
ccw - > count = 0 ;
ccw - > flags = CCW_FLAG_SLI | CCW_FLAG_CC ;
ccw + + ;
} else
cdev - > private - > pgid . inf . fc | = SPID_FUNC_SINGLE_PATH ;
ccw - > cmd_code = CCW_CMD_SET_PGID ;
ccw - > cda = ( __u32 ) __pa ( & cdev - > private - > pgid ) ;
ccw - > count = sizeof ( struct pgid ) ;
ccw - > flags = CCW_FLAG_SLI ;
/* Reset device status. */
memset ( & cdev - > private - > irb , 0 , sizeof ( struct irb ) ) ;
/* Try multiple times. */
ret = - ENODEV ;
if ( cdev - > private - > iretry > 0 ) {
cdev - > private - > iretry - - ;
ret = cio_start ( sch , cdev - > private - > iccws ,
cdev - > private - > imask ) ;
/* ret is 0, -EBUSY, -EACCES or -ENODEV */
if ( ( ret ! = - EACCES ) & & ( ret ! = - ENODEV ) )
return ret ;
}
/* PGID command failed on this path. Switch it off. */
sch - > lpm & = ~ cdev - > private - > imask ;
sch - > vpm & = ~ cdev - > private - > imask ;
CIO_MSG_EVENT ( 2 , " SPID - Device %04x on Subchannel "
2006-01-06 11:19:25 +03:00
" 0.%x.%04x, lpm %02X, became 'not operational' \n " ,
cdev - > private - > devno , sch - > schid . ssid ,
sch - > schid . sch_no , cdev - > private - > imask ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
/*
* Called from interrupt context to check if a valid answer
* to Set Path Group ID was received .
*/
static int
__ccw_device_check_pgid ( struct ccw_device * cdev )
{
struct subchannel * sch ;
struct irb * irb ;
sch = to_subchannel ( cdev - > dev . parent ) ;
irb = & cdev - > private - > irb ;
if ( irb - > scsw . fctl & ( SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC ) )
return - ETIME ;
if ( irb - > esw . esw0 . erw . cons ) {
if ( irb - > ecw [ 0 ] & SNS0_CMD_REJECT )
return - EOPNOTSUPP ;
/* Hmm, whatever happened, try again. */
2006-01-06 11:19:25 +03:00
CIO_MSG_EVENT ( 2 , " SPID - device 0.%x.%04x, unit check, "
" cnt %02d, "
2005-04-17 02:20:36 +04:00
" sns : %02X%02X%02X%02X %02X%02X%02X%02X ... \n " ,
2006-01-06 11:19:25 +03:00
cdev - > private - > ssid ,
2005-04-17 02:20:36 +04:00
cdev - > private - > devno , irb - > esw . esw0 . erw . scnt ,
irb - > ecw [ 0 ] , irb - > ecw [ 1 ] ,
irb - > ecw [ 2 ] , irb - > ecw [ 3 ] ,
irb - > ecw [ 4 ] , irb - > ecw [ 5 ] ,
irb - > ecw [ 6 ] , irb - > ecw [ 7 ] ) ;
return - EAGAIN ;
}
if ( irb - > scsw . cc = = 3 ) {
2006-01-06 11:19:25 +03:00
CIO_MSG_EVENT ( 2 , " SPID - Device %04x on Subchannel 0.%x.%04x, "
" lpm %02X, became 'not operational' \n " ,
cdev - > private - > devno , sch - > schid . ssid ,
sch - > schid . sch_no , cdev - > private - > imask ) ;
2005-04-17 02:20:36 +04:00
return - EACCES ;
}
return 0 ;
}
static void
__ccw_device_verify_start ( struct ccw_device * cdev )
{
struct subchannel * sch ;
__u8 imask , func ;
int ret ;
sch = to_subchannel ( cdev - > dev . parent ) ;
while ( sch - > vpm ! = sch - > lpm ) {
/* Find first unequal bit in vpm vs. lpm */
for ( imask = 0x80 ; imask ! = 0 ; imask > > = 1 )
if ( ( sch - > vpm & imask ) ! = ( sch - > lpm & imask ) )
break ;
cdev - > private - > imask = imask ;
func = ( sch - > vpm & imask ) ?
SPID_FUNC_RESIGN : SPID_FUNC_ESTABLISH ;
ret = __ccw_device_do_pgid ( cdev , func ) ;
if ( ret = = 0 | | ret = = - EBUSY )
return ;
cdev - > private - > iretry = 5 ;
}
ccw_device_verify_done ( cdev , ( sch - > lpm ! = 0 ) ? 0 : - ENODEV ) ;
}
/*
* Got interrupt for Set Path Group ID .
*/
void
ccw_device_verify_irq ( struct ccw_device * cdev , enum dev_event dev_event )
{
struct subchannel * sch ;
struct irb * irb ;
int ret ;
irb = ( struct irb * ) __LC_IRB ;
/* Retry set pgid for cc=1. */
if ( irb - > scsw . stctl = =
( SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS ) ) {
if ( irb - > scsw . cc = = 1 )
__ccw_device_verify_start ( cdev ) ;
return ;
}
if ( ccw_device_accumulate_and_sense ( cdev , irb ) ! = 0 )
return ;
sch = to_subchannel ( cdev - > dev . parent ) ;
ret = __ccw_device_check_pgid ( cdev ) ;
memset ( & cdev - > private - > irb , 0 , sizeof ( struct irb ) ) ;
switch ( ret ) {
/* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
case 0 :
/* Establish or Resign Path Group done. Update vpm. */
if ( ( sch - > lpm & cdev - > private - > imask ) ! = 0 )
sch - > vpm | = cdev - > private - > imask ;
else
sch - > vpm & = ~ cdev - > private - > imask ;
cdev - > private - > iretry = 5 ;
__ccw_device_verify_start ( cdev ) ;
break ;
case - EOPNOTSUPP :
/*
* One of those strange devices which claim to be able
* to do multipathing but not for Set Path Group ID .
*/
if ( cdev - > private - > flags . pgid_single ) {
ccw_device_verify_done ( cdev , - EOPNOTSUPP ) ;
break ;
}
cdev - > private - > flags . pgid_single = 1 ;
/* fall through. */
case - EAGAIN : /* Try again. */
__ccw_device_verify_start ( cdev ) ;
break ;
case - ETIME : /* Set path group id stopped by timeout. */
ccw_device_verify_done ( cdev , - ETIME ) ;
break ;
case - EACCES : /* channel is not operational. */
sch - > lpm & = ~ cdev - > private - > imask ;
sch - > vpm & = ~ cdev - > private - > imask ;
cdev - > private - > iretry = 5 ;
__ccw_device_verify_start ( cdev ) ;
break ;
}
}
void
ccw_device_verify_start ( struct ccw_device * cdev )
{
2006-01-06 11:19:13 +03:00
struct subchannel * sch = to_subchannel ( cdev - > dev . parent ) ;
2005-04-17 02:20:36 +04:00
cdev - > private - > flags . pgid_single = 0 ;
cdev - > private - > iretry = 5 ;
2006-01-06 11:19:13 +03:00
/*
* Update sch - > lpm with current values to catch paths becoming
* available again .
*/
2006-01-06 11:19:21 +03:00
if ( stsch ( sch - > schid , & sch - > schib ) ) {
2006-01-06 11:19:13 +03:00
ccw_device_verify_done ( cdev , - ENODEV ) ;
return ;
}
sch - > lpm = sch - > schib . pmcw . pim &
sch - > schib . pmcw . pam &
sch - > schib . pmcw . pom &
sch - > opm ;
2005-04-17 02:20:36 +04:00
__ccw_device_verify_start ( cdev ) ;
}
static void
__ccw_device_disband_start ( struct ccw_device * cdev )
{
struct subchannel * sch ;
int ret ;
sch = to_subchannel ( cdev - > dev . parent ) ;
while ( cdev - > private - > imask ! = 0 ) {
if ( sch - > lpm & cdev - > private - > imask ) {
ret = __ccw_device_do_pgid ( cdev , SPID_FUNC_DISBAND ) ;
if ( ret = = 0 )
return ;
}
cdev - > private - > iretry = 5 ;
cdev - > private - > imask > > = 1 ;
}
ccw_device_verify_done ( cdev , ( sch - > lpm ! = 0 ) ? 0 : - ENODEV ) ;
}
/*
* Got interrupt for Unset Path Group ID .
*/
void
ccw_device_disband_irq ( struct ccw_device * cdev , enum dev_event dev_event )
{
struct subchannel * sch ;
struct irb * irb ;
int ret ;
irb = ( struct irb * ) __LC_IRB ;
/* Retry set pgid for cc=1. */
if ( irb - > scsw . stctl = =
( SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS ) ) {
if ( irb - > scsw . cc = = 1 )
__ccw_device_disband_start ( cdev ) ;
return ;
}
if ( ccw_device_accumulate_and_sense ( cdev , irb ) ! = 0 )
return ;
sch = to_subchannel ( cdev - > dev . parent ) ;
ret = __ccw_device_check_pgid ( cdev ) ;
memset ( & cdev - > private - > irb , 0 , sizeof ( struct irb ) ) ;
switch ( ret ) {
/* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
case 0 : /* disband successful. */
sch - > vpm = 0 ;
ccw_device_disband_done ( cdev , ret ) ;
break ;
case - EOPNOTSUPP :
/*
* One of those strange devices which claim to be able
* to do multipathing but not for Unset Path Group ID .
*/
cdev - > private - > flags . pgid_single = 1 ;
/* fall through. */
case - EAGAIN : /* Try again. */
__ccw_device_disband_start ( cdev ) ;
break ;
case - ETIME : /* Set path group id stopped by timeout. */
ccw_device_disband_done ( cdev , - ETIME ) ;
break ;
case - EACCES : /* channel is not operational. */
cdev - > private - > imask > > = 1 ;
cdev - > private - > iretry = 5 ;
__ccw_device_disband_start ( cdev ) ;
break ;
}
}
void
ccw_device_disband_start ( struct ccw_device * cdev )
{
cdev - > private - > flags . pgid_single = 0 ;
cdev - > private - > iretry = 5 ;
cdev - > private - > imask = 0x80 ;
__ccw_device_disband_start ( cdev ) ;
}