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. linux 服务器部署的web项目存入数据库的时间不正确

    在linux获取当前时间 date 获取的时间是正常的 ----- java写了个测试类 public class TestDate { public static void main(String[ ...

  2. @Tranactional事务没有回滚

    一.特性 先来了解一下@Transactional注解事务的特性吧,可以更好排查问题 1.service类标签(一般不建议在接口上)上添加@Transactional,可以将整个类纳入spring事务 ...

  3. 20145315 《Java程序设计》第八周学习总结

    20145315 <Java程序设计>第八周学习总结 教材学习内容总结 第十五章通用API 15.1日志 15.1.1日志API简介 使用日志的起点是logger类,logger实例的创建 ...

  4. LightOJ 1071 Baker Vai(拆点+最大费用流)题解

    题意:给一个n*m的方格,每个格子上都有一个数字表示价值,小A在左上角,他从左上角走到右下角只能向右或向下走,然后再从右下角走上左上角,这次只能向上或向左走,这两条路绝对没有重复,问你怎样走有最大价值 ...

  5. 机器学习-ID3决策树算法(附matlab/octave代码)

    ID3决策树算法是基于信息增益来构建的,信息增益可以由训练集的信息熵算得,这里举一个简单的例子 data=[心情好 天气好  出门 心情好 天气不好 出门 心情不好 天气好 出门 心情不好 天气不好 ...

  6. 使用 Git Hook 自动部署 Hexo 到个人 VPS

    安装 Hexo 既然我的标题都已经那样写了,当然这个小节就不是本篇文章的重点了. 关于 Hexo 的安装跟配置,其实网上已经有很多很多文章了,随便一搜一大把.这里就有一篇超详细的,大家可以参考一下. ...

  7. InfiniBand 与Intel Omni-Path Architecture

    Intel Omni-Path Architecture (OPA) 是一种与InfiniBand相似的网络架构 可以用来避免以下PCI总线一些缺陷: 1.由于采用了基于总线的共享传输模式,在PCI总 ...

  8. Java网络编程学习A轮_07_基于Buffer的Socket编程

    示例代码: https://github.com/gordonklg/study,socket module A. LineSeparate 基于 Buffer 实现逐行读取的 EchoServer ...

  9. 【整理】STL中的bitset(二进制华丽解决假五维偏序题)

    ------------更多Bitset的运用,请看这里http://www.cnblogs.com/hua-dong/p/8519739.html. 由于在学cdq分治,看到了这道题.先来看一道题目 ...

  10. tp5集成淘宝,微信,网易,新浪等第三方登录

    tp5集成淘宝,微信,网易,新浪等第三方登录 一.总结 一句话总结: 接口 链接 实现的话就是这些平台给的一个接口(链接),你通过这些接口登录进去之后,它会给你返回用户名,头像之类的信息,我们的网站存 ...