autosleep框架设计与实现
在低功耗系统中,autosleep是一个较小的模块,是低功耗主流程的入口。在Linux内核中,autosleep是休眠流程的触发点和入口点,PM Core的休眠流程入口pm_suspend()就是被autosleep的睡眠工作队列调用而进入休眠的。
该功能的支持受宏CONFIG_PM_AUTOSLEEP
控制。
1)通过写“mem, disk, standby, freeze” 到 /sys/power/autosleep 中可以开启autosleep功能。
2)通过写“off” 到 /sys/power/autosleep 中可以关闭autosleep功能。
1. 主要接口实现
1.1 pm_autosleep_init()
接口
该接口在PM Core初始化过程中被调用,用于初始化autosleep,主要实现了:
1)注册autosleep wakeup_source autosleep_ws
,用于投票系统是否进入休眠
2)创建autosleep休眠工作队列autosleep_wq
@kernel\power\autosleep.c
static struct wakeup_source *autosleep_ws;
static struct workqueue_struct *autosleep_wq;
int __init pm_autosleep_init(void)
{
autosleep_ws = wakeup_source_register(NULL, "autosleep"); //注册ws
autosleep_wq = alloc_ordered_workqueue("autosleep", 0); //创建工作队列
if (autosleep_wq)
return 0;
}
1.2 queue_up_suspend_work()
接口
该函数的功能主要是启动autosleep_wq
。函数被调用后,会触发工作队列运行,最终调用try_to_suspend()
。
#define PM_SUSPEND_ON ((__force suspend_state_t) 0)
#define PM_SUSPEND_TO_IDLE ((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN PM_SUSPEND_TO_IDLE
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4)
static DECLARE_WORK(suspend_work, try_to_suspend);
void queue_up_suspend_work(void)
{
if (autosleep_state > PM_SUSPEND_ON)
queue_work(autosleep_wq, &suspend_work);
}
1.3 pm_autosleep_set_state()
接口
该函数是供文件节点autosleep使用的,在init.rc中向此节点写入suspend状态,触发autosleep运行。
int pm_autosleep_set_state(suspend_state_t state)
{
#ifndef CONFIG_HIBERNATION
if (state >= PM_SUSPEND_MAX)
return -EINVAL;
#endif
__pm_stay_awake(autosleep_ws);
mutex_lock(&autosleep_lock);
autosleep_state = state; //更新autosleep状态
__pm_relax(autosleep_ws);
if (state > PM_SUSPEND_ON) { //state大于PM_SUSPEND_ON,表示开启低功耗相关特性
pm_wakep_autosleep_enabled(true); //更新所有的ws的autosleep的标记为使能,便于维护记录
queue_up_suspend_work(); //进入休眠流程
} else {
pm_wakep_autosleep_enabled(false);
}
mutex_unlock(&autosleep_lock);
return 0;
}
void pm_wakep_autosleep_enabled(bool set)
{
struct wakeup_source *ws;
ktime_t now = ktime_get();
int srcuidx;
srcuidx = srcu_read_lock(&wakeup_srcu);
list_for_each_entry_rcu_locked(ws, &wakeup_sources, entry) {
spin_lock_irq(&ws->lock);
if (ws->autosleep_enabled != set) { //更新所有的ws的autosleep的标记为使能/失能
ws->autosleep_enabled = set;
if (ws->active) {
if (set)
ws->start_prevent_time = now;
else
update_prevent_sleep_time(ws, now);
}
}
spin_unlock_irq(&ws->lock);
}
srcu_read_unlock(&wakeup_srcu, srcuidx);
}
1.4 try_to_suspend()
模块私有接口
该函数主要根据当前系统中的持锁状态和autosleep_state
的状态来判断是否进入PM CORE休眠流程。
static void try_to_suspend(struct work_struct *work)
{
unsigned int initial_count, final_count;
if (!pm_get_wakeup_count(&initial_count, true)) //阻塞等待直到唤醒事件数为0(也可能是其它因素导致阻塞退出)
goto out;
mutex_lock(&autosleep_lock);
if (!pm_save_wakeup_count(initial_count) ||
system_state != SYSTEM_RUNNING) {
mutex_unlock(&autosleep_lock);
goto out;
}
if (autosleep_state == PM_SUSPEND_ON) {
mutex_unlock(&autosleep_lock);
return;
}
if (autosleep_state >= PM_SUSPEND_MAX)
hibernate(); //挂到磁盘中,嵌入式设备一般不使用。
else
pm_suspend(autosleep_state); //进入PM Core的休眠主流程
mutex_unlock(&autosleep_lock);
if (!pm_get_wakeup_count(&final_count, false)) //退出休眠流程后再次获取唤醒事件数,此处不阻塞
goto out;
/*
* If the wakeup occured for an unknown reason, wait to prevent the
* system from trying to suspend and waking up in a tight loop.
*/
if (final_count == initial_count) //如果被未知原因(未持锁状态)唤醒,则在此处终止睡眠流程
schedule_timeout_uninterruptible(HZ / 2);
out:
queue_up_suspend_work(); //再次尝试检测并进入休眠流程
}
2. 工作时序
Autosleep 工作步骤如下:
- 在init.rc中,向autosleep节点写入功耗控制的状态,通常写入mem状态(
write /sys/power/autosleep mem
);或在控制台中输入echo mem > /sys/power/autosleep
来触发autosleep机制 - 写文件节点会触发调用
pm_autosleep_set_state()
,该函数进行参数的判断、系统状态的更新并调用queue_up_suspend_work()
来触发autosleep的工作队列 queue_up_suspend_work()
被调用后触发工作队列suspend_work
运行- 工作队列
suspend_work
运行后进入函数try_to_suspend()
执行,该函数会根据持锁条件决策是否进入PM Core休眠主流程 try_to_suspend()
通过调用pm_suspend()
来进入PM Core流程pm_suspend()
退出后回到try_to_suspend()
,try_to_suspend()
会再次触发任务或者工作队列调度,期待进入下次的休眠流程try_to_suspend()
通过调用queue_up_suspend_work()
来再次调度autosleep的工作队列suspend_work