Android Framework 框架系列之 PowerManger 基础
极力推荐Android 开发大总结文章:欢迎收藏程序员Android 力荐 ,Android 开发者需要的必备技能
本篇文章主要介绍 Android
开发中的电源管理部分知识点,本篇文章转载网络,通过阅读本篇文章,您将收获以下内容:
Sleep/Suspend
SPM
wakeup 唤醒源
原文地址:http://www.robinheztto.com/2017/04/20/android-power-basic/
1. Sleep/Suspend
系统休眠Sleep,Linux Kernel中称作Suspend。系统进入Suspend状态确切来说时CPU进入了Suspend模式,因为对整个系统来说CPU休眠是整个系统休眠的先决条件。CPU Suspend即CPU进入Wait for interrupt状态(WFI),SW完全不跑了,停在suspend workqueue里面。
灭屏到系统进入Suspend的大体流程
相关代码如下:
/frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
/frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
/system/core/libsuspend//kernel-x.x/kernel/power/
2.SPM
SPM即System Power Manager,管理着包括AP,Modem,Connectivity等子系统。在CPU进入WFI状态后,整个系统就依靠SPM监控各个子系统的状态来控制睡眠/唤醒的流程。
MTK平台:
26M 时钟逻辑
如上图所示,26M时钟有没有关,只需要看SCLKENA信号有没有关闭,而SPM对这个信号的输出以及子系统的信号输入,都记录在SPM的寄存器里面,这个就是我们通过log排查的依据。
/kernel-x.x/drivers/misc/mediatek/base/power/spm_vx/
3. wakeup 唤醒源
主要涉及以下代码
kernel/msm-4.4/drivers/base/power/wakeup.c
kernel/msm-4.4/include/linux/pm_wakeup.h
kernel/msm-4.4/include/linux/pm.h
kernel/msm-4.4/include/linux/device.h
Android以Linux kernel为系统内核,其电源管理也是基于Linux电源管理子系统的,但是由于传统的Linux系统主要针对PC而非移动设备,对于PC系统,Linux默认是在用户不再使用时才再触发系统进入休眠(STR、Standby、Hibernate),但是对于移动设备的使用特点而言,并没有明确的不再使用设备的时候,用户随时随地都可能需要使用设备,于是在Android上就有提出了“Opportunistic suspend”的概念,即逮到机会就睡直到下次被唤醒,以适应移动设备的使用特点。
早期,Android为解决该问题,在标准Linux的基础上增加了Early Suspend和Late Resume机制,Early suspend是在熄屏后,提前将一些不会用到的设备(比如背光、重力感应器和触摸屏等设备)关闭,但此时系统仍能持有wake lock后台运行。该方案将Linux原来suspend的流程改变,并增加了Android自己的处理函数,与kernel的流程与机制相冲突,另外一个问题就是存在suspend和wakeup events之间的同步问题,比如当系统进入suspend流程中,会进行freeze process,device prepared,device suspend,disabled irq等操作,如果在suspend流程中有wakeup events产生,而此时系统无法从suspend过程中唤醒。
后来Linux加入wakeup events framework,包括wake lock、wakeup count、autosleep等机制。用来解决system suspend和system wakeup events之间的同步问题。同时在Android4.4中,Android中也去掉了之前的”wakelocks”机制,利用wakeup events framework重新设计了wakelocks,并且维持上层API不变。
system suspend和system wakeup events之间的同步问题:
内核空间的同步:
用户空间的同步:
代码分析
wakeup events framework sysfs将设备的wakeup信息,以sysfs的形式提供到用户空间,供用户空间访问(在drivers/base/power/sysfs.c中实现)。
wakeup source
在kernel中,只有设备才能唤醒系统,但并不是所有设备都具备唤醒系统的能力,具备唤醒能力的设备即“wakeup source”,会在设备结构体struce device中标志该设备具有唤醒能力。kernel/msm-4.4/include/linux/device.h
struct device {
...
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
...
}
kernel/msm-4.4/include/linux/pm_wakeup.h
static inline bool device_can_wakeup(struct device *dev){ return dev->power.can_wakeup;
}static inline bool device_may_wakeup(struct device *dev){ return dev->power.can_wakeup && !!dev->power.wakeup;
}
由device_can_wakeup()函数可知,通过dev->power.can_wakeup来判断该设备是否能唤醒系统,struct dev_pm_info的定义如下:
kernel/msm-4.4/include/linux/pm.h
..... unsigned int can_wakeup:1;
......#ifdef CONFIG_PM_SLEEP
...... struct wakeup_source *wakeup;
......#else
unsigned int should_wakeup:1;#endif
......
};
struct dev_pm_info中的can_wakeup标识该设备是否具有唤醒能力。具备唤醒能力的设备在sys/devices/xxx/下存在power相关目录,用于提供所有的wakeup信息,这些信息是以struct wakeup_source的结构组织,即struct dev_pm_info中的wakeup指针。
kernel/msm-4.4/include/linux/pm_wakeup.h
/**
* struct wakeup_source - Representation of wakeup sources
*
* @name: 唤醒源的名字
* @entry: 用来将唤醒源挂到链表上,用于管理
* @lock: 同步机制,用于访问链表时使用
* @wakeirq:Optional device specific wakeirq
* @timer: 定时器,用于设置该唤醒源的超时时间
* @timer_expires: 定时器的超时时间
* @total_time: wakeup source处于active状态的总时间,可指示该wakeup source对应的设备的繁忙程度、耗电等级
* @max_time: wakeup source处于active状态的最长时间(越长越不合理)
* @last_time: wakeup source处于active状态的上次时间
* @prevent_sleep_time: wakeup source阻止系统自动休眠的总时间
* @event_count: wakeup source上报wakeup event的个数
* @active_count: wakeup source处于active状态的次数
* @relax_count: wakeup source处于deactive状态的次数
* @expire_count: wakeup source timeout次数
* @wakeup_count: wakeup source abort睡眠的次数
* @active: wakeup source的状态
* @has_timeout: The wakeup source has been activated with a timeout.
*/struct wakeup_source {
const char *name; struct list_head entry;
spinlock_t lock; struct wake_irq *wakeirq;
struct timer_list timer;
unsigned long timer_expires; ktime_t total_time; ktime_t max_time; ktime_t last_time; ktime_t start_prevent_time; ktime_t prevent_sleep_time; unsigned long event_count; unsigned long active_count; unsigned long relax_count; unsigned long expire_count; unsigned long wakeup_count; bool active:1; bool autosleep_enabled:1;
};
wakeup source代表一个具有唤醒能力的设备,该设备产生的可以唤醒系统的事件,就称作wakeup event。当wakeup source产生wakeup event时,需要将wakeup source切换为activate状态;当wakeup event处理完毕后,要切换为deactivate状态。当wakeup source产生wakeup event时,需要切换到activate状态,但并不是每次都需要切换,因此有可能已经处于activate状态了。因此active_count可能小于event_count,换句话说,很有可能在前一个wakeup event没被处理完时,又产生了一个,这从一定程度上反映了wakeup source所代表的设备的繁忙程度。wakeup source在suspend过程中产生wakeup event的话,就会终止suspend过程,wakeup_count记录了wakeup source终止suspend过程的次数(如果发现系统总是suspend失败,检查一下各个wakeup source的该变量,就可以知道问题出在谁身上了)。
kernel/msm-4.4/drivers/base/power/wakeup.c
static int wakeup_sources_stats_show(struct seq_file *m, void *unused){ struct wakeup_source *ws;
seq_puts(m, "name\t\tactive_count\tevent_count\twakeup_count\t"
"expire_count\tactive_since\ttotal_time\tmax_time\t"
"last_change\tprevent_suspend_time\n");
rcu_read_lock();
list_for_each_entry_rcu(ws, &wakeup_sources, entry)
print_wakeup_source_stats(m, ws);
rcu_read_unlock();
print_wakeup_source_stats(m, &deleted_ws); return 0;
}static int wakeup_sources_stats_open(struct inode *inode, struct file *file){ return single_open(file, wakeup_sources_stats_show, NULL);
}static const struct file_operations wakeup_sources_stats_fops = {
.owner = THIS_MODULE,
.open = wakeup_sources_stats_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};static int __init wakeup_sources_debugfs_init(void){
wakeup_sources_stats_dentry = debugfs_create_file("wakeup_sources",
S_IRUGO, NULL, NULL, &wakeup_sources_stats_fops); return 0;
}
postcore_initcall(wakeup_sources_debugfs_init);
如下图示,通过cat /sys/kernel/debug/wakeup_sources
获取wakeup_sources
信息:
机制wakeup events framework中抽象了wakeup source和wakeup event的概念,向各个device driver提供wakeup source的注册、使能等及wakeup event的上报、停止等接口,同时也向上层提供wakeup event的查询接口,以判断是否可以suspend或者是否需要终止正在进行的suspend。
pm_stay_awake()
当设备有wakeup event正在处理时,需要调用该接口通知PM core,该接口的实现如下:
kernel/msm-4.4/drivers/base/power/wakeup.c
void pm_stay_awake(struct device *dev)
{ unsigned long flags; if (!dev) return;
spin_lock_irqsave(&dev->power.lock, flags);
__pm_stay_awake(dev->power.wakeup);
spin_unlock_irqrestore(&dev->power.lock, flags);
}void __pm_stay_awake(struct wakeup_source *ws)
{ unsigned long flags; if (!ws) return;
spin_lock_irqsave(&ws->lock, flags);
wakeup_source_report_event(ws);
del_timer(&ws->timer);
ws->timer_expires = 0;
spin_unlock_irqrestore(&ws->lock, flags);
}static void wakeup_source_report_event(struct wakeup_source *ws)
{
ws->event_count++; /* This is racy, but the counter is approximate anyway. */
if (events_check_enabled)
ws->wakeup_count++; if (!ws->active)
wakeup_source_activate(ws);
}
pm_stay_awake中直接调用pm_stay_awake,\pm_stay_awake直接调用wakeup_source_report_event。kernel/msm-4.4/drivers/base/power/wakeup.c
static void wakeup_source_report_event(struct wakeup_source *ws)
{
ws->event_count++; /* This is racy, but the counter is approximate anyway. */
if (events_check_enabled)
ws->wakeup_count++; if (!ws->active)
wakeup_source_activate(ws);
}
wakeup_source_report_event中增加wakeup source的event_count次数,即表示该source又产生了一个event。然后根据events_check_enabled变量的状态,增加wakeup_count。如果wakeup source没有active,则调用wakeup_source_activate进行activate操作。
kernel/msm-4.4/drivers/base/power/wakeup.c
static void wakeup_source_activate(struct wakeup_source *ws)
{
unsigned int cec; /*
* active wakeup source should bring the system
* out of PM_SUSPEND_FREEZE state
*/
freeze_wake();
ws->active = true;
ws->active_count++;
ws->last_time = ktime_get(); if (ws->autosleep_enabled)
ws->start_prevent_time = ws->last_time; /* Increment the counter of events in progress. */
cec = atomic_inc_return(&combined_event_count);
trace_wakeup_source_activate(ws->name, cec);
}
wakeup_source_activate中首先调用freeze_wake,将系统从suspend to freeze状态下唤醒,然后设置active标志,增加active_count,更新last_time。如果使能了autosleep,更新start_prevent_time,此刻该wakeup source会开始阻止系统auto sleep。增加“wakeup events in progress”计数,增加该计数意味着系统正在处理的wakeup event数目不为零,即系统不能suspend。
pm_relax()pm_relax
和pm_stay_awake
成对出现,用于在wakeup event处理结束后通知PM core,其实现如下
kernel/msm-4.4/drivers/base/power/wakeup.c
void pm_relax(struct device *dev)
{ unsigned long flags; if (!dev) return;
spin_lock_irqsave(&dev->power.lock, flags);
__pm_relax(dev->power.wakeup);
spin_unlock_irqrestore(&dev->power.lock, flags);
}void __pm_relax(struct wakeup_source *ws)
{ unsigned long flags; if (!ws) return;
spin_lock_irqsave(&ws->lock, flags); if (ws->active)
wakeup_source_deactivate(ws);
spin_unlock_irqrestore(&ws->lock, flags);
}
pm_relax中直接调用pm_relax,\pm_relax判断wakeup source如果处于active状态,则调用wakeup_source_deactivate接口,deactivate该wakeup source
kernel/msm-4.4/drivers/base/power/wakeup.c
static void wakeup_source_deactivate(struct wakeup_source *ws)
{
unsigned int cnt, inpr, cec;
ktime_t duration;
ktime_t now;
ws->relax_count++; if (ws->relax_count != ws->active_count) {
ws->relax_count--; return;
}
ws->active = false;
now = ktime_get();
duration = ktime_sub(now, ws->last_time);
ws->total_time = ktime_add(ws->total_time, duration); if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))
ws->max_time = duration;
ws->last_time = now;
del_timer(&ws->timer);
ws->timer_expires = 0; if (ws->autosleep_enabled)
update_prevent_sleep_time(ws, now); /*
* Increment the counter of registered wakeup events and decrement the
* couter of wakeup events in progress simultaneously.
*/
cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);
trace_wakeup_source_deactivate(ws->name, cec);
split_counters(&cnt, &inpr); if (!inpr && waitqueue_active(&wakeup_count_wait_queue))
wake_up(&wakeup_count_wait_queue);
}
至此,本篇已结束,如有不对的地方,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!
如有侵权,请联系小编,小编对此深感抱歉,届时小编会删除文章,立即停止侵权行为,请您多多包涵。
既然都看到这里,领两个红包在走吧!以下两个红包每天都可以领取
支付宝搜索 522398497,或扫码支付宝红包海报。支付宝扫一扫,每天领取大红包
2.微信红包,微信扫一扫即可领取红包
微信扫一扫,每天领取微信红包