2005-04-17 02:20:36 +04:00
/*
* drivers / s390 / cio / device . c
* bus driver for ccw devices
*
* Copyright ( C ) 2002 IBM Deutschland Entwicklung GmbH ,
* IBM Corporation
* 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>
# include <asm/ccwdev.h>
# include <asm/cio.h>
2005-10-31 02:03:48 +03:00
# include <asm/param.h> /* HZ */
2005-04-17 02:20:36 +04:00
# include "cio.h"
# include "css.h"
# include "device.h"
# include "ioasm.h"
/******************* 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 ) . */
static int snprint_alias ( char * buf , size_t size , const char * prefix ,
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
2006-09-20 17:59:49 +04:00
len = snprintf ( buf , size , " %sccw:t%04Xm%02X " , prefix , id - > cu_type ,
id - > cu_model ) ;
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 . */
static int ccw_uevent ( struct device * dev , char * * envp , int num_envp ,
char * buffer , int buffer_size )
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
struct ccw_device_id * id = & ( cdev - > id ) ;
int i = 0 ;
int len ;
2005-04-17 02:20:36 +04:00
2006-09-20 17:59:49 +04:00
/* CU_TYPE= */
len = snprintf ( buffer , buffer_size , " CU_TYPE=%04X " , id - > cu_type ) + 1 ;
if ( len > buffer_size | | i > = num_envp )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
envp [ i + + ] = buffer ;
2006-09-20 17:59:49 +04:00
buffer + = len ;
buffer_size - = len ;
/* CU_MODEL= */
len = snprintf ( buffer , buffer_size , " CU_MODEL=%02X " , id - > cu_model ) + 1 ;
if ( len > buffer_size | | i > = num_envp )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2006-09-20 17:59:49 +04:00
envp [ i + + ] = buffer ;
buffer + = len ;
buffer_size - = len ;
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= */
len = snprintf ( buffer , buffer_size , " DEV_TYPE=%04X " , id - > dev_type ) + 1 ;
if ( len > buffer_size | | i > = num_envp )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2006-09-20 17:59:49 +04:00
envp [ i + + ] = buffer ;
buffer + = len ;
buffer_size - = len ;
2005-04-17 02:20:36 +04:00
2006-09-20 17:59:49 +04:00
/* DEV_MODEL= */
len = snprintf ( buffer , buffer_size , " DEV_MODEL=%02X " ,
( unsigned char ) id - > dev_model ) + 1 ;
if ( len > buffer_size | | i > = num_envp )
return - ENOMEM ;
2005-04-17 02:20:36 +04:00
envp [ i + + ] = buffer ;
2006-09-20 17:59:49 +04:00
buffer + = len ;
buffer_size - = len ;
/* MODALIAS= */
len = snprint_alias ( buffer , buffer_size , " MODALIAS= " , id , " " ) + 1 ;
if ( len > buffer_size | | i > = num_envp )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
2006-09-20 17:59:49 +04:00
envp [ i + + ] = buffer ;
buffer + = len ;
buffer_size - = len ;
2005-04-17 02:20:36 +04:00
2006-07-12 18:41:55 +04:00
envp [ i ] = NULL ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2006-01-11 12:56:22 +03:00
struct bus_type ccw_bus_type ;
2005-04-17 02:20:36 +04:00
2006-01-11 12:56:22 +03:00
static int io_subchannel_probe ( struct subchannel * ) ;
static int io_subchannel_remove ( struct subchannel * ) ;
2005-04-17 02:20:36 +04:00
void io_subchannel_irq ( struct device * ) ;
static int io_subchannel_notify ( struct device * , int ) ;
static void io_subchannel_verify ( struct device * ) ;
static void io_subchannel_ioterm ( struct device * ) ;
2006-01-11 12:56:22 +03:00
static void io_subchannel_shutdown ( struct subchannel * ) ;
2005-04-17 02:20:36 +04:00
struct css_driver io_subchannel_driver = {
. subchannel_type = SUBCHANNEL_TYPE_IO ,
. drv = {
. name = " io_subchannel " ,
. bus = & css_bus_type ,
} ,
. irq = io_subchannel_irq ,
. notify = io_subchannel_notify ,
. verify = io_subchannel_verify ,
. termination = io_subchannel_ioterm ,
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 ;
struct workqueue_struct * ccw_device_notify_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
static int __init
init_ccw_bus_type ( void )
{
int ret ;
init_waitqueue_head ( & ccw_device_init_wq ) ;
atomic_set ( & ccw_device_init_count , 0 ) ;
ccw_device_work = create_singlethread_workqueue ( " cio " ) ;
if ( ! ccw_device_work )
return - ENOMEM ; /* FIXME: better errno ? */
ccw_device_notify_work = create_singlethread_workqueue ( " cio_notify " ) ;
if ( ! ccw_device_notify_work ) {
ret = - ENOMEM ; /* FIXME: better errno ? */
goto out_err ;
}
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 ;
if ( ( ret = driver_register ( & io_subchannel_driver . drv ) ) )
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 ( ccw_device_notify_work )
destroy_workqueue ( ccw_device_notify_work ) ;
if ( slow_path_wq )
destroy_workqueue ( slow_path_wq ) ;
return ret ;
}
static void __exit
cleanup_ccw_bus_type ( void )
{
driver_unregister ( & io_subchannel_driver . drv ) ;
bus_unregister ( & ccw_bus_type ) ;
destroy_workqueue ( ccw_device_notify_work ) ;
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 ) ;
struct ssd_info * ssd = & sch - > ssd_info ;
ssize_t ret = 0 ;
int chp ;
for ( chp = 0 ; chp < 8 ; chp + + )
ret + = sprintf ( buf + ret , " %02x " , ssd - > chpid [ chp ] ) ;
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
2006-09-20 17:59:49 +04:00
len = snprint_alias ( buf , PAGE_SIZE , " " , id , " \n " ) + 1 ;
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 " ) ;
}
static void
ccw_device_remove_disconnected ( struct ccw_device * cdev )
{
struct subchannel * sch ;
/*
* Forced offline in disconnected state means
* ' throw away device ' .
*/
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 ) ;
put_device ( & sch - > dev ) ;
}
int
ccw_device_set_offline ( struct ccw_device * cdev )
{
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 ) ;
return ret ;
}
spin_unlock_irq ( cdev - > ccwlock ) ;
if ( ret = = 0 )
wait_event ( cdev - > private - > wait_q , dev_fsm_final_state ( cdev ) ) ;
else {
pr_debug ( " ccw_device_offline returned %d, device %s \n " ,
ret , cdev - > dev . bus_id ) ;
cdev - > online = 1 ;
}
return ret ;
}
int
ccw_device_set_online ( struct ccw_device * cdev )
{
int ret ;
if ( ! cdev )
return - ENODEV ;
if ( cdev - > online | | ! cdev - > drv )
return - EINVAL ;
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 {
pr_debug ( " ccw_device_online returned %d, device %s \n " ,
ret , cdev - > dev . bus_id ) ;
return ret ;
}
if ( cdev - > private - > state ! = DEV_STATE_ONLINE )
return - ENODEV ;
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 ) ) ;
else
pr_debug ( " ccw_device_offline returned %d, device %s \n " ,
ret , cdev - > dev . bus_id ) ;
2006-02-18 00:52:49 +03:00
return ( ret = = 0 ) ? - ENODEV : ret ;
2005-04-17 02:20:36 +04:00
}
static ssize_t
2005-05-17 14:43:27 +04:00
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 ) ;
int i , force , ret ;
char * tmp ;
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 ;
} else {
force = 0 ;
i = simple_strtoul ( buf , & tmp , 16 ) ;
}
if ( i = = 1 ) {
/* Do device recognition, if needed. */
if ( cdev - > id . cu_type = = 0 ) {
ret = ccw_device_recognition ( cdev ) ;
if ( ret ) {
printk ( KERN_WARNING " Couldn't start recognition "
" for device %s (ret=%d) \n " ,
cdev - > dev . bus_id , ret ) ;
goto out ;
}
wait_event ( cdev - > private - > wait_q ,
cdev - > private - > flags . recog_done ) ;
}
if ( cdev - > drv & & cdev - > drv - > set_online )
ccw_device_set_online ( cdev ) ;
} else if ( i = = 0 ) {
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 ) ;
}
if ( force & & cdev - > private - > state = = DEV_STATE_BOXED ) {
ret = ccw_device_stlck ( cdev ) ;
if ( ret ) {
printk ( KERN_WARNING " ccw_device_stlck for device %s "
" returned %d! \n " , cdev - > dev . bus_id , ret ) ;
goto out ;
}
/* Do device recognition, if needed. */
if ( cdev - > id . cu_type = = 0 ) {
cdev - > private - > state = DEV_STATE_NOT_OPER ;
ret = ccw_device_recognition ( cdev ) ;
if ( ret ) {
printk ( KERN_WARNING " Couldn't start recognition "
" for device %s (ret=%d) \n " ,
cdev - > dev . bus_id , ret ) ;
goto out ;
}
wait_event ( cdev - > private - > wait_q ,
cdev - > private - > flags . recog_done ) ;
}
if ( cdev - > drv & & cdev - > drv - > set_online )
ccw_device_set_online ( cdev ) ;
}
out :
if ( cdev - > drv )
module_put ( cdev - > drv - > owner ) ;
atomic_set ( & cdev - > private - > onoff , 0 ) ;
return count ;
}
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 ;
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 ) ;
extern struct device_attribute dev_attr_cmb_enable ;
static DEVICE_ATTR ( availability , 0444 , available_show , NULL ) ;
static struct attribute * subch_attrs [ ] = {
& dev_attr_chpids . attr ,
& dev_attr_pimpampom . attr ,
NULL ,
} ;
static struct attribute_group subch_attr_group = {
. attrs = subch_attrs ,
} ;
static inline int
subchannel_add_files ( struct device * dev )
{
return sysfs_create_group ( & dev - > kobj , & subch_attr_group ) ;
}
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 ,
} ;
static inline int
device_add_files ( struct device * dev )
{
return sysfs_create_group ( & dev - > kobj , & ccwdev_attr_group ) ;
}
static inline void
device_remove_files ( struct device * dev )
{
sysfs_remove_group ( & dev - > kobj , & ccwdev_attr_group ) ;
}
/* this is a simple abstraction for device_register that sets the
* correct bus type and adds the bus specific files */
int
ccw_device_register ( struct ccw_device * cdev )
{
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 ) ;
if ( ( ret = device_add_files ( dev ) ) ) {
if ( test_and_clear_bit ( 1 , & cdev - > private - > registered ) )
device_del ( dev ) ;
}
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-10-11 17:31:38 +04:00
ccw_dev_id_is_equal ( & cdev - > private - > dev_id , & d - > dev_id ) & &
2005-06-26 01:55:27 +04:00
( cdev ! = d - > sibling ) ) {
cdev - > private - > state = DEV_STATE_NOT_OPER ;
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
}
static void
ccw_device_add_changed ( void * data )
{
struct ccw_device * cdev ;
2006-10-11 17:31:47 +04:00
cdev = data ;
2005-04-17 02:20:36 +04:00
if ( device_add ( & cdev - > dev ) ) {
put_device ( & cdev - > dev ) ;
return ;
}
set_bit ( 1 , & cdev - > private - > registered ) ;
if ( device_add_files ( & cdev - > dev ) ) {
if ( test_and_clear_bit ( 1 , & cdev - > private - > registered ) )
device_unregister ( & cdev - > dev ) ;
}
}
extern int css_get_ssd_info ( struct subchannel * sch ) ;
void
ccw_device_do_unreg_rereg ( void * data )
{
struct ccw_device * cdev ;
struct subchannel * sch ;
int need_rename ;
2006-10-11 17:31:47 +04:00
cdev = data ;
2005-04-17 02:20:36 +04:00
sch = to_subchannel ( cdev - > dev . parent ) ;
2006-10-11 17:31:38 +04:00
if ( cdev - > private - > dev_id . devno ! = sch - > schib . pmcw . dev ) {
2005-04-17 02:20:36 +04:00
/*
* The device number has changed . This is usually only when
* a device has been detached under VM and then re - appeared
* on another subchannel because of a different attachment
* order than before . Ideally , we should should just switch
* subchannels , but unfortunately , this is not possible with
* the current implementation .
* Instead , we search for the old subchannel for this device
* number and deregister so there are no collisions with the
* newly registered ccw_device .
* FIXME : Find another solution so the block layer doesn ' t
* get possibly sick . . .
*/
struct ccw_device * other_cdev ;
2006-10-11 17:31:38 +04:00
struct ccw_dev_id dev_id ;
2005-04-17 02:20:36 +04:00
need_rename = 1 ;
2006-10-11 17:31:38 +04:00
dev_id . devno = sch - > schib . pmcw . dev ;
dev_id . ssid = sch - > schid . ssid ;
other_cdev = get_disc_ccwdev_by_dev_id ( & dev_id , cdev ) ;
2005-04-17 02:20:36 +04:00
if ( other_cdev ) {
struct subchannel * other_sch ;
other_sch = to_subchannel ( other_cdev - > dev . parent ) ;
if ( get_device ( & other_sch - > dev ) ) {
2006-01-06 11:19:21 +03:00
stsch ( other_sch - > schid , & other_sch - > schib ) ;
2005-04-17 02:20:36 +04:00
if ( other_sch - > schib . pmcw . dnv ) {
other_sch - > schib . pmcw . intparm = 0 ;
cio_modify ( other_sch ) ;
}
2006-07-12 18:39:50 +04:00
css_sch_device_unregister ( other_sch ) ;
2005-04-17 02:20:36 +04:00
}
}
/* Update ssd info here. */
css_get_ssd_info ( sch ) ;
2006-10-11 17:31:38 +04:00
cdev - > private - > dev_id . devno = sch - > schib . pmcw . dev ;
2005-04-17 02:20:36 +04:00
} else
need_rename = 0 ;
device_remove_files ( & cdev - > dev ) ;
if ( test_and_clear_bit ( 1 , & cdev - > private - > registered ) )
device_del ( & cdev - > dev ) ;
if ( need_rename )
2006-01-06 11:19:25 +03:00
snprintf ( cdev - > dev . bus_id , BUS_ID_SIZE , " 0.%x.%04x " ,
sch - > schid . ssid , sch - > schib . pmcw . dev ) ;
2005-04-17 02:20:36 +04:00
PREPARE_WORK ( & cdev - > private - > kick_work ,
2006-10-11 17:31:47 +04:00
ccw_device_add_changed , cdev ) ;
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 ) ;
kfree ( cdev - > private ) ;
kfree ( cdev ) ;
}
/*
* Register recognized device .
*/
static void
io_subchannel_register ( void * data )
{
struct ccw_device * cdev ;
struct subchannel * sch ;
int ret ;
unsigned long flags ;
2006-10-11 17:31:47 +04:00
cdev = data ;
2005-04-17 02:20:36 +04:00
sch = to_subchannel ( cdev - > dev . parent ) ;
2005-06-26 01:55:27 +04:00
if ( klist_node_attached ( & cdev - > dev . knode_parent ) ) {
2005-04-17 02:20:36 +04:00
bus_rescan_devices ( & ccw_bus_type ) ;
goto out ;
}
/* make it known to the system */
ret = ccw_device_register ( cdev ) ;
if ( ret ) {
printk ( KERN_WARNING " %s: could not register %s \n " ,
__func__ , cdev - > dev . bus_id ) ;
put_device ( & cdev - > dev ) ;
spin_lock_irqsave ( & sch - > lock , flags ) ;
sch - > dev . driver_data = NULL ;
spin_unlock_irqrestore ( & sch - > lock , flags ) ;
kfree ( cdev - > private ) ;
kfree ( cdev ) ;
put_device ( & sch - > dev ) ;
if ( atomic_dec_and_test ( & ccw_device_init_count ) )
wake_up ( & ccw_device_init_wq ) ;
return ;
}
ret = subchannel_add_files ( cdev - > dev . parent ) ;
if ( ret )
printk ( KERN_WARNING " %s: could not add attributes to %s \n " ,
__func__ , sch - > dev . bus_id ) ;
put_device ( & cdev - > dev ) ;
out :
cdev - > private - > flags . recog_done = 1 ;
put_device ( & sch - > dev ) ;
wake_up ( & cdev - > private - > wait_q ) ;
if ( atomic_dec_and_test ( & ccw_device_init_count ) )
wake_up ( & ccw_device_init_wq ) ;
}
void
ccw_device_call_sch_unregister ( void * data )
{
struct ccw_device * cdev = data ;
struct subchannel * sch ;
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 ) ;
put_device ( & cdev - > dev ) ;
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-10-11 17:31:47 +04:00
ccw_device_call_sch_unregister , cdev ) ;
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-10-11 17:31:47 +04:00
io_subchannel_register , cdev ) ;
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 ;
sch - > dev . driver_data = cdev ;
sch - > driver = & io_subchannel_driver ;
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. */
2006-01-06 11:19:25 +03:00
snprintf ( cdev - > dev . bus_id , BUS_ID_SIZE , " 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. */
spin_lock_irq ( & sch - > lock ) ;
rc = ccw_device_recognition ( cdev ) ;
spin_unlock_irq ( & sch - > lock ) ;
if ( rc ) {
if ( atomic_dec_and_test ( & ccw_device_init_count ) )
wake_up ( & ccw_device_init_wq ) ;
}
return rc ;
}
static int
2006-01-11 12:56:22 +03:00
io_subchannel_probe ( struct subchannel * sch )
2005-04-17 02:20:36 +04:00
{
struct ccw_device * cdev ;
int rc ;
unsigned long flags ;
if ( sch - > dev . driver_data ) {
/*
* This subchannel already has an associated ccw_device .
* Register it and exit . This happens for all early
* device , e . g . the console .
*/
cdev = sch - > dev . driver_data ;
device_initialize ( & cdev - > dev ) ;
ccw_device_register ( cdev ) ;
subchannel_add_files ( & sch - > dev ) ;
/*
* Check if the device is already online . If it is
* the reference count needs to be corrected
* ( see ccw_device_online and css_init_done for the
* ugly details ) .
*/
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 ;
}
2006-03-24 14:15:31 +03:00
cdev = kzalloc ( sizeof ( * cdev ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! cdev )
return - ENOMEM ;
2006-03-24 14:15:31 +03:00
cdev - > private = kzalloc ( sizeof ( struct ccw_device_private ) ,
2005-04-17 02:20:36 +04:00
GFP_KERNEL | GFP_DMA ) ;
if ( ! cdev - > private ) {
kfree ( cdev ) ;
return - ENOMEM ;
}
atomic_set ( & cdev - > private - > onoff , 0 ) ;
2006-08-30 16:33:35 +04:00
cdev - > dev . parent = & sch - > dev ;
cdev - > dev . release = ccw_device_release ;
2005-04-17 02:20:36 +04:00
INIT_LIST_HEAD ( & cdev - > private - > kick_work . entry ) ;
/* 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 ;
}
2006-01-11 12:56:22 +03:00
rc = io_subchannel_recog ( cdev , sch ) ;
2005-04-17 02:20:36 +04:00
if ( rc ) {
spin_lock_irqsave ( & sch - > lock , flags ) ;
sch - > dev . driver_data = NULL ;
spin_unlock_irqrestore ( & sch - > lock , flags ) ;
if ( cdev - > dev . release )
cdev - > dev . release ( & cdev - > dev ) ;
}
return rc ;
}
static void
ccw_device_unregister ( void * data )
{
struct ccw_device * cdev ;
cdev = ( struct ccw_device * ) data ;
if ( test_and_clear_bit ( 1 , & cdev - > private - > registered ) )
device_unregister ( & cdev - > dev ) ;
put_device ( & cdev - > dev ) ;
}
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 ;
2006-01-11 12:56:22 +03:00
if ( ! sch - > dev . driver_data )
2005-04-17 02:20:36 +04:00
return 0 ;
2006-01-11 12:56:22 +03:00
cdev = sch - > dev . driver_data ;
2005-04-17 02:20:36 +04:00
/* Set ccw device to not operational and drop reference. */
spin_lock_irqsave ( cdev - > ccwlock , flags ) ;
2006-01-11 12:56:22 +03:00
sch - > dev . driver_data = NULL ;
2005-04-17 02:20:36 +04:00
cdev - > private - > state = DEV_STATE_NOT_OPER ;
spin_unlock_irqrestore ( cdev - > ccwlock , flags ) ;
/*
* Put unregistration on workqueue to avoid livelocks on the css bus
* semaphore .
*/
if ( get_device ( & cdev - > dev ) ) {
PREPARE_WORK ( & cdev - > private - > kick_work ,
2006-10-11 17:31:47 +04:00
ccw_device_unregister , cdev ) ;
2005-04-17 02:20:36 +04:00
queue_work ( ccw_device_work , & cdev - > private - > kick_work ) ;
}
return 0 ;
}
static int
io_subchannel_notify ( struct device * dev , int event )
{
struct ccw_device * cdev ;
cdev = dev - > driver_data ;
if ( ! cdev )
return 0 ;
if ( ! cdev - > drv )
return 0 ;
if ( ! cdev - > online )
return 0 ;
return cdev - > drv - > notify ? cdev - > drv - > notify ( cdev , event ) : 0 ;
}
static void
io_subchannel_verify ( struct device * dev )
{
struct ccw_device * cdev ;
cdev = dev - > driver_data ;
if ( cdev )
dev_fsm_event ( cdev , DEV_EVENT_VERIFY ) ;
}
static void
io_subchannel_ioterm ( struct device * dev )
{
struct ccw_device * cdev ;
cdev = dev - > driver_data ;
if ( ! cdev )
return ;
cdev - > private - > state = DEV_STATE_CLEAR_VERIFY ;
if ( cdev - > handler )
cdev - > handler ( cdev , cdev - > private - > intparm ,
ERR_PTR ( - EIO ) ) ;
}
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 ;
2006-01-11 12:56:22 +03:00
cdev = sch - > dev . driver_data ;
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 ) ;
}
# ifdef CONFIG_CCW_CONSOLE
static struct ccw_device console_cdev ;
static struct ccw_device_private console_private ;
static int console_cdev_in_use ;
static int
ccw_device_console_enable ( struct ccw_device * cdev , struct subchannel * sch )
{
int rc ;
/* 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 ;
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 ;
}
# 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
return ( strncmp ( bus_id , dev - > bus_id , BUS_ID_SIZE ) = = 0 ) ;
}
2005-04-17 02:20:36 +04:00
struct ccw_device *
get_ccwdev_by_busid ( struct ccw_driver * cdrv , const char * bus_id )
{
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 ;
pr_debug ( " removing device %s \n " , cdev - > dev . bus_id ) ;
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
//FIXME: we can't fail!
pr_debug ( " ccw_device_offline returned %d, device %s \n " ,
ret , cdev - > dev . bus_id ) ;
}
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 ;
}
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 ,
} ;
2005-04-17 02:20:36 +04:00
int
ccw_driver_register ( struct ccw_driver * cdriver )
{
struct device_driver * drv = & cdriver - > driver ;
drv - > bus = & ccw_bus_type ;
drv - > name = cdriver - > name ;
return driver_register ( drv ) ;
}
void
ccw_driver_unregister ( struct ccw_driver * cdriver )
{
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 ) ;
EXPORT_SYMBOL ( ccw_device_notify_work ) ;
2006-01-06 11:19:21 +03:00
EXPORT_SYMBOL_GPL ( ccw_device_get_subchannel_id ) ;