2005-04-17 02:20:36 +04:00
/*
* sys . c - pseudo - bus for system ' devices ' ( cpus , PICs , timers , etc )
*
* Copyright ( c ) 2002 - 3 Patrick Mochel
* 2002 - 3 Open Source Development Lab
*
* This file is released under the GPLv2
*
* This exports a ' system ' bus type .
* By default , a ' sys ' bus gets added to the root of the system . There will
* always be core system devices . Devices can use sysdev_register ( ) to
* add themselves as children of the system bus .
*/
# include <linux/sysdev.h>
# include <linux/err.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/slab.h>
# include <linux/string.h>
2005-04-17 02:25:24 +04:00
# include <linux/pm.h>
2006-01-19 19:30:17 +03:00
# include <linux/device.h>
2007-05-24 01:19:42 +04:00
# include <linux/mutex.h>
2005-04-17 02:20:36 +04:00
2006-01-19 19:30:17 +03:00
# include "base.h"
2005-04-17 02:20:36 +04:00
# define to_sysdev(k) container_of(k, struct sys_device, kobj)
# define to_sysdev_attr(a) container_of(a, struct sysdev_attribute, attr)
static ssize_t
sysdev_show ( struct kobject * kobj , struct attribute * attr , char * buffer )
{
struct sys_device * sysdev = to_sysdev ( kobj ) ;
struct sysdev_attribute * sysdev_attr = to_sysdev_attr ( attr ) ;
if ( sysdev_attr - > show )
return sysdev_attr - > show ( sysdev , buffer ) ;
2005-04-29 10:23:47 +04:00
return - EIO ;
2005-04-17 02:20:36 +04:00
}
static ssize_t
sysdev_store ( struct kobject * kobj , struct attribute * attr ,
const char * buffer , size_t count )
{
struct sys_device * sysdev = to_sysdev ( kobj ) ;
struct sysdev_attribute * sysdev_attr = to_sysdev_attr ( attr ) ;
if ( sysdev_attr - > store )
return sysdev_attr - > store ( sysdev , buffer , count ) ;
2005-04-29 10:23:47 +04:00
return - EIO ;
2005-04-17 02:20:36 +04:00
}
static struct sysfs_ops sysfs_ops = {
. show = sysdev_show ,
. store = sysdev_store ,
} ;
static struct kobj_type ktype_sysdev = {
. sysfs_ops = & sysfs_ops ,
} ;
int sysdev_create_file ( struct sys_device * s , struct sysdev_attribute * a )
{
return sysfs_create_file ( & s - > kobj , & a - > attr ) ;
}
void sysdev_remove_file ( struct sys_device * s , struct sysdev_attribute * a )
{
sysfs_remove_file ( & s - > kobj , & a - > attr ) ;
}
EXPORT_SYMBOL_GPL ( sysdev_create_file ) ;
EXPORT_SYMBOL_GPL ( sysdev_remove_file ) ;
2006-05-08 09:45:57 +04:00
# define to_sysdev_class(k) container_of(k, struct sysdev_class, kset.kobj)
# define to_sysdev_class_attr(a) container_of(a, \
struct sysdev_class_attribute , attr )
static ssize_t sysdev_class_show ( struct kobject * kobj , struct attribute * attr ,
char * buffer )
{
struct sysdev_class * class = to_sysdev_class ( kobj ) ;
struct sysdev_class_attribute * class_attr = to_sysdev_class_attr ( attr ) ;
if ( class_attr - > show )
return class_attr - > show ( class , buffer ) ;
return - EIO ;
}
static ssize_t sysdev_class_store ( struct kobject * kobj , struct attribute * attr ,
const char * buffer , size_t count )
{
struct sysdev_class * class = to_sysdev_class ( kobj ) ;
struct sysdev_class_attribute * class_attr = to_sysdev_class_attr ( attr ) ;
if ( class_attr - > store )
return class_attr - > store ( class , buffer , count ) ;
return - EIO ;
}
static struct sysfs_ops sysfs_class_ops = {
. show = sysdev_class_show ,
. store = sysdev_class_store ,
} ;
static struct kobj_type ktype_sysdev_class = {
. sysfs_ops = & sysfs_class_ops ,
} ;
int sysdev_class_create_file ( struct sysdev_class * c ,
struct sysdev_class_attribute * a )
{
return sysfs_create_file ( & c - > kset . kobj , & a - > attr ) ;
}
EXPORT_SYMBOL_GPL ( sysdev_class_create_file ) ;
void sysdev_class_remove_file ( struct sysdev_class * c ,
struct sysdev_class_attribute * a )
{
sysfs_remove_file ( & c - > kset . kobj , & a - > attr ) ;
}
EXPORT_SYMBOL_GPL ( sysdev_class_remove_file ) ;
2007-11-01 18:29:06 +03:00
static struct kset * system_kset ;
2005-04-17 02:20:36 +04:00
int sysdev_class_register ( struct sysdev_class * cls )
{
pr_debug ( " Registering sysdev class '%s' \n " ,
kobject_name ( & cls - > kset . kobj ) ) ;
INIT_LIST_HEAD ( & cls - > drivers ) ;
2007-11-01 18:29:06 +03:00
cls - > kset . kobj . parent = & system_kset - > kobj ;
2007-10-16 20:11:44 +04:00
cls - > kset . kobj . ktype = & ktype_sysdev_class ;
2007-11-01 18:29:06 +03:00
cls - > kset . kobj . kset = system_kset ;
2007-12-20 04:09:39 +03:00
kobject_set_name ( & cls - > kset . kobj , cls - > name ) ;
2005-04-17 02:20:36 +04:00
return kset_register ( & cls - > kset ) ;
}
void sysdev_class_unregister ( struct sysdev_class * cls )
{
pr_debug ( " Unregistering sysdev class '%s' \n " ,
kobject_name ( & cls - > kset . kobj ) ) ;
kset_unregister ( & cls - > kset ) ;
}
EXPORT_SYMBOL_GPL ( sysdev_class_register ) ;
EXPORT_SYMBOL_GPL ( sysdev_class_unregister ) ;
2007-05-24 01:19:42 +04:00
static DEFINE_MUTEX ( sysdev_drivers_lock ) ;
2005-04-17 02:20:36 +04:00
/**
* sysdev_driver_register - Register auxillary driver
2007-08-19 11:51:14 +04:00
* @ cls : Device class driver belongs to .
2005-04-17 02:20:36 +04:00
* @ drv : Driver .
*
2007-08-19 11:51:14 +04:00
* @ drv is inserted into @ cls - > drivers to be
2005-04-17 02:20:36 +04:00
* called on each operation on devices of that class . The refcount
* of @ cls is incremented .
*/
2007-08-19 11:51:14 +04:00
int sysdev_driver_register ( struct sysdev_class * cls , struct sysdev_driver * drv )
2005-04-17 02:20:36 +04:00
{
2007-08-19 11:51:14 +04:00
int err = 0 ;
2007-05-24 01:19:42 +04:00
mutex_lock ( & sysdev_drivers_lock ) ;
2005-04-17 02:20:36 +04:00
if ( cls & & kset_get ( & cls - > kset ) ) {
list_add_tail ( & drv - > entry , & cls - > drivers ) ;
/* If devices of this class already exist, tell the driver */
if ( drv - > add ) {
struct sys_device * dev ;
list_for_each_entry ( dev , & cls - > kset . list , kobj . entry )
drv - > add ( dev ) ;
}
2007-08-19 11:51:14 +04:00
} else {
err = - EINVAL ;
printk ( KERN_ERR " %s: invalid device class \n " , __FUNCTION__ ) ;
WARN_ON ( 1 ) ;
}
2007-05-24 01:19:42 +04:00
mutex_unlock ( & sysdev_drivers_lock ) ;
2007-08-19 11:51:14 +04:00
return err ;
2005-04-17 02:20:36 +04:00
}
/**
* sysdev_driver_unregister - Remove an auxillary driver .
* @ cls : Class driver belongs to .
* @ drv : Driver .
*/
void sysdev_driver_unregister ( struct sysdev_class * cls ,
struct sysdev_driver * drv )
{
2007-05-24 01:19:42 +04:00
mutex_lock ( & sysdev_drivers_lock ) ;
2005-04-17 02:20:36 +04:00
list_del_init ( & drv - > entry ) ;
if ( cls ) {
if ( drv - > remove ) {
struct sys_device * dev ;
list_for_each_entry ( dev , & cls - > kset . list , kobj . entry )
drv - > remove ( dev ) ;
}
kset_put ( & cls - > kset ) ;
}
2007-05-24 01:19:42 +04:00
mutex_unlock ( & sysdev_drivers_lock ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL_GPL ( sysdev_driver_register ) ;
EXPORT_SYMBOL_GPL ( sysdev_driver_unregister ) ;
/**
* sysdev_register - add a system device to the tree
* @ sysdev : device in question
*
*/
int sysdev_register ( struct sys_device * sysdev )
{
int error ;
struct sysdev_class * cls = sysdev - > cls ;
if ( ! cls )
return - EINVAL ;
2007-12-17 22:54:39 +03:00
pr_debug ( " Registering sys device '%s' \n " , kobject_name ( & sysdev - > kobj ) ) ;
2005-04-17 02:20:36 +04:00
/* Make sure the kset is set */
sysdev - > kobj . kset = & cls - > kset ;
/* Register the object */
2007-12-17 22:54:39 +03:00
error = kobject_init_and_add ( & sysdev - > kobj , & ktype_sysdev , NULL ,
" %s%d " , kobject_name ( & cls - > kset . kobj ) ,
sysdev - > id ) ;
2005-04-17 02:20:36 +04:00
if ( ! error ) {
struct sysdev_driver * drv ;
2007-05-24 01:19:42 +04:00
mutex_lock ( & sysdev_drivers_lock ) ;
2005-04-17 02:20:36 +04:00
/* Generic notification is implicit, because it's that
* code that should have called us .
*/
/* Notify class auxillary drivers */
list_for_each_entry ( drv , & cls - > drivers , entry ) {
if ( drv - > add )
drv - > add ( sysdev ) ;
}
2007-05-24 01:19:42 +04:00
mutex_unlock ( & sysdev_drivers_lock ) ;
2005-04-17 02:20:36 +04:00
}
2007-12-17 22:54:39 +03:00
kobject_uevent ( & sysdev - > kobj , KOBJ_ADD ) ;
2005-04-17 02:20:36 +04:00
return error ;
}
void sysdev_unregister ( struct sys_device * sysdev )
{
struct sysdev_driver * drv ;
2007-05-24 01:19:42 +04:00
mutex_lock ( & sysdev_drivers_lock ) ;
2005-04-17 02:20:36 +04:00
list_for_each_entry ( drv , & sysdev - > cls - > drivers , entry ) {
if ( drv - > remove )
drv - > remove ( sysdev ) ;
}
2007-05-24 01:19:42 +04:00
mutex_unlock ( & sysdev_drivers_lock ) ;
2005-04-17 02:20:36 +04:00
2007-12-20 19:13:05 +03:00
kobject_put ( & sysdev - > kobj ) ;
2005-04-17 02:20:36 +04:00
}
/**
* sysdev_shutdown - Shut down all system devices .
*
* Loop over each class of system devices , and the devices in each
* of those classes . For each device , we call the shutdown method for
2007-08-19 11:51:14 +04:00
* each driver registered for the device - the auxillaries ,
2005-04-17 02:20:36 +04:00
* and the class driver .
*
* Note : The list is iterated in reverse order , so that we shut down
* child devices before we shut down thier parents . The list ordering
* is guaranteed by virtue of the fact that child devices are registered
* after their parents .
*/
void sysdev_shutdown ( void )
{
struct sysdev_class * cls ;
pr_debug ( " Shutting Down System Devices \n " ) ;
2007-05-24 01:19:42 +04:00
mutex_lock ( & sysdev_drivers_lock ) ;
2007-11-01 18:29:06 +03:00
list_for_each_entry_reverse ( cls , & system_kset - > list , kset . kobj . entry ) {
2005-04-17 02:20:36 +04:00
struct sys_device * sysdev ;
pr_debug ( " Shutting down type '%s': \n " ,
kobject_name ( & cls - > kset . kobj ) ) ;
list_for_each_entry ( sysdev , & cls - > kset . list , kobj . entry ) {
struct sysdev_driver * drv ;
pr_debug ( " %s \n " , kobject_name ( & sysdev - > kobj ) ) ;
2007-08-19 11:51:14 +04:00
/* Call auxillary drivers first */
2005-04-17 02:20:36 +04:00
list_for_each_entry ( drv , & cls - > drivers , entry ) {
if ( drv - > shutdown )
drv - > shutdown ( sysdev ) ;
}
/* Now call the generic one */
if ( cls - > shutdown )
cls - > shutdown ( sysdev ) ;
}
}
2007-05-24 01:19:42 +04:00
mutex_unlock ( & sysdev_drivers_lock ) ;
2005-04-17 02:20:36 +04:00
}
2005-08-11 06:37:39 +04:00
static void __sysdev_resume ( struct sys_device * dev )
{
struct sysdev_class * cls = dev - > cls ;
struct sysdev_driver * drv ;
/* First, call the class-specific one */
if ( cls - > resume )
cls - > resume ( dev ) ;
/* Call auxillary drivers next. */
list_for_each_entry ( drv , & cls - > drivers , entry ) {
if ( drv - > resume )
drv - > resume ( dev ) ;
}
}
2005-04-17 02:20:36 +04:00
/**
* sysdev_suspend - Suspend all system devices .
* @ state : Power state to enter .
*
* We perform an almost identical operation as sys_device_shutdown ( )
* above , though calling - > suspend ( ) instead . Interrupts are disabled
* when this called . Devices are responsible for both saving state and
* quiescing or powering down the device .
*
* This is only called by the device PM core , so we let them handle
* all synchronization .
*/
2005-04-17 02:25:24 +04:00
int sysdev_suspend ( pm_message_t state )
2005-04-17 02:20:36 +04:00
{
struct sysdev_class * cls ;
2005-08-11 06:37:39 +04:00
struct sys_device * sysdev , * err_dev ;
struct sysdev_driver * drv , * err_drv ;
int ret ;
2005-04-17 02:20:36 +04:00
pr_debug ( " Suspending System Devices \n " ) ;
2007-11-01 18:29:06 +03:00
list_for_each_entry_reverse ( cls , & system_kset - > list , kset . kobj . entry ) {
2005-04-17 02:20:36 +04:00
pr_debug ( " Suspending type '%s': \n " ,
kobject_name ( & cls - > kset . kobj ) ) ;
list_for_each_entry ( sysdev , & cls - > kset . list , kobj . entry ) {
pr_debug ( " %s \n " , kobject_name ( & sysdev - > kobj ) ) ;
2007-08-19 11:51:14 +04:00
/* Call auxillary drivers first */
2005-04-17 02:20:36 +04:00
list_for_each_entry ( drv , & cls - > drivers , entry ) {
2005-08-11 06:37:39 +04:00
if ( drv - > suspend ) {
ret = drv - > suspend ( sysdev , state ) ;
if ( ret )
goto aux_driver ;
}
2005-04-17 02:20:36 +04:00
}
/* Now call the generic one */
2005-08-11 06:37:39 +04:00
if ( cls - > suspend ) {
ret = cls - > suspend ( sysdev , state ) ;
if ( ret )
goto cls_driver ;
}
2005-04-17 02:20:36 +04:00
}
}
return 0 ;
2005-08-11 06:37:39 +04:00
/* resume current sysdev */
cls_driver :
drv = NULL ;
printk ( KERN_ERR " Class suspend failed for %s \n " ,
kobject_name ( & sysdev - > kobj ) ) ;
aux_driver :
if ( drv )
printk ( KERN_ERR " Class driver suspend failed for %s \n " ,
kobject_name ( & sysdev - > kobj ) ) ;
list_for_each_entry ( err_drv , & cls - > drivers , entry ) {
if ( err_drv = = drv )
break ;
if ( err_drv - > resume )
err_drv - > resume ( sysdev ) ;
}
/* resume other sysdevs in current class */
list_for_each_entry ( err_dev , & cls - > kset . list , kobj . entry ) {
if ( err_dev = = sysdev )
break ;
pr_debug ( " %s \n " , kobject_name ( & err_dev - > kobj ) ) ;
__sysdev_resume ( err_dev ) ;
}
/* resume other classes */
2007-11-01 18:29:06 +03:00
list_for_each_entry_continue ( cls , & system_kset - > list , kset . kobj . entry ) {
2005-08-11 06:37:39 +04:00
list_for_each_entry ( err_dev , & cls - > kset . list , kobj . entry ) {
pr_debug ( " %s \n " , kobject_name ( & err_dev - > kobj ) ) ;
__sysdev_resume ( err_dev ) ;
}
}
return ret ;
2005-04-17 02:20:36 +04:00
}
/**
* sysdev_resume - Bring system devices back to life .
*
* Similar to sys_device_suspend ( ) , but we iterate the list forwards
* to guarantee that parent devices are resumed before their children .
*
* Note : Interrupts are disabled when called .
*/
int sysdev_resume ( void )
{
struct sysdev_class * cls ;
pr_debug ( " Resuming System Devices \n " ) ;
2007-11-01 18:29:06 +03:00
list_for_each_entry ( cls , & system_kset - > list , kset . kobj . entry ) {
2005-04-17 02:20:36 +04:00
struct sys_device * sysdev ;
pr_debug ( " Resuming type '%s': \n " ,
kobject_name ( & cls - > kset . kobj ) ) ;
list_for_each_entry ( sysdev , & cls - > kset . list , kobj . entry ) {
pr_debug ( " %s \n " , kobject_name ( & sysdev - > kobj ) ) ;
2005-08-11 06:37:39 +04:00
__sysdev_resume ( sysdev ) ;
2005-04-17 02:20:36 +04:00
}
}
return 0 ;
}
int __init system_bus_init ( void )
{
2007-11-01 18:29:06 +03:00
system_kset = kset_create_and_add ( " system " , NULL , & devices_kset - > kobj ) ;
if ( ! system_kset )
return - ENOMEM ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL_GPL ( sysdev_register ) ;
EXPORT_SYMBOL_GPL ( sysdev_unregister ) ;