linux autosleep機制
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);
}