2005-04-17 02:20:36 +04:00
/*
* bus driver for ccwgroup
*
2012-05-15 19:49:12 +04:00
* Copyright IBM Corp . 2002 , 2012
2009-06-16 12:30:21 +04:00
*
* Author ( s ) : Arnd Bergmann ( arndb @ de . ibm . com )
* Cornelia Huck ( cornelia . huck @ de . ibm . com )
2005-04-17 02:20:36 +04:00
*/
# include <linux/module.h>
# include <linux/errno.h>
# include <linux/slab.h>
# include <linux/list.h>
# include <linux/device.h>
# include <linux/init.h>
# include <linux/ctype.h>
# include <linux/dcache.h>
2012-05-15 19:52:07 +04:00
# include <asm/cio.h>
2005-04-17 02:20:36 +04:00
# include <asm/ccwdev.h>
# include <asm/ccwgroup.h>
2012-05-15 19:52:07 +04:00
# include "device.h"
# define CCW_BUS_ID_SIZE 10
2008-12-25 15:38:55 +03:00
2005-04-17 02:20:36 +04:00
/* In Linux 2.4, we had a channel device layer called "chandev"
* that did all sorts of obscure stuff for networking devices .
* This is another driver that serves as a replacement for just
* one of its functions , namely the translation of single subchannels
* to devices that use multiple subchannels .
*/
2006-01-05 17:42:09 +03:00
static struct bus_type ccwgroup_bus_type ;
2005-04-17 02:20:36 +04:00
2011-10-30 18:16:53 +04:00
static void __ccwgroup_remove_symlinks ( struct ccwgroup_device * gdev )
2005-04-17 02:20:36 +04:00
{
int i ;
char str [ 8 ] ;
for ( i = 0 ; i < gdev - > count ; i + + ) {
sprintf ( str , " cdev%d " , i ) ;
sysfs_remove_link ( & gdev - > dev . kobj , str ) ;
sysfs_remove_link ( & gdev - > cdev [ i ] - > dev . kobj , " group_device " ) ;
}
}
2011-01-05 14:48:13 +03:00
/*
* Remove references from ccw devices to ccw group device and from
* ccw group device to ccw devices .
*/
static void __ccwgroup_remove_cdev_refs ( struct ccwgroup_device * gdev )
{
struct ccw_device * cdev ;
int i ;
for ( i = 0 ; i < gdev - > count ; i + + ) {
cdev = gdev - > cdev [ i ] ;
if ( ! cdev )
continue ;
spin_lock_irq ( cdev - > ccwlock ) ;
dev_set_drvdata ( & cdev - > dev , NULL ) ;
spin_unlock_irq ( cdev - > ccwlock ) ;
gdev - > cdev [ i ] = NULL ;
put_device ( & cdev - > dev ) ;
}
}
2012-11-22 18:56:39 +04:00
/**
* ccwgroup_set_online ( ) - enable a ccwgroup device
* @ gdev : target ccwgroup device
*
* This function attempts to put the ccwgroup device into the online state .
* Returns :
* % 0 on success and a negative error value on failure .
*/
int ccwgroup_set_online ( struct ccwgroup_device * gdev )
2011-10-30 18:16:53 +04:00
{
struct ccwgroup_driver * gdrv = to_ccwgroupdrv ( gdev - > dev . driver ) ;
2012-11-09 17:33:06 +04:00
int ret = - EINVAL ;
2011-10-30 18:16:53 +04:00
if ( atomic_cmpxchg ( & gdev - > onoff , 0 , 1 ) ! = 0 )
return - EAGAIN ;
if ( gdev - > state = = CCWGROUP_ONLINE )
goto out ;
if ( gdrv - > set_online )
ret = gdrv - > set_online ( gdev ) ;
if ( ret )
goto out ;
gdev - > state = CCWGROUP_ONLINE ;
out :
atomic_set ( & gdev - > onoff , 0 ) ;
return ret ;
}
2012-11-22 18:56:39 +04:00
EXPORT_SYMBOL ( ccwgroup_set_online ) ;
2011-10-30 18:16:53 +04:00
2012-11-22 18:56:39 +04:00
/**
* ccwgroup_set_offline ( ) - disable a ccwgroup device
* @ gdev : target ccwgroup device
*
* This function attempts to put the ccwgroup device into the offline state .
* Returns :
* % 0 on success and a negative error value on failure .
*/
int ccwgroup_set_offline ( struct ccwgroup_device * gdev )
2011-10-30 18:16:53 +04:00
{
struct ccwgroup_driver * gdrv = to_ccwgroupdrv ( gdev - > dev . driver ) ;
2012-11-09 17:33:06 +04:00
int ret = - EINVAL ;
2011-10-30 18:16:53 +04:00
if ( atomic_cmpxchg ( & gdev - > onoff , 0 , 1 ) ! = 0 )
return - EAGAIN ;
if ( gdev - > state = = CCWGROUP_OFFLINE )
goto out ;
if ( gdrv - > set_offline )
ret = gdrv - > set_offline ( gdev ) ;
if ( ret )
goto out ;
gdev - > state = CCWGROUP_OFFLINE ;
out :
atomic_set ( & gdev - > onoff , 0 ) ;
return ret ;
}
2012-11-22 18:56:39 +04:00
EXPORT_SYMBOL ( ccwgroup_set_offline ) ;
2011-10-30 18:16:53 +04:00
2011-10-30 18:16:52 +04:00
static ssize_t ccwgroup_online_store ( struct device * dev ,
struct device_attribute * attr ,
2011-10-30 18:16:53 +04:00
const char * buf , size_t count )
{
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
struct ccwgroup_driver * gdrv = to_ccwgroupdrv ( dev - > driver ) ;
unsigned long value ;
int ret ;
if ( ! dev - > driver )
return - EINVAL ;
if ( ! try_module_get ( gdrv - > driver . owner ) )
return - EINVAL ;
2013-07-22 05:18:15 +04:00
ret = kstrtoul ( buf , 0 , & value ) ;
2011-10-30 18:16:53 +04:00
if ( ret )
goto out ;
if ( value = = 1 )
ret = ccwgroup_set_online ( gdev ) ;
else if ( value = = 0 )
ret = ccwgroup_set_offline ( gdev ) ;
else
ret = - EINVAL ;
out :
module_put ( gdrv - > driver . owner ) ;
return ( ret = = 0 ) ? count : ret ;
}
2011-10-30 18:16:52 +04:00
static ssize_t ccwgroup_online_show ( struct device * dev ,
struct device_attribute * attr ,
2011-10-30 18:16:53 +04:00
char * buf )
{
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
int online ;
online = ( gdev - > state = = CCWGROUP_ONLINE ) ? 1 : 0 ;
return scnprintf ( buf , PAGE_SIZE , " %d \n " , online ) ;
}
2005-04-17 02:20:36 +04:00
/*
* Provide an ' ungroup ' attribute so the user can remove group devices no
* longer needed or accidentially created . Saves memory : )
*/
2007-03-15 22:50:34 +03:00
static void ccwgroup_ungroup_callback ( struct device * dev )
{
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
2007-04-27 18:01:37 +04:00
mutex_lock ( & gdev - > reg_mutex ) ;
2008-01-26 16:10:50 +03:00
if ( device_is_registered ( & gdev - > dev ) ) {
__ccwgroup_remove_symlinks ( gdev ) ;
device_unregister ( dev ) ;
2011-01-05 14:48:13 +03:00
__ccwgroup_remove_cdev_refs ( gdev ) ;
2008-01-26 16:10:50 +03:00
}
2007-04-27 18:01:37 +04:00
mutex_unlock ( & gdev - > reg_mutex ) ;
2007-03-15 22:50:34 +03:00
}
2011-10-30 18:16:53 +04:00
static ssize_t ccwgroup_ungroup_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
2011-10-30 18:16:53 +04:00
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
2007-03-15 22:50:34 +03:00
int rc ;
2005-04-17 02:20:36 +04:00
2008-12-25 15:39:04 +03:00
/* Prevent concurrent online/offline processing and ungrouping. */
if ( atomic_cmpxchg ( & gdev - > onoff , 0 , 1 ) ! = 0 )
return - EAGAIN ;
if ( gdev - > state ! = CCWGROUP_OFFLINE ) {
rc = - EINVAL ;
goto out ;
}
2007-03-15 22:50:34 +03:00
/* Note that we cannot unregister the device from one of its
* attribute methods , so we have to use this roundabout approach .
*/
rc = device_schedule_callback ( dev , ccwgroup_ungroup_callback ) ;
2008-12-25 15:39:04 +03:00
out :
if ( rc ) {
2009-03-13 21:07:36 +03:00
if ( rc ! = - EAGAIN )
/* Release onoff "lock" when ungrouping failed. */
atomic_set ( & gdev - > onoff , 0 ) ;
2008-12-25 15:39:04 +03:00
return rc ;
}
2005-04-17 02:20:36 +04:00
return count ;
}
static DEVICE_ATTR ( ungroup , 0200 , NULL , ccwgroup_ungroup_store ) ;
2011-10-30 18:16:52 +04:00
static DEVICE_ATTR ( online , 0644 , ccwgroup_online_show , ccwgroup_online_store ) ;
static struct attribute * ccwgroup_attrs [ ] = {
& dev_attr_online . attr ,
& dev_attr_ungroup . attr ,
NULL ,
} ;
static struct attribute_group ccwgroup_attr_group = {
. attrs = ccwgroup_attrs ,
} ;
static const struct attribute_group * ccwgroup_attr_groups [ ] = {
& ccwgroup_attr_group ,
NULL ,
} ;
2005-04-17 02:20:36 +04:00
2011-10-30 18:16:53 +04:00
static void ccwgroup_release ( struct device * dev )
2005-04-17 02:20:36 +04:00
{
2011-01-05 14:48:13 +03:00
kfree ( to_ccwgroupdev ( dev ) ) ;
2005-04-17 02:20:36 +04:00
}
2011-10-30 18:16:53 +04:00
static int __ccwgroup_create_symlinks ( struct ccwgroup_device * gdev )
2005-04-17 02:20:36 +04:00
{
char str [ 8 ] ;
int i , rc ;
for ( i = 0 ; i < gdev - > count ; i + + ) {
2011-10-30 18:16:53 +04:00
rc = sysfs_create_link ( & gdev - > cdev [ i ] - > dev . kobj ,
& gdev - > dev . kobj , " group_device " ) ;
2005-04-17 02:20:36 +04:00
if ( rc ) {
for ( - - i ; i > = 0 ; i - - )
sysfs_remove_link ( & gdev - > cdev [ i ] - > dev . kobj ,
" group_device " ) ;
return rc ;
}
}
for ( i = 0 ; i < gdev - > count ; i + + ) {
sprintf ( str , " cdev%d " , i ) ;
2011-10-30 18:16:53 +04:00
rc = sysfs_create_link ( & gdev - > dev . kobj ,
& gdev - > cdev [ i ] - > dev . kobj , str ) ;
2005-04-17 02:20:36 +04:00
if ( rc ) {
for ( - - i ; i > = 0 ; i - - ) {
sprintf ( str , " cdev%d " , i ) ;
sysfs_remove_link ( & gdev - > dev . kobj , str ) ;
}
for ( i = 0 ; i < gdev - > count ; i + + )
sysfs_remove_link ( & gdev - > cdev [ i ] - > dev . kobj ,
" group_device " ) ;
return rc ;
}
}
return 0 ;
}
2012-05-15 19:52:07 +04:00
static int __get_next_id ( const char * * buf , struct ccw_dev_id * id )
2008-04-24 12:15:20 +04:00
{
2012-05-15 19:52:07 +04:00
unsigned int cssid , ssid , devno ;
int ret = 0 , len ;
2008-04-24 12:15:20 +04:00
char * start , * end ;
start = ( char * ) * buf ;
end = strchr ( start , ' , ' ) ;
if ( ! end ) {
/* Last entry. Strip trailing newline, if applicable. */
end = strchr ( start , ' \n ' ) ;
if ( end )
* end = ' \0 ' ;
len = strlen ( start ) + 1 ;
} else {
len = end - start + 1 ;
end + + ;
}
2012-05-15 19:52:07 +04:00
if ( len < = CCW_BUS_ID_SIZE ) {
if ( sscanf ( start , " %2x.%1x.%04x " , & cssid , & ssid , & devno ) ! = 3 )
ret = - EINVAL ;
2008-04-24 12:15:20 +04:00
} else
2012-05-15 19:52:07 +04:00
ret = - EINVAL ;
2008-04-24 12:15:20 +04:00
2012-05-15 19:52:07 +04:00
if ( ! ret ) {
id - > ssid = ssid ;
id - > devno = devno ;
}
* buf = end ;
return ret ;
2008-04-24 12:15:20 +04:00
}
2007-10-12 18:11:17 +04:00
/**
2012-05-15 19:49:12 +04:00
* ccwgroup_create_dev ( ) - create and register a ccw group device
* @ parent : parent device for the new device
* @ gdrv : driver for the new group device
2008-04-24 12:15:20 +04:00
* @ num_devices : number of slave devices
* @ buf : buffer containing comma separated bus ids of slave devices
2007-10-12 18:11:17 +04:00
*
2012-05-15 19:49:12 +04:00
* Create and register a new ccw group device as a child of @ parent . Slave
2012-05-15 19:52:07 +04:00
* devices are obtained from the list of bus ids given in @ buf .
2007-10-12 18:11:17 +04:00
* Returns :
* % 0 on success and an error code on failure .
* Context :
* non - atomic
2005-04-17 02:20:36 +04:00
*/
2012-05-15 20:03:46 +04:00
int ccwgroup_create_dev ( struct device * parent , struct ccwgroup_driver * gdrv ,
int num_devices , const char * buf )
2005-04-17 02:20:36 +04:00
{
struct ccwgroup_device * gdev ;
2012-05-15 19:52:07 +04:00
struct ccw_dev_id dev_id ;
2008-04-24 12:15:20 +04:00
int rc , i ;
2005-04-17 02:20:36 +04:00
2008-04-24 12:15:20 +04:00
gdev = kzalloc ( sizeof ( * gdev ) + num_devices * sizeof ( gdev - > cdev [ 0 ] ) ,
GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! gdev )
return - ENOMEM ;
atomic_set ( & gdev - > onoff , 0 ) ;
2007-04-27 18:01:37 +04:00
mutex_init ( & gdev - > reg_mutex ) ;
mutex_lock ( & gdev - > reg_mutex ) ;
2008-08-21 21:46:36 +04:00
gdev - > count = num_devices ;
gdev - > dev . bus = & ccwgroup_bus_type ;
2012-05-15 19:49:12 +04:00
gdev - > dev . parent = parent ;
2008-08-21 21:46:36 +04:00
gdev - > dev . release = ccwgroup_release ;
device_initialize ( & gdev - > dev ) ;
2012-05-15 19:52:07 +04:00
for ( i = 0 ; i < num_devices & & buf ; i + + ) {
rc = __get_next_id ( & buf , & dev_id ) ;
2008-04-24 12:15:20 +04:00
if ( rc ! = 0 )
goto error ;
2012-05-15 19:52:07 +04:00
gdev - > cdev [ i ] = get_ccwdev_by_dev_id ( & dev_id ) ;
2008-04-24 12:15:20 +04:00
/*
* All devices have to be of the same type in
* order to be grouped .
*/
2012-05-15 19:52:07 +04:00
if ( ! gdev - > cdev [ i ] | | ! gdev - > cdev [ i ] - > drv | |
gdev - > cdev [ i ] - > drv ! = gdev - > cdev [ 0 ] - > drv | |
gdev - > cdev [ i ] - > id . driver_info ! =
2005-04-17 02:20:36 +04:00
gdev - > cdev [ 0 ] - > id . driver_info ) {
rc = - EINVAL ;
2007-04-27 18:01:37 +04:00
goto error ;
2005-04-17 02:20:36 +04:00
}
/* Don't allow a device to belong to more than one group. */
2010-05-27 01:27:07 +04:00
spin_lock_irq ( gdev - > cdev [ i ] - > ccwlock ) ;
2008-01-26 16:10:46 +03:00
if ( dev_get_drvdata ( & gdev - > cdev [ i ] - > dev ) ) {
2010-05-27 01:27:07 +04:00
spin_unlock_irq ( gdev - > cdev [ i ] - > ccwlock ) ;
2005-04-17 02:20:36 +04:00
rc = - EINVAL ;
2007-04-27 18:01:37 +04:00
goto error ;
2005-04-17 02:20:36 +04:00
}
2008-01-26 16:10:46 +03:00
dev_set_drvdata ( & gdev - > cdev [ i ] - > dev , gdev ) ;
2010-05-27 01:27:07 +04:00
spin_unlock_irq ( gdev - > cdev [ i ] - > ccwlock ) ;
2006-07-27 16:00:33 +04:00
}
2008-04-24 12:15:20 +04:00
/* Check for sufficient number of bus ids. */
2012-05-15 19:52:07 +04:00
if ( i < num_devices ) {
2008-04-24 12:15:20 +04:00
rc = - EINVAL ;
goto error ;
}
/* Check for trailing stuff. */
2012-05-15 19:52:07 +04:00
if ( i = = num_devices & & strlen ( buf ) > 0 ) {
2008-04-24 12:15:20 +04:00
rc = - EINVAL ;
goto error ;
}
2005-04-17 02:20:36 +04:00
2008-10-10 23:33:10 +04:00
dev_set_name ( & gdev - > dev , " %s " , dev_name ( & gdev - > cdev [ 0 ] - > dev ) ) ;
2011-10-30 18:16:52 +04:00
gdev - > dev . groups = ccwgroup_attr_groups ;
2012-05-15 19:49:12 +04:00
if ( gdrv ) {
gdev - > dev . driver = & gdrv - > driver ;
rc = gdrv - > setup ? gdrv - > setup ( gdev ) : 0 ;
if ( rc )
goto error ;
}
2008-08-21 21:46:36 +04:00
rc = device_add ( & gdev - > dev ) ;
2005-04-17 02:20:36 +04:00
if ( rc )
2007-04-27 18:01:37 +04:00
goto error ;
2005-04-17 02:20:36 +04:00
rc = __ccwgroup_create_symlinks ( gdev ) ;
2011-10-30 18:16:53 +04:00
if ( rc ) {
device_del ( & gdev - > dev ) ;
goto error ;
2005-04-17 02:20:36 +04:00
}
2011-10-30 18:16:53 +04:00
mutex_unlock ( & gdev - > reg_mutex ) ;
return 0 ;
2005-04-17 02:20:36 +04:00
error :
2008-04-24 12:15:20 +04:00
for ( i = 0 ; i < num_devices ; i + + )
2005-04-17 02:20:36 +04:00
if ( gdev - > cdev [ i ] ) {
2010-05-27 01:27:07 +04:00
spin_lock_irq ( gdev - > cdev [ i ] - > ccwlock ) ;
2008-01-26 16:10:46 +03:00
if ( dev_get_drvdata ( & gdev - > cdev [ i ] - > dev ) = = gdev )
dev_set_drvdata ( & gdev - > cdev [ i ] - > dev , NULL ) ;
2010-05-27 01:27:07 +04:00
spin_unlock_irq ( gdev - > cdev [ i ] - > ccwlock ) ;
2006-07-27 16:00:33 +04:00
put_device ( & gdev - > cdev [ i ] - > dev ) ;
[S390] cio: Fix driver_data handling for ccwgroup devices.
Since 16f7f9564c3ae190954f2ec55f385a268b93ac4d, we've seen
oopses when grouping/ungrouping devices:
Unable to handle kernel pointer dereference at virtual kernel address 0000000000
114000
Oops: 0004 [#1] PREEMPT SMP
Modules linked in: bonding qeth_l2 dm_multipath sunrpc qeth_l3 dm_mod qeth chsc_
sch ccwgroup
CPU: 1 Not tainted 2.6.26-29.x.20080815-s390xdefault #1
Process iperf (pid: 24412, task: 000000003f446038, ksp: 000000003c929e08)
Krnl PSW : 0404d00180000000 000003e00006f6e6 (qeth_irq+0xda/0xb28 [qeth])
R:0 T:1 IO:0 EX:0 Key:0 M:1 W:0 P:0 AS:3 CC:1 PM:0 EA:3
Krnl GPRS: 0000000000000000 000003e000000003 0000000000000000 0000000000114ccc
000000003fb82e48 000003e00006f60c 000000000000000c 000000003ce72100
0000000000114944 000000003fb82e48 0000000000114ccc 000000003fe8fd28
000003e000066000 000003e000076128 000000003fe8fdb8 000000003fe8fd28
Krnl Code: 000003e00006f6da: bf3f2024 icm %r3,15,36(%r2)
000003e00006f6de: a774023c brc 7,3e00006fb56
000003e00006f6e2: a7280000 lhi %r2,0
>000003e00006f6e6: 5020a1a0 st %r2,416(%r10)
000003e00006f6ea: 58109000 l %r1,0(%r9)
000003e00006f6ee: a7111000 tmll %r1,4096
000003e00006f6f2: a77400f9 brc 7,3e00006f8e4
000003e00006f6f6: 8810000c srl %r1,12
Call Trace:
([<000000003fe8fd20>] 0x3fe8fd20)
[<000000000033bf2a>] ccw_device_call_handler+0xb2/0xd8
[<0000000000339e1c>] ccw_device_irq+0x124/0x164
[<0000000000339758>] io_subchannel_irq+0x8c/0x118
[<00000000003309ba>] do_IRQ+0x192/0x1bc
[<0000000000114f66>] io_return+0x0/0x8
[<00000000001149cc>] sysc_do_svc+0x0/0x22
([<0000000000114a18>] sysc_noemu+0x10/0x16)
[<00000200002e047c>] 0x200002e047c
Last Breaking-Event-Address:
[<000003e00006f6d6>] qeth_irq+0xca/0xb28 [qeth]
The problem is that dev->driver_data for a ccw device is NULL,
while it should point to the ccwgroup device it is a member of.
This happened due to incorrect cleanup if creating a ccwgroup
device failed because the ccw devices were already grouped.
Fix this by setting cdev[i] to NULL in the error handling of
ccwgroup_create_from_string() after we give up our reference and
by checking if the driver_data points to the ccwgroup device in
ccwgroup_release() just to be really sure.
Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
2008-09-16 20:32:18 +04:00
gdev - > cdev [ i ] = NULL ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 18:01:37 +04:00
mutex_unlock ( & gdev - > reg_mutex ) ;
put_device ( & gdev - > dev ) ;
2005-04-17 02:20:36 +04:00
return rc ;
}
2012-05-15 19:49:12 +04:00
EXPORT_SYMBOL ( ccwgroup_create_dev ) ;
2009-03-26 17:24:15 +03:00
static int ccwgroup_notifier ( struct notifier_block * nb , unsigned long action ,
2011-10-30 18:16:53 +04:00
void * data )
{
struct device * dev = data ;
if ( action = = BUS_NOTIFY_UNBIND_DRIVER )
device_schedule_callback ( dev , ccwgroup_ungroup_callback ) ;
return NOTIFY_OK ;
}
2009-03-26 17:24:15 +03:00
static struct notifier_block ccwgroup_nb = {
. notifier_call = ccwgroup_notifier
} ;
static int __init init_ccwgroup ( void )
2005-04-17 02:20:36 +04:00
{
2009-03-26 17:24:15 +03:00
int ret ;
ret = bus_register ( & ccwgroup_bus_type ) ;
if ( ret )
return ret ;
ret = bus_register_notifier ( & ccwgroup_bus_type , & ccwgroup_nb ) ;
if ( ret )
bus_unregister ( & ccwgroup_bus_type ) ;
return ret ;
2005-04-17 02:20:36 +04:00
}
2009-03-26 17:24:15 +03:00
static void __exit cleanup_ccwgroup ( void )
2005-04-17 02:20:36 +04:00
{
2009-03-26 17:24:15 +03:00
bus_unregister_notifier ( & ccwgroup_bus_type , & ccwgroup_nb ) ;
bus_unregister ( & ccwgroup_bus_type ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( init_ccwgroup ) ;
module_exit ( cleanup_ccwgroup ) ;
/************************** driver stuff ******************************/
2011-10-30 18:16:53 +04:00
static int ccwgroup_remove ( struct device * dev )
2005-04-17 02:20:36 +04:00
{
2011-10-30 18:16:53 +04:00
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
struct ccwgroup_driver * gdrv = to_ccwgroupdrv ( dev - > driver ) ;
2005-04-17 02:20:36 +04:00
2009-03-26 17:24:14 +03:00
if ( ! dev - > driver )
return 0 ;
if ( gdrv - > remove )
2005-04-17 02:20:36 +04:00
gdrv - > remove ( gdev ) ;
2009-03-26 17:24:14 +03:00
2005-04-17 02:20:36 +04:00
return 0 ;
}
2008-02-05 18:50:36 +03:00
static void ccwgroup_shutdown ( struct device * dev )
{
2011-10-30 18:16:53 +04:00
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
struct ccwgroup_driver * gdrv = to_ccwgroupdrv ( dev - > driver ) ;
2008-02-05 18:50:36 +03:00
2009-03-26 17:24:14 +03:00
if ( ! dev - > driver )
return ;
if ( gdrv - > shutdown )
2008-02-05 18:50:36 +03:00
gdrv - > shutdown ( gdev ) ;
}
2009-06-16 12:30:21 +04:00
static int ccwgroup_pm_prepare ( struct device * dev )
{
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
struct ccwgroup_driver * gdrv = to_ccwgroupdrv ( gdev - > dev . driver ) ;
/* Fail while device is being set online/offline. */
if ( atomic_read ( & gdev - > onoff ) )
return - EAGAIN ;
if ( ! gdev - > dev . driver | | gdev - > state ! = CCWGROUP_ONLINE )
return 0 ;
return gdrv - > prepare ? gdrv - > prepare ( gdev ) : 0 ;
}
static void ccwgroup_pm_complete ( struct device * dev )
{
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
struct ccwgroup_driver * gdrv = to_ccwgroupdrv ( dev - > driver ) ;
if ( ! gdev - > dev . driver | | gdev - > state ! = CCWGROUP_ONLINE )
return ;
if ( gdrv - > complete )
gdrv - > complete ( gdev ) ;
}
static int ccwgroup_pm_freeze ( struct device * dev )
{
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
struct ccwgroup_driver * gdrv = to_ccwgroupdrv ( gdev - > dev . driver ) ;
if ( ! gdev - > dev . driver | | gdev - > state ! = CCWGROUP_ONLINE )
return 0 ;
return gdrv - > freeze ? gdrv - > freeze ( gdev ) : 0 ;
}
static int ccwgroup_pm_thaw ( struct device * dev )
{
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
struct ccwgroup_driver * gdrv = to_ccwgroupdrv ( gdev - > dev . driver ) ;
if ( ! gdev - > dev . driver | | gdev - > state ! = CCWGROUP_ONLINE )
return 0 ;
return gdrv - > thaw ? gdrv - > thaw ( gdev ) : 0 ;
}
static int ccwgroup_pm_restore ( struct device * dev )
{
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
struct ccwgroup_driver * gdrv = to_ccwgroupdrv ( gdev - > dev . driver ) ;
if ( ! gdev - > dev . driver | | gdev - > state ! = CCWGROUP_ONLINE )
return 0 ;
return gdrv - > restore ? gdrv - > restore ( gdev ) : 0 ;
}
2009-12-15 05:00:08 +03:00
static const struct dev_pm_ops ccwgroup_pm_ops = {
2009-06-16 12:30:21 +04:00
. prepare = ccwgroup_pm_prepare ,
. complete = ccwgroup_pm_complete ,
. freeze = ccwgroup_pm_freeze ,
. thaw = ccwgroup_pm_thaw ,
. restore = ccwgroup_pm_restore ,
} ;
2006-01-05 17:42:09 +03:00
static struct bus_type ccwgroup_bus_type = {
. name = " ccwgroup " ,
. remove = ccwgroup_remove ,
2008-02-05 18:50:36 +03:00
. shutdown = ccwgroup_shutdown ,
2009-06-16 12:30:21 +04:00
. pm = & ccwgroup_pm_ops ,
2006-01-05 17:42:09 +03:00
} ;
2007-10-12 18:11:17 +04:00
/**
* ccwgroup_driver_register ( ) - register a ccw group driver
* @ cdriver : driver to be registered
*
* This function is mainly a wrapper around driver_register ( ) .
*/
int ccwgroup_driver_register ( struct ccwgroup_driver * cdriver )
2005-04-17 02:20:36 +04:00
{
/* register our new driver with the core */
2006-08-30 16:33:35 +04:00
cdriver - > driver . bus = & ccwgroup_bus_type ;
2005-04-17 02:20:36 +04:00
return driver_register ( & cdriver - > driver ) ;
}
2011-10-30 18:16:53 +04:00
EXPORT_SYMBOL ( ccwgroup_driver_register ) ;
2005-04-17 02:20:36 +04:00
2011-10-30 18:16:53 +04:00
static int __ccwgroup_match_all ( struct device * dev , void * data )
2005-04-17 02:20:36 +04:00
{
2006-06-29 16:56:52 +04:00
return 1 ;
2005-04-17 02:20:36 +04:00
}
2007-10-12 18:11:17 +04:00
/**
* ccwgroup_driver_unregister ( ) - deregister a ccw group driver
* @ cdriver : driver to be deregistered
*
* This function is mainly a wrapper around driver_unregister ( ) .
*/
void ccwgroup_driver_unregister ( struct ccwgroup_driver * cdriver )
2005-04-17 02:20:36 +04:00
{
2006-06-29 16:56:52 +04:00
struct device * dev ;
2005-04-17 02:20:36 +04:00
/* We don't want ccwgroup devices to live longer than their driver. */
2006-06-29 16:56:52 +04:00
while ( ( dev = driver_find_device ( & cdriver - > driver , NULL , NULL ,
__ccwgroup_match_all ) ) ) {
2007-04-27 18:01:37 +04:00
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
mutex_lock ( & gdev - > reg_mutex ) ;
__ccwgroup_remove_symlinks ( gdev ) ;
2006-06-29 16:56:52 +04:00
device_unregister ( dev ) ;
2011-01-05 14:48:13 +03:00
__ccwgroup_remove_cdev_refs ( gdev ) ;
2007-04-27 18:01:37 +04:00
mutex_unlock ( & gdev - > reg_mutex ) ;
2006-06-29 16:56:52 +04:00
put_device ( dev ) ;
}
2005-04-17 02:20:36 +04:00
driver_unregister ( & cdriver - > driver ) ;
}
2011-10-30 18:16:53 +04:00
EXPORT_SYMBOL ( ccwgroup_driver_unregister ) ;
2005-04-17 02:20:36 +04:00
2007-10-12 18:11:17 +04:00
/**
* ccwgroup_probe_ccwdev ( ) - probe function for slave devices
* @ cdev : ccw device to be probed
*
* This is a dummy probe function for ccw devices that are slave devices in
* a ccw group device .
* Returns :
* always % 0
*/
int ccwgroup_probe_ccwdev ( struct ccw_device * cdev )
2005-04-17 02:20:36 +04:00
{
return 0 ;
}
2011-10-30 18:16:53 +04:00
EXPORT_SYMBOL ( ccwgroup_probe_ccwdev ) ;
2005-04-17 02:20:36 +04:00
2007-10-12 18:11:17 +04:00
/**
* ccwgroup_remove_ccwdev ( ) - remove function for slave devices
* @ cdev : ccw device to be removed
*
* This is a remove function for ccw devices that are slave devices in a ccw
* group device . It sets the ccw device offline and also deregisters the
* embedding ccw group device .
*/
void ccwgroup_remove_ccwdev ( struct ccw_device * cdev )
2005-04-17 02:20:36 +04:00
{
struct ccwgroup_device * gdev ;
/* Ignore offlining errors, device is gone anyway. */
ccw_device_set_offline ( cdev ) ;
/* If one of its devices is gone, the whole group is done for. */
2011-01-05 14:48:13 +03:00
spin_lock_irq ( cdev - > ccwlock ) ;
gdev = dev_get_drvdata ( & cdev - > dev ) ;
if ( ! gdev ) {
spin_unlock_irq ( cdev - > ccwlock ) ;
return ;
}
/* Get ccwgroup device reference for local processing. */
get_device ( & gdev - > dev ) ;
spin_unlock_irq ( cdev - > ccwlock ) ;
/* Unregister group device. */
mutex_lock ( & gdev - > reg_mutex ) ;
if ( device_is_registered ( & gdev - > dev ) ) {
2005-04-17 02:20:36 +04:00
__ccwgroup_remove_symlinks ( gdev ) ;
device_unregister ( & gdev - > dev ) ;
2011-01-05 14:48:13 +03:00
__ccwgroup_remove_cdev_refs ( gdev ) ;
2005-04-17 02:20:36 +04:00
}
2011-01-05 14:48:13 +03:00
mutex_unlock ( & gdev - > reg_mutex ) ;
/* Release ccwgroup device reference for local processing. */
put_device ( & gdev - > dev ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( ccwgroup_remove_ccwdev ) ;
2011-10-30 18:16:53 +04:00
MODULE_LICENSE ( " GPL " ) ;