2009-12-07 12:51:25 +01:00
/*
* Handling of internal CCW device requests .
*
2011-10-30 15:16:34 +01:00
* Copyright IBM Corp . 2009 , 2011
2009-12-07 12:51:25 +01:00
* Author ( s ) : Peter Oberparleiter < peter . oberparleiter @ de . ibm . com >
*/
2011-10-30 15:16:34 +01:00
# define KMSG_COMPONENT "cio"
# define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
2009-12-07 12:51:25 +01:00
# include <linux/types.h>
# include <linux/err.h>
# include <asm/ccwdev.h>
# include <asm/cio.h>
# include "io_sch.h"
# include "cio.h"
# include "device.h"
# include "cio_debug.h"
/**
* lpm_adjust - adjust path mask
* @ lpm : path mask to adjust
* @ mask : mask of available paths
*
* Shift @ lpm right until @ lpm and @ mask have at least one bit in common or
* until @ lpm is zero . Return the resulting lpm .
*/
int lpm_adjust ( int lpm , int mask )
{
while ( lpm & & ( ( lpm & mask ) = = 0 ) )
lpm > > = 1 ;
return lpm ;
}
/*
* Adjust path mask to use next path and reset retry count . Return resulting
* path mask .
*/
static u16 ccwreq_next_path ( struct ccw_device * cdev )
{
struct ccw_request * req = & cdev - > private - > req ;
2010-08-09 18:12:53 +02:00
if ( ! req - > singlepath ) {
req - > mask = 0 ;
goto out ;
}
2009-12-07 12:51:25 +01:00
req - > retries = req - > maxretries ;
2014-05-22 13:35:50 +02:00
req - > mask = lpm_adjust ( req - > mask > > 1 , req - > lpm ) ;
2010-08-09 18:12:53 +02:00
out :
2009-12-07 12:51:25 +01:00
return req - > mask ;
}
/*
* Clean up device state and report to callback .
*/
static void ccwreq_stop ( struct ccw_device * cdev , int rc )
{
struct ccw_request * req = & cdev - > private - > req ;
if ( req - > done )
return ;
req - > done = 1 ;
ccw_device_set_timeout ( cdev , 0 ) ;
memset ( & cdev - > private - > irb , 0 , sizeof ( struct irb ) ) ;
if ( rc & & rc ! = - ENODEV & & req - > drc )
rc = req - > drc ;
req - > callback ( cdev , req - > data , rc ) ;
}
/*
* ( Re - ) Start the operation until retries and paths are exhausted .
*/
static void ccwreq_do ( struct ccw_device * cdev )
{
struct ccw_request * req = & cdev - > private - > req ;
struct subchannel * sch = to_subchannel ( cdev - > dev . parent ) ;
struct ccw1 * cp = req - > cp ;
int rc = - EACCES ;
while ( req - > mask ) {
if ( req - > retries - - = = 0 ) {
/* Retries exhausted, try next path. */
ccwreq_next_path ( cdev ) ;
continue ;
}
/* Perform start function. */
memset ( & cdev - > private - > irb , 0 , sizeof ( struct irb ) ) ;
2009-12-07 12:51:40 +01:00
rc = cio_start ( sch , cp , ( u8 ) req - > mask ) ;
2009-12-07 12:51:25 +01:00
if ( rc = = 0 ) {
/* I/O started successfully. */
ccw_device_set_timeout ( cdev , req - > timeout ) ;
return ;
}
if ( rc = = - ENODEV ) {
/* Permanent device error. */
break ;
}
if ( rc = = - EACCES ) {
/* Permant path error. */
ccwreq_next_path ( cdev ) ;
continue ;
}
/* Temporary improper status. */
rc = cio_clear ( sch ) ;
if ( rc )
break ;
return ;
}
ccwreq_stop ( cdev , rc ) ;
}
/**
* ccw_request_start - perform I / O request
* @ cdev : ccw device
*
* Perform the I / O request specified by cdev - > req .
*/
void ccw_request_start ( struct ccw_device * cdev )
{
struct ccw_request * req = & cdev - > private - > req ;
2010-08-09 18:12:53 +02:00
if ( req - > singlepath ) {
/* Try all paths twice to counter link flapping. */
req - > mask = 0x8080 ;
} else
req - > mask = req - > lpm ;
2009-12-07 12:51:25 +01:00
req - > retries = req - > maxretries ;
req - > mask = lpm_adjust ( req - > mask , req - > lpm ) ;
req - > drc = 0 ;
req - > done = 0 ;
req - > cancel = 0 ;
if ( ! req - > mask )
goto out_nopath ;
ccwreq_do ( cdev ) ;
return ;
out_nopath :
ccwreq_stop ( cdev , - EACCES ) ;
}
/**
* ccw_request_cancel - cancel running I / O request
* @ cdev : ccw device
*
* Cancel the I / O request specified by cdev - > req . Return non - zero if request
* has already finished , zero otherwise .
*/
int ccw_request_cancel ( struct ccw_device * cdev )
{
struct subchannel * sch = to_subchannel ( cdev - > dev . parent ) ;
struct ccw_request * req = & cdev - > private - > req ;
int rc ;
if ( req - > done )
return 1 ;
req - > cancel = 1 ;
rc = cio_clear ( sch ) ;
if ( rc )
ccwreq_stop ( cdev , rc ) ;
return 0 ;
}
/*
* Return the status of the internal I / O started on the specified ccw device .
* Perform BASIC SENSE if required .
*/
static enum io_status ccwreq_status ( struct ccw_device * cdev , struct irb * lcirb )
{
struct irb * irb = & cdev - > private - > irb ;
struct cmd_scsw * scsw = & irb - > scsw . cmd ;
2010-05-26 23:27:08 +02:00
enum uc_todo todo ;
2009-12-07 12:51:25 +01:00
/* Perform BASIC SENSE if needed. */
if ( ccw_device_accumulate_and_sense ( cdev , lcirb ) )
return IO_RUNNING ;
/* Check for halt/clear interrupt. */
if ( scsw - > fctl & ( SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC ) )
return IO_KILLED ;
/* Check for path error. */
if ( scsw - > cc = = 3 | | scsw - > pno )
return IO_PATH_ERROR ;
/* Handle BASIC SENSE data. */
if ( irb - > esw . esw0 . erw . cons ) {
CIO_TRACE_EVENT ( 2 , " sensedata " ) ;
CIO_HEX_EVENT ( 2 , & cdev - > private - > dev_id ,
sizeof ( struct ccw_dev_id ) ) ;
CIO_HEX_EVENT ( 2 , & cdev - > private - > irb . ecw , SENSE_MAX_COUNT ) ;
/* Check for command reject. */
if ( irb - > ecw [ 0 ] & SNS0_CMD_REJECT )
return IO_REJECTED ;
2010-05-26 23:27:08 +02:00
/* Ask the driver what to do */
if ( cdev - > drv & & cdev - > drv - > uc_handler ) {
todo = cdev - > drv - > uc_handler ( cdev , lcirb ) ;
2010-08-09 18:12:51 +02:00
CIO_TRACE_EVENT ( 2 , " uc_response " ) ;
CIO_HEX_EVENT ( 2 , & todo , sizeof ( todo ) ) ;
2010-05-26 23:27:08 +02:00
switch ( todo ) {
case UC_TODO_RETRY :
return IO_STATUS_ERROR ;
case UC_TODO_RETRY_ON_NEW_PATH :
return IO_PATH_ERROR ;
case UC_TODO_STOP :
return IO_REJECTED ;
default :
return IO_STATUS_ERROR ;
}
}
2009-12-07 12:51:25 +01:00
/* Assume that unexpected SENSE data implies an error. */
return IO_STATUS_ERROR ;
}
/* Check for channel errors. */
if ( scsw - > cstat ! = 0 )
return IO_STATUS_ERROR ;
/* Check for device errors. */
if ( scsw - > dstat & ~ ( DEV_STAT_CHN_END | DEV_STAT_DEV_END ) )
return IO_STATUS_ERROR ;
/* Check for final state. */
if ( ! ( scsw - > dstat & DEV_STAT_DEV_END ) )
return IO_RUNNING ;
/* Check for other improper status. */
if ( scsw - > cc = = 1 & & ( scsw - > stctl & SCSW_STCTL_ALERT_STATUS ) )
return IO_STATUS_ERROR ;
return IO_DONE ;
}
/*
* Log ccw request status .
*/
static void ccwreq_log_status ( struct ccw_device * cdev , enum io_status status )
{
struct ccw_request * req = & cdev - > private - > req ;
struct {
struct ccw_dev_id dev_id ;
u16 retries ;
u8 lpm ;
u8 status ;
} __attribute__ ( ( packed ) ) data ;
data . dev_id = cdev - > private - > dev_id ;
data . retries = req - > retries ;
2009-12-07 12:51:40 +01:00
data . lpm = ( u8 ) req - > mask ;
2009-12-07 12:51:25 +01:00
data . status = ( u8 ) status ;
CIO_TRACE_EVENT ( 2 , " reqstat " ) ;
CIO_HEX_EVENT ( 2 , & data , sizeof ( data ) ) ;
}
/**
* ccw_request_handler - interrupt handler for I / O request procedure .
* @ cdev : ccw device
*
* Handle interrupt during I / O request procedure .
*/
void ccw_request_handler ( struct ccw_device * cdev )
{
2014-08-17 12:30:46 -05:00
struct irb * irb = this_cpu_ptr ( & cio_irb ) ;
2009-12-07 12:51:25 +01:00
struct ccw_request * req = & cdev - > private - > req ;
enum io_status status ;
int rc = - EOPNOTSUPP ;
/* Check status of I/O request. */
status = ccwreq_status ( cdev , irb ) ;
if ( req - > filter )
status = req - > filter ( cdev , req - > data , irb , status ) ;
if ( status ! = IO_RUNNING )
ccw_device_set_timeout ( cdev , 0 ) ;
if ( status ! = IO_DONE & & status ! = IO_RUNNING )
ccwreq_log_status ( cdev , status ) ;
switch ( status ) {
case IO_DONE :
break ;
case IO_RUNNING :
return ;
case IO_REJECTED :
goto err ;
case IO_PATH_ERROR :
goto out_next_path ;
case IO_STATUS_ERROR :
goto out_restart ;
case IO_KILLED :
/* Check if request was cancelled on purpose. */
if ( req - > cancel ) {
rc = - EIO ;
goto err ;
}
goto out_restart ;
}
/* Check back with request initiator. */
if ( ! req - > check )
goto out ;
switch ( req - > check ( cdev , req - > data ) ) {
case 0 :
break ;
case - EAGAIN :
goto out_restart ;
case - EACCES :
goto out_next_path ;
default :
goto err ;
}
out :
ccwreq_stop ( cdev , 0 ) ;
return ;
out_next_path :
/* Try next path and restart I/O. */
if ( ! ccwreq_next_path ( cdev ) ) {
rc = - EACCES ;
goto err ;
}
out_restart :
/* Restart. */
ccwreq_do ( cdev ) ;
return ;
err :
ccwreq_stop ( cdev , rc ) ;
}
/**
* ccw_request_timeout - timeout handler for I / O request procedure
* @ cdev : ccw device
*
* Handle timeout during I / O request procedure .
*/
void ccw_request_timeout ( struct ccw_device * cdev )
{
struct subchannel * sch = to_subchannel ( cdev - > dev . parent ) ;
struct ccw_request * req = & cdev - > private - > req ;
2011-10-30 15:16:34 +01:00
int rc = - ENODEV , chp ;
if ( cio_update_schib ( sch ) )
goto err ;
for ( chp = 0 ; chp < 8 ; chp + + ) {
if ( ( 0x80 > > chp ) & sch - > schib . pmcw . lpum )
2016-03-03 20:49:57 -08:00
pr_warn ( " %s: No interrupt was received within %lus (CS=%02x, DS=%02x, CHPID=%x.%02x) \n " ,
dev_name ( & cdev - > dev ) , req - > timeout / HZ ,
scsw_cstat ( & sch - > schib . scsw ) ,
scsw_dstat ( & sch - > schib . scsw ) ,
sch - > schid . cssid ,
sch - > schib . pmcw . chpid [ chp ] ) ;
2011-10-30 15:16:34 +01:00
}
2009-12-07 12:51:25 +01:00
if ( ! ccwreq_next_path ( cdev ) ) {
/* set the final return code for this request */
req - > drc = - ETIME ;
}
rc = cio_clear ( sch ) ;
if ( rc )
goto err ;
return ;
err :
ccwreq_stop ( cdev , rc ) ;
}
/**
* ccw_request_notoper - notoper handler for I / O request procedure
* @ cdev : ccw device
*
2011-10-30 15:16:34 +01:00
* Handle notoper during I / O request procedure .
2009-12-07 12:51:25 +01:00
*/
void ccw_request_notoper ( struct ccw_device * cdev )
{
ccwreq_stop ( cdev , - ENODEV ) ;
}