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; ...@@ -30,9 +30,11 @@ extern struct subsystem devices_subsys;
* they only get one called once when interrupts are disabled. * they only get one called once when interrupts are disabled.
*/ */
extern int sys_device_shutdown(void); extern int sysdev_shutdown(void);
extern int sys_device_suspend(u32 state); extern int sysdev_save(u32 state);
extern int sys_device_resume(void); 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 * device_suspend - suspend/remove all devices on the device ree
...@@ -64,8 +66,19 @@ int device_suspend(u32 state, u32 level) ...@@ -64,8 +66,19 @@ int device_suspend(u32 state, u32 level)
} }
up_write(&devices_subsys.rwsem); 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; return error;
} }
...@@ -82,8 +95,16 @@ void device_resume(u32 level) ...@@ -82,8 +95,16 @@ void device_resume(u32 level)
{ {
struct list_head * node; struct list_head * node;
if (level == RESUME_POWER_ON) switch (level) {
sys_device_resume(); case RESUME_POWER_ON:
sysdev_resume();
break;
case RESUME_RESTORE_STATE:
sysdev_restore();
break;
default:
break;
}
down_write(&devices_subsys.rwsem); down_write(&devices_subsys.rwsem);
list_for_each_prev(node,&devices_subsys.kset.list) { list_for_each_prev(node,&devices_subsys.kset.list) {
...@@ -119,7 +140,7 @@ void device_shutdown(void) ...@@ -119,7 +140,7 @@ void device_shutdown(void)
} }
up_write(&devices_subsys.rwsem); up_write(&devices_subsys.rwsem);
sys_device_shutdown(); sysdev_shutdown();
} }
EXPORT_SYMBOL(device_suspend); EXPORT_SYMBOL(device_suspend);
......
...@@ -200,7 +200,7 @@ void sys_device_unregister(struct sys_device * sysdev) ...@@ -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 * Loop over each class of system devices, and the devices in each
* of those classes. For each device, we call the shutdown method for * of those classes. For each device, we call the shutdown method for
...@@ -213,7 +213,7 @@ void sys_device_unregister(struct sys_device * sysdev) ...@@ -213,7 +213,7 @@ void sys_device_unregister(struct sys_device * sysdev)
* after their parents. * after their parents.
*/ */
void sys_device_shutdown(void) void sysdev_shutdown(void)
{ {
struct sysdev_class * cls; struct sysdev_class * cls;
...@@ -252,7 +252,53 @@ void sys_device_shutdown(void) ...@@ -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. * @state: Power state to enter.
* *
* We perform an almost identical operation as sys_device_shutdown() * We perform an almost identical operation as sys_device_shutdown()
...@@ -263,7 +309,7 @@ void sys_device_shutdown(void) ...@@ -263,7 +309,7 @@ void sys_device_shutdown(void)
* warning and return an error. * warning and return an error.
*/ */
int sys_device_suspend(u32 state) int sysdev_suspend(u32 state)
{ {
struct sysdev_class * cls; struct sysdev_class * cls;
...@@ -308,7 +354,7 @@ int sys_device_suspend(u32 state) ...@@ -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 * Similar to sys_device_suspend(), but we iterate the list forwards
* to guarantee that parent devices are resumed before their children. * to guarantee that parent devices are resumed before their children.
...@@ -316,7 +362,7 @@ int sys_device_suspend(u32 state) ...@@ -316,7 +362,7 @@ int sys_device_suspend(u32 state)
* Note: Interrupts are disabled when called. * Note: Interrupts are disabled when called.
*/ */
int sys_device_resume(void) int sysdev_resume(void)
{ {
struct sysdev_class * cls; struct sysdev_class * cls;
...@@ -334,27 +380,72 @@ int sys_device_resume(void) ...@@ -334,27 +380,72 @@ int sys_device_resume(void)
struct sysdev_driver * drv; struct sysdev_driver * drv;
pr_debug(" %s\n",sysdev->kobj.name); pr_debug(" %s\n",sysdev->kobj.name);
/* Call global drivers first. */ /* First, call the class-specific one */
list_for_each_entry(drv,&global_drivers,entry) { if (cls->resume)
cls->resume(sysdev);
/* Call auxillary drivers next. */
list_for_each_entry(drv,&cls->drivers,entry) {
if (drv->resume) if (drv->resume)
drv->resume(sysdev); drv->resume(sysdev);
} }
/* Call auxillary drivers next. */ /* Call global drivers. */
list_for_each_entry(drv,&cls->drivers,entry) { list_for_each_entry(drv,&global_drivers,entry) {
if (drv->resume) if (drv->resume)
drv->resume(sysdev); drv->resume(sysdev);
} }
/* Now call the generic one */
if (cls->resume)
cls->resume(sysdev);
} }
} }
up_write(&system_subsys.rwsem); up_write(&system_subsys.rwsem);
return 0; 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) int __init sys_bus_init(void)
{ {
system_subsys.kset.kobj.parent = &devices_subsys.kset.kobj; system_subsys.kset.kobj.parent = &devices_subsys.kset.kobj;
......
...@@ -31,8 +31,10 @@ struct sysdev_class { ...@@ -31,8 +31,10 @@ struct sysdev_class {
/* Default operations for these types of devices */ /* Default operations for these types of devices */
int (*shutdown)(struct sys_device *); int (*shutdown)(struct sys_device *);
int (*save)(struct sys_device *, u32 state);
int (*suspend)(struct sys_device *, u32 state); int (*suspend)(struct sys_device *, u32 state);
int (*resume)(struct sys_device *); int (*resume)(struct sys_device *);
int (*restore)(struct sys_device *);
struct kset kset; struct kset kset;
}; };
...@@ -50,8 +52,10 @@ struct sysdev_driver { ...@@ -50,8 +52,10 @@ struct sysdev_driver {
int (*add)(struct sys_device *); int (*add)(struct sys_device *);
int (*remove)(struct sys_device *); int (*remove)(struct sys_device *);
int (*shutdown)(struct sys_device *); int (*shutdown)(struct sys_device *);
int (*save)(struct sys_device *, u32 state);
int (*suspend)(struct sys_device *, u32 state); int (*suspend)(struct sys_device *, u32 state);
int (*resume)(struct sys_device *); 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