2005-04-17 02:20:36 +04:00
/*
* drivers / s390 / cio / device_ops . c
*
* Copyright ( C ) 2002 IBM Deutschland Entwicklung GmbH ,
* IBM Corporation
* Author ( s ) : Martin Schwidefsky ( schwidefsky @ 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
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/slab.h>
# include <linux/list.h>
# include <linux/device.h>
# include <linux/delay.h>
# include <asm/ccwdev.h>
# include <asm/idals.h>
# include "cio.h"
# include "cio_debug.h"
# include "css.h"
# include "chsc.h"
# include "device.h"
int
ccw_device_set_options ( struct ccw_device * cdev , unsigned long flags )
{
/*
* The flag usage is mutal exclusive . . .
*/
if ( ( flags & CCWDEV_EARLY_NOTIFICATION ) & &
( flags & CCWDEV_REPORT_ALL ) )
return - EINVAL ;
cdev - > private - > options . fast = ( flags & CCWDEV_EARLY_NOTIFICATION ) ! = 0 ;
cdev - > private - > options . repall = ( flags & CCWDEV_REPORT_ALL ) ! = 0 ;
cdev - > private - > options . pgroup = ( flags & CCWDEV_DO_PATHGROUP ) ! = 0 ;
cdev - > private - > options . force = ( flags & CCWDEV_ALLOW_FORCE ) ! = 0 ;
return 0 ;
}
int
ccw_device_clear ( struct ccw_device * cdev , unsigned long intparm )
{
struct subchannel * sch ;
int ret ;
if ( ! cdev )
return - ENODEV ;
if ( cdev - > private - > state = = DEV_STATE_NOT_OPER )
return - ENODEV ;
if ( cdev - > private - > state ! = DEV_STATE_ONLINE & &
cdev - > private - > state ! = DEV_STATE_WAIT4IO & &
cdev - > private - > state ! = DEV_STATE_W4SENSE )
return - EINVAL ;
sch = to_subchannel ( cdev - > dev . parent ) ;
if ( ! sch )
return - ENODEV ;
ret = cio_clear ( sch ) ;
if ( ret = = 0 )
cdev - > private - > intparm = intparm ;
return ret ;
}
int
ccw_device_start_key ( struct ccw_device * cdev , struct ccw1 * cpa ,
unsigned long intparm , __u8 lpm , __u8 key ,
unsigned long flags )
{
struct subchannel * sch ;
int ret ;
if ( ! cdev )
return - ENODEV ;
sch = to_subchannel ( cdev - > dev . parent ) ;
if ( ! sch )
return - ENODEV ;
if ( cdev - > private - > state = = DEV_STATE_NOT_OPER )
return - ENODEV ;
2006-06-29 17:03:35 +04:00
if ( cdev - > private - > state = = DEV_STATE_VERIFY | |
cdev - > private - > state = = DEV_STATE_CLEAR_VERIFY ) {
2005-04-17 02:20:36 +04:00
/* Remember to fake irb when finished. */
if ( ! cdev - > private - > flags . fake_irb ) {
cdev - > private - > flags . fake_irb = 1 ;
cdev - > private - > intparm = intparm ;
return 0 ;
} else
/* There's already a fake I/O around. */
return - EBUSY ;
}
if ( cdev - > private - > state ! = DEV_STATE_ONLINE | |
( ( sch - > schib . scsw . stctl & SCSW_STCTL_PRIM_STATUS ) & &
! ( sch - > schib . scsw . stctl & SCSW_STCTL_SEC_STATUS ) ) | |
cdev - > private - > flags . doverify )
return - EBUSY ;
ret = cio_set_options ( sch , flags ) ;
if ( ret )
return ret ;
2006-09-20 17:59:57 +04:00
/* Adjust requested path mask to excluded varied off paths. */
if ( lpm ) {
lpm & = sch - > opm ;
if ( lpm = = 0 )
return - EACCES ;
}
2005-04-17 02:20:36 +04:00
ret = cio_start_key ( sch , cpa , lpm , key ) ;
if ( ret = = 0 )
cdev - > private - > intparm = intparm ;
return ret ;
}
int
ccw_device_start_timeout_key ( struct ccw_device * cdev , struct ccw1 * cpa ,
unsigned long intparm , __u8 lpm , __u8 key ,
unsigned long flags , int expires )
{
int ret ;
if ( ! cdev )
return - ENODEV ;
ccw_device_set_timeout ( cdev , expires ) ;
ret = ccw_device_start_key ( cdev , cpa , intparm , lpm , key , flags ) ;
if ( ret ! = 0 )
ccw_device_set_timeout ( cdev , 0 ) ;
return ret ;
}
int
ccw_device_start ( struct ccw_device * cdev , struct ccw1 * cpa ,
unsigned long intparm , __u8 lpm , unsigned long flags )
{
return ccw_device_start_key ( cdev , cpa , intparm , lpm ,
2005-05-01 19:58:58 +04:00
PAGE_DEFAULT_KEY , flags ) ;
2005-04-17 02:20:36 +04:00
}
int
ccw_device_start_timeout ( struct ccw_device * cdev , struct ccw1 * cpa ,
unsigned long intparm , __u8 lpm , unsigned long flags ,
int expires )
{
return ccw_device_start_timeout_key ( cdev , cpa , intparm , lpm ,
2005-05-01 19:58:58 +04:00
PAGE_DEFAULT_KEY , flags ,
2005-04-17 02:20:36 +04:00
expires ) ;
}
int
ccw_device_halt ( struct ccw_device * cdev , unsigned long intparm )
{
struct subchannel * sch ;
int ret ;
if ( ! cdev )
return - ENODEV ;
if ( cdev - > private - > state = = DEV_STATE_NOT_OPER )
return - ENODEV ;
if ( cdev - > private - > state ! = DEV_STATE_ONLINE & &
cdev - > private - > state ! = DEV_STATE_WAIT4IO & &
cdev - > private - > state ! = DEV_STATE_W4SENSE )
return - EINVAL ;
sch = to_subchannel ( cdev - > dev . parent ) ;
if ( ! sch )
return - ENODEV ;
ret = cio_halt ( sch ) ;
if ( ret = = 0 )
cdev - > private - > intparm = intparm ;
return ret ;
}
int
ccw_device_resume ( struct ccw_device * cdev )
{
struct subchannel * sch ;
if ( ! cdev )
return - ENODEV ;
sch = to_subchannel ( cdev - > dev . parent ) ;
if ( ! sch )
return - ENODEV ;
if ( cdev - > private - > state = = DEV_STATE_NOT_OPER )
return - ENODEV ;
if ( cdev - > private - > state ! = DEV_STATE_ONLINE | |
! ( sch - > schib . scsw . actl & SCSW_ACTL_SUSPENDED ) )
return - EINVAL ;
return cio_resume ( sch ) ;
}
/*
* Pass interrupt to device driver .
*/
int
ccw_device_call_handler ( struct ccw_device * cdev )
{
struct subchannel * sch ;
unsigned int stctl ;
int ending_status ;
sch = to_subchannel ( cdev - > dev . parent ) ;
/*
* we allow for the device action handler if .
* - we received ending status
* - the action handler requested to see all interrupts
* - we received an intermediate status
* - fast notification was requested ( primary status )
* - unsolicited interrupts
*/
stctl = cdev - > private - > irb . scsw . stctl ;
ending_status = ( stctl & SCSW_STCTL_SEC_STATUS ) | |
( stctl = = ( SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND ) ) | |
( stctl = = SCSW_STCTL_STATUS_PEND ) ;
if ( ! ending_status & &
! cdev - > private - > options . repall & &
! ( stctl & SCSW_STCTL_INTER_STATUS ) & &
! ( cdev - > private - > options . fast & &
( stctl & SCSW_STCTL_PRIM_STATUS ) ) )
return 0 ;
/*
* Now we are ready to call the device driver interrupt handler .
*/
if ( cdev - > handler )
cdev - > handler ( cdev , cdev - > private - > intparm ,
& cdev - > private - > irb ) ;
/*
* Clear the old and now useless interrupt response block .
*/
memset ( & cdev - > private - > irb , 0 , sizeof ( struct irb ) ) ;
return 1 ;
}
/*
* Search for CIW command in extended sense data .
*/
struct ciw *
ccw_device_get_ciw ( struct ccw_device * cdev , __u32 ct )
{
int ciw_cnt ;
if ( cdev - > private - > flags . esid = = 0 )
return NULL ;
for ( ciw_cnt = 0 ; ciw_cnt < MAX_CIWS ; ciw_cnt + + )
if ( cdev - > private - > senseid . ciw [ ciw_cnt ] . ct = = ct )
return cdev - > private - > senseid . ciw + ciw_cnt ;
return NULL ;
}
__u8
ccw_device_get_path_mask ( struct ccw_device * cdev )
{
struct subchannel * sch ;
sch = to_subchannel ( cdev - > dev . parent ) ;
if ( ! sch )
return 0 ;
else
2006-09-20 17:59:59 +04:00
return sch - > lpm ;
2005-04-17 02:20:36 +04:00
}
static void
ccw_device_wake_up ( struct ccw_device * cdev , unsigned long ip , struct irb * irb )
{
if ( ! ip )
/* unsolicited interrupt */
return ;
/* Abuse intparm for error reporting. */
if ( IS_ERR ( irb ) )
cdev - > private - > intparm = - EIO ;
2006-08-07 19:00:30 +04:00
else if ( irb - > scsw . cc = = 1 )
/* Retry for deferred condition code. */
cdev - > private - > intparm = - EAGAIN ;
2005-04-17 02:20:36 +04:00
else if ( ( irb - > scsw . dstat ! =
( DEV_STAT_CHN_END | DEV_STAT_DEV_END ) ) | |
( irb - > scsw . cstat ! = 0 ) ) {
/*
* We didn ' t get channel end / device end . Check if path
* verification has been started ; we can retry after it has
* finished . We also retry unit checks except for command reject
2006-06-29 16:56:38 +04:00
* or intervention required . Also check for long busy
* conditions .
2005-04-17 02:20:36 +04:00
*/
if ( cdev - > private - > flags . doverify | |
cdev - > private - > state = = DEV_STATE_VERIFY )
cdev - > private - > intparm = - EAGAIN ;
if ( ( irb - > scsw . dstat & DEV_STAT_UNIT_CHECK ) & &
! ( irb - > ecw [ 0 ] &
( SNS0_CMD_REJECT | SNS0_INTERVENTION_REQ ) ) )
cdev - > private - > intparm = - EAGAIN ;
2006-06-29 16:56:38 +04:00
else if ( ( irb - > scsw . dstat & DEV_STAT_ATTENTION ) & &
( irb - > scsw . dstat & DEV_STAT_DEV_END ) & &
( irb - > scsw . dstat & DEV_STAT_UNIT_EXCEP ) )
cdev - > private - > intparm = - EAGAIN ;
2005-04-17 02:20:36 +04:00
else
cdev - > private - > intparm = - EIO ;
} else
cdev - > private - > intparm = 0 ;
wake_up ( & cdev - > private - > wait_q ) ;
}
static inline int
__ccw_device_retry_loop ( struct ccw_device * cdev , struct ccw1 * ccw , long magic , __u8 lpm )
{
int ret ;
struct subchannel * sch ;
sch = to_subchannel ( cdev - > dev . parent ) ;
do {
ret = cio_start ( sch , ccw , lpm ) ;
2006-09-20 17:59:57 +04:00
if ( ret = = - EBUSY ) {
2005-04-17 02:20:36 +04:00
/* Try again later. */
spin_unlock_irq ( & sch - > lock ) ;
msleep ( 10 ) ;
spin_lock_irq ( & sch - > lock ) ;
continue ;
}
if ( ret ! = 0 )
/* Non-retryable error. */
break ;
/* Wait for end of request. */
cdev - > private - > intparm = magic ;
spin_unlock_irq ( & sch - > lock ) ;
wait_event ( cdev - > private - > wait_q ,
( cdev - > private - > intparm = = - EIO ) | |
( cdev - > private - > intparm = = - EAGAIN ) | |
( cdev - > private - > intparm = = 0 ) ) ;
spin_lock_irq ( & sch - > lock ) ;
/* Check at least for channel end / device end */
if ( cdev - > private - > intparm = = - EIO ) {
/* Non-retryable error. */
ret = - EIO ;
break ;
}
if ( cdev - > private - > intparm = = 0 )
/* Success. */
break ;
/* Try again later. */
spin_unlock_irq ( & sch - > lock ) ;
msleep ( 10 ) ;
spin_lock_irq ( & sch - > lock ) ;
} while ( 1 ) ;
return ret ;
}
/**
* read_dev_chars ( ) - read device characteristics
* @ param cdev target ccw device
* @ param buffer pointer to buffer for rdc data
* @ param length size of rdc data
* @ returns 0 for success , negative error value on failure
*
* Context :
* called for online device , lock not held
* */
int
read_dev_chars ( struct ccw_device * cdev , void * * buffer , int length )
{
void ( * handler ) ( struct ccw_device * , unsigned long , struct irb * ) ;
struct subchannel * sch ;
int ret ;
struct ccw1 * rdc_ccw ;
if ( ! cdev )
return - ENODEV ;
if ( ! buffer | | ! length )
return - EINVAL ;
sch = to_subchannel ( cdev - > dev . parent ) ;
CIO_TRACE_EVENT ( 4 , " rddevch " ) ;
CIO_TRACE_EVENT ( 4 , sch - > dev . bus_id ) ;
2006-03-24 14:15:31 +03:00
rdc_ccw = kzalloc ( sizeof ( struct ccw1 ) , GFP_KERNEL | GFP_DMA ) ;
2005-04-17 02:20:36 +04:00
if ( ! rdc_ccw )
return - ENOMEM ;
rdc_ccw - > cmd_code = CCW_CMD_RDC ;
rdc_ccw - > count = length ;
rdc_ccw - > flags = CCW_FLAG_SLI ;
ret = set_normalized_cda ( rdc_ccw , ( * buffer ) ) ;
if ( ret ! = 0 ) {
kfree ( rdc_ccw ) ;
return ret ;
}
spin_lock_irq ( & sch - > lock ) ;
/* Save interrupt handler. */
handler = cdev - > handler ;
/* Temporarily install own handler. */
cdev - > handler = ccw_device_wake_up ;
if ( cdev - > private - > state ! = DEV_STATE_ONLINE )
ret = - ENODEV ;
else if ( ( ( sch - > schib . scsw . stctl & SCSW_STCTL_PRIM_STATUS ) & &
! ( sch - > schib . scsw . stctl & SCSW_STCTL_SEC_STATUS ) ) | |
cdev - > private - > flags . doverify )
ret = - EBUSY ;
else
/* 0x00D9C4C3 == ebcdic "RDC" */
ret = __ccw_device_retry_loop ( cdev , rdc_ccw , 0x00D9C4C3 , 0 ) ;
/* Restore interrupt handler. */
cdev - > handler = handler ;
spin_unlock_irq ( & sch - > lock ) ;
clear_normalized_cda ( rdc_ccw ) ;
kfree ( rdc_ccw ) ;
return ret ;
}
/*
* Read Configuration data using path mask
*/
int
read_conf_data_lpm ( struct ccw_device * cdev , void * * buffer , int * length , __u8 lpm )
{
void ( * handler ) ( struct ccw_device * , unsigned long , struct irb * ) ;
struct subchannel * sch ;
struct ciw * ciw ;
char * rcd_buf ;
int ret ;
struct ccw1 * rcd_ccw ;
if ( ! cdev )
return - ENODEV ;
if ( ! buffer | | ! length )
return - EINVAL ;
sch = to_subchannel ( cdev - > dev . parent ) ;
CIO_TRACE_EVENT ( 4 , " rdconf " ) ;
CIO_TRACE_EVENT ( 4 , sch - > dev . bus_id ) ;
/*
* scan for RCD command in extended SenseID data
*/
ciw = ccw_device_get_ciw ( cdev , CIW_TYPE_RCD ) ;
if ( ! ciw | | ciw - > cmd = = 0 )
return - EOPNOTSUPP ;
2006-09-20 17:59:57 +04:00
/* Adjust requested path mask to excluded varied off paths. */
if ( lpm ) {
lpm & = sch - > opm ;
if ( lpm = = 0 )
return - EACCES ;
}
2006-03-24 14:15:31 +03:00
rcd_ccw = kzalloc ( sizeof ( struct ccw1 ) , GFP_KERNEL | GFP_DMA ) ;
2005-04-17 02:20:36 +04:00
if ( ! rcd_ccw )
return - ENOMEM ;
2006-03-24 14:15:31 +03:00
rcd_buf = kzalloc ( ciw - > count , GFP_KERNEL | GFP_DMA ) ;
2005-04-17 02:20:36 +04:00
if ( ! rcd_buf ) {
kfree ( rcd_ccw ) ;
return - ENOMEM ;
}
rcd_ccw - > cmd_code = ciw - > cmd ;
rcd_ccw - > cda = ( __u32 ) __pa ( rcd_buf ) ;
rcd_ccw - > count = ciw - > count ;
rcd_ccw - > flags = CCW_FLAG_SLI ;
spin_lock_irq ( & sch - > lock ) ;
/* Save interrupt handler. */
handler = cdev - > handler ;
/* Temporarily install own handler. */
cdev - > handler = ccw_device_wake_up ;
if ( cdev - > private - > state ! = DEV_STATE_ONLINE )
ret = - ENODEV ;
else if ( ( ( sch - > schib . scsw . stctl & SCSW_STCTL_PRIM_STATUS ) & &
! ( sch - > schib . scsw . stctl & SCSW_STCTL_SEC_STATUS ) ) | |
cdev - > private - > flags . doverify )
ret = - EBUSY ;
else
/* 0x00D9C3C4 == ebcdic "RCD" */
ret = __ccw_device_retry_loop ( cdev , rcd_ccw , 0x00D9C3C4 , lpm ) ;
/* Restore interrupt handler. */
cdev - > handler = handler ;
spin_unlock_irq ( & sch - > lock ) ;
/*
* on success we update the user input parms
*/
if ( ret ) {
kfree ( rcd_buf ) ;
* buffer = NULL ;
* length = 0 ;
} else {
* length = ciw - > count ;
* buffer = rcd_buf ;
}
kfree ( rcd_ccw ) ;
return ret ;
}
/*
* Read Configuration data
*/
int
read_conf_data ( struct ccw_device * cdev , void * * buffer , int * length )
{
return read_conf_data_lpm ( cdev , buffer , length , 0 ) ;
}
/*
* Try to break the lock on a boxed device .
*/
int
ccw_device_stlck ( struct ccw_device * cdev )
{
void * buf , * buf2 ;
unsigned long flags ;
struct subchannel * sch ;
int ret ;
if ( ! cdev )
return - ENODEV ;
if ( cdev - > drv & & ! cdev - > private - > options . force )
return - EINVAL ;
sch = to_subchannel ( cdev - > dev . parent ) ;
CIO_TRACE_EVENT ( 2 , " stl lock " ) ;
CIO_TRACE_EVENT ( 2 , cdev - > dev . bus_id ) ;
buf = kmalloc ( 32 * sizeof ( char ) , GFP_DMA | GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
buf2 = kmalloc ( 32 * sizeof ( char ) , GFP_DMA | GFP_KERNEL ) ;
if ( ! buf2 ) {
kfree ( buf ) ;
return - ENOMEM ;
}
spin_lock_irqsave ( & sch - > lock , flags ) ;
ret = cio_enable_subchannel ( sch , 3 ) ;
if ( ret )
goto out_unlock ;
/*
* Setup ccw . We chain an unconditional reserve and a release so we
* only break the lock .
*/
cdev - > private - > iccws [ 0 ] . cmd_code = CCW_CMD_STLCK ;
cdev - > private - > iccws [ 0 ] . cda = ( __u32 ) __pa ( buf ) ;
cdev - > private - > iccws [ 0 ] . count = 32 ;
cdev - > private - > iccws [ 0 ] . flags = CCW_FLAG_CC ;
cdev - > private - > iccws [ 1 ] . cmd_code = CCW_CMD_RELEASE ;
cdev - > private - > iccws [ 1 ] . cda = ( __u32 ) __pa ( buf2 ) ;
cdev - > private - > iccws [ 1 ] . count = 32 ;
cdev - > private - > iccws [ 1 ] . flags = 0 ;
ret = cio_start ( sch , cdev - > private - > iccws , 0 ) ;
if ( ret ) {
cio_disable_subchannel ( sch ) ; //FIXME: return code?
goto out_unlock ;
}
cdev - > private - > irb . scsw . actl | = SCSW_ACTL_START_PEND ;
spin_unlock_irqrestore ( & sch - > lock , flags ) ;
wait_event ( cdev - > private - > wait_q , cdev - > private - > irb . scsw . actl = = 0 ) ;
spin_lock_irqsave ( & sch - > lock , flags ) ;
cio_disable_subchannel ( sch ) ; //FIXME: return code?
if ( ( cdev - > private - > irb . scsw . dstat ! =
( DEV_STAT_CHN_END | DEV_STAT_DEV_END ) ) | |
( cdev - > private - > irb . scsw . cstat ! = 0 ) )
ret = - EIO ;
/* Clear irb. */
memset ( & cdev - > private - > irb , 0 , sizeof ( struct irb ) ) ;
out_unlock :
2005-11-07 12:01:30 +03:00
kfree ( buf ) ;
kfree ( buf2 ) ;
2005-04-17 02:20:36 +04:00
spin_unlock_irqrestore ( & sch - > lock , flags ) ;
return ret ;
}
void *
ccw_device_get_chp_desc ( struct ccw_device * cdev , int chp_no )
{
struct subchannel * sch ;
sch = to_subchannel ( cdev - > dev . parent ) ;
return chsc_get_chp_desc ( sch , chp_no ) ;
}
// FIXME: these have to go:
int
_ccw_device_get_subchannel_number ( struct ccw_device * cdev )
{
2006-01-06 11:19:21 +03:00
return cdev - > private - > sch_no ;
2005-04-17 02:20:36 +04:00
}
int
_ccw_device_get_device_number ( struct ccw_device * cdev )
{
return cdev - > private - > devno ;
}
MODULE_LICENSE ( " GPL " ) ;
EXPORT_SYMBOL ( ccw_device_set_options ) ;
EXPORT_SYMBOL ( ccw_device_clear ) ;
EXPORT_SYMBOL ( ccw_device_halt ) ;
EXPORT_SYMBOL ( ccw_device_resume ) ;
EXPORT_SYMBOL ( ccw_device_start_timeout ) ;
EXPORT_SYMBOL ( ccw_device_start ) ;
EXPORT_SYMBOL ( ccw_device_start_timeout_key ) ;
EXPORT_SYMBOL ( ccw_device_start_key ) ;
EXPORT_SYMBOL ( ccw_device_get_ciw ) ;
EXPORT_SYMBOL ( ccw_device_get_path_mask ) ;
EXPORT_SYMBOL ( read_conf_data ) ;
EXPORT_SYMBOL ( read_dev_chars ) ;
EXPORT_SYMBOL ( _ccw_device_get_subchannel_number ) ;
EXPORT_SYMBOL ( _ccw_device_get_device_number ) ;
EXPORT_SYMBOL_GPL ( ccw_device_get_chp_desc ) ;
EXPORT_SYMBOL_GPL ( read_conf_data_lpm ) ;