2005-04-17 02:20:36 +04:00
/*
* drivers / s390 / cio / device . c
* bus driver for ccw devices
* $ Revision : 1.131 $
*
* Copyright ( C ) 2002 IBM Deutschland Entwicklung GmbH ,
* IBM Corporation
* Author ( s ) : Arnd Bergmann ( arndb @ de . ibm . com )
* Cornelia Huck ( cohuck @ de . ibm . com )
* Martin Schwidefsky ( schwidefsky @ de . ibm . com )
*/
# include <linux/config.h>
# 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>
# 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 ;
}
/*
* Hotplugging interface for ccw devices .
* Heavily modeled on pci and usb hotplug .
*/
static int
ccw_hotplug ( struct device * dev , char * * envp , int num_envp ,
char * buffer , int buffer_size )
{
struct ccw_device * cdev = to_ccwdev ( dev ) ;
int i = 0 ;
int length = 0 ;
if ( ! cdev )
return - ENODEV ;
/* what we want to pass to /sbin/hotplug */
envp [ i + + ] = buffer ;
length + = scnprintf ( buffer , buffer_size - length , " CU_TYPE=%04X " ,
cdev - > id . cu_type ) ;
if ( ( buffer_size - length < = 0 ) | | ( i > = num_envp ) )
return - ENOMEM ;
+ + length ;
buffer + = length ;
envp [ i + + ] = buffer ;
length + = scnprintf ( buffer , buffer_size - length , " CU_MODEL=%02X " ,
cdev - > id . cu_model ) ;
if ( ( buffer_size - length < = 0 ) | | ( i > = num_envp ) )
return - ENOMEM ;
+ + length ;
buffer + = length ;
/* The next two can be zero, that's ok for us */
envp [ i + + ] = buffer ;
length + = scnprintf ( buffer , buffer_size - length , " DEV_TYPE=%04X " ,
cdev - > id . dev_type ) ;
if ( ( buffer_size - length < = 0 ) | | ( i > = num_envp ) )
return - ENOMEM ;
+ + length ;
buffer + = length ;
envp [ i + + ] = buffer ;
length + = scnprintf ( buffer , buffer_size - length , " DEV_MODEL=%02X " ,
cdev - > id . dev_model ) ;
if ( ( buffer_size - length < = 0 ) | | ( i > = num_envp ) )
return - ENOMEM ;
envp [ i ] = 0 ;
return 0 ;
}
struct bus_type ccw_bus_type = {
. name = " ccw " ,
. match = & ccw_bus_match ,
. hotplug = & ccw_hotplug ,
} ;
static int io_subchannel_probe ( struct device * ) ;
static int io_subchannel_remove ( struct device * ) ;
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 * ) ;
static void io_subchannel_shutdown ( struct device * ) ;
struct css_driver io_subchannel_driver = {
. subchannel_type = SUBCHANNEL_TYPE_IO ,
. drv = {
. name = " io_subchannel " ,
. bus = & css_bus_type ,
. probe = & io_subchannel_probe ,
. remove = & io_subchannel_remove ,
. shutdown = & io_subchannel_shutdown ,
} ,
. irq = io_subchannel_irq ,
. notify = io_subchannel_notify ,
. verify = io_subchannel_verify ,
. termination = io_subchannel_ioterm ,
} ;
struct workqueue_struct * ccw_device_work ;
struct workqueue_struct * ccw_device_notify_work ;
static wait_queue_head_t ccw_device_init_wq ;
static atomic_t ccw_device_init_count ;
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 ) ;
}
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 ) ;
device_unregister ( & sch - > dev ) ;
/* 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 ) ;
return ( ret = 0 ) ? - ENODEV : ret ;
}
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 ;
if ( atomic_compare_and_swap ( 0 , 1 , & cdev - > private - > onoff ) )
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 ) ;
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 ,
& 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 ;
}
static struct ccw_device *
get_disc_ccwdev_by_devno ( unsigned int devno , struct ccw_device * sibling )
{
struct ccw_device * cdev ;
struct list_head * entry ;
struct device * dev ;
if ( ! get_bus ( & ccw_bus_type ) )
return NULL ;
down_read ( & ccw_bus_type . subsys . rwsem ) ;
cdev = NULL ;
list_for_each ( entry , & ccw_bus_type . devices . list ) {
dev = get_device ( container_of ( entry ,
struct device , bus_list ) ) ;
if ( ! dev )
continue ;
cdev = to_ccwdev ( dev ) ;
if ( ( cdev - > private - > state = = DEV_STATE_DISCONNECTED ) & &
( cdev - > private - > devno = = devno ) & &
( cdev ! = sibling ) ) {
cdev - > private - > state = DEV_STATE_NOT_OPER ;
break ;
}
put_device ( dev ) ;
cdev = NULL ;
}
up_read ( & ccw_bus_type . subsys . rwsem ) ;
put_bus ( & ccw_bus_type ) ;
return cdev ;
}
static void
ccw_device_add_changed ( void * data )
{
struct ccw_device * cdev ;
cdev = ( struct ccw_device * ) data ;
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 ;
cdev = ( struct ccw_device * ) data ;
sch = to_subchannel ( cdev - > dev . parent ) ;
if ( cdev - > private - > devno ! = sch - > schib . pmcw . dev ) {
/*
* 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 ;
need_rename = 1 ;
other_cdev = get_disc_ccwdev_by_devno ( sch - > schib . pmcw . dev ,
cdev ) ;
if ( other_cdev ) {
struct subchannel * other_sch ;
other_sch = to_subchannel ( other_cdev - > dev . parent ) ;
if ( get_device ( & other_sch - > dev ) ) {
stsch ( other_sch - > irq , & other_sch - > schib ) ;
if ( other_sch - > schib . pmcw . dnv ) {
other_sch - > schib . pmcw . intparm = 0 ;
cio_modify ( other_sch ) ;
}
device_unregister ( & other_sch - > dev ) ;
}
}
/* Update ssd info here. */
css_get_ssd_info ( sch ) ;
cdev - > private - > devno = sch - > schib . pmcw . dev ;
} 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 )
snprintf ( cdev - > dev . bus_id , BUS_ID_SIZE , " 0.0.%04x " ,
sch - > schib . pmcw . dev ) ;
PREPARE_WORK ( & cdev - > private - > kick_work ,
ccw_device_add_changed , ( void * ) cdev ) ;
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 ;
cdev = ( struct ccw_device * ) data ;
sch = to_subchannel ( cdev - > dev . parent ) ;
if ( ! list_empty ( & sch - > dev . children ) ) {
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 ) ;
device_unregister ( & sch - > dev ) ;
/* 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 ,
ccw_device_call_sch_unregister , ( void * ) cdev ) ;
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 ,
io_subchannel_register , ( void * ) cdev ) ;
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 ;
/* Init private data. */
priv = cdev - > private ;
priv - > devno = sch - > schib . pmcw . dev ;
priv - > irq = sch - > irq ;
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. */
snprintf ( cdev - > dev . bus_id , BUS_ID_SIZE , " 0.0.%04x " ,
sch - > schib . pmcw . dev ) ;
/* 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
io_subchannel_probe ( struct device * pdev )
{
struct subchannel * sch ;
struct ccw_device * cdev ;
int rc ;
unsigned long flags ;
sch = to_subchannel ( pdev ) ;
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 ;
}
cdev = kmalloc ( sizeof ( * cdev ) , GFP_KERNEL ) ;
if ( ! cdev )
return - ENOMEM ;
memset ( cdev , 0 , sizeof ( struct ccw_device ) ) ;
cdev - > private = kmalloc ( sizeof ( struct ccw_device_private ) ,
GFP_KERNEL | GFP_DMA ) ;
if ( ! cdev - > private ) {
kfree ( cdev ) ;
return - ENOMEM ;
}
memset ( cdev - > private , 0 , sizeof ( struct ccw_device_private ) ) ;
atomic_set ( & cdev - > private - > onoff , 0 ) ;
cdev - > dev = ( struct device ) {
. parent = pdev ,
. release = ccw_device_release ,
} ;
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 ;
}
rc = io_subchannel_recog ( cdev , to_subchannel ( pdev ) ) ;
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
io_subchannel_remove ( struct device * dev )
{
struct ccw_device * cdev ;
unsigned long flags ;
if ( ! dev - > driver_data )
return 0 ;
cdev = dev - > driver_data ;
/* Set ccw device to not operational and drop reference. */
spin_lock_irqsave ( cdev - > ccwlock , flags ) ;
dev - > driver_data = NULL ;
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 ,
ccw_device_unregister , ( void * ) cdev ) ;
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
io_subchannel_shutdown ( struct device * dev )
{
struct subchannel * sch ;
struct ccw_device * cdev ;
int ret ;
sch = to_subchannel ( dev ) ;
cdev = dev - > driver_data ;
if ( cio_is_console ( sch - > irq ) )
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. */
cdev - > dev = ( struct device ) {
. parent = & sch - > dev ,
} ;
/* Initialize the subchannel structure */
sch - > dev . parent = & css_bus_device ;
sch - > dev . bus = & css_bus_type ;
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 )
return NULL ;
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
*/
struct ccw_device *
get_ccwdev_by_busid ( struct ccw_driver * cdrv , const char * bus_id )
{
struct device * d , * dev ;
struct device_driver * drv ;
drv = get_driver ( & cdrv - > driver ) ;
if ( ! drv )
return 0 ;
down_read ( & drv - > bus - > subsys . rwsem ) ;
dev = NULL ;
list_for_each_entry ( d , & drv - > devices , driver_list ) {
dev = get_device ( d ) ;
if ( dev & & ! strncmp ( bus_id , dev - > bus_id , BUS_ID_SIZE ) )
break ;
else if ( dev ) {
put_device ( dev ) ;
dev = NULL ;
}
}
up_read ( & drv - > bus - > subsys . rwsem ) ;
put_driver ( drv ) ;
return dev ? to_ccwdev ( dev ) : 0 ;
}
/************************** 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 ) {
cdev - > drv = 0 ;
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 ) ;
cdev - > drv = 0 ;
return 0 ;
}
int
ccw_driver_register ( struct ccw_driver * cdriver )
{
struct device_driver * drv = & cdriver - > driver ;
drv - > bus = & ccw_bus_type ;
drv - > name = cdriver - > name ;
drv - > probe = ccw_device_probe ;
drv - > remove = ccw_device_remove ;
return driver_register ( drv ) ;
}
void
ccw_driver_unregister ( struct ccw_driver * cdriver )
{
driver_unregister ( & cdriver - > driver ) ;
}
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 ) ;