aarch32 linux4.9
常用的linux系统进入suspend的命令是echo mem > /sys/power/state,auto sleep的功能则是为了实现“Opportunistic suspend”即循环的监测到系统有没有wakeup_event如果没有就让系统suspend下去,auto_sleep会跟pm的wakeup source和wakeup count有相关性
user space的使用方法echo mem > /sys/power/auto_sleep, debugfs可以监测系统的wakeup source事件
echo 操作会enable auto_sleep而且调用queue_up_suspend_work, workfunction是try_to_suspend,会调用kernel pm入口pm_suspend,code flow 如下
static ssize_t autosleep_store(struct kobject *kobj,struct kobj_attribute *attr,const char *buf, size_t n)
{
if (state == PM_SUSPEND_ON
&& strcmp(buf, "off") && strcmp(buf, "off\n"))
return -EINVAL;
"auto_sleep的不能从ON-->OFF"
error = pm_autosleep_set_state(state);
}
int pm_autosleep_set_state(suspend_state_t state)
{
__pm_stay_awake(autosleep_ws);
autosleep_state = state;
__pm_relax(autosleep_ws);
if (state > PM_SUSPEND_ON) {
pm_wakep_autosleep_enabled(true);
queue_up_suspend_work();
} else {
pm_wakep_autosleep_enabled(false);
}
}
void pm_wakep_autosleep_enabled(bool set)
{
list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
spin_lock_irq(&ws->lock);
if (ws->autosleep_enabled != set) {
ws->autosleep_enabled = set;
}
}
spin_unlock_irq(&ws->lock);
}
}
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);
}
static void try_to_suspend(struct work_struct *work)
{
if (!pm_get_wakeup_count(&initial_count, true))
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);
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();
"失败的话继续添加work到work queue,所以fail的话会有一个用work queue构造的循环"
}
try_to_suspend fail的判断条件有如下三个
if (!pm_get_wakeup_count(&initial_count, true))
goto out;
wakeup count 有存储wakeup event 总数和当前正在处理的wakeup event,如果当前的in process的wakeup event个数不是0的话就代表还有wakeup event要处理就会在out处继续在try_to_suspend状态循环
/* Return 'false' if the current number of wakeup events being processed is
* nonzero. Otherwise return 'true'.
*/
bool pm_get_wakeup_count(unsigned int *count, bool block)
{
if (block) {
DEFINE_WAIT(wait);
for (;;) {
prepare_to_wait(&wakeup_count_wait_queue, &wait,
TASK_INTERRUPTIBLE);
split_counters(&cnt, &inpr);
if (inpr == 0 || signal_pending(current))
break;
schedule();
}
finish_wait(&wakeup_count_wait_queue, &wait);
}
split_counters(&cnt, &inpr);
*count = cnt;
return !inpr;
}
if (!pm_save_wakeup_count(initial_count) || system_state != SYSTEM_RUNNING)
goto out;
init_count是刚刚try_to_freeze开始后获取的当前注册的wakeup event总数,combined_event_count。out的条件也是检查wakeup event in process 是否为0,或者当前系统的状态是不是running
/**
* pm_save_wakeup_count - Save the current number of registered wakeup events.
* @count: Value to compare with the current number of registered wakeup events.
*
* If @count is equal to the current number of registered wakeup events and the
* current number of wakeup events being processed is zero, store @count as the
* old number of registered wakeup events for pm_check_wakeup_events(), enable
* wakeup events detection and return 'true'. Otherwise disable wakeup events
* detection and return 'false'.
*/
bool pm_save_wakeup_count(unsigned int count)
{
events_check_enabled = false;
spin_lock_irqsave(&events_lock, flags);
split_counters(&cnt, &inpr);
if (cnt == count && inpr == 0) {
saved_count = count;
events_check_enabled = true;
}
spin_unlock_irqrestore(&events_lock, flags);
return events_check_enabled;
}
if (autosleep_state == PM_SUSPEND_ON)
return;
如果try_to_suspend期间autosleep收到了切换pm state 为ON则继续在try_to_suspend中循环等待
kernel space的task想要让自己的某部分代码执行过程中不能进suspend的话需要调用如下函数,这两个函数成对出现,pm_wake_lock和pm_wake_unlock也是类似机制实现
pm_stay_awak();//如下源码 call wakeup_source_active会increase the counter of wakeup event in process,这里combine_event_counter正是try_to_freeze中pm_get_wakeup_counte的函数中获取的全局变量的值
//my work
pm_relex();//combined_event_count 中的in process的event的值减1
/**
* __pm_stay_awake - Notify the PM core of a wakeup event.
* @ws: Wakeup source object associated with the source of the event.
* It is safe to call this function from interrupt context.
*/
void __pm_stay_awake(struct wakeup_source *ws)
{
wakeup_source_report_event(ws);
del_timer(&ws->timer);
ws->timer_expires = 0;
}
static void wakeup_source_report_event(struct wakeup_source *ws)
{
ws->event_count++;
if (events_check_enabled)
ws->wakeup_count++;
if (!ws->active)
wakeup_source_activate(ws);
}
static void wakeup_source_activate(struct wakeup_source *ws)
{
/*
* 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);
}
/**
* pm_relax - Notify the PM core that processing of a wakeup event has ended.
* @dev: Device that signaled the event.
*
* Execute __pm_relax() for the @dev's wakeup source object.
*/
void pm_relax(struct device *dev)
{
__pm_relax(dev->power.wakeup);
}
void __pm_relax(struct wakeup_source *ws)
{
if (ws->active)
wakeup_source_deactivate(ws);
}
/* Update the @ws' statistics and notify the PM core that the wakeup source has
* become inactive by decrementing the counter of wakeup events being processed
* and incrementing the counter of registered wakeup events.
*/
static void wakeup_source_deactivate(struct wakeup_source *ws)
{
ws->relax_count++;
/*
* __pm_relax() may be called directly or from a timer function.
* If it is called directly right after the timer function has been
* started, but before the timer function calls __pm_relax(), it is
* possible that __pm_stay_awake() will be called in the meantime and
* will set ws->active. Then, ws->active may be cleared immediately
* by the __pm_relax() called from the timer function, but in such a
* case ws->relax_count will be different from ws->active_count.
*/
cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);
split_counters(&cnt, &inpr);
if (!inpr && waitqueue_active(&wakeup_count_wait_queue))
wake_up(&wakeup_count_wait_queue);
}