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 框架的更多相关文章

  1. 图解linux内核编译框架

    内核是如何编译成的 -知其然而不知其所以然 (第一篇) 转载:http://blog.chinaunix.net/uid-28236237-id-3840137.html Linux内核有分门别类的目 ...

  2. 深入理解linux内核v4l2框架之videobuf2【转】

    转自:https://blog.csdn.net/ramon1892/article/details/8444193 Videobuf2框架 1. 什么是videobuf2框架? 它是一个针对多媒体设 ...

  3. vlan linux内核数据流程

    转:http://blog.sina.com.cn/s/blog_62bbc49c0100fs0n.html 一.前言 前几天做协议划分vlan的时候看了一些linux内核,了解不深,整理了下vlan ...

  4. Linux内核中网络数据包的接收-第一部分 概念和框架

    与网络数据包的发送不同,网络收包是异步的的.由于你不确定谁会在什么时候突然发一个网络包给你.因此这个网络收包逻辑事实上包括两件事:1.数据包到来后的通知2.收到通知并从数据包中获取数据这两件事发生在协 ...

  5. Linux内核下包过滤框架——iptables&netfilter

    iptables & netfilter 1.简介 netfilter/iptables(下文中简称为iptables)组成Linux内核下的包过滤防火墙,完成封包过滤.封包重定向和网络地址转 ...

  6. linux内核中有哪些子系统(框架)呢?

    注意: 分析用的linux内核版本为5.1.3 1. RTC子系统 2. Remote Processor子系统 3. Remote Processor Message子系统 4. SCSI子系统 5 ...

  7. linux 内核源代码情景分析——linux 内存管理的基本框架

    386 CPU中的页式存管的基本思路是:通过页面目录和页面表分两个层次实现从线性地址到物理地址的映射.这种映射模式在大多数情况下可以节省页面表所占用的空间.因为大多数进程不会用到整个虚存空间,在虚存空 ...

  8. Linux 内核引导选项简介

    Linux 内核引导选项简介 作者:金步国 连接地址:http://www.jinbuguo.com/kernel/boot_parameters.html 参考参数:https://www.cnbl ...

  9. Linux 内核引导参数简介

    概述 内核引导参数大体上可以分为两类:一类与设备无关.另一类与设备有关.与设备有关的引导参数多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导参数.比如,如果你想知道可以向 AHA ...

随机推荐

  1. 2018-2019 ACM-ICPC, Asia Seoul Regional Contest

    ProblemA Circuits Solved. 题意: 有$n$个矩形,可以放两条平行与$x$轴的线,求怎么放置两条无线长的平行于$x$轴的线,使得他们与矩形相交个数最多 如果一个矩形同时与两条线 ...

  2. 使用vux实现上拉刷新的总结

    最近公司在研发app,选择了基于Vue框架的vux组件库,现总结在实现上拉刷新功能遇到的坑: 1.问题:只刷新一次,解决方法:需要自己手动重置状态 this.scrollerStatus.pullup ...

  3. Mysql性能调优工具Explain结合语句讲解

    Explain简称执行计划,可以模拟SQL语句,来分析查询语句或者表结构是否有性能瓶颈.Explain的作用有哪些,可以看到哪些?可以看到表的读取顺序,数据读取操作的操作类型,哪些索引可以使用,哪些索 ...

  4. s3c2440中U-boot移植时执行cp.b提示:Flash not Erased【转】

    本文转载自:https://blog.csdn.net/baiyang139/article/details/79054415 版权声明:本文为博主原创文章,未经博主允许不得转载. https://b ...

  5. 在iframe外层head中插入link

    let src = 'abc.css?v='+Math.random(); let link = window.parent.document.createElement('link'); link. ...

  6. Bridge(桥接)

    意图: 将抽象部分与它的实现部分分离,使它们都可以独立地变化. 适用性: 你不希望在抽象和它的实现部分之间有一个固定的绑定关系.例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换. 类 ...

  7. Integer与int的种种比较你知道多少

    如果面试官问Integer与int的区别:估计大多数人只会说道两点,Ingeter是int的包装类,int的初值为0,Ingeter的初值为null. 但是如果面试官再问一下Integer i = 1 ...

  8. Swagger使用总结(十九)

    1. Swagger是什么? Swagger 是一款RESTFUL接口的文档在线自动生成+功能测试功能软件. 官方说法:Swagger是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTfu ...

  9. 046——VUE中组件之使用动态组件灵活设置页面布局

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. Centos7 firewalld命令行

    使用命令行管理firewall之前,说明有关于防火墙的策略独立性:明确的策略,策略之间无关联. 比如mysql使用3306,firewall添加mysql服务但未添加3306,当查询3306端口状态会 ...