Commit d507789a authored by Patrick Mochel's avatar Patrick Mochel

[driver model] Add save() and restore() methods for system device drivers.

It turns out that at least some system device drivers need to allocate 
memory and/or sleep for one reason or another when either saving or 
restoring state. 

Instead of adding a 'level' paramter to the suspend() and resume() methods,
which I despise and think is a horrible programming interface, two new 
methods have been added to struct sysdev_driver:

        int     (*save)(struct sys_device *, u32 state);
        int     (*restore)(struct sys_device *);

that are called explicitly before and after suspend() and resume() 
respectively, with interrupts enabled. This gives the drivers the
flexibility to allocate memory and sleep, if necessary. 
parent 4db4540f
......@@ -30,9 +30,11 @@ extern struct subsystem devices_subsys;
* they only get one called once when interrupts are disabled.
*/
extern int sys_device_shutdown(void);
extern int sys_device_suspend(u32 state);
extern int sys_device_resume(void);
extern int sysdev_shutdown(void);
extern int sysdev_save(u32 state);
extern int sysdev_suspend(u32 state);
extern int sysdev_resume(void);
extern int sysdev_restore(void);
/**
* device_suspend - suspend/remove all devices on the device ree
......@@ -64,8 +66,19 @@ int device_suspend(u32 state, u32 level)
}
up_write(&devices_subsys.rwsem);
if (level == SUSPEND_POWER_DOWN)
sys_device_suspend(state);
/*
* Make sure system devices are suspended.
*/
switch(level) {
case SUSPEND_SAVE_STATE:
sysdev_save(state);
break;
case SUSPEND_POWER_DOWN:
sysdev_suspend(state);
break;
default:
break;
}
return error;
}
......@@ -82,8 +95,16 @@ void device_resume(u32 level)
{
struct list_head * node;
if (level == RESUME_POWER_ON)
sys_device_resume();
switch (level) {
case RESUME_POWER_ON:
sysdev_resume();
break;
case RESUME_RESTORE_STATE:
sysdev_restore();
break;
default:
break;
}
down_write(&devices_subsys.rwsem);
list_for_each_prev(node,&devices_subsys.kset.list) {
......@@ -119,7 +140,7 @@ void device_shutdown(void)
}
up_write(&devices_subsys.rwsem);
sys_device_shutdown();
sysdev_shutdown();
}
EXPORT_SYMBOL(device_suspend);
......
......@@ -200,7 +200,7 @@ void sys_device_unregister(struct sys_device * sysdev)
/**
* sys_device_shutdown - Shut down all system devices.
* 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
......@@ -213,7 +213,7 @@ void sys_device_unregister(struct sys_device * sysdev)
* after their parents.
*/
void sys_device_shutdown(void)
void sysdev_shutdown(void)
{
struct sysdev_class * cls;
......@@ -252,7 +252,53 @@ void sys_device_shutdown(void)
/**
* sys_device_suspend - Suspend all system devices.
* sysdev_save - Save system device state
* @state: Power state we're entering.
*
* This is called when the system is going to sleep, but before interrupts
* have been disabled. This allows system device drivers to allocate and
* save device state, including sleeping during the process..
*/
int sysdev_save(u32 state)
{
struct sysdev_class * cls;
pr_debug("Saving System Device State\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("Saving state for 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);
list_for_each_entry(drv,&global_drivers,entry) {
if (drv->save)
drv->save(sysdev,state);
}
list_for_each_entry(drv,&cls->drivers,entry) {
if (drv->save)
drv->save(sysdev,state);
}
if (cls->save)
cls->save(sysdev,state);
}
}
up_write(&system_subsys.rwsem);
return 0;
}
/**
* sysdev_suspend - Suspend all system devices.
* @state: Power state to enter.
*
* We perform an almost identical operation as sys_device_shutdown()
......@@ -263,7 +309,7 @@ void sys_device_shutdown(void)
* warning and return an error.
*/
int sys_device_suspend(u32 state)
int sysdev_suspend(u32 state)
{
struct sysdev_class * cls;
......@@ -308,7 +354,7 @@ int sys_device_suspend(u32 state)
/**
* sys_device_resume - Bring system devices back to life.
* 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.
......@@ -316,7 +362,7 @@ int sys_device_suspend(u32 state)
* Note: Interrupts are disabled when called.
*/
int sys_device_resume(void)
int sysdev_resume(void)
{
struct sysdev_class * cls;
......@@ -334,27 +380,72 @@ int sys_device_resume(void)
struct sysdev_driver * drv;
pr_debug(" %s\n",sysdev->kobj.name);
/* Call global drivers first. */
list_for_each_entry(drv,&global_drivers,entry) {
/* First, call the class-specific one */
if (cls->resume)
cls->resume(sysdev);
/* Call auxillary drivers next. */
list_for_each_entry(drv,&cls->drivers,entry) {
if (drv->resume)
drv->resume(sysdev);
}
/* Call auxillary drivers next. */
list_for_each_entry(drv,&cls->drivers,entry) {
/* Call global drivers. */
list_for_each_entry(drv,&global_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;
}
/**
* sysdev_restore - Restore system device state
*
* This is called during a suspend/resume cycle last, after interrupts
* have been re-enabled. This is intended for auxillary drivers, etc,
* that may sleep when restoring state.
*/
int sysdev_restore(void)
{
struct sysdev_class * cls;
down_write(&system_subsys.rwsem);
pr_debug("Restoring System Device State\n");
list_for_each_entry(cls,&system_subsys.kset.list,kset.kobj.entry) {
struct sys_device * sysdev;
pr_debug("Restoring state for 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);
if (cls->restore)
cls->restore(sysdev);
list_for_each_entry(drv,&cls->drivers,entry) {
if (drv->restore)
drv->restore(sysdev);
}
list_for_each_entry(drv,&global_drivers,entry) {
if (drv->restore)
drv->restore(sysdev);
}
}
}
up_write(&system_subsys.rwsem);
return 0;
}
int __init sys_bus_init(void)
{
system_subsys.kset.kobj.parent = &devices_subsys.kset.kobj;
......
......@@ -31,8 +31,10 @@ struct sysdev_class {
/* Default operations for these types of devices */
int (*shutdown)(struct sys_device *);
int (*save)(struct sys_device *, u32 state);
int (*suspend)(struct sys_device *, u32 state);
int (*resume)(struct sys_device *);
int (*restore)(struct sys_device *);
struct kset kset;
};
......@@ -50,8 +52,10 @@ struct sysdev_driver {
int (*add)(struct sys_device *);
int (*remove)(struct sys_device *);
int (*shutdown)(struct sys_device *);
int (*save)(struct sys_device *, u32 state);
int (*suspend)(struct sys_device *, u32 state);
int (*resume)(struct sys_device *);
int (*restore)(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