autosleep框架设计与实现

在低功耗系统中,autosleep是一个较小的模块,是低功耗主流程的入口。在Linux内核中,autosleep是休眠流程的触发点和入口点,PM Core的休眠流程入口pm_suspend()就是被autosleep的睡眠工作队列调用而进入休眠的。
image

该功能的支持受宏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 工作步骤如下:

  1. 在init.rc中,向autosleep节点写入功耗控制的状态,通常写入mem状态(write /sys/power/autosleep mem);或在控制台中输入echo mem > /sys/power/autosleep来触发autosleep机制
  2. 写文件节点会触发调用pm_autosleep_set_state(),该函数进行参数的判断、系统状态的更新并调用queue_up_suspend_work()来触发autosleep的工作队列
  3. queue_up_suspend_work()被调用后触发工作队列suspend_work运行
  4. 工作队列suspend_work运行后进入函数try_to_suspend()执行,该函数会根据持锁条件决策是否进入PM Core休眠主流程
  5. try_to_suspend()通过调用pm_suspend()来进入PM Core流程
  6. pm_suspend()退出后回到try_to_suspend()try_to_suspend()会再次触发任务或者工作队列调度,期待进入下次的休眠流程
  7. try_to_suspend()通过调用queue_up_suspend_work()来再次调度autosleep的工作队列suspend_work
    image
posted @ 2024-06-10 14:35  Jayfan_Ma  阅读(38)  评论(0编辑  收藏  举报