2005-04-16 15:20:36 -07:00
/*
* drivers / s390 / cio / ccwgroup . c
* bus driver for ccwgroup
*
* Copyright ( C ) 2002 IBM Deutschland Entwicklung GmbH ,
* IBM Corporation
* Author ( s ) : Arnd Bergmann ( arndb @ de . ibm . com )
2006-01-14 13:21:04 -08:00
* Cornelia Huck ( cornelia . huck @ de . ibm . com )
2005-04-16 15:20:36 -07: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>
# include <asm/ccwdev.h>
# include <asm/ccwgroup.h>
2008-12-25 13:38:55 +01:00
# define CCW_BUS_ID_SIZE 20
2005-04-16 15:20:36 -07: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 .
*/
/* a device matches a driver if all its slave devices match the same
* entry of the driver */
static int
ccwgroup_bus_match ( struct device * dev , struct device_driver * drv )
{
struct ccwgroup_device * gdev ;
struct ccwgroup_driver * gdrv ;
2008-01-26 14:10:38 +01:00
gdev = to_ccwgroupdev ( dev ) ;
gdrv = to_ccwgroupdrv ( drv ) ;
2005-04-16 15:20:36 -07:00
if ( gdev - > creator_id = = gdrv - > driver_id )
return 1 ;
return 0 ;
}
static int
2007-08-14 15:15:12 +02:00
ccwgroup_uevent ( struct device * dev , struct kobj_uevent_env * env )
2005-04-16 15:20:36 -07:00
{
/* TODO */
return 0 ;
}
2006-01-05 14:42:09 +00:00
static struct bus_type ccwgroup_bus_type ;
2005-04-16 15:20:36 -07:00
2007-02-05 21:18:53 +01:00
static void
2005-04-16 15:20:36 -07:00
__ccwgroup_remove_symlinks ( struct ccwgroup_device * gdev )
{
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 " ) ;
}
}
/*
* Provide an ' ungroup ' attribute so the user can remove group devices no
* longer needed or accidentially created . Saves memory : )
*/
2007-03-15 15:50:34 -04:00
static void ccwgroup_ungroup_callback ( struct device * dev )
{
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
2007-04-27 16:01:37 +02:00
mutex_lock ( & gdev - > reg_mutex ) ;
2008-01-26 14:10:50 +01:00
if ( device_is_registered ( & gdev - > dev ) ) {
__ccwgroup_remove_symlinks ( gdev ) ;
device_unregister ( dev ) ;
}
2007-04-27 16:01:37 +02:00
mutex_unlock ( & gdev - > reg_mutex ) ;
2007-03-15 15:50:34 -04:00
}
2005-04-16 15:20:36 -07:00
static ssize_t
2005-05-17 06:43:27 -04:00
ccwgroup_ungroup_store ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-16 15:20:36 -07:00
{
struct ccwgroup_device * gdev ;
2007-03-15 15:50:34 -04:00
int rc ;
2005-04-16 15:20:36 -07:00
gdev = to_ccwgroupdev ( dev ) ;
if ( gdev - > state ! = CCWGROUP_OFFLINE )
return - EINVAL ;
2007-03-15 15:50:34 -04: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 ) ;
if ( rc )
count = rc ;
2005-04-16 15:20:36 -07:00
return count ;
}
static DEVICE_ATTR ( ungroup , 0200 , NULL , ccwgroup_ungroup_store ) ;
static void
ccwgroup_release ( struct device * dev )
{
struct ccwgroup_device * gdev ;
int i ;
gdev = to_ccwgroupdev ( dev ) ;
for ( i = 0 ; i < gdev - > count ; i + + ) {
2008-08-21 19:46:36 +02:00
if ( gdev - > cdev [ i ] ) {
[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 09:32:18 -07:00
if ( dev_get_drvdata ( & gdev - > cdev [ i ] - > dev ) = = gdev )
dev_set_drvdata ( & gdev - > cdev [ i ] - > dev , NULL ) ;
2008-08-21 19:46:36 +02:00
put_device ( & gdev - > cdev [ i ] - > dev ) ;
}
2005-04-16 15:20:36 -07:00
}
kfree ( gdev ) ;
}
2007-02-05 21:18:53 +01:00
static int
2005-04-16 15:20:36 -07:00
__ccwgroup_create_symlinks ( struct ccwgroup_device * gdev )
{
char str [ 8 ] ;
int i , rc ;
for ( i = 0 ; i < gdev - > count ; i + + ) {
rc = sysfs_create_link ( & gdev - > cdev [ i ] - > dev . kobj , & gdev - > dev . kobj ,
" group_device " ) ;
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 ) ;
rc = sysfs_create_link ( & gdev - > dev . kobj , & gdev - > cdev [ i ] - > dev . kobj ,
str ) ;
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 ;
}
2008-04-24 10:15:20 +02:00
static int __get_next_bus_id ( const char * * buf , char * bus_id )
{
int rc , len ;
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 + + ;
}
2008-12-25 13:38:55 +01:00
if ( len < CCW_BUS_ID_SIZE ) {
2008-04-24 10:15:20 +02:00
strlcpy ( bus_id , start , len ) ;
rc = 0 ;
} else
rc = - EINVAL ;
* buf = end ;
return rc ;
}
2008-12-25 13:38:55 +01:00
static int __is_valid_bus_id ( char bus_id [ CCW_BUS_ID_SIZE ] )
2008-04-24 10:15:20 +02:00
{
int cssid , ssid , devno ;
/* Must be of form %x.%x.%04x */
if ( sscanf ( bus_id , " %x.%1x.%04x " , & cssid , & ssid , & devno ) ! = 3 )
return 0 ;
return 1 ;
}
2007-10-12 16:11:17 +02:00
/**
2008-04-24 10:15:20 +02:00
* ccwgroup_create_from_string ( ) - create and register a ccw group device
2007-10-12 16:11:17 +02:00
* @ root : parent device for the new device
* @ creator_id : identifier of creating driver
* @ cdrv : ccw driver of slave devices
2008-04-24 10:15:20 +02:00
* @ num_devices : number of slave devices
* @ buf : buffer containing comma separated bus ids of slave devices
2007-10-12 16:11:17 +02:00
*
* Create and register a new ccw group device as a child of @ root . Slave
2008-04-24 10:15:20 +02:00
* devices are obtained from the list of bus ids given in @ buf and must all
2007-10-12 16:11:17 +02:00
* belong to @ cdrv .
* Returns :
* % 0 on success and an error code on failure .
* Context :
* non - atomic
2005-04-16 15:20:36 -07:00
*/
2008-04-24 10:15:20 +02:00
int ccwgroup_create_from_string ( struct device * root , unsigned int creator_id ,
struct ccw_driver * cdrv , int num_devices ,
const char * buf )
2005-04-16 15:20:36 -07:00
{
struct ccwgroup_device * gdev ;
2008-04-24 10:15:20 +02:00
int rc , i ;
2008-12-25 13:38:55 +01:00
char tmp_bus_id [ CCW_BUS_ID_SIZE ] ;
2008-04-24 10:15:20 +02:00
const char * curr_buf ;
2005-04-16 15:20:36 -07:00
2008-04-24 10:15:20 +02:00
gdev = kzalloc ( sizeof ( * gdev ) + num_devices * sizeof ( gdev - > cdev [ 0 ] ) ,
GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
if ( ! gdev )
return - ENOMEM ;
atomic_set ( & gdev - > onoff , 0 ) ;
2007-04-27 16:01:37 +02:00
mutex_init ( & gdev - > reg_mutex ) ;
mutex_lock ( & gdev - > reg_mutex ) ;
2008-08-21 19:46:36 +02:00
gdev - > creator_id = creator_id ;
gdev - > count = num_devices ;
gdev - > dev . bus = & ccwgroup_bus_type ;
gdev - > dev . parent = root ;
gdev - > dev . release = ccwgroup_release ;
device_initialize ( & gdev - > dev ) ;
2008-04-24 10:15:20 +02:00
curr_buf = buf ;
for ( i = 0 ; i < num_devices & & curr_buf ; i + + ) {
rc = __get_next_bus_id ( & curr_buf , tmp_bus_id ) ;
if ( rc ! = 0 )
goto error ;
if ( ! __is_valid_bus_id ( tmp_bus_id ) ) {
rc = - EINVAL ;
goto error ;
}
gdev - > cdev [ i ] = get_ccwdev_by_busid ( cdrv , tmp_bus_id ) ;
/*
* All devices have to be of the same type in
* order to be grouped .
*/
2005-04-16 15:20:36 -07:00
if ( ! gdev - > cdev [ i ]
| | gdev - > cdev [ i ] - > id . driver_info ! =
gdev - > cdev [ 0 ] - > id . driver_info ) {
rc = - EINVAL ;
2007-04-27 16:01:37 +02:00
goto error ;
2005-04-16 15:20:36 -07:00
}
/* Don't allow a device to belong to more than one group. */
2008-01-26 14:10:46 +01:00
if ( dev_get_drvdata ( & gdev - > cdev [ i ] - > dev ) ) {
2005-04-16 15:20:36 -07:00
rc = - EINVAL ;
2007-04-27 16:01:37 +02:00
goto error ;
2005-04-16 15:20:36 -07:00
}
2008-01-26 14:10:46 +01:00
dev_set_drvdata ( & gdev - > cdev [ i ] - > dev , gdev ) ;
2006-07-27 14:00:33 +02:00
}
2008-04-24 10:15:20 +02:00
/* Check for sufficient number of bus ids. */
if ( i < num_devices & & ! curr_buf ) {
rc = - EINVAL ;
goto error ;
}
/* Check for trailing stuff. */
if ( i = = num_devices & & strlen ( curr_buf ) > 0 ) {
rc = - EINVAL ;
goto error ;
}
2005-04-16 15:20:36 -07:00
2008-10-10 21:33:10 +02:00
dev_set_name ( & gdev - > dev , " %s " , dev_name ( & gdev - > cdev [ 0 ] - > dev ) ) ;
2005-04-16 15:20:36 -07:00
2008-08-21 19:46:36 +02:00
rc = device_add ( & gdev - > dev ) ;
2005-04-16 15:20:36 -07:00
if ( rc )
2007-04-27 16:01:37 +02:00
goto error ;
2005-04-16 15:20:36 -07:00
get_device ( & gdev - > dev ) ;
rc = device_create_file ( & gdev - > dev , & dev_attr_ungroup ) ;
if ( rc ) {
device_unregister ( & gdev - > dev ) ;
goto error ;
}
rc = __ccwgroup_create_symlinks ( gdev ) ;
if ( ! rc ) {
2007-04-27 16:01:37 +02:00
mutex_unlock ( & gdev - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
put_device ( & gdev - > dev ) ;
return 0 ;
}
device_remove_file ( & gdev - > dev , & dev_attr_ungroup ) ;
device_unregister ( & gdev - > dev ) ;
error :
2008-04-24 10:15:20 +02:00
for ( i = 0 ; i < num_devices ; i + + )
2005-04-16 15:20:36 -07:00
if ( gdev - > cdev [ i ] ) {
2008-01-26 14:10:46 +01:00
if ( dev_get_drvdata ( & gdev - > cdev [ i ] - > dev ) = = gdev )
dev_set_drvdata ( & gdev - > cdev [ i ] - > dev , NULL ) ;
2006-07-27 14:00:33 +02: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 09:32:18 -07:00
gdev - > cdev [ i ] = NULL ;
2005-04-16 15:20:36 -07:00
}
2007-04-27 16:01:37 +02:00
mutex_unlock ( & gdev - > reg_mutex ) ;
put_device ( & gdev - > dev ) ;
2005-04-16 15:20:36 -07:00
return rc ;
}
2008-04-24 10:15:20 +02:00
EXPORT_SYMBOL ( ccwgroup_create_from_string ) ;
2005-04-16 15:20:36 -07:00
static int __init
init_ccwgroup ( void )
{
return bus_register ( & ccwgroup_bus_type ) ;
}
static void __exit
cleanup_ccwgroup ( void )
{
bus_unregister ( & ccwgroup_bus_type ) ;
}
module_init ( init_ccwgroup ) ;
module_exit ( cleanup_ccwgroup ) ;
/************************** driver stuff ******************************/
static int
ccwgroup_set_online ( struct ccwgroup_device * gdev )
{
struct ccwgroup_driver * gdrv ;
int ret ;
2006-01-06 00:19:07 -08:00
if ( atomic_cmpxchg ( & gdev - > onoff , 0 , 1 ) ! = 0 )
2005-04-16 15:20:36 -07:00
return - EAGAIN ;
if ( gdev - > state = = CCWGROUP_ONLINE ) {
ret = 0 ;
goto out ;
}
if ( ! gdev - > dev . driver ) {
ret = - EINVAL ;
goto out ;
}
gdrv = to_ccwgroupdrv ( gdev - > dev . driver ) ;
2005-11-07 00:59:05 -08:00
if ( ( ret = gdrv - > set_online ? gdrv - > set_online ( gdev ) : 0 ) )
2005-04-16 15:20:36 -07:00
goto out ;
gdev - > state = CCWGROUP_ONLINE ;
out :
atomic_set ( & gdev - > onoff , 0 ) ;
return ret ;
}
static int
ccwgroup_set_offline ( struct ccwgroup_device * gdev )
{
struct ccwgroup_driver * gdrv ;
int ret ;
2006-01-06 00:19:07 -08:00
if ( atomic_cmpxchg ( & gdev - > onoff , 0 , 1 ) ! = 0 )
2005-04-16 15:20:36 -07:00
return - EAGAIN ;
if ( gdev - > state = = CCWGROUP_OFFLINE ) {
ret = 0 ;
goto out ;
}
if ( ! gdev - > dev . driver ) {
ret = - EINVAL ;
goto out ;
}
gdrv = to_ccwgroupdrv ( gdev - > dev . driver ) ;
2005-11-07 00:59:05 -08:00
if ( ( ret = gdrv - > set_offline ? gdrv - > set_offline ( gdev ) : 0 ) )
2005-04-16 15:20:36 -07:00
goto out ;
gdev - > state = CCWGROUP_OFFLINE ;
out :
atomic_set ( & gdev - > onoff , 0 ) ;
return ret ;
}
static ssize_t
2005-05-17 06:43:27 -04:00
ccwgroup_online_store ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-16 15:20:36 -07:00
{
struct ccwgroup_device * gdev ;
struct ccwgroup_driver * gdrv ;
2008-04-30 13:38:33 +02:00
unsigned long value ;
2005-04-16 15:20:36 -07:00
int ret ;
gdev = to_ccwgroupdev ( dev ) ;
if ( ! dev - > driver )
return count ;
gdrv = to_ccwgroupdrv ( gdev - > dev . driver ) ;
if ( ! try_module_get ( gdrv - > owner ) )
return - EINVAL ;
2008-04-30 13:38:33 +02:00
ret = strict_strtoul ( buf , 0 , & value ) ;
if ( ret )
goto out ;
2005-04-16 15:20:36 -07:00
ret = count ;
if ( value = = 1 )
ccwgroup_set_online ( gdev ) ;
else if ( value = = 0 )
ccwgroup_set_offline ( gdev ) ;
else
ret = - EINVAL ;
2008-04-30 13:38:33 +02:00
out :
2005-04-16 15:20:36 -07:00
module_put ( gdrv - > owner ) ;
return ret ;
}
static ssize_t
2005-05-17 06:43:27 -04:00
ccwgroup_online_show ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-16 15:20:36 -07:00
{
int online ;
online = ( to_ccwgroupdev ( dev ) - > state = = CCWGROUP_ONLINE ) ;
return sprintf ( buf , online ? " 1 \n " : " 0 \n " ) ;
}
static DEVICE_ATTR ( online , 0644 , ccwgroup_online_show , ccwgroup_online_store ) ;
static int
ccwgroup_probe ( struct device * dev )
{
struct ccwgroup_device * gdev ;
struct ccwgroup_driver * gdrv ;
int ret ;
gdev = to_ccwgroupdev ( dev ) ;
gdrv = to_ccwgroupdrv ( dev - > driver ) ;
if ( ( ret = device_create_file ( dev , & dev_attr_online ) ) )
return ret ;
ret = gdrv - > probe ? gdrv - > probe ( gdev ) : - ENODEV ;
if ( ret )
device_remove_file ( dev , & dev_attr_online ) ;
return ret ;
}
static int
ccwgroup_remove ( struct device * dev )
{
struct ccwgroup_device * gdev ;
struct ccwgroup_driver * gdrv ;
gdev = to_ccwgroupdev ( dev ) ;
gdrv = to_ccwgroupdrv ( dev - > driver ) ;
device_remove_file ( dev , & dev_attr_online ) ;
if ( gdrv & & gdrv - > remove )
gdrv - > remove ( gdev ) ;
return 0 ;
}
2008-02-05 16:50:36 +01:00
static void ccwgroup_shutdown ( struct device * dev )
{
struct ccwgroup_device * gdev ;
struct ccwgroup_driver * gdrv ;
gdev = to_ccwgroupdev ( dev ) ;
gdrv = to_ccwgroupdrv ( dev - > driver ) ;
if ( gdrv & & gdrv - > shutdown )
gdrv - > shutdown ( gdev ) ;
}
2006-01-05 14:42:09 +00:00
static struct bus_type ccwgroup_bus_type = {
. name = " ccwgroup " ,
. match = ccwgroup_bus_match ,
. uevent = ccwgroup_uevent ,
. probe = ccwgroup_probe ,
. remove = ccwgroup_remove ,
2008-02-05 16:50:36 +01:00
. shutdown = ccwgroup_shutdown ,
2006-01-05 14:42:09 +00:00
} ;
2007-10-12 16:11:17 +02: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-16 15:20:36 -07:00
{
/* register our new driver with the core */
2006-08-30 14:33:35 +02:00
cdriver - > driver . bus = & ccwgroup_bus_type ;
cdriver - > driver . name = cdriver - > name ;
2008-01-26 14:10:47 +01:00
cdriver - > driver . owner = cdriver - > owner ;
2005-04-16 15:20:36 -07:00
return driver_register ( & cdriver - > driver ) ;
}
2005-06-25 14:55:27 -07:00
static int
2006-06-29 14:56:52 +02:00
__ccwgroup_match_all ( struct device * dev , void * data )
2005-04-16 15:20:36 -07:00
{
2006-06-29 14:56:52 +02:00
return 1 ;
2005-04-16 15:20:36 -07:00
}
2007-10-12 16:11:17 +02: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-16 15:20:36 -07:00
{
2006-06-29 14:56:52 +02:00
struct device * dev ;
2005-04-16 15:20:36 -07:00
/* We don't want ccwgroup devices to live longer than their driver. */
get_driver ( & cdriver - > driver ) ;
2006-06-29 14:56:52 +02:00
while ( ( dev = driver_find_device ( & cdriver - > driver , NULL , NULL ,
__ccwgroup_match_all ) ) ) {
2007-04-27 16:01:37 +02:00
struct ccwgroup_device * gdev = to_ccwgroupdev ( dev ) ;
mutex_lock ( & gdev - > reg_mutex ) ;
__ccwgroup_remove_symlinks ( gdev ) ;
2006-06-29 14:56:52 +02:00
device_unregister ( dev ) ;
2007-04-27 16:01:37 +02:00
mutex_unlock ( & gdev - > reg_mutex ) ;
2006-06-29 14:56:52 +02:00
put_device ( dev ) ;
}
2005-04-16 15:20:36 -07:00
put_driver ( & cdriver - > driver ) ;
driver_unregister ( & cdriver - > driver ) ;
}
2007-10-12 16:11:17 +02: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-16 15:20:36 -07:00
{
return 0 ;
}
2007-02-05 21:18:53 +01:00
static struct ccwgroup_device *
2005-04-16 15:20:36 -07:00
__ccwgroup_get_gdev_by_cdev ( struct ccw_device * cdev )
{
struct ccwgroup_device * gdev ;
2008-01-26 14:10:46 +01:00
gdev = dev_get_drvdata ( & cdev - > dev ) ;
if ( gdev ) {
2005-04-16 15:20:36 -07:00
if ( get_device ( & gdev - > dev ) ) {
2007-04-27 16:01:37 +02:00
mutex_lock ( & gdev - > reg_mutex ) ;
2005-09-22 00:47:24 -07:00
if ( device_is_registered ( & gdev - > dev ) )
2005-04-16 15:20:36 -07:00
return gdev ;
2007-04-27 16:01:37 +02:00
mutex_unlock ( & gdev - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
put_device ( & gdev - > dev ) ;
}
return NULL ;
}
return NULL ;
}
2007-10-12 16:11:17 +02: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-16 15:20:36 -07: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. */
gdev = __ccwgroup_get_gdev_by_cdev ( cdev ) ;
if ( gdev ) {
__ccwgroup_remove_symlinks ( gdev ) ;
device_unregister ( & gdev - > dev ) ;
2007-04-27 16:01:37 +02:00
mutex_unlock ( & gdev - > reg_mutex ) ;
2005-04-16 15:20:36 -07:00
put_device ( & gdev - > dev ) ;
}
}
MODULE_LICENSE ( " GPL " ) ;
EXPORT_SYMBOL ( ccwgroup_driver_register ) ;
EXPORT_SYMBOL ( ccwgroup_driver_unregister ) ;
EXPORT_SYMBOL ( ccwgroup_probe_ccwdev ) ;
EXPORT_SYMBOL ( ccwgroup_remove_ccwdev ) ;