Linux内核 runtime_PM 框架
runtime PM (runtime power management) 简介:
怎样动态地打开关闭设备的电源 ? 最简单的方法:在驱动程序中,open时打开电源,在close时关闭电源。但是有一个缺点,当多个App使用该设备时可能造成干扰。
解决方法:给驱动添加计数值,当该值大于0时打开电源,等于0时关闭电源。
多在ioctl中进行控制,例如alsa的驱动代码
runtime PM只是提供辅助函数,比如:
(1).增加计数/减少计数
(2).使能runtime pm
最好的资料是runtime_pm.txt TODO:翻译它
例子:\drivers\input\misc\bma150.c
pm_runtime_enable //bma150_probe
pm_runtime_disable //bma150_remove
pm_runtime_get_sync //bma150_open
pm_runtime_put_sync //bma150_close
pm_runtime_enable/pm_runtime_disable 使能/禁止runtime PM,分别对dev->power.disable_depth执行++和--操作,这个变量的初始化值是1,默认是disable的状态。
pm_runtime_get_sync/pm_runtime_put_sync 增加/减少计数值,并判断是否进入suspend/resume。
1. 在struct dev_pm_ops提供了3个回调函数:runtime_suspend,runtime_resume,runtime_idle,一般runtime_idle这个空闲函数不需要提供
2. 上面4个函数不会直接导致runtime_suspend,runtime_resume,runtime_idle被调用,只是使能和修改计数值,当引用计数减为0,调用suspend,
从0变为大于0调用resume。
3. 对于runtime PM,默认状态下设备的状态是suspend,如果硬件上它是运行状态,需要调用pm_runtime_set_active()来修改它的状态,然后调用
pm_runtime_enable()来使能runtime PM。一般是在probe()的结尾处使用,以为它可能导致runtime的suspend/resume函数立即调用。一般在驱动remove中
调用pm_runtime_disable()
4. 在open()/release()接口中可以调用pm_runtime_get_sync/pm_runtime_put_sync
5. autosuspend:
为了不想让设备频繁地开、关,可以使用autosuspend功能,驱动中执行update_autosuspend()来启用autosuspend功能。[TODO]
put设备时换做执行:
pm_runtime_mark_last_busy()
pm_runtime_put_sync_autosuspend()
用户空间可以通过设置下面sysfs文件来设置autosuspend延迟时间:
ehco 2000 > /sys/devices/.../power/autosuspend_delay_ms
6. struct dev_pm_ops 注解翻译:
用于定义要在所有情况下使用的一组PM操作(如系统挂起,休眠或运行时PM)。 注意:通常,系统挂起回调.suspend 和.resume 应该与
对应的运行时PM回调.runtime_suspend 和.runtime_resume 不同,因为.runtime_suspend 始终适用于已经暂停的设备,而.suspend 应
该假设在调用它时设备可能正在做某事(它应该确保设备在它返回后可靠地处于暂停状态)。 因此,最好将“late” suspend 和“early”resume
回调指针.suspend_late和.resume_early分别指向与.runtime_suspend和.runtime_resume相同的例程(类似于休眠)。
7.流程分析:
pm_runtime_get_sync
__pm_runtime_resume(dev, RPM_GET_PUT)
atomic_inc(&dev->power.usage_count); // 若上级arg2&RPM_GET_PUT为真,才调用
rpm_resume(dev, rpmflags) //关闭本地CPU中断后调用它
if (dev->power.disable_depth > ) retval = -EACCES; //若要使用runtime PM的函数,需要首先pm_runtime_enable。
if (!dev->power.timer_autosuspends) /*为了防止设备频繁的开关,可以设置timer_autosuspends的值*/
pm_runtime_deactivate_timer(dev);
if (dev->power.runtime_status == RPM_ACTIVE) { /*如果已经是ACTIVE,就没有必要再次resume*/
if (dev->power.runtime_status == RPM_RESUMING || dev->power.runtime_status == RPM_SUSPENDING) 如果设备正处于RPM_RESUMING和RPM_SUSPENDING状态,等待其完成
if (!parent && dev->parent) //增加父级的使用计数器并在必要时恢复它,在resume设备本身之前先resume父设备。
开始resume设备自己:
dev->pm_domain->ops->runtime_resume //或
dev->type->pm->runtime_resume //或
dev->class->pm->runtime_resume //或
dev->bus->pm->runtime_resume //或 前4个被称为subsystem level的callback,优先调用,第5个是驱动级别的。
dev->driver->pm->runtime_resume //或
__update_runtime_status(dev, RPM_SUSPENDED); //如果resume失败,重新设置回SUSPENDED状态
if (parent) atomic_inc(&parent->power.child_count); //如果resume成功时给父亲的child_count加1 wake_up_all(&dev->power.wait_queue); //唤醒其它进程 pm_runtime_put_sync
__pm_runtime_idle(dev, RPM_GET_PUT)
if (!atomic_dec_and_test(&dev->power.usage_count)) //减少usage_count引用计数
rpm_idle(dev, rpmflags); //让设备进入idle状态
rpm_check_suspend_allowed //检查是否允许设备进入suspend状态,看来内核把idle和suspend一样看待了。
if (dev->power.disable_depth > ) retval = -EACCES; //调用时还没有pm_runtime_enable,就失败。
if (atomic_read(&dev->power.usage_count) > ) retval = -EAGAIN;
if (!dev->power.ignore_children && atomic_read(&dev->power.child_count)) retval = -EBUSY; //它的孩子不全睡眠它是不能睡眠的
if (dev->power.runtime_status != RPM_ACTIVE) retval = -EAGAIN; //如果不是出于ACTIVE状态直接返回。
开始suspend设备自己:
dev->pm_domain->ops->runtime_suspend //或
dev->type->pm->runtime_suspend //或
dev->class->pm->runtime_suspend //或
dev->bus->pm->runtime_suspend //或 前4个被称为subsystem level的callback,优先调用,第5个是驱动级别的。
dev->driver->pm->runtime_suspend //或
wake_up_all(&dev->power.wait_queue); //唤醒其它进程
8. 将当前进程放入等待队列中睡眠:
rpm_resume():
DEFINE_WAIT(wait);
for (;;) {
prepare_to_wait(&dev->power.wait_queue, &wait, TASK_UNINTERRUPTIBLE);
if (dev->power.runtime_status != RPM_RESUMING && dev->power.runtime_status != RPM_SUSPENDING)
break;
schedule();
}
finish_wait(&dev->power.wait_queue, &wait);
9. 另外,额外说一下异步实现原理
request_firmware_nowait()
INIT_WORK(&fw_work->work, request_firmware_work_func);
schedule_work(&fw_work->work);
10. 如何使用runtime PM
(1). 驱动封装接口,App去调用,如把pm_runtime_get_sync放在open()中。
(2). 通过sysfs接口使用:
操作 /sys/devices/.../power/control 导致 drivers/base/power/sysfs.c/control_store() 被调用。对自己的驱动设置了runtime PM 操作后也可以使用
这种操作来测试自己的runtime PM 中的suspend/resume。
//App 禁止驱动程序对设备进行runtime PM
echo auto > /sys/devices/.../power/control:control_store --> pm_runtime_forbid -->
atomic_inc(&dev->power.usage_count);
rpm_resume(dev, );
//App 允许驱动程序对设备进行runtime PM
echo on > /sys/devices/.../power/control:control_store --> pm_runtime_allow -->
if (atomic_dec_and_test(&dev->power.usage_count))
rpm_idle(dev, RPM_AUTO | RPM_ASYNC); 可以echo on > /sys/devices/.../power/control //来启用一个休眠的设备
unsigned int runtime_auto;
- 如果被设置了,则表示用户空间允许设备驱动程序通过/sys/devices/.../power/control接口在运行时为设备供电; 它只能在pm_runtime_allow()
和pm_runtime_forbid()辅助函数的帮助下修改.
用户空间可以通过将其/sys/devices/.../power/control属性的值更改为“on”来有效地禁止设备的驱动程序在运行时对设备进行电源管理,
这会导致调用pm_runtime_forbid()。
原则上,驱动程序还可以使用该机制来有效地关闭设备的运行时电源管理,直到用户空间将其打开为止。 即,在初始化期间,驱动程序可
以确保设备的运行时PM状态为“活动”并调用pm_runtime_forbid()。
但是,应该注意的是,如果用户空间已经故意将/sys/devices/.../power/control的值更改为“auto”以允许驱动程序在运行时对设备进行电
源管理,则驱动程序这样使用pm_runtime_forbid()可能会导致混淆。
11. 修改驱动程序使用runtime PM, 可以参考:drivers\input\misc\bma150.c
/*不使用autosuspend的电源管理框架:*/ static struct platform_device lcd_dev; /* 提供给用户的电源管理框架 */
static int mylcd_open(struct fb_info *info, int user)
{
pm_runtime_get_sync(&lcd_dev.dev);
return ;
}
static int mylcd_release(struct fb_info *info, int user)
{
pm_runtime_put_sync(&lcd_dev.dev);
return ;
} /* suspend和resume的通知,见第一篇博客 */
static int lcd_suspend_notifier(struct notifier_block *nb,
unsigned long event,
void *dummy)
{ switch (event) {
case PM_SUSPEND_PREPARE:
printk("lcd suspend notifiler test: PM_SUSPEND_PREPARE\n");
return NOTIFY_OK;
case PM_POST_SUSPEND:
printk("lcd suspend notifiler test: PM_POST_SUSPEND\n");
return NOTIFY_OK; default:
return NOTIFY_DONE;
}
} static struct notifier_block lcd_pm_notif_block = {
.notifier_call = lcd_suspend_notifier,
}; static void lcd_release(struct device * dev)
{
} static struct platform_device lcd_dev = {
.name = "mylcd",
.id = -,
.dev = {
.release = lcd_release,
},
};
static int lcd_probe(struct platform_device *pdev)
{
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return ;
}
static int lcd_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return ;
}
static int lcd_suspend(struct device *dev)
{
int i;
unsigned long *dest = &lcd_regs_backup;
unsigned long *src = lcd_regs; /* 1.保存寄存器状态 */
for (i = ; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
{
dest[i] = src[i];
} /* 2.断设备的电 */
lcd_regs->lcdcon1 &= ~(<<); /* 关闭LCD本身 */
*gpbdat &= ~; /* 关闭背光 */
return ;
} static int lcd_resume(struct device *dev)
{
int i;
unsigned long *dest = lcd_regs;
unsigned long *src = &lcd_regs_backup; /* 1.还原到掉电之前的状态 */
struct clk *clk = clk_get(NULL, "lcd");
clk_enable(clk);
clk_put(clk);
for (i = ; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
{
dest[i] = src[i];
} /* 2.重新上电*/
lcd_regs->lcdcon1 |= (<<); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (<<); /* 使能LCD本身 */
*gpbdat |= ; /* 输出高电平, 使能背光 */
return ;
} static struct dev_pm_ops lcd_pm = {
.suspend = lcd_suspend,
.resume = lcd_resume,
.runtime_suspend = lcd_suspend,
.runtime_resume = lcd_resume,
}; struct platform_driver lcd_drv = {
.probe = lcd_probe,
.remove = lcd_remove,
.driver = {
.name = "mylcd",
.pm = &lcd_pm,
}
}; static int lcd_init(void)
{
/* 电源管理 */
register_pm_notifier(&lcd_pm_notif_block); /*一注册就可能导致电源runtime函数立即被调用*/ platform_device_register(&lcd_dev);
platform_driver_register(&lcd_drv); return ;
} static void lcd_exit(void)
{
unregister_pm_notifier(&lcd_pm_notif_block);
platform_device_unregister(&lcd_dev);
platform_driver_unregister(&lcd_drv);
} module_init(lcd_init);
module_exit(lcd_exit); MODULE_LICENSE("GPL");
/*不使用autosuspend的电源管理框架:*/ static struct platform_device lcd_dev; static int mylcd_open(struct fb_info *info, int user)
{
pm_runtime_get_sync(&lcd_dev.dev);
return ;
}
static int mylcd_release(struct fb_info *info, int user)
{
pm_runtime_mark_last_busy(&lcd_dev.dev);
pm_runtime_put_sync_autosuspend(&lcd_dev.dev);
return ;
} static int lcd_suspend_notifier(struct notifier_block *nb,
unsigned long event,
void *dummy)
{ switch (event) {
case PM_SUSPEND_PREPARE:
printk("lcd suspend notifiler test: PM_SUSPEND_PREPARE\n");
return NOTIFY_OK;
case PM_POST_SUSPEND:
printk("lcd suspend notifiler test: PM_POST_SUSPEND\n");
return NOTIFY_OK; default:
return NOTIFY_DONE;
}
} static struct notifier_block lcd_pm_notif_block = {
.notifier_call = lcd_suspend_notifier,
}; static void lcd_release(struct device * dev)
{
} static struct platform_device lcd_dev = {
.name = "mylcd",
.id = -,
.dev = {
.release = lcd_release,
},
};
static int lcd_probe(struct platform_device *pdev)
{
/* 因为runtime PM 默认上电是关闭的,而这个设备默认上电就是使用的 */
pm_runtime_set_active(&pdev->dev);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return ;
}
static int lcd_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return ;
}
static int lcd_suspend(struct device *dev)
{
int i;
unsigned long *dest = &lcd_regs_backup;
unsigned long *src = lcd_regs; for (i = ; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
{
dest[i] = src[i];
} lcd_regs->lcdcon1 &= ~(<<); /* 关闭LCD本身 */
*gpbdat &= ~; /* 关闭背光 */
return ;
} static int lcd_resume(struct device *dev)
{
int i;
unsigned long *dest = lcd_regs;
unsigned long *src = &lcd_regs_backup; struct clk *clk = clk_get(NULL, "lcd");
clk_enable(clk);
clk_put(clk); for (i = ; i < sizeof(lcd_regs_backup)/sizeof(unsigned long); i++)
{
dest[i] = src[i];
} lcd_regs->lcdcon1 |= (<<); /* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (<<); /* 使能LCD本身 */
*gpbdat |= ; /* 输出高电平, 使能背光 */
return ;
} static struct dev_pm_ops lcd_pm = {
.suspend = lcd_suspend,
.resume = lcd_resume,
.runtime_suspend = lcd_suspend,
.runtime_resume = lcd_resume,
}; struct platform_driver lcd_drv = {
.probe = lcd_probe,
.remove = lcd_remove,
.driver = {
.name = "mylcd",
.pm = &lcd_pm,
}
}; static int lcd_init(void)
{
/* 电源管理 */
register_pm_notifier(&lcd_pm_notif_block); platform_device_register(&lcd_dev);
platform_driver_register(&lcd_drv); return ;
} static void lcd_exit(void)
{
unregister_pm_notifier(&lcd_pm_notif_block);
platform_device_unregister(&lcd_dev);
platform_driver_unregister(&lcd_drv);
} module_init(lcd_init);
module_exit(lcd_exit); MODULE_LICENSE("GPL");
Linux内核 runtime_PM 框架的更多相关文章
- 图解linux内核编译框架
内核是如何编译成的 -知其然而不知其所以然 (第一篇) 转载:http://blog.chinaunix.net/uid-28236237-id-3840137.html Linux内核有分门别类的目 ...
- 深入理解linux内核v4l2框架之videobuf2【转】
转自:https://blog.csdn.net/ramon1892/article/details/8444193 Videobuf2框架 1. 什么是videobuf2框架? 它是一个针对多媒体设 ...
- vlan linux内核数据流程
转:http://blog.sina.com.cn/s/blog_62bbc49c0100fs0n.html 一.前言 前几天做协议划分vlan的时候看了一些linux内核,了解不深,整理了下vlan ...
- Linux内核中网络数据包的接收-第一部分 概念和框架
与网络数据包的发送不同,网络收包是异步的的.由于你不确定谁会在什么时候突然发一个网络包给你.因此这个网络收包逻辑事实上包括两件事:1.数据包到来后的通知2.收到通知并从数据包中获取数据这两件事发生在协 ...
- Linux内核下包过滤框架——iptables&netfilter
iptables & netfilter 1.简介 netfilter/iptables(下文中简称为iptables)组成Linux内核下的包过滤防火墙,完成封包过滤.封包重定向和网络地址转 ...
- linux内核中有哪些子系统(框架)呢?
注意: 分析用的linux内核版本为5.1.3 1. RTC子系统 2. Remote Processor子系统 3. Remote Processor Message子系统 4. SCSI子系统 5 ...
- linux 内核源代码情景分析——linux 内存管理的基本框架
386 CPU中的页式存管的基本思路是:通过页面目录和页面表分两个层次实现从线性地址到物理地址的映射.这种映射模式在大多数情况下可以节省页面表所占用的空间.因为大多数进程不会用到整个虚存空间,在虚存空 ...
- Linux 内核引导选项简介
Linux 内核引导选项简介 作者:金步国 连接地址:http://www.jinbuguo.com/kernel/boot_parameters.html 参考参数:https://www.cnbl ...
- Linux 内核引导参数简介
概述 内核引导参数大体上可以分为两类:一类与设备无关.另一类与设备有关.与设备有关的引导参数多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导参数.比如,如果你想知道可以向 AHA ...
随机推荐
- R语言统计词频 画词云
原始数据: 程序: #统计词频 library(wordcloud) # F:/master2017/ch4/weibo170.cut.txt text <- readLines("F ...
- VS2010/MFC编程入门之九(对话框:为控件添加消息处理函数)
创建对话框类和添加控件变量在上一讲中已经讲过,这一讲的主要内容是如何为控件添加消息处理函数. MFC为对话框和控件等定义了诸多消息,我们对它们操作时会触发消息,这些消息最终由消息处理函数处理.比如我们 ...
- 没的选择时,存在就是合理的::与李旭科书法字QQ聊天记录
2015,8,11,晚上,与李旭科书法字作者,在Q上聊了下 有些资料 涉及到字库设计.字库产业,对大家也有益处 按惯例 没细整理,直接发blog了 ps,9.11 靠,今天是911,早上查资料,在 f ...
- 尚未指定报表“Report1”的报表定义
在做RDLC项目中遇到这样的错误 本地报表处理期间出错. 尚未指定报表“Report1”的报表定义 未将对象引用设置到对象的实例. 解决方案: 打开reportViewer->LocalRepo ...
- springcloud21---Config-bus实现配置自动刷新
Pivotal(毕威拓)有VMware和EMC成立的. RabbitMQ是由ERlang(爱立信开发的,面向并发的编程语言),安装RabbitMQ先要安装ERlang. package com.itm ...
- web实现负载均衡的几种实现方式
摘要: 负载均衡(Load Balance)是集群技术(Cluster)的一种应用.负载均衡可以将工作任务分摊到多个处理单元,从而提高并发处理能力.目前最常见的负载均衡应用是Web负载均衡.根据实现的 ...
- Google发布机器学习平台Tensorflow游乐场~带你玩神经网络(转载)
Google发布机器学习平台Tensorflow游乐场-带你玩神经网络 原文地址:http://f.dataguru.cn/article-9324-1.html> 摘要: 昨天,Google发 ...
- Tomcat 启动图解
Tomcat server.xml结构 startup.bat执行流程 catalina.bat执行流程 Tomcat Server处理一个http请求的过程
- hdu_2048 错排问题
错排问题本质上就是一个动态规划问题,其状态转移方程为: 记d[n]为n个人错排情况的总数. 那么策略可以描述为:分析第n个人错排的可能情况: 1)前n-1个人满足错排的情况,那么第n个人加入后还要错排 ...
- UVa 10294 项链和手镯(polya)
https://vjudge.net/problem/UVA-10294 题意: 手镯可以翻转,但项链不可以.输入n和t,输出用t种颜色的n颗珠子能制作成的项链和手镯的个数. 思路: 经典等价类计数问 ...