Commit a4ff342a authored by Patrick Mochel's avatar Patrick Mochel

[driver model] Rewrite system device API

System devices are special, and after two years of listening to Linus
preach this, it finally sunk in enough to do something about. We don't
need to regard them as real devices that reside on a peripheral bus and
can be dynamically bound to drivers. If we discover, e.g. a CPU, we know
by default that we have a driver for it, and we know damn well that we
have a CPU. We still need to keep track of all the devices, and all the
devices of a particular type. The kobject infrastructure allows us to do
this, without the overhead of the regular model.

A new subsystem is defined that registers as a child object of 
devices_subsys, giving us:

        /sys/devices/system/

struct sysdev_class {
        struct list_head        drivers;

        /* Default operations for these types of devices */
        int     (*shutdown)(struct sys_device *);
        int     (*suspend)(struct sys_device *, u32 state);
        int     (*resume)(struct sys_device *);
        struct kset             kset;
};

Defines a type of system device. These are registered on startup, by e.g. 
drivers/base/cpu.c. The methods are default operations for devices of that 
type that may or may not be used. For things like the i8259 controller, 
these will be filled in, since it is registered by the same component that 
the device controls reside in. 

For things like CPUs, generic code will register the class, but other 
architecture-specific or otherwise configurable drivers may register 
auxillary drivers, that look like: 

struct sysdev_driver {
        struct list_head        entry;
        int     (*add)(struct sys_device *);
        int     (*remove)(struct sys_device *);
        int     (*shutdown)(struct sys_device *);
        int     (*suspend)(struct sys_device *, u32 state);
        int     (*resume)(struct sys_device *);
};


Each auxillary driver gets called during each operation on a device of a 
particular class. 
Auxillary drivers may register with a NULL class parameter, in which case 
they will be added to a list of 'global drivers' that get called for each 
device of each class. 


Besides providing a decent of cleanup for system device drivers, this also 
allows:

- Special handling of system devices during power transitions. 

  We no longer have to worry about shutting down the PIC before we shut 
  down any devices. We can shut down the system devices after we've shut 
  down every other device. 

  Ditto for suspend/resume cycles. Almost (if not) all PM actions for 
  system devices happen with interrupts off, and require only one call, 
  which makes that easier. But, we can also make sure we take care of 
  these last during suspend and first during resume.

- Easy expression of configurable device-specific interfaces. 

  Namely cpufreq and mtrr. We don't have to worry about mispresentation in 
  the driver model (like recent MTRR patches) or using a cumbersome 
  interface ({device,class}_interface) that don't receive all the 
  necessary calls. 

- Consolidation of userspace representation.

  No longer do we have /sys/devices/sys, /sys/bus/sys, and /sys/class/cpu,
  etc. We have only /sys/devices/system: 

# tree /sys/devices/system/
/sys/devices/system/
|-- cpu
|   `-- cpu0
|-- i8259
|   `-- i82590
|-- lapic
|   `-- lapic0
|-- rtc
|   `-- rtc0
`-- timer
    `-- timer0

Each directory in 'system' is the class, and each directory under that is 
the instance of each device in that class. 
parent cb4acf11
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
* add themselves as children of the system bus. * add themselves as children of the system bus.
*/ */
#undef DEBUG #define DEBUG
#include <linux/device.h> #include <linux/device.h>
#include <linux/err.h> #include <linux/err.h>
...@@ -22,130 +22,307 @@ ...@@ -22,130 +22,307 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/string.h> #include <linux/string.h>
/* The default system device parent. */
static struct device system_bus = {
.name = "System Bus",
.bus_id = "sys",
};
extern struct subsystem devices_subsys;
/*
* declare system_subsys
*/
decl_subsys(system,NULL,NULL);
int sysdev_class_register(struct sysdev_class * cls)
{
pr_debug("Registering sysdev class '%s'\n",cls->kset.kobj.name);
INIT_LIST_HEAD(&cls->drivers);
cls->kset.subsys = &system_subsys;
kset_set_kset_s(cls,system_subsys);
return kset_register(&cls->kset);
}
void sysdev_class_unregister(struct sysdev_class * cls)
{
pr_debug("Unregistering sysdev class '%s'\n",cls->kset.kobj.name);
kset_unregister(&cls->kset);
}
EXPORT_SYMBOL(sysdev_class_register);
EXPORT_SYMBOL(sysdev_class_unregister);
static LIST_HEAD(global_drivers);
/** /**
* sys_register_root - add a subordinate system root * sysdev_driver_register - Register auxillary driver
* @root: new root * @cls: Device class driver belongs to.
* * @drv: Driver.
* This is for NUMA-like systems so they can accurately
* represent the topology of the entire system.
* As boards are discovered, a new struct sys_root should
* be allocated and registered.
* The discovery mechanism should initialize the id field
* of the struture, as well as much of the embedded device
* structure as possible, inlcuding the name, the bus_id
* and parent fields.
* *
* This simply calls device_register on the embedded device. * If @cls is valid, then @drv is inserted into @cls->drivers to be
* On success, it will use the struct @root->sysdev * called on each operation on devices of that class. The refcount
* device to create a pseudo-parent for system devices * of @cls is incremented.
* on that board. * Otherwise, @drv is inserted into global_drivers, and called for
* each device.
*/
int sysdev_driver_register(struct sysdev_class * cls,
struct sysdev_driver * drv)
{
down_write(&system_subsys.rwsem);
if (kset_get(&cls->kset))
list_add_tail(&drv->entry,&cls->drivers);
else
list_add_tail(&drv->entry,&global_drivers);
up_write(&system_subsys.rwsem);
return 0;
}
/**
* 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)
{
down_write(&system_subsys.rwsem);
list_del_init(&drv->entry);
if (cls)
kset_put(&cls->kset);
up_write(&system_subsys.rwsem);
}
/**
* sys_device_register - add a system device to the tree
* @sysdev: device in question
* *
* The platform code can then use @root to specifiy the
* controlling board when discovering and registering
* system devices.
*/ */
int sys_register_root(struct sys_root * root) int sys_device_register(struct sys_device * sysdev)
{ {
int error = 0; int error;
struct sysdev_class * cls = sysdev->cls;
if (!root) if (!cls)
return -EINVAL; return -EINVAL;
if (!root->dev.parent) /* Make sure the kset is set */
root->dev.parent = &system_bus; sysdev->kobj.kset = &cls->kset;
/* set the kobject name */
snprintf(sysdev->kobj.name,KOBJ_NAME_LEN,"%s%d",
cls->kset.kobj.name,sysdev->id);
pr_debug("Registering system board %d\n",root->id); pr_debug("Registering sys device '%s'\n",sysdev->kobj.name);
/* Register the object */
error = kobject_register(&sysdev->kobj);
error = device_register(&root->dev);
if (!error) { if (!error) {
strlcpy(root->sysdev.bus_id,"sys",BUS_ID_SIZE); struct sysdev_driver * drv;
strlcpy(root->sysdev.name,"System Bus",DEVICE_NAME_SIZE);
root->sysdev.parent = &root->dev; down_read(&system_subsys.rwsem);
error = device_register(&root->sysdev); /* Generic notification is implicit, because it's that
}; * code that should have called us.
*/
/* Notify global drivers */
list_for_each_entry(drv,&global_drivers,entry) {
if (drv->add)
drv->add(sysdev);
}
/* Notify class auxillary drivers */
list_for_each_entry(drv,&cls->drivers,entry) {
if (drv->add)
drv->add(sysdev);
}
up_read(&system_subsys.rwsem);
}
return error; return error;
} }
void sys_device_unregister(struct sys_device * sysdev)
{
struct sysdev_driver * drv;
down_read(&system_subsys.rwsem);
list_for_each_entry(drv,&global_drivers,entry) {
if (drv->remove)
drv->remove(sysdev);
}
list_for_each_entry(drv,&sysdev->cls->drivers,entry) {
if (drv->remove)
drv->remove(sysdev);
}
up_read(&system_subsys.rwsem);
kobject_unregister(&sysdev->kobj);
}
/** /**
* sys_unregister_root - remove subordinate root from tree * sys_device_shutdown - Shut down all system devices.
* @root: subordinate root in question.
* *
* We only decrement the reference count on @root->sysdev * Loop over each class of system devices, and the devices in each
* and @root->dev. * of those classes. For each device, we call the shutdown method for
* If both are 0, they will be cleaned up by the core. * each driver registered for the device - the globals, the auxillaries,
* 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 sys_unregister_root(struct sys_root *root)
void sys_device_shutdown(void)
{ {
device_unregister(&root->sysdev); struct sysdev_class * cls;
device_unregister(&root->dev);
pr_debug("Shutting Down System Devices\n");
down_write(&system_subsys.rwsem);
list_for_each_entry_reverse(cls,&system_subsys.kset.list,
kset.kobj.entry) {
struct sys_device * sysdev;
pr_debug("Shutting down type '%s':\n",cls->kset.kobj.name);
list_for_each_entry(sysdev,&cls->kset.list,kobj.entry) {
struct sysdev_driver * drv;
pr_debug(" %s\n",sysdev->kobj.name);
/* Call global drivers first. */
list_for_each_entry(drv,&global_drivers,entry) {
if (drv->shutdown)
drv->shutdown(sysdev);
}
/* Call auxillary drivers next. */
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);
}
}
up_write(&system_subsys.rwsem);
} }
/** /**
* sys_device_register - add a system device to the tree * sys_device_suspend - Suspend all system devices.
* @sysdev: device in question * @state: Power state to enter.
* *
* The hardest part about this is getting the ancestry right. * We perform an almost identical operation as sys_device_shutdown()
* If the device has a parent - super! We do nothing. * above, though calling ->suspend() instead.
* If the device doesn't, but @dev->root is set, then we're
* dealing with a NUMA like architecture where each root
* has a system pseudo-bus to foster the device.
* If not, then we fallback to system_bus (at the top of
* this file).
* *
* One way or another, we call device_register() on it and * Note: Interrupts are disabled when called, so we can't sleep when
* are done. * trying to get the subsystem's rwsem. If that happens, print a nasty
* * warning and return an error.
* The caller is also responsible for initializing the bus_id
* and name fields of @sysdev->dev.
*/ */
int sys_device_register(struct sys_device * sysdev)
int sys_device_suspend(u32 state)
{ {
if (!sysdev) struct sysdev_class * cls;
return -EINVAL;
if (!sysdev->dev.parent) { pr_debug("Suspending System Devices\n");
if (sysdev->root)
sysdev->dev.parent = &sysdev->root->sysdev; if (!down_write_trylock(&system_subsys.rwsem)) {
else printk("%s: Cannot acquire semaphore; Failing\n",__FUNCTION__);
sysdev->dev.parent = &system_bus; return -EFAULT;
} }
/* make sure bus type is set */ list_for_each_entry_reverse(cls,&system_subsys.kset.list,
if (!sysdev->dev.bus) kset.kobj.entry) {
sysdev->dev.bus = &system_bus_type; struct sys_device * sysdev;
/* construct bus_id */ pr_debug("Suspending type '%s':\n",cls->kset.kobj.name);
snprintf(sysdev->dev.bus_id,BUS_ID_SIZE,"%s%u",sysdev->name,sysdev->id);
pr_debug("Registering system device %s\n", sysdev->dev.bus_id); list_for_each_entry(sysdev,&cls->kset.list,kobj.entry) {
struct sysdev_driver * drv;
pr_debug(" %s\n",sysdev->kobj.name);
return device_register(&sysdev->dev); /* Call global drivers first. */
list_for_each_entry(drv,&global_drivers,entry) {
if (drv->suspend)
drv->suspend(sysdev,state);
}
/* Call auxillary drivers next. */
list_for_each_entry(drv,&cls->drivers,entry) {
if (drv->suspend)
drv->suspend(sysdev,state);
}
/* Now call the generic one */
if (cls->suspend)
cls->suspend(sysdev,state);
}
}
up_write(&system_subsys.rwsem);
return 0;
} }
void sys_device_unregister(struct sys_device * sysdev)
/**
* sys_device_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 sys_device_resume(void)
{ {
if (sysdev) struct sysdev_class * cls;
device_unregister(&sysdev->dev);
}
struct bus_type system_bus_type = { pr_debug("Resuming System Devices\n");
.name = "system",
}; if(!down_write_trylock(&system_subsys.rwsem))
return -EFAULT;
list_for_each_entry(cls,&system_subsys.kset.list,kset.kobj.entry) {
struct sys_device * sysdev;
pr_debug("Resuming type '%s':\n",cls->kset.kobj.name);
list_for_each_entry(sysdev,&cls->kset.list,kobj.entry) {
struct sysdev_driver * drv;
pr_debug(" %s\n",sysdev->kobj.name);
/* Call global drivers first. */
list_for_each_entry(drv,&global_drivers,entry) {
if (drv->resume)
drv->resume(sysdev);
}
/* Call auxillary drivers next. */
list_for_each_entry(drv,&cls->drivers,entry) {
if (drv->resume)
drv->resume(sysdev);
}
/* Now call the generic one */
if (cls->resume)
cls->resume(sysdev);
}
}
up_write(&system_subsys.rwsem);
return 0;
}
int __init sys_bus_init(void) int __init sys_bus_init(void)
{ {
bus_register(&system_bus_type); system_subsys.kset.kobj.parent = &devices_subsys.kset.kobj;
return device_register(&system_bus); return subsystem_register(&system_subsys);
} }
EXPORT_SYMBOL(system_bus_type);
EXPORT_SYMBOL(sys_device_register); EXPORT_SYMBOL(sys_device_register);
EXPORT_SYMBOL(sys_device_unregister); EXPORT_SYMBOL(sys_device_unregister);
...@@ -351,24 +351,72 @@ extern int (*platform_notify_remove)(struct device * dev); ...@@ -351,24 +351,72 @@ extern int (*platform_notify_remove)(struct device * dev);
extern struct device * get_device(struct device * dev); extern struct device * get_device(struct device * dev);
extern void put_device(struct device * dev); extern void put_device(struct device * dev);
/* drivers/base/sys.c */ /* drivers/base/sys.c */
struct sys_root { /**
u32 id; * System devices follow a slightly different driver model.
struct device dev; * They don't need to do dynammic driver binding, can't be probed,
struct device sysdev; * and don't reside on any type of peripheral bus.
* So, we represent and treat them a little differently.
*
* We still have a notion of a driver for a system device, because we still
* want to perform basic operations on these devices.
*
* We also support auxillary drivers binding to devices of a certain class.
*
* This allows configurable drivers to register themselves for devices of
* a certain type. And, it allows class definitions to reside in generic
* code while arch-specific code can register specific drivers.
*
* Auxillary drivers registered with a NULL cls are registered as drivers
* for all system devices, and get notification calls for each device.
*/
struct sys_device;
struct sysdev_class {
struct list_head drivers;
/* Default operations for these types of devices */
int (*shutdown)(struct sys_device *);
int (*suspend)(struct sys_device *, u32 state);
int (*resume)(struct sys_device *);
struct kset kset;
}; };
extern int sys_register_root(struct sys_root *);
extern void sys_unregister_root(struct sys_root *); extern int sysdev_class_register(struct sysdev_class *);
extern void sysdev_class_unregister(struct sysdev_class *);
/**
* Auxillary system device drivers.
*/
struct sysdev_driver {
struct list_head entry;
int (*add)(struct sys_device *);
int (*remove)(struct sys_device *);
int (*shutdown)(struct sys_device *);
int (*suspend)(struct sys_device *, u32 state);
int (*resume)(struct sys_device *);
};
extern int sysdev_driver_register(struct sysdev_class *, struct sysdev_driver *);
extern void sysdev_driver_unregister(struct sysdev_class *, struct sysdev_driver *);
/**
* sys_devices can be simplified a lot from regular devices, because they're
* simply not as versatile.
*/
struct sys_device { struct sys_device {
char * name;
u32 id; u32 id;
struct sys_root * root; struct sysdev_class * cls;
struct device dev; struct kobject kobj;
struct class_device class_dev;
}; };
extern int sys_device_register(struct sys_device *); extern int sys_device_register(struct sys_device *);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment