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

 

你可能感兴趣的:(LINUX,ARM)