2005-04-17 02:20:36 +04:00
/*
* drivers / s390 / cio / device . c
* bus driver for ccw devices
*
2008-07-14 11:58:45 +04:00
* Copyright IBM Corp . 2002 , 2008
2005-04-17 02:20:36 +04:00
* Author ( s ) : Arnd Bergmann ( arndb @ 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
* Martin Schwidefsky ( schwidefsky @ de . ibm . com )
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/spinlock.h>
# include <linux/errno.h>
# include <linux/err.h>
# include <linux/slab.h>
# include <linux/list.h>
# include <linux/device.h>
# include <linux/workqueue.h>
2008-01-26 16:10:52 +03:00
# include <linux/timer.h>
2005-04-17 02:20:36 +04:00
# include <asm/ccwdev.h>
# include <asm/cio.h>
2005-10-31 02:03:48 +03:00
# include <asm/param.h> /* HZ */
2007-10-12 18:11:22 +04:00
# include <asm/cmb.h>
2008-07-14 11:58:58 +04:00
# include <asm/isc.h>
2005-04-17 02:20:36 +04:00
2008-07-14 11:58:43 +04:00
# include "chp.h"
2005-04-17 02:20:36 +04:00
# include "cio.h"
2006-12-08 17:54:28 +03:00
# include "cio_debug.h"
2005-04-17 02:20:36 +04:00
# include "css.h"
# include "device.h"
# include "ioasm.h"
2008-01-26 16:10:43 +03:00
# include "io_sch.h"
2008-10-10 23:33:06 +04:00
# include "blacklist.h"
2005-04-17 02:20:36 +04:00
2008-01-26 16:10:52 +03:00
static struct timer_list recovery_timer ;
2008-02-19 17:29:23 +03:00
static DEFINE_SPINLOCK ( recovery_lock ) ;
2008-01-26 16:10:52 +03:00
static int recovery_phase ;
static const unsigned long recovery_delay [ ] = { 3 , 30 , 300 } ;
2005-04-17 02:20:36 +04:00
/******************* bus type handling ***********************/
/* The Linux driver model distinguishes between a bus type and
* the bus itself . Of course we only have one channel
* subsystem driver and one channel system per machine , but
* we still use the abstraction . T . R . says it ' s a good idea . */
static int
ccw_bus_match ( struct device * dev , struct device_driver * drv )
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
struct ccw_driver * cdrv = to_ccwdrv ( drv ) ;
const struct ccw_device_id * ids = cdrv - > ids , * found ;
if ( ! ids )
return 0 ;
found = ccw_device_id_match ( ids , & cdev - > id ) ;
if ( ! found )
return 0 ;
cdev - > id . driver_info = found - > driver_info ;
return 1 ;
}
2006-09-20 17:59:49 +04:00
/* Store modalias string delimited by prefix/suffix string into buffer with
* specified size . Return length of resulting string ( excluding trailing ' \0 ' )
* even if string doesn ' t fit buffer ( snprintf semantics ) . */
2007-04-27 18:01:32 +04:00
static int snprint_alias ( char * buf , size_t size ,
2006-09-20 17:59:49 +04:00
struct ccw_device_id * id , const char * suffix )
2005-04-17 02:20:36 +04:00
{
2006-09-20 17:59:49 +04:00
int len ;
2005-04-17 02:20:36 +04:00
2007-04-27 18:01:32 +04:00
len = snprintf ( buf , size , " ccw:t%04Xm%02X " , id - > cu_type , id - > cu_model ) ;
2006-09-20 17:59:49 +04:00
if ( len > size )
return len ;
buf + = len ;
size - = len ;
2005-04-17 02:20:36 +04:00
2006-09-20 17:59:49 +04:00
if ( id - > dev_type ! = 0 )
len + = snprintf ( buf , size , " dt%04Xdm%02X%s " , id - > dev_type ,
id - > dev_model , suffix ) ;
else
len + = snprintf ( buf , size , " dtdm%s " , suffix ) ;
2005-04-17 02:20:36 +04:00
2006-09-20 17:59:49 +04:00
return len ;
}
/* Set up environment variables for ccw device uevent. Return 0 on success,
* non - zero otherwise . */
2007-08-14 17:15:12 +04:00
static int ccw_uevent ( struct device * dev , struct kobj_uevent_env * env )
2006-09-20 17:59:49 +04:00
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
struct ccw_device_id * id = & ( cdev - > id ) ;
2007-04-27 18:01:32 +04:00
int ret ;
char modalias_buf [ 30 ] ;
2005-04-17 02:20:36 +04:00
2006-09-20 17:59:49 +04:00
/* CU_TYPE= */
2007-08-14 17:15:12 +04:00
ret = add_uevent_var ( env , " CU_TYPE=%04X " , id - > cu_type ) ;
2007-04-27 18:01:32 +04:00
if ( ret )
return ret ;
2006-09-20 17:59:49 +04:00
/* CU_MODEL= */
2007-08-14 17:15:12 +04:00
ret = add_uevent_var ( env , " CU_MODEL=%02X " , id - > cu_model ) ;
2007-04-27 18:01:32 +04:00
if ( ret )
return ret ;
2005-04-17 02:20:36 +04:00
/* The next two can be zero, that's ok for us */
2006-09-20 17:59:49 +04:00
/* DEV_TYPE= */
2007-08-14 17:15:12 +04:00
ret = add_uevent_var ( env , " DEV_TYPE=%04X " , id - > dev_type ) ;
2007-04-27 18:01:32 +04:00
if ( ret )
return ret ;
2005-04-17 02:20:36 +04:00
2006-09-20 17:59:49 +04:00
/* DEV_MODEL= */
2007-08-14 17:15:12 +04:00
ret = add_uevent_var ( env , " DEV_MODEL=%02X " , id - > dev_model ) ;
2007-04-27 18:01:32 +04:00
if ( ret )
return ret ;
2006-09-20 17:59:49 +04:00
/* MODALIAS= */
2007-04-27 18:01:32 +04:00
snprint_alias ( modalias_buf , sizeof ( modalias_buf ) , id , " " ) ;
2007-08-14 17:15:12 +04:00
ret = add_uevent_var ( env , " MODALIAS=%s " , modalias_buf ) ;
return ret ;
2005-04-17 02:20:36 +04:00
}
2006-01-11 12:56:22 +03:00
struct bus_type ccw_bus_type ;
2005-04-17 02:20:36 +04:00
2008-01-26 16:10:39 +03:00
static void io_subchannel_irq ( struct subchannel * ) ;
static int io_subchannel_probe ( struct subchannel * ) ;
static int io_subchannel_remove ( struct subchannel * ) ;
2006-01-11 12:56:22 +03:00
static void io_subchannel_shutdown ( struct subchannel * ) ;
2008-07-14 11:58:45 +04:00
static int io_subchannel_sch_event ( struct subchannel * , int ) ;
2008-07-14 11:59:02 +04:00
static int io_subchannel_chp_event ( struct subchannel * , struct chp_link * ,
int ) ;
2005-04-17 02:20:36 +04:00
2008-07-14 11:59:03 +04:00
static struct css_device_id io_subchannel_ids [ ] = {
{ . match_flags = 0x1 , . type = SUBCHANNEL_TYPE_IO , } ,
{ /* end of list */ } ,
} ;
MODULE_DEVICE_TABLE ( css , io_subchannel_ids ) ;
2007-05-10 17:45:43 +04:00
static struct css_driver io_subchannel_driver = {
2008-01-26 16:10:47 +03:00
. owner = THIS_MODULE ,
2008-07-14 11:59:03 +04:00
. subchannel_type = io_subchannel_ids ,
2008-01-26 16:10:41 +03:00
. name = " io_subchannel " ,
2005-04-17 02:20:36 +04:00
. irq = io_subchannel_irq ,
2008-07-14 11:58:45 +04:00
. sch_event = io_subchannel_sch_event ,
. chp_event = io_subchannel_chp_event ,
2006-01-11 12:56:22 +03:00
. probe = io_subchannel_probe ,
. remove = io_subchannel_remove ,
. shutdown = io_subchannel_shutdown ,
2005-04-17 02:20:36 +04:00
} ;
struct workqueue_struct * ccw_device_work ;
2006-06-29 16:57:03 +04:00
wait_queue_head_t ccw_device_init_wq ;
atomic_t ccw_device_init_count ;
2005-04-17 02:20:36 +04:00
2008-01-26 16:10:52 +03:00
static void recovery_func ( unsigned long data ) ;
2005-04-17 02:20:36 +04:00
static int __init
init_ccw_bus_type ( void )
{
int ret ;
init_waitqueue_head ( & ccw_device_init_wq ) ;
atomic_set ( & ccw_device_init_count , 0 ) ;
2008-01-26 16:10:52 +03:00
setup_timer ( & recovery_timer , recovery_func , 0 ) ;
2005-04-17 02:20:36 +04:00
ccw_device_work = create_singlethread_workqueue ( " cio " ) ;
if ( ! ccw_device_work )
return - ENOMEM ; /* FIXME: better errno ? */
slow_path_wq = create_singlethread_workqueue ( " kslowcrw " ) ;
if ( ! slow_path_wq ) {
ret = - ENOMEM ; /* FIXME: better errno ? */
goto out_err ;
}
if ( ( ret = bus_register ( & ccw_bus_type ) ) )
goto out_err ;
2008-01-26 16:10:41 +03:00
ret = css_driver_register ( & io_subchannel_driver ) ;
if ( ret )
2005-04-17 02:20:36 +04:00
goto out_err ;
wait_event ( ccw_device_init_wq ,
atomic_read ( & ccw_device_init_count ) = = 0 ) ;
flush_workqueue ( ccw_device_work ) ;
return 0 ;
out_err :
if ( ccw_device_work )
destroy_workqueue ( ccw_device_work ) ;
if ( slow_path_wq )
destroy_workqueue ( slow_path_wq ) ;
return ret ;
}
static void __exit
cleanup_ccw_bus_type ( void )
{
2008-01-26 16:10:41 +03:00
css_driver_unregister ( & io_subchannel_driver ) ;
2005-04-17 02:20:36 +04:00
bus_unregister ( & ccw_bus_type ) ;
destroy_workqueue ( ccw_device_work ) ;
}
subsys_initcall ( init_ccw_bus_type ) ;
module_exit ( cleanup_ccw_bus_type ) ;
/************************ device handling **************************/
/*
* A ccw_device has some interfaces in sysfs in addition to the
* standard ones .
* The following entries are designed to export the information which
* resided in 2.4 in / proc / subchannels . Subchannel and device number
* are obvious , so they don ' t have an entry : )
* TODO : Split chpids and pimpampom up ? Where is " in use " in the tree ?
*/
static ssize_t
2005-05-17 14:43:27 +04:00
chpids_show ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct subchannel * sch = to_subchannel ( dev ) ;
2007-04-27 18:01:35 +04:00
struct chsc_ssd_info * ssd = & sch - > ssd_info ;
2005-04-17 02:20:36 +04:00
ssize_t ret = 0 ;
int chp ;
2007-04-27 18:01:35 +04:00
int mask ;
for ( chp = 0 ; chp < 8 ; chp + + ) {
mask = 0x80 > > chp ;
if ( ssd - > path_mask & mask )
ret + = sprintf ( buf + ret , " %02x " , ssd - > chpid [ chp ] . id ) ;
else
ret + = sprintf ( buf + ret , " 00 " ) ;
}
2005-04-17 02:20:36 +04:00
ret + = sprintf ( buf + ret , " \n " ) ;
return min ( ( ssize_t ) PAGE_SIZE , ret ) ;
}
static ssize_t
2005-05-17 14:43:27 +04:00
pimpampom_show ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct subchannel * sch = to_subchannel ( dev ) ;
struct pmcw * pmcw = & sch - > schib . pmcw ;
return sprintf ( buf , " %02x %02x %02x \n " ,
pmcw - > pim , pmcw - > pam , pmcw - > pom ) ;
}
static ssize_t
2005-05-17 14:43:27 +04:00
devtype_show ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
struct ccw_device_id * id = & ( cdev - > id ) ;
if ( id - > dev_type ! = 0 )
return sprintf ( buf , " %04x/%02x \n " ,
id - > dev_type , id - > dev_model ) ;
else
return sprintf ( buf , " n/a \n " ) ;
}
static ssize_t
2005-05-17 14:43:27 +04:00
cutype_show ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
struct ccw_device_id * id = & ( cdev - > id ) ;
return sprintf ( buf , " %04x/%02x \n " ,
id - > cu_type , id - > cu_model ) ;
}
2005-10-31 02:00:12 +03:00
static ssize_t
modalias_show ( struct device * dev , struct device_attribute * attr , char * buf )
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
struct ccw_device_id * id = & ( cdev - > id ) ;
2006-09-20 17:59:49 +04:00
int len ;
2005-10-31 02:00:12 +03:00
2007-07-17 15:36:08 +04:00
len = snprint_alias ( buf , PAGE_SIZE , id , " \n " ) ;
2006-09-20 17:59:49 +04:00
return len > PAGE_SIZE ? PAGE_SIZE : len ;
2005-10-31 02:00:12 +03:00
}
2005-04-17 02:20:36 +04:00
static ssize_t
2005-05-17 14:43:27 +04:00
online_show ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
return sprintf ( buf , cdev - > online ? " 1 \n " : " 0 \n " ) ;
}
2006-12-08 17:54:28 +03:00
int ccw_device_is_orphan ( struct ccw_device * cdev )
{
return sch_is_pseudo_sch ( to_subchannel ( cdev - > dev . parent ) ) ;
}
2007-04-27 18:01:39 +04:00
static void ccw_device_unregister ( struct ccw_device * cdev )
2006-12-08 17:54:21 +03:00
{
if ( test_and_clear_bit ( 1 , & cdev - > private - > registered ) )
2007-04-27 18:01:39 +04:00
device_del ( & cdev - > dev ) ;
2006-12-08 17:54:21 +03:00
}
2008-10-10 23:33:05 +04:00
static void ccw_device_remove_orphan_cb ( struct work_struct * work )
2007-05-31 19:38:06 +04:00
{
2008-10-10 23:33:05 +04:00
struct ccw_device_private * priv ;
struct ccw_device * cdev ;
2007-05-31 19:38:06 +04:00
2008-10-10 23:33:05 +04:00
priv = container_of ( work , struct ccw_device_private , kick_work ) ;
cdev = priv - > cdev ;
2007-05-31 19:38:06 +04:00
ccw_device_unregister ( cdev ) ;
put_device ( & cdev - > dev ) ;
2008-10-10 23:33:05 +04:00
/* Release cdev reference for workqueue processing. */
put_device ( & cdev - > dev ) ;
2007-05-31 19:38:06 +04:00
}
2008-10-10 23:33:05 +04:00
static void ccw_device_call_sch_unregister ( struct work_struct * work ) ;
2007-05-31 19:38:06 +04:00
2005-04-17 02:20:36 +04:00
static void
ccw_device_remove_disconnected ( struct ccw_device * cdev )
{
2006-12-08 17:54:28 +03:00
unsigned long flags ;
2007-05-31 19:38:06 +04:00
2005-04-17 02:20:36 +04:00
/*
* Forced offline in disconnected state means
* ' throw away device ' .
*/
2008-10-10 23:33:05 +04:00
/* Get cdev reference for workqueue processing. */
if ( ! get_device ( & cdev - > dev ) )
return ;
2006-12-08 17:54:28 +03:00
if ( ccw_device_is_orphan ( cdev ) ) {
2007-05-31 19:38:06 +04:00
/*
* Deregister ccw device .
* Unfortunately , we cannot do this directly from the
* attribute method .
*/
2006-12-08 17:54:28 +03:00
spin_lock_irqsave ( cdev - > ccwlock , flags ) ;
cdev - > private - > state = DEV_STATE_NOT_OPER ;
spin_unlock_irqrestore ( cdev - > ccwlock , flags ) ;
2008-10-10 23:33:05 +04:00
PREPARE_WORK ( & cdev - > private - > kick_work ,
ccw_device_remove_orphan_cb ) ;
} else
/* Deregister subchannel, which will kill the ccw device. */
PREPARE_WORK ( & cdev - > private - > kick_work ,
ccw_device_call_sch_unregister ) ;
queue_work ( slow_path_wq , & cdev - > private - > kick_work ) ;
2005-04-17 02:20:36 +04:00
}
2007-10-12 18:11:17 +04:00
/**
* ccw_device_set_offline ( ) - disable a ccw device for I / O
* @ cdev : target ccw device
*
* This function calls the driver ' s set_offline ( ) function for @ cdev , if
* given , and then disables @ cdev .
* Returns :
* % 0 on success and a negative error value on failure .
* Context :
* enabled , ccw device lock not held
*/
int ccw_device_set_offline ( struct ccw_device * cdev )
2005-04-17 02:20:36 +04:00
{
int ret ;
if ( ! cdev )
return - ENODEV ;
if ( ! cdev - > online | | ! cdev - > drv )
return - EINVAL ;
if ( cdev - > drv - > set_offline ) {
ret = cdev - > drv - > set_offline ( cdev ) ;
if ( ret ! = 0 )
return ret ;
}
cdev - > online = 0 ;
spin_lock_irq ( cdev - > ccwlock ) ;
ret = ccw_device_offline ( cdev ) ;
if ( ret = = - ENODEV ) {
if ( cdev - > private - > state ! = DEV_STATE_NOT_OPER ) {
cdev - > private - > state = DEV_STATE_OFFLINE ;
dev_fsm_event ( cdev , DEV_EVENT_NOTOPER ) ;
}
spin_unlock_irq ( cdev - > ccwlock ) ;
2008-12-25 15:39:06 +03:00
/* Give up reference from ccw_device_set_online(). */
put_device ( & cdev - > dev ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
spin_unlock_irq ( cdev - > ccwlock ) ;
2008-12-25 15:39:06 +03:00
if ( ret = = 0 ) {
2005-04-17 02:20:36 +04:00
wait_event ( cdev - > private - > wait_q , dev_fsm_final_state ( cdev ) ) ;
2008-12-25 15:39:06 +03:00
/* Give up reference from ccw_device_set_online(). */
put_device ( & cdev - > dev ) ;
} else {
2008-05-07 11:22:54 +04:00
CIO_MSG_EVENT ( 0 , " ccw_device_offline returned %d, "
2007-07-27 14:29:19 +04:00
" device 0.%x.%04x \n " ,
ret , cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno ) ;
2005-04-17 02:20:36 +04:00
cdev - > online = 1 ;
}
2008-12-25 15:39:06 +03:00
return ret ;
2005-04-17 02:20:36 +04:00
}
2007-10-12 18:11:17 +04:00
/**
* ccw_device_set_online ( ) - enable a ccw device for I / O
* @ cdev : target ccw device
*
* This function first enables @ cdev and then calls the driver ' s set_online ( )
* function for @ cdev , if given . If set_online ( ) returns an error , @ cdev is
* disabled again .
* Returns :
* % 0 on success and a negative error value on failure .
* Context :
* enabled , ccw device lock not held
*/
int ccw_device_set_online ( struct ccw_device * cdev )
2005-04-17 02:20:36 +04:00
{
int ret ;
if ( ! cdev )
return - ENODEV ;
if ( cdev - > online | | ! cdev - > drv )
return - EINVAL ;
2008-12-25 15:39:06 +03:00
/* Hold on to an extra reference while device is online. */
if ( ! get_device ( & cdev - > dev ) )
return - ENODEV ;
2005-04-17 02:20:36 +04:00
spin_lock_irq ( cdev - > ccwlock ) ;
ret = ccw_device_online ( cdev ) ;
spin_unlock_irq ( cdev - > ccwlock ) ;
if ( ret = = 0 )
wait_event ( cdev - > private - > wait_q , dev_fsm_final_state ( cdev ) ) ;
else {
2008-05-07 11:22:54 +04:00
CIO_MSG_EVENT ( 0 , " ccw_device_online returned %d, "
2007-07-27 14:29:19 +04:00
" device 0.%x.%04x \n " ,
ret , cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno ) ;
2008-12-25 15:39:06 +03:00
/* Give up online reference since onlining failed. */
put_device ( & cdev - > dev ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
2008-12-25 15:39:06 +03:00
if ( cdev - > private - > state ! = DEV_STATE_ONLINE ) {
/* Give up online reference since onlining failed. */
put_device ( & cdev - > dev ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
2008-12-25 15:39:06 +03:00
}
2005-04-17 02:20:36 +04:00
if ( ! cdev - > drv - > set_online | | cdev - > drv - > set_online ( cdev ) = = 0 ) {
cdev - > online = 1 ;
return 0 ;
}
spin_lock_irq ( cdev - > ccwlock ) ;
ret = ccw_device_offline ( cdev ) ;
spin_unlock_irq ( cdev - > ccwlock ) ;
if ( ret = = 0 )
wait_event ( cdev - > private - > wait_q , dev_fsm_final_state ( cdev ) ) ;
2007-07-27 14:29:19 +04:00
else
2008-05-07 11:22:54 +04:00
CIO_MSG_EVENT ( 0 , " ccw_device_offline returned %d, "
2007-07-27 14:29:19 +04:00
" device 0.%x.%04x \n " ,
ret , cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno ) ;
2008-12-25 15:39:06 +03:00
/* Give up online reference since onlining failed. */
put_device ( & cdev - > dev ) ;
2006-02-18 00:52:49 +03:00
return ( ret = = 0 ) ? - ENODEV : ret ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 18:01:30 +04:00
static void online_store_handle_offline ( struct ccw_device * cdev )
{
if ( cdev - > private - > state = = DEV_STATE_DISCONNECTED )
ccw_device_remove_disconnected ( cdev ) ;
else if ( cdev - > drv & & cdev - > drv - > set_offline )
ccw_device_set_offline ( cdev ) ;
}
static int online_store_recog_and_online ( struct ccw_device * cdev )
{
int ret ;
/* Do device recognition, if needed. */
if ( cdev - > id . cu_type = = 0 ) {
ret = ccw_device_recognition ( cdev ) ;
if ( ret ) {
2007-07-27 14:29:19 +04:00
CIO_MSG_EVENT ( 0 , " Couldn't start recognition "
" for device 0.%x.%04x (ret=%d) \n " ,
cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno , ret ) ;
2007-04-27 18:01:30 +04:00
return ret ;
}
wait_event ( cdev - > private - > wait_q ,
cdev - > private - > flags . recog_done ) ;
}
if ( cdev - > drv & & cdev - > drv - > set_online )
ccw_device_set_online ( cdev ) ;
return 0 ;
}
2008-07-14 11:59:22 +04:00
static int online_store_handle_online ( struct ccw_device * cdev , int force )
2007-04-27 18:01:30 +04:00
{
int ret ;
ret = online_store_recog_and_online ( cdev ) ;
if ( ret )
2008-07-14 11:59:22 +04:00
return ret ;
2007-04-27 18:01:30 +04:00
if ( force & & cdev - > private - > state = = DEV_STATE_BOXED ) {
ret = ccw_device_stlck ( cdev ) ;
2008-07-14 11:59:22 +04:00
if ( ret )
return ret ;
2007-04-27 18:01:30 +04:00
if ( cdev - > id . cu_type = = 0 )
cdev - > private - > state = DEV_STATE_NOT_OPER ;
online_store_recog_and_online ( cdev ) ;
}
2008-07-14 11:59:22 +04:00
return 0 ;
2007-04-27 18:01:30 +04:00
}
static ssize_t online_store ( struct device * dev , struct device_attribute * attr ,
const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
2008-04-30 15:38:33 +04:00
int force , ret ;
unsigned long i ;
2005-04-17 02:20:36 +04:00
2006-01-06 11:19:07 +03:00
if ( atomic_cmpxchg ( & cdev - > private - > onoff , 0 , 1 ) ! = 0 )
2005-04-17 02:20:36 +04:00
return - EAGAIN ;
if ( cdev - > drv & & ! try_module_get ( cdev - > drv - > owner ) ) {
atomic_set ( & cdev - > private - > onoff , 0 ) ;
return - EINVAL ;
}
if ( ! strncmp ( buf , " force \n " , count ) ) {
force = 1 ;
i = 1 ;
2008-04-30 15:38:33 +04:00
ret = 0 ;
2005-04-17 02:20:36 +04:00
} else {
force = 0 ;
2008-04-30 15:38:33 +04:00
ret = strict_strtoul ( buf , 16 , & i ) ;
2005-04-17 02:20:36 +04:00
}
2008-04-30 15:38:33 +04:00
if ( ret )
goto out ;
2007-04-27 18:01:30 +04:00
switch ( i ) {
case 0 :
online_store_handle_offline ( cdev ) ;
2008-04-30 15:38:33 +04:00
ret = count ;
2007-04-27 18:01:30 +04:00
break ;
case 1 :
2008-07-14 11:59:22 +04:00
ret = online_store_handle_online ( cdev , force ) ;
if ( ! ret )
ret = count ;
2007-04-27 18:01:30 +04:00
break ;
default :
2008-04-30 15:38:33 +04:00
ret = - EINVAL ;
2005-04-17 02:20:36 +04:00
}
2008-04-30 15:38:33 +04:00
out :
2005-04-17 02:20:36 +04:00
if ( cdev - > drv )
module_put ( cdev - > drv - > owner ) ;
atomic_set ( & cdev - > private - > onoff , 0 ) ;
2008-04-30 15:38:33 +04:00
return ret ;
2005-04-17 02:20:36 +04:00
}
static ssize_t
2005-05-17 14:43:27 +04:00
available_show ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
struct subchannel * sch ;
2006-12-08 17:54:28 +03:00
if ( ccw_device_is_orphan ( cdev ) )
return sprintf ( buf , " no device \n " ) ;
2005-04-17 02:20:36 +04:00
switch ( cdev - > private - > state ) {
case DEV_STATE_BOXED :
return sprintf ( buf , " boxed \n " ) ;
case DEV_STATE_DISCONNECTED :
case DEV_STATE_DISCONNECTED_SENSE_ID :
case DEV_STATE_NOT_OPER :
sch = to_subchannel ( dev - > parent ) ;
if ( ! sch - > lpm )
return sprintf ( buf , " no path \n " ) ;
else
return sprintf ( buf , " no device \n " ) ;
default :
/* All other states considered fine. */
return sprintf ( buf , " good \n " ) ;
}
}
static DEVICE_ATTR ( chpids , 0444 , chpids_show , NULL ) ;
static DEVICE_ATTR ( pimpampom , 0444 , pimpampom_show , NULL ) ;
static DEVICE_ATTR ( devtype , 0444 , devtype_show , NULL ) ;
static DEVICE_ATTR ( cutype , 0444 , cutype_show , NULL ) ;
2005-10-31 02:00:12 +03:00
static DEVICE_ATTR ( modalias , 0444 , modalias_show , NULL ) ;
2005-04-17 02:20:36 +04:00
static DEVICE_ATTR ( online , 0644 , online_show , online_store ) ;
static DEVICE_ATTR ( availability , 0444 , available_show , NULL ) ;
2008-07-14 11:58:44 +04:00
static struct attribute * io_subchannel_attrs [ ] = {
2005-04-17 02:20:36 +04:00
& dev_attr_chpids . attr ,
& dev_attr_pimpampom . attr ,
NULL ,
} ;
2008-07-14 11:58:44 +04:00
static struct attribute_group io_subchannel_attr_group = {
. attrs = io_subchannel_attrs ,
2006-12-08 17:55:57 +03:00
} ;
2005-04-17 02:20:36 +04:00
static struct attribute * ccwdev_attrs [ ] = {
& dev_attr_devtype . attr ,
& dev_attr_cutype . attr ,
2005-10-31 02:00:12 +03:00
& dev_attr_modalias . attr ,
2005-04-17 02:20:36 +04:00
& dev_attr_online . attr ,
& dev_attr_cmb_enable . attr ,
& dev_attr_availability . attr ,
NULL ,
} ;
static struct attribute_group ccwdev_attr_group = {
. attrs = ccwdev_attrs ,
} ;
2007-05-10 17:45:43 +04:00
static struct attribute_group * ccwdev_attr_groups [ ] = {
2007-04-27 18:01:39 +04:00
& ccwdev_attr_group ,
NULL ,
} ;
2005-04-17 02:20:36 +04:00
/* this is a simple abstraction for device_register that sets the
* correct bus type and adds the bus specific files */
2006-10-27 14:39:33 +04:00
static int ccw_device_register ( struct ccw_device * cdev )
2005-04-17 02:20:36 +04:00
{
struct device * dev = & cdev - > dev ;
int ret ;
dev - > bus = & ccw_bus_type ;
if ( ( ret = device_add ( dev ) ) )
return ret ;
set_bit ( 1 , & cdev - > private - > registered ) ;
return ret ;
}
2005-06-26 01:55:27 +04:00
struct match_data {
2006-10-11 17:31:38 +04:00
struct ccw_dev_id dev_id ;
2005-06-26 01:55:27 +04:00
struct ccw_device * sibling ;
} ;
static int
match_devno ( struct device * dev , void * data )
{
2006-10-11 17:31:38 +04:00
struct match_data * d = data ;
2005-06-26 01:55:27 +04:00
struct ccw_device * cdev ;
cdev = to_ccwdev ( dev ) ;
if ( ( cdev - > private - > state = = DEV_STATE_DISCONNECTED ) & &
2006-12-08 17:54:28 +03:00
! ccw_device_is_orphan ( cdev ) & &
2006-10-11 17:31:38 +04:00
ccw_dev_id_is_equal ( & cdev - > private - > dev_id , & d - > dev_id ) & &
2006-12-08 17:54:28 +03:00
( cdev ! = d - > sibling ) )
2005-06-26 01:55:27 +04:00
return 1 ;
return 0 ;
}
2006-10-11 17:31:38 +04:00
static struct ccw_device * get_disc_ccwdev_by_dev_id ( struct ccw_dev_id * dev_id ,
struct ccw_device * sibling )
2005-04-17 02:20:36 +04:00
{
struct device * dev ;
2006-08-30 16:33:35 +04:00
struct match_data data ;
2005-04-17 02:20:36 +04:00
2006-10-11 17:31:38 +04:00
data . dev_id = * dev_id ;
2006-08-30 16:33:35 +04:00
data . sibling = sibling ;
2005-10-11 19:28:59 +04:00
dev = bus_find_device ( & ccw_bus_type , NULL , & data , match_devno ) ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:55:27 +04:00
return dev ? to_ccwdev ( dev ) : NULL ;
2005-04-17 02:20:36 +04:00
}
2006-12-08 17:54:28 +03:00
static int match_orphan ( struct device * dev , void * data )
{
struct ccw_dev_id * dev_id ;
struct ccw_device * cdev ;
dev_id = data ;
cdev = to_ccwdev ( dev ) ;
return ccw_dev_id_is_equal ( & cdev - > private - > dev_id , dev_id ) ;
}
static struct ccw_device *
get_orphaned_ccwdev_by_dev_id ( struct channel_subsystem * css ,
struct ccw_dev_id * dev_id )
{
struct device * dev ;
dev = device_find_child ( & css - > pseudo_subchannel - > dev , dev_id ,
match_orphan ) ;
return dev ? to_ccwdev ( dev ) : NULL ;
}
2005-04-17 02:20:36 +04:00
static void
2006-12-08 17:53:57 +03:00
ccw_device_add_changed ( struct work_struct * work )
2005-04-17 02:20:36 +04:00
{
2006-12-08 17:53:57 +03:00
struct ccw_device_private * priv ;
2005-04-17 02:20:36 +04:00
struct ccw_device * cdev ;
2006-12-08 17:53:57 +03:00
priv = container_of ( work , struct ccw_device_private , kick_work ) ;
cdev = priv - > cdev ;
2005-04-17 02:20:36 +04:00
if ( device_add ( & cdev - > dev ) ) {
put_device ( & cdev - > dev ) ;
return ;
}
set_bit ( 1 , & cdev - > private - > registered ) ;
}
2006-12-08 17:54:28 +03:00
void ccw_device_do_unreg_rereg ( struct work_struct * work )
2005-04-17 02:20:36 +04:00
{
2006-12-08 17:53:57 +03:00
struct ccw_device_private * priv ;
2005-04-17 02:20:36 +04:00
struct ccw_device * cdev ;
struct subchannel * sch ;
2006-12-08 17:53:57 +03:00
priv = container_of ( work , struct ccw_device_private , kick_work ) ;
cdev = priv - > cdev ;
2005-04-17 02:20:36 +04:00
sch = to_subchannel ( cdev - > dev . parent ) ;
2006-12-08 17:54:28 +03:00
2007-04-27 18:01:39 +04:00
ccw_device_unregister ( cdev ) ;
2005-04-17 02:20:36 +04:00
PREPARE_WORK ( & cdev - > private - > kick_work ,
2006-12-08 17:53:57 +03:00
ccw_device_add_changed ) ;
2005-04-17 02:20:36 +04:00
queue_work ( ccw_device_work , & cdev - > private - > kick_work ) ;
}
static void
ccw_device_release ( struct device * dev )
{
struct ccw_device * cdev ;
cdev = to_ccwdev ( dev ) ;
2008-12-25 15:39:07 +03:00
/* Release reference of parent subchannel. */
put_device ( cdev - > dev . parent ) ;
2005-04-17 02:20:36 +04:00
kfree ( cdev - > private ) ;
kfree ( cdev ) ;
}
2006-12-08 17:54:21 +03:00
static struct ccw_device * io_subchannel_allocate_dev ( struct subchannel * sch )
{
struct ccw_device * cdev ;
cdev = kzalloc ( sizeof ( * cdev ) , GFP_KERNEL ) ;
if ( cdev ) {
cdev - > private = kzalloc ( sizeof ( struct ccw_device_private ) ,
GFP_KERNEL | GFP_DMA ) ;
if ( cdev - > private )
return cdev ;
}
kfree ( cdev ) ;
return ERR_PTR ( - ENOMEM ) ;
}
static int io_subchannel_initialize_dev ( struct subchannel * sch ,
struct ccw_device * cdev )
{
cdev - > private - > cdev = cdev ;
atomic_set ( & cdev - > private - > onoff , 0 ) ;
cdev - > dev . parent = & sch - > dev ;
cdev - > dev . release = ccw_device_release ;
2007-11-05 13:10:07 +03:00
INIT_WORK ( & cdev - > private - > kick_work , NULL ) ;
2007-04-27 18:01:39 +04:00
cdev - > dev . groups = ccwdev_attr_groups ;
2006-12-08 17:54:21 +03:00
/* Do first half of device_register. */
device_initialize ( & cdev - > dev ) ;
if ( ! get_device ( & sch - > dev ) ) {
if ( cdev - > dev . release )
cdev - > dev . release ( & cdev - > dev ) ;
return - ENODEV ;
}
return 0 ;
}
static struct ccw_device * io_subchannel_create_ccwdev ( struct subchannel * sch )
{
struct ccw_device * cdev ;
int ret ;
cdev = io_subchannel_allocate_dev ( sch ) ;
if ( ! IS_ERR ( cdev ) ) {
ret = io_subchannel_initialize_dev ( sch , cdev ) ;
if ( ret ) {
kfree ( cdev ) ;
cdev = ERR_PTR ( ret ) ;
}
}
return cdev ;
}
2006-12-08 17:54:28 +03:00
static int io_subchannel_recog ( struct ccw_device * , struct subchannel * ) ;
static void sch_attach_device ( struct subchannel * sch ,
struct ccw_device * cdev )
{
2007-04-27 18:01:36 +04:00
css_update_ssd_info ( sch ) ;
2006-12-08 17:54:28 +03:00
spin_lock_irq ( sch - > lock ) ;
2008-01-26 16:10:46 +03:00
sch_set_cdev ( sch , cdev ) ;
2006-12-08 17:54:28 +03:00
cdev - > private - > schid = sch - > schid ;
cdev - > ccwlock = sch - > lock ;
2008-07-14 11:58:45 +04:00
ccw_device_trigger_reprobe ( cdev ) ;
2006-12-08 17:54:28 +03:00
spin_unlock_irq ( sch - > lock ) ;
}
static void sch_attach_disconnected_device ( struct subchannel * sch ,
struct ccw_device * cdev )
{
struct subchannel * other_sch ;
int ret ;
2008-12-25 15:39:07 +03:00
/* Get reference for new parent. */
if ( ! get_device ( & sch - > dev ) )
return ;
other_sch = to_subchannel ( cdev - > dev . parent ) ;
/* Note: device_move() changes cdev->dev.parent */
2006-12-08 17:54:28 +03:00
ret = device_move ( & cdev - > dev , & sch - > dev ) ;
if ( ret ) {
2008-05-07 11:22:54 +04:00
CIO_MSG_EVENT ( 0 , " Moving disconnected device 0.%x.%04x failed "
2006-12-08 17:54:28 +03:00
" (ret=%d)! \n " , cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno , ret ) ;
2008-12-25 15:39:07 +03:00
/* Put reference for new parent. */
put_device ( & sch - > dev ) ;
2006-12-08 17:54:28 +03:00
return ;
}
2008-01-26 16:10:46 +03:00
sch_set_cdev ( other_sch , NULL ) ;
2006-12-08 17:54:28 +03:00
/* No need to keep a subchannel without ccw device around. */
css_sch_device_unregister ( other_sch ) ;
sch_attach_device ( sch , cdev ) ;
2008-12-25 15:39:07 +03:00
/* Put reference for old parent. */
put_device ( & other_sch - > dev ) ;
2006-12-08 17:54:28 +03:00
}
static void sch_attach_orphaned_device ( struct subchannel * sch ,
struct ccw_device * cdev )
{
int ret ;
2008-12-25 15:39:07 +03:00
struct subchannel * pseudo_sch ;
2006-12-08 17:54:28 +03:00
2008-12-25 15:39:07 +03:00
/* Get reference for new parent. */
if ( ! get_device ( & sch - > dev ) )
return ;
pseudo_sch = to_subchannel ( cdev - > dev . parent ) ;
/*
* Try to move the ccw device to its new subchannel .
* Note : device_move ( ) changes cdev - > dev . parent
*/
2006-12-08 17:54:28 +03:00
ret = device_move ( & cdev - > dev , & sch - > dev ) ;
if ( ret ) {
CIO_MSG_EVENT ( 0 , " Moving device 0.%x.%04x from orphanage "
" failed (ret=%d)! \n " ,
cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno , ret ) ;
2008-12-25 15:39:07 +03:00
/* Put reference for new parent. */
put_device ( & sch - > dev ) ;
2006-12-08 17:54:28 +03:00
return ;
}
sch_attach_device ( sch , cdev ) ;
2008-12-25 15:39:07 +03:00
/* Put reference on pseudo subchannel. */
put_device ( & pseudo_sch - > dev ) ;
2006-12-08 17:54:28 +03:00
}
static void sch_create_and_recog_new_device ( struct subchannel * sch )
{
struct ccw_device * cdev ;
/* Need to allocate a new ccw device. */
cdev = io_subchannel_create_ccwdev ( sch ) ;
if ( IS_ERR ( cdev ) ) {
/* OK, we did everything we could... */
css_sch_device_unregister ( sch ) ;
return ;
}
spin_lock_irq ( sch - > lock ) ;
2008-01-26 16:10:46 +03:00
sch_set_cdev ( sch , cdev ) ;
2006-12-08 17:54:28 +03:00
spin_unlock_irq ( sch - > lock ) ;
/* Start recognition for the new ccw device. */
if ( io_subchannel_recog ( cdev , sch ) ) {
spin_lock_irq ( sch - > lock ) ;
2008-01-26 16:10:46 +03:00
sch_set_cdev ( sch , NULL ) ;
2006-12-08 17:54:28 +03:00
spin_unlock_irq ( sch - > lock ) ;
css_sch_device_unregister ( sch ) ;
2008-12-25 15:39:07 +03:00
/* Put reference from io_subchannel_create_ccwdev(). */
put_device ( & sch - > dev ) ;
/* Give up initial reference. */
put_device ( & cdev - > dev ) ;
2006-12-08 17:54:28 +03:00
}
}
void ccw_device_move_to_orphanage ( struct work_struct * work )
{
struct ccw_device_private * priv ;
struct ccw_device * cdev ;
struct ccw_device * replacing_cdev ;
struct subchannel * sch ;
int ret ;
struct channel_subsystem * css ;
struct ccw_dev_id dev_id ;
priv = container_of ( work , struct ccw_device_private , kick_work ) ;
cdev = priv - > cdev ;
sch = to_subchannel ( cdev - > dev . parent ) ;
css = to_css ( sch - > dev . parent ) ;
dev_id . devno = sch - > schib . pmcw . dev ;
dev_id . ssid = sch - > schid . ssid ;
2008-12-25 15:39:07 +03:00
/* Increase refcount for pseudo subchannel. */
get_device ( & css - > pseudo_subchannel - > dev ) ;
2006-12-08 17:54:28 +03:00
/*
* Move the orphaned ccw device to the orphanage so the replacing
* ccw device can take its place on the subchannel .
2008-12-25 15:39:07 +03:00
* Note : device_move ( ) changes cdev - > dev . parent
2006-12-08 17:54:28 +03:00
*/
ret = device_move ( & cdev - > dev , & css - > pseudo_subchannel - > dev ) ;
if ( ret ) {
CIO_MSG_EVENT ( 0 , " Moving device 0.%x.%04x to orphanage failed "
" (ret=%d)! \n " , cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno , ret ) ;
2008-12-25 15:39:07 +03:00
/* Decrease refcount for pseudo subchannel again. */
put_device ( & css - > pseudo_subchannel - > dev ) ;
2006-12-08 17:54:28 +03:00
return ;
}
cdev - > ccwlock = css - > pseudo_subchannel - > lock ;
/*
* Search for the replacing ccw device
* - among the disconnected devices
* - in the orphanage
*/
replacing_cdev = get_disc_ccwdev_by_dev_id ( & dev_id , cdev ) ;
if ( replacing_cdev ) {
sch_attach_disconnected_device ( sch , replacing_cdev ) ;
2008-11-14 20:18:06 +03:00
/* Release reference from get_disc_ccwdev_by_dev_id() */
2008-12-25 15:39:05 +03:00
put_device ( & replacing_cdev - > dev ) ;
2008-12-25 15:39:07 +03:00
/* Release reference of subchannel from old cdev. */
put_device ( & sch - > dev ) ;
2006-12-08 17:54:28 +03:00
return ;
}
replacing_cdev = get_orphaned_ccwdev_by_dev_id ( css , & dev_id ) ;
if ( replacing_cdev ) {
sch_attach_orphaned_device ( sch , replacing_cdev ) ;
2008-11-14 20:18:06 +03:00
/* Release reference from get_orphaned_ccwdev_by_dev_id() */
2008-12-25 15:39:05 +03:00
put_device ( & replacing_cdev - > dev ) ;
2008-12-25 15:39:07 +03:00
/* Release reference of subchannel from old cdev. */
put_device ( & sch - > dev ) ;
2006-12-08 17:54:28 +03:00
return ;
}
sch_create_and_recog_new_device ( sch ) ;
2008-12-25 15:39:07 +03:00
/* Release reference of subchannel from old cdev. */
put_device ( & sch - > dev ) ;
2006-12-08 17:54:28 +03:00
}
2005-04-17 02:20:36 +04:00
/*
* Register recognized device .
*/
static void
2006-12-08 17:53:57 +03:00
io_subchannel_register ( struct work_struct * work )
2005-04-17 02:20:36 +04:00
{
2006-12-08 17:53:57 +03:00
struct ccw_device_private * priv ;
2005-04-17 02:20:36 +04:00
struct ccw_device * cdev ;
struct subchannel * sch ;
int ret ;
unsigned long flags ;
2006-12-08 17:53:57 +03:00
priv = container_of ( work , struct ccw_device_private , kick_work ) ;
cdev = priv - > cdev ;
2005-04-17 02:20:36 +04:00
sch = to_subchannel ( cdev - > dev . parent ) ;
2008-12-25 15:39:08 +03:00
/*
* Check if subchannel is still registered . It may have become
* unregistered if a machine check hit us after finishing
* device recognition but before the register work could be
* queued .
*/
if ( ! device_is_registered ( & sch - > dev ) )
goto out_err ;
2007-04-27 18:01:36 +04:00
css_update_ssd_info ( sch ) ;
2006-12-04 17:41:07 +03:00
/*
* io_subchannel_register ( ) will also be called after device
* recognition has been done for a boxed device ( which will already
* be registered ) . We need to reprobe since we may now have sense id
* information .
*/
2005-06-26 01:55:27 +04:00
if ( klist_node_attached ( & cdev - > dev . knode_parent ) ) {
2006-12-04 17:41:07 +03:00
if ( ! cdev - > drv ) {
ret = device_reprobe ( & cdev - > dev ) ;
if ( ret )
/* We can't do much here. */
2008-05-07 11:22:54 +04:00
CIO_MSG_EVENT ( 0 , " device_reprobe() returned "
2007-07-27 14:29:19 +04:00
" %d for 0.%x.%04x \n " , ret ,
cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno ) ;
2006-12-04 17:41:07 +03:00
}
2005-04-17 02:20:36 +04:00
goto out ;
}
2007-04-26 11:12:03 +04:00
/*
* Now we know this subchannel will stay , we can throw
* our delayed uevent .
*/
sch - > dev . uevent_suppress = 0 ;
kobject_uevent ( & sch - > dev . kobj , KOBJ_ADD ) ;
2005-04-17 02:20:36 +04:00
/* make it known to the system */
ret = ccw_device_register ( cdev ) ;
if ( ret ) {
2007-07-27 14:29:19 +04:00
CIO_MSG_EVENT ( 0 , " Could not register ccw dev 0.%x.%04x: %d \n " ,
cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno , ret ) ;
2006-12-08 17:54:26 +03:00
spin_lock_irqsave ( sch - > lock , flags ) ;
2008-01-26 16:10:46 +03:00
sch_set_cdev ( sch , NULL ) ;
2006-12-08 17:54:26 +03:00
spin_unlock_irqrestore ( sch - > lock , flags ) ;
2008-12-25 15:39:07 +03:00
/* Release initial device reference. */
put_device ( & cdev - > dev ) ;
2008-12-25 15:39:08 +03:00
goto out_err ;
2005-04-17 02:20:36 +04:00
}
out :
cdev - > private - > flags . recog_done = 1 ;
wake_up ( & cdev - > private - > wait_q ) ;
2008-12-25 15:39:08 +03:00
out_err :
/* Release reference for workqueue processing. */
put_device ( & cdev - > dev ) ;
2005-04-17 02:20:36 +04:00
if ( atomic_dec_and_test ( & ccw_device_init_count ) )
wake_up ( & ccw_device_init_wq ) ;
}
2007-10-12 18:11:26 +04:00
static void ccw_device_call_sch_unregister ( struct work_struct * work )
2005-04-17 02:20:36 +04:00
{
2006-12-08 17:53:57 +03:00
struct ccw_device_private * priv ;
struct ccw_device * cdev ;
2005-04-17 02:20:36 +04:00
struct subchannel * sch ;
2006-12-08 17:53:57 +03:00
priv = container_of ( work , struct ccw_device_private , kick_work ) ;
cdev = priv - > cdev ;
2008-10-10 23:33:05 +04:00
/* Get subchannel reference for local processing. */
if ( ! get_device ( cdev - > dev . parent ) )
return ;
2005-04-17 02:20:36 +04:00
sch = to_subchannel ( cdev - > dev . parent ) ;
2006-07-12 18:39:50 +04:00
css_sch_device_unregister ( sch ) ;
2005-04-17 02:20:36 +04:00
/* Reset intparm to zeroes. */
sch - > schib . pmcw . intparm = 0 ;
cio_modify ( sch ) ;
2008-10-10 23:33:05 +04:00
/* Release cdev reference for workqueue processing.*/
2005-04-17 02:20:36 +04:00
put_device ( & cdev - > dev ) ;
2008-10-10 23:33:05 +04:00
/* Release subchannel reference for local processing. */
2005-04-17 02:20:36 +04:00
put_device ( & sch - > dev ) ;
}
/*
* subchannel recognition done . Called from the state machine .
*/
void
io_subchannel_recog_done ( struct ccw_device * cdev )
{
struct subchannel * sch ;
if ( css_init_done = = 0 ) {
cdev - > private - > flags . recog_done = 1 ;
return ;
}
switch ( cdev - > private - > state ) {
case DEV_STATE_NOT_OPER :
cdev - > private - > flags . recog_done = 1 ;
/* Remove device found not operational. */
if ( ! get_device ( & cdev - > dev ) )
break ;
sch = to_subchannel ( cdev - > dev . parent ) ;
PREPARE_WORK ( & cdev - > private - > kick_work ,
2006-12-08 17:53:57 +03:00
ccw_device_call_sch_unregister ) ;
2005-04-17 02:20:36 +04:00
queue_work ( slow_path_wq , & cdev - > private - > kick_work ) ;
if ( atomic_dec_and_test ( & ccw_device_init_count ) )
wake_up ( & ccw_device_init_wq ) ;
break ;
case DEV_STATE_BOXED :
/* Device did not respond in time. */
case DEV_STATE_OFFLINE :
/*
* We can ' t register the device in interrupt context so
* we schedule a work item .
*/
if ( ! get_device ( & cdev - > dev ) )
break ;
PREPARE_WORK ( & cdev - > private - > kick_work ,
2006-12-08 17:53:57 +03:00
io_subchannel_register ) ;
2005-04-17 02:20:36 +04:00
queue_work ( slow_path_wq , & cdev - > private - > kick_work ) ;
break ;
}
}
static int
io_subchannel_recog ( struct ccw_device * cdev , struct subchannel * sch )
{
int rc ;
struct ccw_device_private * priv ;
2008-01-26 16:10:46 +03:00
sch_set_cdev ( sch , cdev ) ;
2006-12-08 17:54:26 +03:00
cdev - > ccwlock = sch - > lock ;
2006-01-06 11:19:25 +03:00
2005-04-17 02:20:36 +04:00
/* Init private data. */
priv = cdev - > private ;
2006-10-11 17:31:38 +04:00
priv - > dev_id . devno = sch - > schib . pmcw . dev ;
priv - > dev_id . ssid = sch - > schid . ssid ;
priv - > schid = sch - > schid ;
2005-04-17 02:20:36 +04:00
priv - > state = DEV_STATE_NOT_OPER ;
INIT_LIST_HEAD ( & priv - > cmb_list ) ;
init_waitqueue_head ( & priv - > wait_q ) ;
init_timer ( & priv - > timer ) ;
/* Set an initial name for the device. */
2008-10-10 23:33:14 +04:00
if ( cio_is_console ( sch - > schid ) )
cdev - > dev . init_name = cio_get_console_cdev_name ( sch ) ;
else
dev_set_name ( & cdev - > dev , " 0.%x.%04x " ,
sch - > schid . ssid , sch - > schib . pmcw . dev ) ;
2005-04-17 02:20:36 +04:00
/* Increase counter of devices currently in recognition. */
atomic_inc ( & ccw_device_init_count ) ;
/* Start async. device sensing. */
2006-12-08 17:54:26 +03:00
spin_lock_irq ( sch - > lock ) ;
2005-04-17 02:20:36 +04:00
rc = ccw_device_recognition ( cdev ) ;
2006-12-08 17:54:26 +03:00
spin_unlock_irq ( sch - > lock ) ;
2005-04-17 02:20:36 +04:00
if ( rc ) {
if ( atomic_dec_and_test ( & ccw_device_init_count ) )
wake_up ( & ccw_device_init_wq ) ;
}
return rc ;
}
2006-12-08 17:54:28 +03:00
static void ccw_device_move_to_sch ( struct work_struct * work )
{
struct ccw_device_private * priv ;
int rc ;
struct subchannel * sch ;
struct ccw_device * cdev ;
struct subchannel * former_parent ;
priv = container_of ( work , struct ccw_device_private , kick_work ) ;
sch = priv - > sch ;
cdev = priv - > cdev ;
2008-12-25 15:39:07 +03:00
former_parent = to_subchannel ( cdev - > dev . parent ) ;
/* Get reference for new parent. */
if ( ! get_device ( & sch - > dev ) )
return ;
2006-12-08 17:54:28 +03:00
mutex_lock ( & sch - > reg_mutex ) ;
2008-12-25 15:39:07 +03:00
/*
* Try to move the ccw device to its new subchannel .
* Note : device_move ( ) changes cdev - > dev . parent
*/
2006-12-08 17:54:28 +03:00
rc = device_move ( & cdev - > dev , & sch - > dev ) ;
mutex_unlock ( & sch - > reg_mutex ) ;
if ( rc ) {
2008-05-07 11:22:54 +04:00
CIO_MSG_EVENT ( 0 , " Moving device 0.%x.%04x to subchannel "
2006-12-08 17:54:28 +03:00
" 0.%x.%04x failed (ret=%d)! \n " ,
cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno , sch - > schid . ssid ,
sch - > schid . sch_no , rc ) ;
css_sch_device_unregister ( sch ) ;
2008-12-25 15:39:07 +03:00
/* Put reference for new parent again. */
put_device ( & sch - > dev ) ;
2006-12-08 17:54:28 +03:00
goto out ;
}
2008-12-25 15:39:07 +03:00
if ( ! sch_is_pseudo_sch ( former_parent ) ) {
2006-12-08 17:54:28 +03:00
spin_lock_irq ( former_parent - > lock ) ;
2008-01-26 16:10:46 +03:00
sch_set_cdev ( former_parent , NULL ) ;
2006-12-08 17:54:28 +03:00
spin_unlock_irq ( former_parent - > lock ) ;
css_sch_device_unregister ( former_parent ) ;
/* Reset intparm to zeroes. */
former_parent - > schib . pmcw . intparm = 0 ;
cio_modify ( former_parent ) ;
}
sch_attach_device ( sch , cdev ) ;
out :
2008-12-25 15:39:07 +03:00
/* Put reference for old parent. */
put_device ( & former_parent - > dev ) ;
2006-12-08 17:54:28 +03:00
put_device ( & cdev - > dev ) ;
}
2008-01-26 16:10:39 +03:00
static void io_subchannel_irq ( struct subchannel * sch )
{
struct ccw_device * cdev ;
2008-01-26 16:10:46 +03:00
cdev = sch_get_cdev ( sch ) ;
2008-01-26 16:10:39 +03:00
CIO_TRACE_EVENT ( 3 , " IRQ " ) ;
2008-10-10 23:33:09 +04:00
CIO_TRACE_EVENT ( 3 , dev_name ( & sch - > dev ) ) ;
2008-01-26 16:10:39 +03:00
if ( cdev )
dev_fsm_event ( cdev , DEV_EVENT_INTERRUPT ) ;
}
2008-07-14 11:58:43 +04:00
static void io_subchannel_init_fields ( struct subchannel * sch )
{
if ( cio_is_console ( sch - > schid ) )
sch - > opm = 0xff ;
else
sch - > opm = chp_get_sch_opm ( sch ) ;
sch - > lpm = sch - > schib . pmcw . pam & sch - > opm ;
2008-07-14 11:58:58 +04:00
sch - > isc = cio_is_console ( sch - > schid ) ? CONSOLE_ISC : IO_SCH_ISC ;
2008-07-14 11:58:43 +04:00
CIO_MSG_EVENT ( 6 , " Detected device %04x on subchannel 0.%x.%04X "
" - PIM = %02X, PAM = %02X, POM = %02X \n " ,
sch - > schib . pmcw . dev , sch - > schid . ssid ,
sch - > schid . sch_no , sch - > schib . pmcw . pim ,
sch - > schib . pmcw . pam , sch - > schib . pmcw . pom ) ;
/* Initially set up some fields in the pmcw. */
sch - > schib . pmcw . ena = 0 ;
sch - > schib . pmcw . csense = 1 ; /* concurrent sense */
if ( ( sch - > lpm & ( sch - > lpm - 1 ) ) ! = 0 )
sch - > schib . pmcw . mp = 1 ; /* multipath mode */
/* clean up possible residual cmf stuff */
sch - > schib . pmcw . mme = 0 ;
sch - > schib . pmcw . mbfc = 0 ;
sch - > schib . pmcw . mbi = 0 ;
sch - > schib . mba = 0 ;
}
2008-12-25 15:39:09 +03:00
static void io_subchannel_do_unreg ( struct work_struct * work )
{
struct subchannel * sch ;
sch = container_of ( work , struct subchannel , work ) ;
css_sch_device_unregister ( sch ) ;
/* Reset intparm to zeroes. */
sch - > schib . pmcw . intparm = 0 ;
cio_modify ( sch ) ;
put_device ( & sch - > dev ) ;
}
/* Schedule unregister if we have no cdev. */
static void io_subchannel_schedule_removal ( struct subchannel * sch )
{
get_device ( & sch - > dev ) ;
INIT_WORK ( & sch - > work , io_subchannel_do_unreg ) ;
queue_work ( slow_path_wq , & sch - > work ) ;
}
/*
* Note : We always return 0 so that we bind to the device even on error .
* This is needed so that our remove function is called on unregister .
*/
2008-07-14 11:58:43 +04:00
static int io_subchannel_probe ( struct subchannel * sch )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev ;
int rc ;
unsigned long flags ;
2006-12-08 17:54:28 +03:00
struct ccw_dev_id dev_id ;
2005-04-17 02:20:36 +04:00
2008-01-26 16:10:46 +03:00
cdev = sch_get_cdev ( sch ) ;
if ( cdev ) {
2008-07-14 11:58:44 +04:00
rc = sysfs_create_group ( & sch - > dev . kobj ,
& io_subchannel_attr_group ) ;
if ( rc )
CIO_MSG_EVENT ( 0 , " Failed to create io subchannel "
" attributes for subchannel "
" 0.%x.%04x (rc=%d) \n " ,
sch - > schid . ssid , sch - > schid . sch_no , rc ) ;
2005-04-17 02:20:36 +04:00
/*
* This subchannel already has an associated ccw_device .
2008-07-14 11:58:44 +04:00
* Throw the delayed uevent for the subchannel , register
* the ccw_device and exit . This happens for all early
* devices , e . g . the console .
2005-04-17 02:20:36 +04:00
*/
2008-07-14 11:58:44 +04:00
sch - > dev . uevent_suppress = 0 ;
kobject_uevent ( & sch - > dev . kobj , KOBJ_ADD ) ;
2007-10-12 18:11:23 +04:00
cdev - > dev . groups = ccwdev_attr_groups ;
2005-04-17 02:20:36 +04:00
device_initialize ( & cdev - > dev ) ;
ccw_device_register ( cdev ) ;
/*
* Check if the device is already online . If it is
2008-12-25 15:39:06 +03:00
* the reference count needs to be corrected since we
* didn ' t obtain a reference in ccw_device_set_online .
2005-04-17 02:20:36 +04:00
*/
if ( cdev - > private - > state ! = DEV_STATE_NOT_OPER & &
cdev - > private - > state ! = DEV_STATE_OFFLINE & &
cdev - > private - > state ! = DEV_STATE_BOXED )
get_device ( & cdev - > dev ) ;
return 0 ;
}
2008-07-14 11:58:43 +04:00
io_subchannel_init_fields ( sch ) ;
2008-07-14 11:58:44 +04:00
rc = sysfs_create_group ( & sch - > dev . kobj ,
& io_subchannel_attr_group ) ;
if ( rc )
2008-12-25 15:39:09 +03:00
goto out_schedule ;
2008-01-26 16:10:43 +03:00
/* Allocate I/O subchannel private data. */
sch - > private = kzalloc ( sizeof ( struct io_subchannel_private ) ,
GFP_KERNEL | GFP_DMA ) ;
2008-12-25 15:39:09 +03:00
if ( ! sch - > private )
2008-07-14 11:58:44 +04:00
goto out_err ;
2008-12-25 15:39:03 +03:00
/*
* First check if a fitting device may be found amongst the
* disconnected devices or in the orphanage .
*/
dev_id . devno = sch - > schib . pmcw . dev ;
dev_id . ssid = sch - > schid . ssid ;
2006-12-08 17:54:28 +03:00
cdev = get_disc_ccwdev_by_dev_id ( & dev_id , NULL ) ;
if ( ! cdev )
cdev = get_orphaned_ccwdev_by_dev_id ( to_css ( sch - > dev . parent ) ,
& dev_id ) ;
if ( cdev ) {
/*
* Schedule moving the device until when we have a registered
* subchannel to move to and succeed the probe . We can
* unregister later again , when the probe is through .
*/
cdev - > private - > sch = sch ;
PREPARE_WORK ( & cdev - > private - > kick_work ,
ccw_device_move_to_sch ) ;
queue_work ( slow_path_wq , & cdev - > private - > kick_work ) ;
return 0 ;
}
2006-12-08 17:54:21 +03:00
cdev = io_subchannel_create_ccwdev ( sch ) ;
2008-12-25 15:39:09 +03:00
if ( IS_ERR ( cdev ) )
2008-07-14 11:58:44 +04:00
goto out_err ;
2006-01-11 12:56:22 +03:00
rc = io_subchannel_recog ( cdev , sch ) ;
2005-04-17 02:20:36 +04:00
if ( rc ) {
2006-12-08 17:54:26 +03:00
spin_lock_irqsave ( sch - > lock , flags ) ;
2008-12-25 15:39:09 +03:00
io_subchannel_recog_done ( cdev ) ;
2006-12-08 17:54:26 +03:00
spin_unlock_irqrestore ( sch - > lock , flags ) ;
2005-04-17 02:20:36 +04:00
}
2008-07-14 11:58:44 +04:00
return 0 ;
out_err :
kfree ( sch - > private ) ;
sysfs_remove_group ( & sch - > dev . kobj , & io_subchannel_attr_group ) ;
2008-12-25 15:39:09 +03:00
out_schedule :
io_subchannel_schedule_removal ( sch ) ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
static int
2006-01-11 12:56:22 +03:00
io_subchannel_remove ( struct subchannel * sch )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev ;
unsigned long flags ;
2008-01-26 16:10:46 +03:00
cdev = sch_get_cdev ( sch ) ;
if ( ! cdev )
2005-04-17 02:20:36 +04:00
return 0 ;
/* Set ccw device to not operational and drop reference. */
spin_lock_irqsave ( cdev - > ccwlock , flags ) ;
2008-01-26 16:10:46 +03:00
sch_set_cdev ( sch , NULL ) ;
2005-04-17 02:20:36 +04:00
cdev - > private - > state = DEV_STATE_NOT_OPER ;
spin_unlock_irqrestore ( cdev - > ccwlock , flags ) ;
2007-04-27 18:01:39 +04:00
ccw_device_unregister ( cdev ) ;
put_device ( & cdev - > dev ) ;
2008-01-26 16:10:43 +03:00
kfree ( sch - > private ) ;
2008-07-14 11:58:44 +04:00
sysfs_remove_group ( & sch - > dev . kobj , & io_subchannel_attr_group ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2008-01-26 16:10:39 +03:00
static int io_subchannel_notify ( struct subchannel * sch , int event )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev ;
2008-01-26 16:10:46 +03:00
cdev = sch_get_cdev ( sch ) ;
2005-04-17 02:20:36 +04:00
if ( ! cdev )
return 0 ;
2008-07-14 11:58:45 +04:00
return ccw_device_notify ( cdev , event ) ;
2005-04-17 02:20:36 +04:00
}
2008-01-26 16:10:39 +03:00
static void io_subchannel_verify ( struct subchannel * sch )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev ;
2008-01-26 16:10:46 +03:00
cdev = sch_get_cdev ( sch ) ;
2005-04-17 02:20:36 +04:00
if ( cdev )
dev_fsm_event ( cdev , DEV_EVENT_VERIFY ) ;
}
2008-07-14 11:58:45 +04:00
static int check_for_io_on_path ( struct subchannel * sch , int mask )
2005-04-17 02:20:36 +04:00
{
2008-07-14 11:58:45 +04:00
int cc ;
2005-04-17 02:20:36 +04:00
2008-07-14 11:58:45 +04:00
cc = stsch ( sch - > schid , & sch - > schib ) ;
if ( cc )
return 0 ;
2008-07-14 11:58:50 +04:00
if ( scsw_actl ( & sch - > schib . scsw ) & & sch - > schib . pmcw . lpum = = mask )
2008-07-14 11:58:45 +04:00
return 1 ;
return 0 ;
}
static void terminate_internal_io ( struct subchannel * sch ,
struct ccw_device * cdev )
{
if ( cio_clear ( sch ) ) {
/* Recheck device in case clear failed. */
sch - > lpm = 0 ;
if ( cdev - > online )
dev_fsm_event ( cdev , DEV_EVENT_VERIFY ) ;
else
css_schedule_eval ( sch - > schid ) ;
2006-12-04 17:41:04 +03:00
return ;
2008-07-14 11:58:45 +04:00
}
2005-04-17 02:20:36 +04:00
cdev - > private - > state = DEV_STATE_CLEAR_VERIFY ;
2008-07-14 11:58:45 +04:00
/* Request retry of internal operation. */
cdev - > private - > flags . intretry = 1 ;
/* Call handler. */
2005-04-17 02:20:36 +04:00
if ( cdev - > handler )
cdev - > handler ( cdev , cdev - > private - > intparm ,
ERR_PTR ( - EIO ) ) ;
}
2008-07-14 11:58:45 +04:00
static void io_subchannel_terminate_path ( struct subchannel * sch , u8 mask )
{
struct ccw_device * cdev ;
cdev = sch_get_cdev ( sch ) ;
if ( ! cdev )
return ;
if ( check_for_io_on_path ( sch , mask ) ) {
if ( cdev - > private - > state = = DEV_STATE_ONLINE )
ccw_device_kill_io ( cdev ) ;
else {
terminate_internal_io ( sch , cdev ) ;
/* Re-start path verification. */
dev_fsm_event ( cdev , DEV_EVENT_VERIFY ) ;
}
} else
/* trigger path verification. */
dev_fsm_event ( cdev , DEV_EVENT_VERIFY ) ;
}
2008-07-14 11:59:02 +04:00
static int io_subchannel_chp_event ( struct subchannel * sch ,
struct chp_link * link , int event )
2008-07-14 11:58:45 +04:00
{
int mask ;
2008-07-14 11:59:02 +04:00
mask = chp_ssd_get_mask ( & sch - > ssd_info , link ) ;
2008-07-14 11:58:45 +04:00
if ( ! mask )
return 0 ;
switch ( event ) {
case CHP_VARY_OFF :
sch - > opm & = ~ mask ;
sch - > lpm & = ~ mask ;
io_subchannel_terminate_path ( sch , mask ) ;
break ;
case CHP_VARY_ON :
sch - > opm | = mask ;
sch - > lpm | = mask ;
io_subchannel_verify ( sch ) ;
break ;
case CHP_OFFLINE :
if ( stsch ( sch - > schid , & sch - > schib ) )
return - ENXIO ;
if ( ! css_sch_is_valid ( & sch - > schib ) )
return - ENODEV ;
io_subchannel_terminate_path ( sch , mask ) ;
break ;
case CHP_ONLINE :
if ( stsch ( sch - > schid , & sch - > schib ) )
return - ENXIO ;
sch - > lpm | = mask & sch - > opm ;
io_subchannel_verify ( sch ) ;
break ;
}
return 0 ;
}
2005-04-17 02:20:36 +04:00
static void
2006-01-11 12:56:22 +03:00
io_subchannel_shutdown ( struct subchannel * sch )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev ;
int ret ;
2008-01-26 16:10:46 +03:00
cdev = sch_get_cdev ( sch ) ;
2005-04-17 02:20:36 +04:00
2006-01-06 11:19:21 +03:00
if ( cio_is_console ( sch - > schid ) )
2005-04-17 02:20:36 +04:00
return ;
if ( ! sch - > schib . pmcw . ena )
/* Nothing to do. */
return ;
ret = cio_disable_subchannel ( sch ) ;
if ( ret ! = - EBUSY )
/* Subchannel is disabled, we're done. */
return ;
cdev - > private - > state = DEV_STATE_QUIESCE ;
if ( cdev - > handler )
cdev - > handler ( cdev , cdev - > private - > intparm ,
ERR_PTR ( - EIO ) ) ;
ret = ccw_device_cancel_halt_clear ( cdev ) ;
if ( ret = = - EBUSY ) {
ccw_device_set_timeout ( cdev , HZ / 10 ) ;
wait_event ( cdev - > private - > wait_q , dev_fsm_final_state ( cdev ) ) ;
}
cio_disable_subchannel ( sch ) ;
}
2008-07-14 11:58:45 +04:00
static int io_subchannel_get_status ( struct subchannel * sch )
{
struct schib schib ;
if ( stsch ( sch - > schid , & schib ) | | ! schib . pmcw . dnv )
return CIO_GONE ;
if ( sch - > schib . pmcw . dnv & & ( schib . pmcw . dev ! = sch - > schib . pmcw . dev ) )
return CIO_REVALIDATE ;
if ( ! sch - > lpm )
return CIO_NO_PATH ;
return CIO_OPER ;
}
static int device_is_disconnected ( struct ccw_device * cdev )
{
if ( ! cdev )
return 0 ;
return ( cdev - > private - > state = = DEV_STATE_DISCONNECTED | |
cdev - > private - > state = = DEV_STATE_DISCONNECTED_SENSE_ID ) ;
}
static int recovery_check ( struct device * dev , void * data )
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
int * redo = data ;
spin_lock_irq ( cdev - > ccwlock ) ;
switch ( cdev - > private - > state ) {
case DEV_STATE_DISCONNECTED :
CIO_MSG_EVENT ( 3 , " recovery: trigger 0.%x.%04x \n " ,
cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno ) ;
dev_fsm_event ( cdev , DEV_EVENT_VERIFY ) ;
* redo = 1 ;
break ;
case DEV_STATE_DISCONNECTED_SENSE_ID :
* redo = 1 ;
break ;
}
spin_unlock_irq ( cdev - > ccwlock ) ;
return 0 ;
}
static void recovery_work_func ( struct work_struct * unused )
{
int redo = 0 ;
bus_for_each_dev ( & ccw_bus_type , NULL , & redo , recovery_check ) ;
if ( redo ) {
spin_lock_irq ( & recovery_lock ) ;
if ( ! timer_pending ( & recovery_timer ) ) {
if ( recovery_phase < ARRAY_SIZE ( recovery_delay ) - 1 )
recovery_phase + + ;
mod_timer ( & recovery_timer , jiffies +
recovery_delay [ recovery_phase ] * HZ ) ;
}
spin_unlock_irq ( & recovery_lock ) ;
} else
CIO_MSG_EVENT ( 4 , " recovery: end \n " ) ;
}
static DECLARE_WORK ( recovery_work , recovery_work_func ) ;
static void recovery_func ( unsigned long data )
{
/*
* We can ' t do our recovery in softirq context and it ' s not
* performance critical , so we schedule it .
*/
schedule_work ( & recovery_work ) ;
}
static void ccw_device_schedule_recovery ( void )
{
unsigned long flags ;
CIO_MSG_EVENT ( 4 , " recovery: schedule \n " ) ;
spin_lock_irqsave ( & recovery_lock , flags ) ;
if ( ! timer_pending ( & recovery_timer ) | | ( recovery_phase ! = 0 ) ) {
recovery_phase = 0 ;
mod_timer ( & recovery_timer , jiffies + recovery_delay [ 0 ] * HZ ) ;
}
spin_unlock_irqrestore ( & recovery_lock , flags ) ;
}
2008-10-10 23:33:06 +04:00
static int purge_fn ( struct device * dev , void * data )
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
struct ccw_device_private * priv = cdev - > private ;
int unreg ;
spin_lock_irq ( cdev - > ccwlock ) ;
unreg = is_blacklisted ( priv - > dev_id . ssid , priv - > dev_id . devno ) & &
( priv - > state = = DEV_STATE_OFFLINE ) ;
spin_unlock_irq ( cdev - > ccwlock ) ;
if ( ! unreg )
goto out ;
if ( ! get_device ( & cdev - > dev ) )
goto out ;
CIO_MSG_EVENT ( 3 , " ccw: purging 0.%x.%04x \n " , priv - > dev_id . ssid ,
priv - > dev_id . devno ) ;
PREPARE_WORK ( & cdev - > private - > kick_work , ccw_device_call_sch_unregister ) ;
queue_work ( slow_path_wq , & cdev - > private - > kick_work ) ;
out :
/* Abort loop in case of pending signal. */
if ( signal_pending ( current ) )
return - EINTR ;
return 0 ;
}
/**
* ccw_purge_blacklisted - purge unused , blacklisted devices
*
* Unregister all ccw devices that are offline and on the blacklist .
*/
int ccw_purge_blacklisted ( void )
{
CIO_MSG_EVENT ( 2 , " ccw: purging blacklisted devices \n " ) ;
bus_for_each_dev ( & ccw_bus_type , NULL , NULL , purge_fn ) ;
return 0 ;
}
2008-07-14 11:58:45 +04:00
static void device_set_disconnected ( struct ccw_device * cdev )
{
if ( ! cdev )
return ;
ccw_device_set_timeout ( cdev , 0 ) ;
cdev - > private - > flags . fake_irb = 0 ;
cdev - > private - > state = DEV_STATE_DISCONNECTED ;
if ( cdev - > online )
ccw_device_schedule_recovery ( ) ;
}
2008-08-21 21:46:39 +04:00
void ccw_device_set_notoper ( struct ccw_device * cdev )
{
struct subchannel * sch = to_subchannel ( cdev - > dev . parent ) ;
CIO_TRACE_EVENT ( 2 , " notoper " ) ;
2008-10-10 23:33:11 +04:00
CIO_TRACE_EVENT ( 2 , dev_name ( & sch - > dev ) ) ;
2008-08-21 21:46:39 +04:00
ccw_device_set_timeout ( cdev , 0 ) ;
cio_disable_subchannel ( sch ) ;
cdev - > private - > state = DEV_STATE_NOT_OPER ;
}
2008-07-14 11:58:45 +04:00
static int io_subchannel_sch_event ( struct subchannel * sch , int slow )
{
int event , ret , disc ;
unsigned long flags ;
2008-08-21 21:46:39 +04:00
enum { NONE , UNREGISTER , UNREGISTER_PROBE , REPROBE , DISC } action ;
2008-07-14 11:58:45 +04:00
struct ccw_device * cdev ;
spin_lock_irqsave ( sch - > lock , flags ) ;
cdev = sch_get_cdev ( sch ) ;
disc = device_is_disconnected ( cdev ) ;
if ( disc & & slow ) {
/* Disconnected devices are evaluated directly only.*/
spin_unlock_irqrestore ( sch - > lock , flags ) ;
return 0 ;
}
/* No interrupt after machine check - kill pending timers. */
if ( cdev )
ccw_device_set_timeout ( cdev , 0 ) ;
if ( ! disc & & ! slow ) {
/* Non-disconnected devices are evaluated on the slow path. */
spin_unlock_irqrestore ( sch - > lock , flags ) ;
return - EAGAIN ;
}
event = io_subchannel_get_status ( sch ) ;
CIO_MSG_EVENT ( 4 , " Evaluating schid 0.%x.%04x, event %d, %s, %s path. \n " ,
sch - > schid . ssid , sch - > schid . sch_no , event ,
disc ? " disconnected " : " normal " ,
slow ? " slow " : " fast " ) ;
/* Analyze subchannel status. */
action = NONE ;
switch ( event ) {
case CIO_NO_PATH :
if ( disc ) {
/* Check if paths have become available. */
action = REPROBE ;
break ;
}
/* fall through */
case CIO_GONE :
/* Ask driver what to do with device. */
2008-08-21 21:46:39 +04:00
if ( io_subchannel_notify ( sch , event ) )
action = DISC ;
else
action = UNREGISTER ;
2008-07-14 11:58:45 +04:00
break ;
case CIO_REVALIDATE :
/* Device will be removed, so no notify necessary. */
if ( disc )
/* Reprobe because immediate unregister might block. */
action = REPROBE ;
else
action = UNREGISTER_PROBE ;
break ;
case CIO_OPER :
if ( disc )
/* Get device operational again. */
action = REPROBE ;
break ;
}
/* Perform action. */
ret = 0 ;
switch ( action ) {
case UNREGISTER :
case UNREGISTER_PROBE :
2008-08-21 21:46:39 +04:00
ccw_device_set_notoper ( cdev ) ;
2008-07-14 11:58:45 +04:00
/* Unregister device (will use subchannel lock). */
spin_unlock_irqrestore ( sch - > lock , flags ) ;
css_sch_device_unregister ( sch ) ;
spin_lock_irqsave ( sch - > lock , flags ) ;
/* Reset intparm to zeroes. */
sch - > schib . pmcw . intparm = 0 ;
cio_modify ( sch ) ;
break ;
case REPROBE :
ccw_device_trigger_reprobe ( cdev ) ;
break ;
2008-08-21 21:46:39 +04:00
case DISC :
device_set_disconnected ( cdev ) ;
break ;
2008-07-14 11:58:45 +04:00
default :
break ;
}
spin_unlock_irqrestore ( sch - > lock , flags ) ;
/* Probe if necessary. */
if ( action = = UNREGISTER_PROBE )
ret = css_probe_device ( sch - > schid ) ;
return ret ;
}
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_CCW_CONSOLE
static struct ccw_device console_cdev ;
2008-10-10 23:33:14 +04:00
static char console_cdev_name [ 10 ] = " 0.x.xxxx " ;
2005-04-17 02:20:36 +04:00
static struct ccw_device_private console_private ;
static int console_cdev_in_use ;
2006-12-08 17:54:26 +03:00
static DEFINE_SPINLOCK ( ccw_console_lock ) ;
spinlock_t * cio_get_console_lock ( void )
{
return & ccw_console_lock ;
}
2008-07-14 11:58:43 +04:00
static int ccw_device_console_enable ( struct ccw_device * cdev ,
struct subchannel * sch )
2005-04-17 02:20:36 +04:00
{
int rc ;
2008-01-26 16:10:43 +03:00
/* Attach subchannel private data. */
sch - > private = cio_get_console_priv ( ) ;
memset ( sch - > private , 0 , sizeof ( struct io_subchannel_private ) ) ;
2008-07-14 11:58:43 +04:00
io_subchannel_init_fields ( sch ) ;
sch - > driver = & io_subchannel_driver ;
2005-04-17 02:20:36 +04:00
/* Initialize the ccw_device structure. */
2006-08-30 16:33:35 +04:00
cdev - > dev . parent = & sch - > dev ;
2005-04-17 02:20:36 +04:00
rc = io_subchannel_recog ( cdev , sch ) ;
if ( rc )
return rc ;
/* Now wait for the async. recognition to come to an end. */
spin_lock_irq ( cdev - > ccwlock ) ;
while ( ! dev_fsm_final_state ( cdev ) )
wait_cons_dev ( ) ;
rc = - EIO ;
if ( cdev - > private - > state ! = DEV_STATE_OFFLINE )
goto out_unlock ;
ccw_device_online ( cdev ) ;
while ( ! dev_fsm_final_state ( cdev ) )
wait_cons_dev ( ) ;
if ( cdev - > private - > state ! = DEV_STATE_ONLINE )
goto out_unlock ;
rc = 0 ;
out_unlock :
spin_unlock_irq ( cdev - > ccwlock ) ;
return 0 ;
}
struct ccw_device *
ccw_device_probe_console ( void )
{
struct subchannel * sch ;
int ret ;
if ( xchg ( & console_cdev_in_use , 1 ) ! = 0 )
2006-02-01 14:06:35 +03:00
return ERR_PTR ( - EBUSY ) ;
2005-04-17 02:20:36 +04:00
sch = cio_probe_console ( ) ;
if ( IS_ERR ( sch ) ) {
console_cdev_in_use = 0 ;
return ( void * ) sch ;
}
memset ( & console_cdev , 0 , sizeof ( struct ccw_device ) ) ;
memset ( & console_private , 0 , sizeof ( struct ccw_device_private ) ) ;
console_cdev . private = & console_private ;
2006-12-08 17:53:57 +03:00
console_private . cdev = & console_cdev ;
2005-04-17 02:20:36 +04:00
ret = ccw_device_console_enable ( & console_cdev , sch ) ;
if ( ret ) {
cio_release_console ( ) ;
console_cdev_in_use = 0 ;
return ERR_PTR ( ret ) ;
}
console_cdev . online = 1 ;
return & console_cdev ;
}
2008-10-10 23:33:14 +04:00
const char * cio_get_console_cdev_name ( struct subchannel * sch )
{
snprintf ( console_cdev_name , 10 , " 0.%x.%04x " ,
sch - > schid . ssid , sch - > schib . pmcw . dev ) ;
return ( const char * ) console_cdev_name ;
}
2005-04-17 02:20:36 +04:00
# endif
/*
* get ccw_device matching the busid , but only if owned by cdrv
*/
2005-06-26 01:55:27 +04:00
static int
__ccwdev_check_busid ( struct device * dev , void * id )
{
char * bus_id ;
2006-10-11 17:31:47 +04:00
bus_id = id ;
2005-06-26 01:55:27 +04:00
2008-12-25 15:38:55 +03:00
return ( strcmp ( bus_id , dev_name ( dev ) ) = = 0 ) ;
2005-06-26 01:55:27 +04:00
}
2007-10-12 18:11:17 +04:00
/**
* get_ccwdev_by_busid ( ) - obtain device from a bus id
* @ cdrv : driver the device is owned by
* @ bus_id : bus id of the device to be searched
*
* This function searches all devices owned by @ cdrv for a device with a bus
* id matching @ bus_id .
* Returns :
* If a match is found , its reference count of the found device is increased
* and it is returned ; else % NULL is returned .
*/
struct ccw_device * get_ccwdev_by_busid ( struct ccw_driver * cdrv ,
const char * bus_id )
2005-04-17 02:20:36 +04:00
{
2005-06-26 01:55:27 +04:00
struct device * dev ;
2005-04-17 02:20:36 +04:00
struct device_driver * drv ;
drv = get_driver ( & cdrv - > driver ) ;
if ( ! drv )
2005-06-26 01:55:27 +04:00
return NULL ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:55:27 +04:00
dev = driver_find_device ( drv , NULL , ( void * ) bus_id ,
__ccwdev_check_busid ) ;
2005-04-17 02:20:36 +04:00
put_driver ( drv ) ;
2006-07-12 18:41:55 +04:00
return dev ? to_ccwdev ( dev ) : NULL ;
2005-04-17 02:20:36 +04:00
}
/************************** device driver handling ************************/
/* This is the implementation of the ccw_driver class. The probe, remove
* and release methods are initially very similar to the device_driver
* implementations , with the difference that they have ccw_device
* arguments .
*
* A ccw driver also contains the information that is needed for
* device matching .
*/
static int
ccw_device_probe ( struct device * dev )
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
struct ccw_driver * cdrv = to_ccwdrv ( dev - > driver ) ;
int ret ;
cdev - > drv = cdrv ; /* to let the driver call _set_online */
ret = cdrv - > probe ? cdrv - > probe ( cdev ) : - ENODEV ;
if ( ret ) {
2006-07-12 18:41:55 +04:00
cdev - > drv = NULL ;
2005-04-17 02:20:36 +04:00
return ret ;
}
return 0 ;
}
static int
ccw_device_remove ( struct device * dev )
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
struct ccw_driver * cdrv = cdev - > drv ;
int ret ;
if ( cdrv - > remove )
cdrv - > remove ( cdev ) ;
if ( cdev - > online ) {
cdev - > online = 0 ;
spin_lock_irq ( cdev - > ccwlock ) ;
ret = ccw_device_offline ( cdev ) ;
spin_unlock_irq ( cdev - > ccwlock ) ;
if ( ret = = 0 )
wait_event ( cdev - > private - > wait_q ,
dev_fsm_final_state ( cdev ) ) ;
else
2008-05-07 11:22:54 +04:00
CIO_MSG_EVENT ( 0 , " ccw_device_offline returned %d, "
2007-07-27 14:29:19 +04:00
" device 0.%x.%04x \n " ,
ret , cdev - > private - > dev_id . ssid ,
cdev - > private - > dev_id . devno ) ;
2008-12-25 15:39:06 +03:00
/* Give up reference obtained in ccw_device_set_online(). */
put_device ( & cdev - > dev ) ;
2005-04-17 02:20:36 +04:00
}
ccw_device_set_timeout ( cdev , 0 ) ;
2006-07-12 18:41:55 +04:00
cdev - > drv = NULL ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2007-10-12 18:11:21 +04:00
static void ccw_device_shutdown ( struct device * dev )
{
struct ccw_device * cdev ;
cdev = to_ccwdev ( dev ) ;
if ( cdev - > drv & & cdev - > drv - > shutdown )
cdev - > drv - > shutdown ( cdev ) ;
2007-10-12 18:11:22 +04:00
disable_cmf ( cdev ) ;
2007-10-12 18:11:21 +04:00
}
2006-01-11 12:56:22 +03:00
struct bus_type ccw_bus_type = {
. name = " ccw " ,
. match = ccw_bus_match ,
. uevent = ccw_uevent ,
. probe = ccw_device_probe ,
. remove = ccw_device_remove ,
2007-10-12 18:11:21 +04:00
. shutdown = ccw_device_shutdown ,
2006-01-11 12:56:22 +03:00
} ;
2007-10-12 18:11:17 +04:00
/**
* ccw_driver_register ( ) - register a ccw driver
* @ cdriver : driver to be registered
*
* This function is mainly a wrapper around driver_register ( ) .
* Returns :
* % 0 on success and a negative error value on failure .
*/
int ccw_driver_register ( struct ccw_driver * cdriver )
2005-04-17 02:20:36 +04:00
{
struct device_driver * drv = & cdriver - > driver ;
drv - > bus = & ccw_bus_type ;
drv - > name = cdriver - > name ;
2008-01-26 16:10:47 +03:00
drv - > owner = cdriver - > owner ;
2005-04-17 02:20:36 +04:00
return driver_register ( drv ) ;
}
2007-10-12 18:11:17 +04:00
/**
* ccw_driver_unregister ( ) - deregister a ccw driver
* @ cdriver : driver to be deregistered
*
* This function is mainly a wrapper around driver_unregister ( ) .
*/
void ccw_driver_unregister ( struct ccw_driver * cdriver )
2005-04-17 02:20:36 +04:00
{
driver_unregister ( & cdriver - > driver ) ;
}
2006-01-06 11:19:21 +03:00
/* Helper func for qdio. */
struct subchannel_id
ccw_device_get_subchannel_id ( struct ccw_device * cdev )
{
struct subchannel * sch ;
sch = to_subchannel ( cdev - > dev . parent ) ;
return sch - > schid ;
}
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;
EXPORT_SYMBOL ( ccw_device_set_online ) ;
EXPORT_SYMBOL ( ccw_device_set_offline ) ;
EXPORT_SYMBOL ( ccw_driver_register ) ;
EXPORT_SYMBOL ( ccw_driver_unregister ) ;
EXPORT_SYMBOL ( get_ccwdev_by_busid ) ;
EXPORT_SYMBOL ( ccw_bus_type ) ;
EXPORT_SYMBOL ( ccw_device_work ) ;
2006-01-06 11:19:21 +03:00
EXPORT_SYMBOL_GPL ( ccw_device_get_subchannel_id ) ;