2005-04-17 02:20:36 +04: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-15 00:21:04 +03:00
* 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>
# include <asm/semaphore.h>
# include <asm/ccwdev.h>
# include <asm/ccwgroup.h>
/* 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 ;
gdev = container_of ( dev , struct ccwgroup_device , dev ) ;
gdrv = container_of ( drv , struct ccwgroup_driver , driver ) ;
if ( gdev - > creator_id = = gdrv - > driver_id )
return 1 ;
return 0 ;
}
static int
2005-11-26 07:04:26 +03:00
ccwgroup_uevent ( struct device * dev , char * * envp , int num_envp , char * buffer ,
2005-04-17 02:20:36 +04:00
int buffer_size )
{
/* TODO */
return 0 ;
}
2006-01-05 17:42:09 +03:00
static struct bus_type ccwgroup_bus_type ;
2005-04-17 02:20:36 +04:00
static inline void
__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 : )
*/
static ssize_t
2005-05-17 14:43:27 +04:00
ccwgroup_ungroup_store ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
struct ccwgroup_device * gdev ;
gdev = to_ccwgroupdev ( dev ) ;
if ( gdev - > state ! = CCWGROUP_OFFLINE )
return - EINVAL ;
__ccwgroup_remove_symlinks ( gdev ) ;
device_unregister ( dev ) ;
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 + + ) {
gdev - > cdev [ i ] - > dev . driver_data = NULL ;
put_device ( & gdev - > cdev [ i ] - > dev ) ;
}
kfree ( gdev ) ;
}
static inline int
__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 ;
}
/*
* try to add a new ccwgroup device for one driver
* argc and argv [ ] are a list of bus_id ' s of devices
* belonging to the driver .
*/
int
ccwgroup_create ( struct device * root ,
unsigned int creator_id ,
struct ccw_driver * cdrv ,
int argc , char * argv [ ] )
{
struct ccwgroup_device * gdev ;
int i ;
int rc ;
if ( argc > 256 ) /* disallow dumb users */
return - EINVAL ;
2006-03-24 14:15:31 +03:00
gdev = kzalloc ( sizeof ( * gdev ) + argc * sizeof ( gdev - > cdev [ 0 ] ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! gdev )
return - ENOMEM ;
atomic_set ( & gdev - > onoff , 0 ) ;
for ( i = 0 ; i < argc ; i + + ) {
gdev - > cdev [ i ] = get_ccwdev_by_busid ( cdrv , argv [ i ] ) ;
/* all devices have to be of the same type in
* order to be grouped */
if ( ! gdev - > cdev [ i ]
| | gdev - > cdev [ i ] - > id . driver_info ! =
gdev - > cdev [ 0 ] - > id . driver_info ) {
rc = - EINVAL ;
goto free_dev ;
}
/* Don't allow a device to belong to more than one group. */
if ( gdev - > cdev [ i ] - > dev . driver_data ) {
rc = - EINVAL ;
goto free_dev ;
}
gdev - > cdev [ i ] - > dev . driver_data = gdev ;
2006-07-27 16:00:33 +04:00
}
2005-04-17 02:20:36 +04:00
gdev - > creator_id = creator_id ;
gdev - > count = argc ;
gdev - > dev = ( struct device ) {
. bus = & ccwgroup_bus_type ,
. parent = root ,
. release = ccwgroup_release ,
} ;
snprintf ( gdev - > dev . bus_id , BUS_ID_SIZE , " %s " ,
gdev - > cdev [ 0 ] - > dev . bus_id ) ;
rc = device_register ( & gdev - > dev ) ;
if ( rc )
goto free_dev ;
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 ) {
put_device ( & gdev - > dev ) ;
return 0 ;
}
device_remove_file ( & gdev - > dev , & dev_attr_ungroup ) ;
device_unregister ( & gdev - > dev ) ;
error :
for ( i = 0 ; i < argc ; i + + )
if ( gdev - > cdev [ i ] ) {
put_device ( & gdev - > cdev [ i ] - > dev ) ;
gdev - > cdev [ i ] - > dev . driver_data = NULL ;
}
put_device ( & gdev - > dev ) ;
return rc ;
free_dev :
for ( i = 0 ; i < argc ; i + + )
if ( gdev - > cdev [ i ] ) {
2006-07-27 16:00:33 +04:00
if ( gdev - > cdev [ i ] - > dev . driver_data = = gdev )
2005-04-17 02:20:36 +04:00
gdev - > cdev [ i ] - > dev . driver_data = NULL ;
2006-07-27 16:00:33 +04:00
put_device ( & gdev - > cdev [ i ] - > dev ) ;
2005-04-17 02:20:36 +04:00
}
kfree ( gdev ) ;
return rc ;
}
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 11:19:07 +03:00
if ( atomic_cmpxchg ( & gdev - > onoff , 0 , 1 ) ! = 0 )
2005-04-17 02:20:36 +04: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 11:59:05 +03:00
if ( ( ret = gdrv - > set_online ? gdrv - > set_online ( gdev ) : 0 ) )
2005-04-17 02:20:36 +04: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 11:19:07 +03:00
if ( atomic_cmpxchg ( & gdev - > onoff , 0 , 1 ) ! = 0 )
2005-04-17 02:20:36 +04: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 11:59:05 +03:00
if ( ( ret = gdrv - > set_offline ? gdrv - > set_offline ( gdev ) : 0 ) )
2005-04-17 02:20:36 +04:00
goto out ;
gdev - > state = CCWGROUP_OFFLINE ;
out :
atomic_set ( & gdev - > onoff , 0 ) ;
return ret ;
}
static ssize_t
2005-05-17 14:43:27 +04:00
ccwgroup_online_store ( struct device * dev , struct device_attribute * attr , const char * buf , size_t count )
2005-04-17 02:20:36 +04:00
{
struct ccwgroup_device * gdev ;
struct ccwgroup_driver * gdrv ;
unsigned int value ;
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 ;
2006-07-12 18:41:55 +04:00
value = simple_strtoul ( buf , NULL , 0 ) ;
2005-04-17 02:20:36 +04:00
ret = count ;
if ( value = = 1 )
ccwgroup_set_online ( gdev ) ;
else if ( value = = 0 )
ccwgroup_set_offline ( gdev ) ;
else
ret = - EINVAL ;
module_put ( gdrv - > owner ) ;
return ret ;
}
static ssize_t
2005-05-17 14:43:27 +04:00
ccwgroup_online_show ( struct device * dev , struct device_attribute * attr , char * buf )
2005-04-17 02:20:36 +04: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 ;
pr_debug ( " %s: device %s \n " , __func__ , gdev - > dev . bus_id ) ;
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 ) ;
pr_debug ( " %s: device %s \n " , __func__ , gdev - > dev . bus_id ) ;
device_remove_file ( dev , & dev_attr_online ) ;
if ( gdrv & & gdrv - > remove )
gdrv - > remove ( gdev ) ;
return 0 ;
}
2006-01-05 17:42:09 +03:00
static struct bus_type ccwgroup_bus_type = {
. name = " ccwgroup " ,
. match = ccwgroup_bus_match ,
. uevent = ccwgroup_uevent ,
. probe = ccwgroup_probe ,
. remove = ccwgroup_remove ,
} ;
2005-04-17 02:20:36 +04:00
int
ccwgroup_driver_register ( struct ccwgroup_driver * cdriver )
{
/* register our new driver with the core */
cdriver - > driver = ( struct device_driver ) {
. bus = & ccwgroup_bus_type ,
. name = cdriver - > name ,
} ;
return driver_register ( & cdriver - > driver ) ;
}
2005-06-26 01:55:27 +04:00
static int
2006-06-29 16:56:52 +04:00
__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
}
void
ccwgroup_driver_unregister ( struct ccwgroup_driver * cdriver )
{
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. */
get_driver ( & cdriver - > driver ) ;
2006-06-29 16:56:52 +04:00
while ( ( dev = driver_find_device ( & cdriver - > driver , NULL , NULL ,
__ccwgroup_match_all ) ) ) {
__ccwgroup_remove_symlinks ( to_ccwgroupdev ( dev ) ) ;
device_unregister ( dev ) ;
put_device ( dev ) ;
}
2005-04-17 02:20:36 +04:00
put_driver ( & cdriver - > driver ) ;
driver_unregister ( & cdriver - > driver ) ;
}
int
ccwgroup_probe_ccwdev ( struct ccw_device * cdev )
{
return 0 ;
}
static inline struct ccwgroup_device *
__ccwgroup_get_gdev_by_cdev ( struct ccw_device * cdev )
{
struct ccwgroup_device * gdev ;
if ( cdev - > dev . driver_data ) {
gdev = ( struct ccwgroup_device * ) cdev - > dev . driver_data ;
if ( get_device ( & gdev - > dev ) ) {
2005-09-22 11:47:24 +04:00
if ( device_is_registered ( & gdev - > dev ) )
2005-04-17 02:20:36 +04:00
return gdev ;
put_device ( & gdev - > dev ) ;
}
return NULL ;
}
return NULL ;
}
void
ccwgroup_remove_ccwdev ( struct ccw_device * cdev )
{
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 ) ;
put_device ( & gdev - > dev ) ;
}
}
MODULE_LICENSE ( " GPL " ) ;
EXPORT_SYMBOL ( ccwgroup_driver_register ) ;
EXPORT_SYMBOL ( ccwgroup_driver_unregister ) ;
EXPORT_SYMBOL ( ccwgroup_create ) ;
EXPORT_SYMBOL ( ccwgroup_probe_ccwdev ) ;
EXPORT_SYMBOL ( ccwgroup_remove_ccwdev ) ;