设备模型8之电源管理(一)(linux-3.2.36,从apm_bios分析suspend和resume调用)
来源:互联网 发布:在淘宝买药要货到付款 编辑:程序博客网 时间:2024/06/16 15:58
刚在论坛转了一下,写这个主题的也有不少且写的很好,我就直接推荐吧,连接:
http://blog.csdn.net/myxmu/article/details/7955232
http://blog.csdn.net/myxmu/article/details/7955321
http://blog.csdn.net/myxmu/article/details/7955375
http://blog.csdn.net/myxmu/article/details/7955982
http://blog.csdn.net/dwyane_zhang/article/details/7099731
高手真多啊,如果我就贴这几个连接,肯定被骂成标题党。
为了我的清白,我就写点什么吧。
首先ACPI是Intel(i386,x86_64,IA64)平台的标准固件规范。看过我之前的文章知道我一直用的是arm平台。所以我就用APM来说,我会告诉你系统如何调用驱动的suspend和resume。
APM是基于bios的,但arm中没有这个玩意,不过linux为我们做了一个字符设备。/dev/apm_bios,源码在drivers/char/apm-emulation.c。//linux-3.2.36
有人可能会说我经常看到echo “standby” >/sys/power/state这是这么回事。
源码
static struct attribute * g[] = {
&state_attr.attr,
…
state_attr的store方法会判断你的输入并调用enter_state,我们的apm_bios也会调用enter_state(下面说它),我想看过我之前的文章对store的调用很熟悉吧。这不是我们要走的路,我们还是看apm_bios。
首先apm_bios初始化会建立kapmd线程,它会在kapmd_queue不为空时,获取queue的事件进行处理。这个线程说的不多,不过以后我们在一些实例中看到。
如何调用suspend,我们就从apm_emulation.c的ioctl入手,对于字符设备ioctl的调用不用细说了吧。
static long
apm_ioctl(struct file *filp, u_int cmd, u_long arg)
{
…
switch (cmd) {
case APM_IOC_SUSPEND:
mutex_lock(&state_lock);
as->suspend_result = -EINTR;
switch (as->suspend_state) {
case SUSPEND_READ:
…
//调用了apm_read,待确认
break;
case SUSPEND_ACKTO:
...//TO就是timeout
break;
default:
as->suspend_state = SUSPEND_WAIT;//表示调用了suspend等待resume
mutex_unlock(&state_lock);
/*
* Otherwise it is a request to suspend the system.
* Just invoke pm_suspend(), we'll handle it from
* there via the notifier.
*/
as->suspend_result = pm_suspend(PM_SUSPEND_MEM);
}
mutex_lock(&state_lock);
err = as->suspend_result;
as->suspend_state = SUSPEND_NONE;//清除状态
mutex_unlock(&state_lock);
break;
}
return err;
}
主要调用pm_suspend
pm_suspend()主要调用enter_state()
kernel/power/suspend.c
int enter_state(suspend_state_t state)
{
…
error = suspend_prepare();//
if (error)
goto Unlock;
if (suspend_test(TEST_FREEZER))//如果你的内核没选suspend debug,它就是空函数,否则它会把测试等级与TEST_FREEZER做比较,相等的话延时5000ms
goto Finish;
…
error = suspend_devices_and_enter(state);
…
}
suspend_prepare()绝不是一个善类,干了不少坏事。
static int suspend_prepare(void)
{
int error;
if (!suspend_ops || !suspend_ops->enter)
return -EPERM;
pm_prepare_console();//分配一个虚拟终端来输出信息,这个会调用drivers/tty/vt。当然是否分配也是要配置的。
error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);//调用通知连,它会通知所有注册的模块。在drivers/char/apm-emulation.c中会调用static int apm_suspend_notifier(struct notifier_block *nb,unsigned long event,void *dummy),event = PM_SUSPEND_PREPARE。它会把用户挂起事件加入kapmd_queue,kapmd线程会去处理。还会判断超时。也就是通知进入suspend了。
if (error)
goto Finish;
error = usermodehelper_disable();//关闭掉用户态的helper进程,helper进程我们在热插拔时有说,能让内核直接新建和运行用户空间程序。
if (error)
goto Finish;
error = suspend_freeze_processes();//冻结所有进程,并保存所有进程当前的状态。
if (error) {
suspend_stats.failed_freeze++;
dpm_save_failed_step(SUSPEND_FREEZE);
//也许有一些进程会拒绝进入冻结状态, 当有这样的进程存在的时候, 会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程
} else
return 0;
suspend_thaw_processes();
usermodehelper_enable();
Finish:
pm_notifier_call_chain(PM_POST_SUSPEND);//退出suspend的通知
pm_restore_console();
return error;
}
suspend_devices_and_enter()是我们的绝对主力。
int suspend_devices_and_enter(suspend_state_t state)
{
int error;
bool wakeup = false;
//suspend_ops和平台有关,这个我们下面看
if (!suspend_ops)
return -ENOSYS;
trace_machine_suspend(state);//我找了整个目录都没找到这个函数
if (suspend_ops->begin) {
error = suspend_ops->begin(state);
if (error)
goto Close;
}
suspend_console();//kernel/printk.c如果console_suspend_enabled==0,则不会执行
/*
看看console_suspend_enabled如何设为0
static int __init console_suspend_disable(char *str)
{
console_suspend_enabled = 0;
return 1;
}
__setup("no_console_suspend", console_suspend_disable);
可以看出启动参数要有no_console_suspend,不然console被suspend就不能输出信息了
*/
//test不管了
suspend_test_start();
error = dpm_suspend_start(PMSG_SUSPEND);// suspend all devices,外设休眠,这是设备中suspend调用的重点,我们下面看。
if (error) {
printk(KERN_ERR "PM: Some devices failed to suspend\n");
goto Recover_platform;
}
suspend_test_finish("suspend devices");
if (suspend_test(TEST_DEVICES))
goto Recover_platform;
do {
error = suspend_enter(state, &wakeup);
} while (!error && !wakeup
&& suspend_ops->suspend_again && suspend_ops->suspend_again());
…
}
int dpm_suspend_start(pm_message_t state)
{
int error;
error = dpm_prepare(state);
if (error) {
suspend_stats.failed_prepare++;
dpm_save_failed_step(SUSPEND_PREPARE);
} else
error = dpm_suspend(state);
return error;
}
suspend要经过,prepare->suspend->suspend_noirq;我们看这个过程
prepare阶段主要是通过阻止新设备注册来防止竟态的发生;如果此时要注册子设备,PM的核心将会不知道一个设备的所有子设备已经被suspend。(相反,设备可以在任何时刻被注销。)不像suspend其他的阶段,prepare阶段设备树会自顶向下进行扫描。
int dpm_prepare(pm_message_t state)
{
int error = 0;
might_sleep();
mutex_lock(&dpm_list_mtx);
while (!list_empty(&dpm_list)) {
struct device *dev = to_device(dpm_list.next);
//上面是编历链表并或取dev
get_device(dev);//引用记数加一
mutex_unlock(&dpm_list_mtx);
error = device_prepare(dev, state);//下面看
mutex_lock(&dpm_list_mtx);
if (error) {
if (error == -EAGAIN) {
put_device(dev);//引用记数减一
error = 0;
continue;
}
printk(KERN_INFO "PM: Device %s not prepared "
"for power transition: code %d\n",
dev_name(dev), error);
put_device(dev); //引用记数减一
break;
}
dev->power.is_prepared = true;//设备正准备着进入省电模式
if (!list_empty(&dev->power.entry))
list_move_tail(&dev->power.entry, &dpm_prepared_list);
//如果dev->power.entry不是空,把dev->power.entry加入dpm_prepared_list中
put_device(dev); //引用记数减一
}
mutex_unlock(&dpm_list_mtx);
return error;
}
static int device_prepare(struct device *dev, pm_message_t state)
{
int error = 0;
device_lock(dev);
dev->power.wakeup_path = device_may_wakeup(dev);//获得设备的唤醒能力,wakeup_path是个bool量,不要被名字骗了。
/*
下面的过程一起说,首先pm_dev_dbg和suspend_report_result是调试打印
prepare的调用顺序
dev->pm_domain-> ops.prepare()
dev->type->pm->prepare()
dev->class->pm->prepare()
dev->bus->pm->prepare()
前提是都存在
*/
if (dev->pm_domain) {
pm_dev_dbg(dev, state, "preparing power domain ");
if (dev->pm_domain->ops.prepare)
error = dev->pm_domain->ops.prepare(dev);
suspend_report_result(dev->pm_domain->ops.prepare, error);
if (error)
goto End;
} else if (dev->type && dev->type->pm) {
pm_dev_dbg(dev, state, "preparing type ");
if (dev->type->pm->prepare)
error = dev->type->pm->prepare(dev);
suspend_report_result(dev->type->pm->prepare, error);
if (error)
goto End;
} else if (dev->class && dev->class->pm) {
pm_dev_dbg(dev, state, "preparing class ");
if (dev->class->pm->prepare)
error = dev->class->pm->prepare(dev);
suspend_report_result(dev->class->pm->prepare, error);
if (error)
goto End;
} else if (dev->bus && dev->bus->pm) {
pm_dev_dbg(dev, state, "preparing ");
if (dev->bus->pm->prepare)
error = dev->bus->pm->prepare(dev);
suspend_report_result(dev->bus->pm->prepare, error);
}
End:
device_unlock(dev);
return error;
}
suspend阶段由suspend回调实现,它停止设备的一切I/O操作。它同时也可以保存设备的寄存器,依据设备所属的总线类型,让设备进入合适的低功耗状态,同时可以使能唤醒事件。
int dpm_suspend(pm_message_t state)
{
ktime_t starttime = ktime_get();
int error = 0;
might_sleep();//为能睡眠的函数注释,需要编译选项
mutex_lock(&dpm_list_mtx);
pm_transition = state;//state是PMSG_SUSPEND
async_error = 0;
while (!list_empty(&dpm_prepared_list)) {
struct device *dev = to_device(dpm_prepared_list.prev);
get_device(dev);
mutex_unlock(&dpm_list_mtx);
//上面看dpm_prepare()
error = device_suspend(dev);//看下面
mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, "", error);
dpm_save_failed_dev(dev_name(dev));
put_device(dev);
break;
}
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &dpm_suspended_list);
//如果dev->power.entry不是空,把dev->power.entry加入dpm_suspended_list中
put_device(dev);
if (async_error)
break;
}
mutex_unlock(&dpm_list_mtx);
async_synchronize_full();//同步系统所有的异步函数,等待这些异步函数执行完。
if (!error)
error = async_error;
if (error) {
suspend_stats.failed_suspend++;
dpm_save_failed_step(SUSPEND_SUSPEND);
} else
dpm_show_time(starttime, state, NULL);//显示时间。
return error;
}
先简单介绍一个叫pm_op()的函数,它会根据state.event执行对应的函数,有很多的事件,现在我们只看这些
static int pm_op(struct device *dev,
const struct dev_pm_ops *ops,
pm_message_t state)
{
…
switch (state.event) {
#ifdef CONFIG_SUSPEND
case PM_EVENT_SUSPEND:
if (ops->suspend) {
error = ops->suspend(dev);//调用suspend()
suspend_report_result(ops->suspend, error);//调试打印
}
break;
case PM_EVENT_RESUME:
if (ops->resume) {
error = ops->resume(dev);//调用resume ()
suspend_report_result(ops->resume, error); //调试打印
}
break;
#endif /* CONFIG_SUSPEND */
…
}
看__device_suspend
static int __device_suspend(struct device *dev, pm_message_t state, bool async)
{
int error = 0;
dpm_wait_for_children(dev, async);
/*
等待所有的孩子节点的pm操作完成,这里可以体现suspend是从叶子节点开始的。前提是使用了异步的,有另一个叫async_suspend()的过程会让async=1,我们的过程不会等
*/
if (async_error)//下面会看到赋值
return 0;
//runtime简单说一下,这种模型允许设备在系统运行阶段进入低功耗状态,原则上,他可以独立于其他的电源管理活动。
pm_runtime_get_noresume(dev);
if (pm_runtime_barrier(dev) && device_may_wakeup(dev))
pm_wakeup_event(dev, 0);
if (pm_wakeup_pending()) {//检查是否要取消suspend
pm_runtime_put_sync(dev);
async_error = -EBUSY;
return 0;
}
device_lock(dev);
/*
下面和prepare差不多,就是个顺序问题。
*/
if (dev->pm_domain) {
pm_dev_dbg(dev, state, "power domain ");
error = pm_op(dev, &dev->pm_domain->ops, state);
goto End;
}
if (dev->type && dev->type->pm) {
pm_dev_dbg(dev, state, "type ");
error = pm_op(dev, dev->type->pm, state);
goto End;
}
//下面看到判断pm和suspend,这是新老电源管理方式的判断
if (dev->class) {
if (dev->class->pm) {
pm_dev_dbg(dev, state, "class ");
error = pm_op(dev, dev->class->pm, state);
goto End;
} else if (dev->class->suspend) {
pm_dev_dbg(dev, state, "legacy class ");
error = legacy_suspend(dev, state, dev->class->suspend);
goto End;
}
}
if (dev->bus) {
if (dev->bus->pm) {
pm_dev_dbg(dev, state, "");
error = pm_op(dev, dev->bus->pm, state);
} else if (dev->bus->suspend) {
pm_dev_dbg(dev, state, "legacy ");
error = legacy_suspend(dev, state, dev->bus->suspend);
}
}
End:
if (!error) {
dev->power.is_suspended = true;//设备被挂起
if (dev->power.wakeup_path
&& dev->parent && !dev->parent->power.ignore_children)
dev->parent->power.wakeup_path = true;
}
device_unlock(dev);
complete_all(&dev->power.completion);//报告完成,还记得我们之前调用dpm_wait_for_children(dev, async);等孩子们都完成,它就等待这个完成量
if (error) {
pm_runtime_put_sync(dev);
async_error = error;
} else if (dev->power.is_suspended) {
__pm_runtime_disable(dev, false);
}
return error;
}
下面是suspend_enter()
小总结一下,上面我们看到了关进程、调用bus、class、device的相关回调,下面是调用平台的相关回调、noirq的suspend调用、关闭cpu、系统核suspend调用,顺便提一下我们现在又回到了kernel/power/suspend.c
//里面有suspend_test是调试用的,有了它可以不真进入suspend,只是观察这个过程。我的例子不用
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
int error;
//suspend_ops是平台的,我在下面详细说
if (suspend_ops->prepare) {
error = suspend_ops->prepare();
if (error)
goto Platform_finish;
}
error = dpm_suspend_noirq(PMSG_SUSPEND);
/*
我们上面的suspend最终调用的是ops->suspend,这个会调用ops->suspend_noirq。过程差不多。
suspend_noirq阶段发生在IRQ被禁止之后,这意味着该回调运行期间,驱动程序的中断处理代码不会被调用。回调方法可以保存上一阶段没有保存的寄存器并最终让设备进入相应的低功耗状态。
*/
if (error) {
printk(KERN_ERR "PM: Some devices failed to power down\n");
goto Platform_finish;
}
if (suspend_ops->prepare_late) {
error = suspend_ops->prepare_late();
if (error)
goto Platform_wake;
}
if (suspend_test(TEST_PLATFORM))
goto Platform_wake;
error = disable_nonboot_cpus();//对与smp,它还要卸掉所有不能直接启动的cpu。
if (error || suspend_test(TEST_CPUS))
goto Enable_cpus;
arch_suspend_disable_irqs();//中断禁止
BUG_ON(!irqs_disabled());
error = syscore_suspend();
/*suspend_enter()会调用syscore_suspend(),会调用所有注册系统核suspend的回调
先说一下,调用的调试信息还受到
if (initcall_debug)
pr_info("PM: Calling %pF\n", ops->suspend);
initcall_debug的控制,赋1方法:
echo 1 > /sys/module/kernel/parameters/initcall_debug
本人的平台
…
static struct sleep_save s3c244x_sleep[] = {
SAVE_ITEM(S3C2440_DSC0),
SAVE_ITEM(S3C2440_DSC1),
SAVE_ITEM(S3C2440_GPJDAT),
SAVE_ITEM(S3C2440_GPJCON),
SAVE_ITEM(S3C2440_GPJUP)
};
static int s3c244x_suspend(void)
{
s3c_pm_do_save(s3c244x_sleep, ARRAY_SIZE(s3c244x_sleep));
return 0;
}
static void s3c244x_resume(void)
{
s3c_pm_do_restore(s3c244x_sleep, ARRAY_SIZE(s3c244x_sleep));
}
struct syscore_ops s3c244x_pm_syscore_ops = {
.suspend = s3c244x_suspend,
.resume = s3c244x_resume,
};
就是suspend时把s3c244x_sleep中的寄存器值保存起来,resume再写进去。
*/
if (!error) {
*wakeup = pm_wakeup_pending();//判断睡眠是否继续
if (!(suspend_test(TEST_CORE) || *wakeup)) {
error = suspend_ops->enter(state);
/*
到这我们的系统已睡了,下面就是resume,对与resume我不一步一步的分析。
我只说它和suspend的过程相反。
*/
events_check_enabled = false;
}
syscore_resume();
}
arch_suspend_enable_irqs();
BUG_ON(irqs_disabled());
Enable_cpus:
enable_nonboot_cpus();
Platform_wake:
if (suspend_ops->wake)
suspend_ops->wake();
dpm_resume_noirq(PMSG_RESUME);
Platform_finish:
if (suspend_ops->finish)
suspend_ops->finish();
return error;
}
- 设备模型8之电源管理(一)(linux-3.2.36,从apm_bios分析suspend和resume调用)
- Linux电源管理_Generic PowerManager 之Suspend功能--(一)
- Linux电源管理-Suspend/Resume流程
- Linux电源管理-Suspend/Resume流程
- 设备模型8之电源管理(二)(平台接口,详细分析mini2440平台的睡眠和唤醒)
- 电源管理之android内核suspend to disk的实现(一)
- 电源管理之android内核suspend to disk的实现(一)
- 电源管理之android内核suspend to disk的实现(一)
- java多线程之(suspend()、resume())
- USB驱动Suspend&Resume 调用流程分析
- Wince电源管理(一) ---- Windows CE设备驱动开发之电源管理
- Linux下USB suspend/resume源码分析
- Linux下USB suspend/resume源码分析
- Linux下USB suspend/resume源码分析
- Linux下USB suspend/resume源码分析
- java中线程阻塞之sleep、suspend、join、wait、resume、notify方法解析(一)
- Linux电源管理(6)_Generic PM之Suspend功能
- Linux电源管理(6)_Generic PM之Suspend功能
- 我逛贴吧后的想法
- 连续drop 表的注意事项
- 位域的用法
- NSString / NSMutableString 字符串处理,常用代码 (实例)
- js初学笔记
- 设备模型8之电源管理(一)(linux-3.2.36,从apm_bios分析suspend和resume调用)
- 初探JDeveloper - <ImageViewRendererUtils> <renderXML> Could not export XML to ResponseWriter解决方案
- 【Java学习笔记】基础知识学习13【Map】
- mx51 TVOUT cvbs双屏支持 .
- 如何知道Linux 主机的时间是否正确
- Unity3d中奇怪的编译错误
- SQL SERVER 2008数据库表中修改字段后不能保存
- ubuntu12.04 下安装 Intellij Idea 12
- mx51 TVOUT分析 .