1. 程式人生 > >linux autosleep機制

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);
}