1、电源管理
1.1 概述
目的:
在系统可维持正常所期望工作状态的情况下,尽可能降低功耗。Linux电源管理涉及到系统待机,频率电压变换,系统空闲处理,运行时期电源管理等多个方面。
措施:
《Linux设备驱动开发详解》一书中对电源管理的总结:
1.2 单片机
对于51单片机的电源管理,提供了低速,空闲和掉电三种模式来省电。
- 通过分频使系统降频工作
- 空闲模式:
- 掉电模式
1.3 linux电源管理模型
1.3.1 休眠模型
(1)电源状态描述
- On(on) S0 - Working
- Standby (standby) S1 - CPU and RAM are powered but not executed
- to RAM(mem) S3 - RAM is powered and the running content is saved to RAM【Suspend】
- Suspend to Disk,Hibernation(disk) S4 - All content is saved to Disk and power down 【Hibernate】
(2)cat /sys/power/state
查看支持的状态:
"suspend" 在嵌入式设备一般是指进入到suspend-to-RAM状态
1.3.2 Runtime模型
在系统运行阶段进入低功耗状态
1.4 Android电源管理
2、Linux电源管理
linux系统电源管理涉及多个子系统,如休眠唤醒,调频调压,充电等等。
2.1 Suspend to RAM
2.1.1 触发方式
查看支持的休眠方式:
cat /sys/power/state
休眠一般是由应用程序触发 -> 内核休眠处理 -> 硬件。最简单的触发方式:
echo mem > /sys/power/state
2.1.2 Suspend 流程
系统平台到驱动级挂起流程
https://blog.csdn.net/lixiaojie1012/article/details/23707681
https://blog.csdn.net/wenjin359/article/details/83056560
https://blog.csdn.net/u011006622/article/details/47417823
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);
};
2.1.3 如何在驱动中支持Suspend to RAM
(1)提供回调函数
定义PM相关回调函数结构体dev_pm_ops,并实现PM相关回调函数。
static struct dev_pm_ops lcd_pm = {
.suspend = lcd_suspend,
.resume = lcd_resume,
};
struct platform_driver lcd_drv = {
.probe = lcd_probe,
.remove = lcd_remove,
.driver = {
.name = "mylcd",
.pm = &lcd_pm,
}
};
(2)如果有需要则注册notifie事件,在休眠唤醒的不同阶段执行准备工作。
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,
};
register_pm_notifier(&lcd_pm_notif_block);
(3)至少设置1个唤醒源,这样就可以完成系统的休眠,并唤醒。
/* 指定这些中断可以用于唤醒系统 */
irq_set_irq_wake(irq, 1);
hrtimer:The count is = 26
hrtimer:The count is = 27
echo mem > /sys/power/state
PM: Syncing filesystems ... done.
Freezing user space processes ... (elapsed 0.001 seconds) done.
Freezing remaining freezable tasks ... (elapsed 0.001 seconds) done.
Suspending console(s) (use no_console_suspend to debug)
PM: suspend of devices complete after 6.982 msecs
PM: suspend devices took 0.010 seconds
PM: late suspend of devices complete after 2.199 msecs
PM: noirq suspend of devices complete after 2.082 msecs
Disabling non-boot CPUs ...
PM: noirq resume of devices complete after 1.463 msecs
PM: early resume of devices complete after 1.646 msecs
gpmi-nand 1806000.gpmi-nand: enable the asynchronous EDO mode 5
PM: resume of devices complete after 73.038 msecs
PM: resume devices took 0.080 seconds
Restarting tasks ... done.
root@mys6ul14x14:/mnt# hrtimer:The count is = 28
hrtimer:The count is = 29
hrtimer:The count is = 30
hrtimer:The count is = 31
hrtimer:The count is = 32
2.2 Runtime Suspend
运行时期的电源管理机制,有些时候,并不希望直接让整个系统Suspend to RAM,那么对于一些独立的设备希望做到电源管理,内核提供Runtim PM,应用程序根据需要,动态的管理某个设备的休眠唤醒。做到对子模块在运行时期的电源管理。将Suspend to RAM与Runtime PM结合会更有效的实现节能降耗。
2.2.1 如何使驱动支持Runtime Suspend
- 驱动初始化中使能rpm
- 提供rpm相关回调函数
- 根据需要在合适的时候调用相关API对设备进行休眠或者唤醒操作
- 销毁函数中禁止掉相关资源
简之,一个驱动想要实现rpm,在dev_pm_ops 增加回调函数,在驱动合适的时候调用rpm相关API,实现对设备的在线时期电源管理。
2.2.2 Runtime API
-
使能和关闭
void pm_runtime_enable(struct device *dev)
static inline void pm_runtime_disable(struct device *dev)
如下:pm_runtime_enable主要是对变量disable_depth进行-1操作。反之pm_runtime_disable对disable_depth进行+1操作。disable_depth在初始化的时候初始值为1。
void pm_runtime_enable(struct device *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->power.lock, flags);
if (dev->power.disable_depth > 0)
dev->power.disable_depth--;
else
dev_warn(dev, "Unbalanced %s!\n", __func__);
spin_unlock_irqrestore(&dev->power.lock, flags);
}
- 休眠与唤醒
static inline int pm_runtime_get_sync(struct device *dev)
static inline int pm_runtime_put_sync(struct device *dev)
pm_runtime_get_sync与pm_runtime_put_sync供给驱动在合适的时候调用,从而导致驱动提供的休眠唤醒回调函数被调用。其大致调用流程如下:
pm_runtime_get_sync流程
(1)增加引用计数
(2)判断是否使能,是否处于休眠等,不满足条件直接返回
(3)检测是否设置timer_autosuspends,决定是否延迟进行操作
(4)如果设备处于RPM_RESUMING或者RPM_SUSPENDING,则等待
(5)如果有父设备的话,进行父设备的相关唤醒操作
(6) 调用子系统及驱动提供的相关回调函数,执行最终目的操作
子系统级别回调函数
callback = dev->pm_domain->ops.runtime_resume; 或
callback = dev->type->pm->runtime_resume; 或
callback = dev->class->pm->runtime_resume; 或
callback = dev->bus->pm->runtime_resume; 或
设备级别回调函数
callback = dev->driver->pm->runtime_resume;
2.2.3 LCD Runtime Suspend例子
1、在probe的时候使能,在remove的时候禁止
static int lcd_probe(struct platform_device *pdev)
{
pm_runtime_set_active(&pdev->dev); //默认状态是运行
pm_runtime_enable(&pdev->dev);
return 0;
}
static int lcd_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return 0;
}
2、提供相关回调函数
struct platform_driver lcd_drv = {
.probe = lcd_probe,
.remove = lcd_remove,
.driver = {
.name = "mylcd",
.pm = &lcd_pm,
}
};
static struct dev_pm_ops lcd_pm = {
.suspend = lcd_suspend,
.resume = lcd_resume,
.runtime_suspend = lcd_suspend,
.runtime_resume = lcd_resume,
};
3、在open的时候唤醒,在关闭时候休眠设备
static int mylcd_open(struct fb_info *info, int user)
{
pm_runtime_get_sync(&lcd_dev.dev);
return 0;
}
static int mylcd_release(struct fb_info *info, int user)
{
pm_runtime_put_sync(&lcd_dev.dev);
return 0;
}
2.2.4 sys接口
上面通过驱动中提供接口的,APP可以在合适的时候触发RPM,系统sys接口也提供了相关接口
echo on > /sys/devices/.../power/control
control_store -> pm_runtime_forbid(dev); :
atomic_inc(&dev->power.usage_count);
rpm_resume(dev, 0);
echo auto > /sys/devices/.../power/control
control_store -> pm_runtime_allow(dev);
atomic_dec_and_test(&dev->power.usage_count)
rpm_idle(dev, RPM_AUTO);
2.3 USB休眠唤醒机制分析
2.3.1 USB PM框架
bus-》devices
2.3.2 USB回调函数
static const struct dev_pm_ops usb_device_pm_ops = {
.prepare = usb_dev_prepare,
.complete = usb_dev_complete,
.suspend = usb_dev_suspend,
.resume = usb_dev_resume,
.freeze = usb_dev_freeze,
.thaw = usb_dev_thaw,
.poweroff = usb_dev_poweroff,
.restore = usb_dev_restore,
#ifdef CONFIG_PM_RUNTIME
.runtime_suspend = usb_runtime_suspend,
.runtime_resume = usb_runtime_resume,
.runtime_idle = usb_runtime_idle,
#endif
};
struct device_type usb_device_type = {
.name = "usb_device",
.release = usb_release_dev,
.uevent = usb_dev_uevent,
.devnode = usb_devnode,
#ifdef CONFIG_PM
.pm = &usb_device_pm_ops,
#endif
};
usb_alloc_dev
->dev->dev.bus = &usb_bus_type;
typedef struct pm_message {
int event;
} pm_message_t;
#define PM_EVENT_INVALID (-1)
#define PM_EVENT_ON 0x0000
#define PM_EVENT_FREEZE 0x0001
#define PM_EVENT_SUSPEND 0x0002
#define PM_EVENT_HIBERNATE 0x0004
#define PM_EVENT_QUIESCE 0x0008
#define PM_EVENT_RESUME 0x0010
#define PM_EVENT_THAW 0x0020
#define PM_EVENT_RESTORE 0x0040
#define PM_EVENT_RECOVER 0x0080
#define PM_EVENT_USER 0x0100
#define PM_EVENT_REMOTE 0x0200
#define PM_EVENT_AUTO 0x0400