1 DPM介绍
1.1 Dpm:
设备电源管理,device power management
主要功能:系统在对外设下电前,需要做一些准备工作,保存一些设备的必要信息,以便在系统退出低功耗状态时能够恢复这些信息,使本设备在上电后能像睡眠前那样正常工作。驱动程序作为一部分,跟随系统级别的低功耗状态。
1.2 系统睡眠模式 System Sleep model:
Suspend:保存到ram中,suspend-to-RAM,也就是我们使用并关注的睡眠模式
Hibernation:保存到硬盘中,suspend-to-disk
2 设备电源管理操作
子系统和驱动程序的设备电源管理操作,都定义在dev_pm_ops结构中,这个结构在include/linux/pm.h中定义:
struct dev_pm_ops {
int (*prepare)(struct device *dev);
void (*complete)(struct device *dev);
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_late)(struct device *dev);
int (*resume_early)(struct device *dev);
int (*freeze_late)(struct device *dev);
int (*thaw_early)(struct device *dev);
int (*poweroff_late)(struct device *dev);
int (*restore_early)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
驱动程序使用的主要由以下8种:
Prepare:做suspend前的准备工作,返回后本设备下不能再注册子设备,后续可跟@suspend(), @freeze(), @poweroff()中的一种回调;其作为dpm众多回调函数中的一个,在执行保存操作时最先执行,优先级最高;
Complete:执行prepare的逆操作,
Suspend:回调各设备的suspend回调函数,保存各设备必要信息,在系统唤醒时能恢复这些信息使该设备能实现睡眠前的功能。
suspend_late:晚于其他设备进行suspend操作,还可以保存suspend阶段没有保存的一些信息;
suspend_noirq: 阶段发生在IRQ被禁止之后,这意味着该回调运行期间,驱动程序的中断处理代码不会被调用。回调方法可以保存上一阶段没有保存的寄存器并最终让设备进入相应的低功耗状态;该回调函数优先级最低,最后执行保存工作,
Resume:回调各设备的resume回调函数,恢复suspend保存的一些必要信息
resume_early:先于其他设备进行恢复操作;对应suspend_late
resume_noirq:唤醒阶段的第一阶段,执行suspend_noirq的逆操作
设备进入suspend和resume的关键方法在bus_type结构、device_type结构和class结构的pm成员中,他是一个dev_pm_ops结构的指针。 1 系统电源管理的各个阶段
suspend和resume是分阶段完成的。在进入下一个阶段之前,都需要为每个设备调用属于本阶段的回调函数。不是所有的总线和设备类都会支持所有这些回调,也不是所有的驱动程序都要使用这些回调。有些阶段需要冻结进程后,解冻进程前执行。此外,*_noirq阶段需要在IRQ被关闭的情况下执行。
2.1 系统挂起(suspend)
当系统进入standby或sleep状态时,需要经历以下阶段:
prepare,suspend,suspend_late, suspend_noirq。
(1) prepare阶段主要是通过阻止新设备注册来防止竟态的发生; prepare回调返回后,该设备的下面将不可以注册新的子设备。回调方法也会让设备或驱动为将要到来的系统电源状态迁移做出准备。
(2) suspend阶段由suspend回调实现,它停止设备的一切I/O操作。它同时也可以保存设备的寄存器,依据设备所属的总线类型,让设备进入合适的低功耗状态。
以上两个回调主要由低功耗接口dpm_suspend_start()来实现。
(3) Suspend_late顾名思义,是在其他设备suspend之后,再进行suspend操作,也就是说如果设备注册了本回调意味着本设备的suspend要晚于其他设备的suspend操作
(4) suspend_noirq阶段发生在IRQ被禁止之后,这意味着该回调运行期间,驱动程序的中断处理代码不会被调用。回调方法可以保存上一阶段没有保存的寄存器并最终让设备进入相应的低功耗状态。大多数子系统(subsystem)和驱动程序不需要实现这一回调。不过,某些允许设备共享中断向量的总线类型,例如PCI,通常需要这一回调;否则,当本设备已经进入低功耗时另一个与他共享中断的设备感知中断的发生,驱动程序将会发生错误。
以上两个回调主要由低功耗接口dpm_suspend_end()来实现。
这些阶段结束后,驱动程序必须停止所有的I/O事务(DMA,IRQs),保存足够的状态信息以便它们能被重新初始化或回复之前的状态(按应将的需要而定),然后让设备进入低功耗状态。很多平台上,它们会关闭某些时钟(只在一个核上使用的设备需要在回调函数中添加关钟和开钟操作);有时还会关闭电源或者是降低电压,V7R2由M3来统一执行下电操作。
只要这些回调中的一个返回错误,系统不会进入所述的低功耗状态,而是由pm的核心对已经suspend的设备发起resume动作进行回退。
2.2 唤醒(resume)
当系统退出standby或sleep状态时,需要经历以下阶段:
resume_noirq,resume_early,resume,complete。
(1) resume_noirq回调方法应该执行所有在中断处理程序被调用前的必须动作。这通常意味着撤销suspend_noirq阶段所做的动作。如果总线类型允许共享中断向量,例如PCI,该回调方法应该使设备和驱动能够识别自身是否是中断源,如果是,还要能正确地处理。例如,对于PCI总线,bus->pm.resume_noirq()让设备进入全电源状态(PCI中称作D0),并回复设备的标准配置寄存器。然后,调用设备驱动程序的 ->pm.resume_noirq()方法来执行特定于设备的动作。
(2) resume_early执行在其他设备恢复信息之前,早于其他设备的恢复,suspend_late的逆操作
以上两个回调主要由低功耗接口dpm_resume_start()来实现。
(3) resume回调方法让设备回到他的工作状态,以便它能执行正常的I/O。这通常等同于执行suspend阶段的撤销工作。
(4) complete阶段撤销prepare阶段所做出的动作。不过请注意,新设备可能在resume回调返回后立刻被注册,而不必等到complete阶段完成。
以上两个回调主要由低功耗接口dpm_resume_end()来实现。
这些阶段结束后,驱动应该和suspend之前一样:I/O能通过DMA或IRQs执行,相应的时钟被打开。
多数阶段使用bus、type和class的回调(也就是定义在dev->bus->pm,dev->type->pm和dev->class->pm中)。
当然也可以直接使用device结构体中的ops。
1 设备注册到dpm_list路径
(Platform_device->device->device_driver指向platform_driver->driver)
2 低功耗接口
dpm_suspend_start():调用注册到dpm_list的设备的回调函数,执行睡眠前的准备和保存工作;
dpm_suspend_end():执行suspend_late和suspend_noirq设备的回调函数,进行睡眠前的准备工作。
睡眠时,设备在链表中的转移:
dpm_list--->dpm_prepared_list-->dpm_suspended_list---->
dpm_late_early_list--->dpm_noirq_list
Dpm_resume_start():执行设备的resume_noirq和resume_early回调,恢复suspend_noirq和suspend_late阶段保存的东西
dpm_resume_end():执行各设备的resume和complete回调接口,做suspend和prepare的逆操作
唤醒时,设备从链表中的转移顺序是上述suspend阶段顺序的逆序。
3 低功耗接口是如何调用到各设备驱动注册的回调函数的
系统进入睡眠前回调设备的回调函数是有选择的:
如果dev->pm_domain域的回调函数注册的话,首选此处的回调函数
否则,如果dev->type域和dev->type->pm域都注册的话,会选择dev->type->pm处的回调函数
否则,如果dev->class 和 dev->class->pm两处都注册回调函数的话,会选择dev->class->pm的回调函数
如果dev->bus 和 dev->bus->pm两处都注册回调函数的话,会选择dev->bus->pm的回调函数
示例方法1和方法4:
3.1 在 dev 域的电源域进行注册
struct platform_device {
const char * name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this device */
void *platform_data; /* Platform specific data, device core doesn't touch it */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
…….}
struct dev_pm_domain {
struct dev_pm_ops ops;
};
struct dev_pm_ops {
int (*prepare)(struct device *dev);
void (*complete)(struct device *dev);
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_late)(struct device *dev);
int (*resume_early)(struct device *dev);
int (*freeze_late)(struct device *dev);
int (*thaw_early)(struct device *dev);
int (*poweroff_late)(struct device *dev);
int (*restore_early)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
低功耗接口在函数__device_suspend函数内会首先调用在struct device电源域注册的回调函数。
唤醒时调用顺序:
3.2 在 bus 域 的ops电源域进行注册
注册设备和注册驱动进行匹配成功后
Platform_device->device->device_driver指针会指向platform_driver->driver成员
设备注册:
(1)int platform_device_add(struct platform_device *pdev)
{
……………
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;
…………..
}
(2)struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
(3)static const struct dev_pm_ops platform_dev_pm_ops = {
.runtime_suspend = pm_generic_runtime_suspend,
.runtime_resume = pm_generic_runtime_resume,
.runtime_idle = pm_generic_runtime_idle,
USE_PLATFORM_PM_SLEEP_OPS
};
(4)#define USE_PLATFORM_PM_SLEEP_OPS \
.suspend = platform_pm_suspend, \
.resume = platform_pm_resume, \
.freeze = platform_pm_freeze, \
.thaw = platform_pm_thaw, \
.poweroff = platform_pm_poweroff, \
.restore = platform_pm_restore,
(5) int platform_pm_suspend(struct device *dev)
{
struct device_driver *drv = dev->driver;
int ret = 0;
if (!drv)
return 0;
if (drv->pm) {
if (drv->pm->suspend)
ret = drv->pm->suspend(dev);
} else {
ret = platform_legacy_suspend(dev, PMSG_SUSPEND);
}
return ret;
}
前边我们讨论分析了linux 内核的dpm实现,分析的目的在于学以致用;在好多实时操作系统中,并没有dpm这个框架,所以我们可以仿照linux的dpm框架,来实现我们自己的dpm,可以运用到需要dpm的系统中。
知识点:链表,回调函数,函数指针,互斥锁,锁中断
为便于描述,本文使用伪代码,仅个别地方具体实现;
1、首先,我们需要定义两个结构体类型,一个用于控制,一个用于各个模块注册;
(1)控制结构体
struct my_dpm{
list_head dpm_list;
list_head prepare_list;
list_head early_list;
list_head suspend_list;
list_head late_list;
spinlock_t lock_mutx;
}dpm_ctrl;
(2)各模块注册dpm使用的结构体
struct dpm_device{
char *device_name;
list_head entry;
void *data;
int (*prepare)(struct dpm_device *dev);
int (*suspend_early)(struct dpm_device *dev);
int (*suspend)(struct dpm_device *dev);
int (*suspend_late)(struct dpm_device *dev);
int (*resume_early)(struct dpm_device *dev);
int (*resume)(struct dpm_device *dev);
int (*resume_late)(struct dpm_device *dev);
int (*complete)(struct dpm_device *dev);
};
2、OK,结构体我们已经定义完了,那么我们接下来需要初始化一下控制结构体的变量
int my_dpm_init(void){
初始化五个链表;
初始化spin_lock锁;
return OK;
}
3、到此,我们自己的dpm已初始化完成,各个模块想要注册dpm,就差一个注册接口了,下面我们来实现注册接口
int dpm_register(struct dpm_device *device
获取互斥锁;
初始化设备结构体中的entry;
加入到dpm_list链表中;
释放互斥锁;
return OK;
}
4、这样,用户就可以调用dpm_register来注册dpm了;但是注册完后,得需要提供接口来供低功耗流程来调用啊,莫急,我们这就来实现供低功耗调用的接口, dpm_suspend和dpm_resume
(1)dpm_suspend:主要回调各个模块注册的保存信息的回调函数,包括prepare、suspend_early、suspend、suspend_late
int dpm_suspend(void){
if(prepare())
return -1;
if(suspend_early())
return -1;
if(suspend())
return -1;
if(suspend_late())
return -1;
return 0;
}
(2)dpm_resume:主要用在唤醒阶段,按照优先级回调各个模块注册的恢复信息的回调函数,包括resume_early、resume、resume_late、complete
int dpm_resume(void)
{
if(resume_early())
return -1;
if(resume())
return -1;
if(resume_late())
return -1;
if(complete())
return -1;
return 0;
}
5、大家可以看到,上面两个接口中分别调用了4个函数,供8个函数,分别用在suspend阶段和resume阶段,接下来我们简单实现一下其中一个函数prepare
int prepare(void)
{
获取互斥锁;
遍历dpm_list链表,检查每个结点是否注册prepare回调函数,如果注册,则执行回调函数:ret=dev->prepare(dev),如果成功,则移入prepare_list,否则,执行恢复操作;
释放互斥锁;
}
其他的suspend_early()、suspend()、suspend_late()、resume()、resume_early()、resume_late()、complete()等具体函数实现基本大致相同,只不过所操作的链表不同罢了。
好了,我们的dpm框架实现完成了,简单吧,我们尝试注册一个吧:
int my_prepare(struct dpm_device *dev){}
int my_suspend dpm_device *dev){}
int my_resumet dpm_device *dev){}
int my_completedpm_device *dev){}
struct dpm_device test_device={
.device_name = "my_test_device";
.prepare = my_prepare;
.suspend = mysuspend;
.resume = my_resume;
.omplete = my_complete;
};
在合适的地方调用dpm_register(&test_device)即可,需要注意的是,dpm回调函数注册一定要配对:prepare--complete suspend--resume suspend_late--resume_early suspend_late--resume_early