Linux休眠唤醒之wakeup count功能

wakeupcount是Linux内核中用于电源管理的一个机制,它确保系统在挂起(suspend)过程中的同步。当有唤醒事件发生时,wakeupcount会阻止系统进入休眠状态。这个功能通过读写/sys/power/wakeup_count文件来实现,用户空间在尝试挂起系统前先读取并回写该值。如果在回写过程中有新的唤醒事件,挂起操作会被取消。这一机制保证了在处理唤醒事件时的非竞争性行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

wakeup count在电源管理中得位置

wakeup count得实现位于wakeup events framework中(drivers/base/power/wakeup.c),主要为两个模块提供接口: 通过PM core向用户空间提供sysfs接口,直接向autosleep提供接口。

在这里插入图片描述

wakeup count得功能

wakeup count得功能是suspend同步,实现思路如下:

1) 任何想发起电源状态切换得实体(可以是用户空间电源管理进程,也可以是内核线程,简称C),在发起状态切换前,读取系统的wakeup counts(该值记录了当前的wakeup event总数), 并将读取的counts告知wakeup events framework。

2) wakeup events framework记录该counts到一个全局变量中(saved_count)l

3) 随后C发起电源状态切换(如STR), 执行suspend过程

4) 在suspend过程中,wakeup events framework照旧工作(直到系统关闭), 上报wakeup events, 增加wakeup events counts.

5) suspend执行的一些时间点, 会调用wakeup events framework提供的接口(pm_wakeup_pending), 检查是否有wakeup有没有处理

6) 检查逻辑很简单,就是比较当前的wakeup events和saved wakeup counts(C发起电源状态切换时的counts), 如果不同,就要终止suspend。

wakeup count的实现逻辑
一个例子:

在进行代码分析之前,我们先用伪代码的方式,写出一个利用wakeup count进行suspend操作的例子, 然后基于该例子,分析相关的实现。

do {
    ret = read(&cnt, "/sys/power/wakeup_count");
    if (ret) {
        ret = write(cnt, "/sys/power/wakeup_count");
    } else {
        countine;
    }
} while (!ret);
 
write("mem", "/sys/power/state");
 
/* goto here after wakeup */
该例子表示:
a) 读取wakeup count的值,如果成功,将读取的值回写。否则说明有正在处理的wakeup event coutinue
b) 回写后, 判断返回值是否成功,如果不成功(说明读写过程中发生了wakeup events), 继续读写, 直到成功。成功后,可以触发电源状态切换。
/sys/power/wakeup_count

wakeup_count文件实在kernel/power/main.c利用power_attr注册的,如下(大家可以仔细阅读一下那一大段注释,内核很多注释写的非常好, 而好的注释就是软件功力的体现):

The 'wakeup_count' attribute, along with the functions defined in drivers/base/power/wakeup.c, provides a means by which wakeup events can be handled in a non-racy way.
“wakeup_count”属性以及 drivers/base/power/wakeup.c 中定义的函数提供了一种以非 racy 方式处理唤醒事件的方法。

If a wakeup event occurs when the system is in a sleep state, it simply is woken up.  
In turn, if an event that would wake the system up from a sleep state occurs when it is undergoing a transition to that sleep state, the transition should be aborted.  
Moreover, if such an event occurs when the system is in the working state, an attempt to start a transition to the given sleep state should fail during certain period after the detection of the event. 
Using the 'state' attribute alone is not sufficient to satisfy these requirements, because a wakeup event may occur exactly when 'state'is being written to and may be delivered to user space right before it is frozen, so the event will remain only partially processed until the system is woken up by another event.
In particular, it won't cause the transition to a sleep state to be aborted.
如果在系统处于睡眠状态时发生唤醒事件,则只是将其唤醒。 
反过来,如果在系统正在过渡到该睡眠状态时发生将系统从睡眠状态唤醒的事件,则应中止该转换。 
此外,如果在系统处于工作状态时发生此类事件,则在检测到事件后的某些时间段内,尝试开始转换到给定睡眠状态应该失败。
仅使用 'state' 属性不足以满足这些要求,因为唤醒事件可能恰好在 'state' 被写入时发生,并且可能会在冻结之前传递到用户空间,因此该事件将仅保持部分处理状态,直到系统被另一个事件唤醒。
特别是,它不会导致过渡到睡眠状态中止。


This difficulty may be overcome if user space uses 'wakeup_count' before writing to 'state'.  It first should read from 'wakeup_count' and store the read value.
Then, after carrying out its own preparations for the system transition to a sleep state, it should write the stored value to 'wakeup_count'.  If that fails, at least one wakeup event has occurred since 'wakeup_count' was read and 'state' should not be written to.
Otherwise, it is allowed to write to 'state', but the transition will be aborted if there are any wakeup events detected after 'wakeup_count' was written to.如果用户空间在写入“state”之前使用“wakeup_count”,则可以克服此困难。 它首先应该从“wakeup_count”读取并存储读取值。
然后,在为系统过渡到睡眠状态进行自己的准备工作后,它应该将存储的值写入“wakeup_count”。 如果失败,则自读取“wakeup_count”以来至少发生了一个唤醒事件,并且不应写入“state”。
否则,允许写入“state”,但如果在写入“wakeup_count”后检测到任何唤醒事件,则转换将中止。
static ssize_t wakeup_count_show(struct kobject *kobj,
				struct kobj_attribute *attr,
				char *buf)
{
	unsigned int val;

	return pm_get_wakeup_count(&val, true) ?
		sprintf(buf, "%u\n", val) : -EINTR;
}

static ssize_t wakeup_count_store(struct kobject *kobj,
				struct kobj_attribute *attr,
				const char *buf, size_t n)
{
	unsigned int val;
	int error;

	error = pm_autosleep_lock();
	if (error)
		return error;

	if (pm_autosleep_state() > PM_SUSPEND_ON) {
		error = -EBUSY;
		goto out;
	}

	error = -EINVAL;
	if (sscanf(buf, "%u", &val) == 1) {
		if (pm_save_wakeup_count(val))
			error = n;
		else
			pm_print_active_wakeup_sources();
	}

 out:
	pm_autosleep_unlock();
	return error;
}

实现很简单:read时,直接调用pm_get_wakeup_count(注意第2个参数); write直接调用pm_save_wakeup_count(注意用户空间的wakeup count功能和auto sleep互斥)。 这两个接口均是wakeup events framework提供的接口。

pm_get_wakeup_count
bool pm_get_wakeup_count(unsigned int *count, bool block)
{
	unsigned int cnt, inpr;

	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;
}
该接口有两个参数,一个是保存返回的count的指针,另一个指示是否block, 具体请参考代码逻辑:
a) 如果block为false, 直接读取registered wakeup events和wakeup events in progress两个counter值, 将register wakeup events交给第一个参数, 并返回wakeup events in progress的状态(若返回false, 说明当前有wakeup events正在处理, 不适合suspend)。
b) 如果block为true, 定义一个等待队列, 等待wakeup in progress为0, 再返回counter.

注: 由伪代码可知,sysfs发起的read动作,block为true, 所以如果有正在处理的wakeup events, read进程会阻塞。其他模块(如auto sleep)发起的read,则可能不需要阻塞。
pm_save_wakeup_count
bool pm_save_wakeup_count(unsigned int count)
{
	unsigned int cnt, inpr;
	unsigned long flags;

	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;
}
1) 注意这个变量, events_check_enabled, 如果它不为真,pm_wakeup_pending接口直接返回false, 意味着如果不利用wakeup count功能, suspend过程中不会做任何wakeup events检查,也就不会进行任何的同步。
2)取出当前的registered wakeup events, waakeup events in progress, 保存在变量cnt和inpr中
3) 如果写入的值cnt不同(说明读、写过程中产生events), 或者inpr不为零(说明events正在被处理), 返回false(说明此时不宜suspend)
4) 否则,events_check_enabled置位(后续的pm_wakeup_pending才会干活), 然会true(可以suspend), 并将当前的wakeup count保存在saved count变量中。
/sys/power/state

再回忆一下suspend的流程,再suspend接口中,suspend前的最后一刻,会调用pm_wakeup_pending接口,代码如下:

static int suspend_enter(suspend_state_t state, bool *wakeup)
{
    ...
    error = syscore_suspend();
    if (!error) {
        *wakeup = pm_wakeup_pending();
        if (!(suspend_test(TEST_CORE) || *wakeup)) {
                error = suspend_ops->enter(state);
                events_check_enabled = false;
        }
        syscore_resume();
    }
    ...
}

在write wakeup_count到调用pm_wakeup_pending这一段时间内,wakeup events framework会照常产生wakeup events, 因此如果pending返回true, 则不能“enter”, 就终止suspend。

注2: wakeup后,会清楚events_check_enabled标记。

之前介绍过的pm_wakeup_pending, 让我们再看一遍

bool pm_wakeup_pending(void)
{
	unsigned long flags;
	bool ret = false;

	spin_lock_irqsave(&events_lock, flags);
	if (events_check_enabled) {
		unsigned int cnt, inpr;

		split_counters(&cnt, &inpr);
		ret = (cnt != saved_count || inpr > 0);
		events_check_enabled = !ret;
	}
	spin_unlock_irqrestore(&events_lock, flags);

	if (ret) {
		pr_info("PM: Wakeup pending, aborting suspend\n");
		pm_print_active_wakeup_sources();
	}

	return ret || pm_abort_suspend;
}
a) 首先会判断events_check_enabled是否有效, 无效直接返回false。
b) 获得cnt和inpr, 如果cnt不等于saved_count(说明这段时间有events产生), 或者inpr不为0(说明有events正在被处理), 返回true(告诉调用者,放弃吧,时机不到)。 同时清除events_check_enabled的状态。
c) 否则,返回false(放心睡吧),同时保持event_check_enabled的置位状态(以免pm_wakeup_pending再次调用)
<think>好的,用户问到了`gpio-key,wakeup`的作用,我需要详细解释这个属性在设备树中的用途。首先,我应该回忆一下Linux设备树中关于GPIO按键的配置选项。记得`gpio-key,wakeup`通常用于指定该按键是否能够唤醒系统。 接下来,我需要确定用户可能的背景。用户之前询问过GPIO按键被识别为两次按下的问题,现在进一步询问特定属性的作用,可能是想深入理解设备树配置,或者解决与唤醒相关的问题。用户可能正在调试电源管理或系统唤醒功能,或者遇到了按键唤醒不工作的情况。 用户的问题直接指向`gpio-key,wakeup`的作用,我需要明确这个属性的功能。根据Linux内核文档,这个属性用于标记该GPIO按键是否可以作为唤醒源,当系统处于睡眠状态时,按下该按键可以唤醒系统。需要确认这个属性如何在内核中处理,以及相关的配置要求。 同时,我需要考虑用户可能的误解,比如是否所有GPIO都支持唤醒功能,或者是否需要额外的硬件或软件配置。例如,某些SoC可能要求特定的GPIO引脚才能作为唤醒源,或者需要配置中断类型为边沿触发。 还要提醒用户在使用这个属性时需要注意的事项,比如确保GPIO控制器支持唤醒功能,正确配置中断触发方式,以及内核配置中是否启用了唤醒源支持。此外,可能还需要检查电源管理框架的配置,比如是否启用了suspend-to-RAM等功能。 最后,应该给出一个实际的设备树配置示例,说明如何正确使用`gpio-key,wakeup`属性,并解释每个相关部分的作用。这样用户可以参考示例进行配置,避免常见的错误。</think>在Linux设备树配置中,`gpio-key,wakeup` 是一个特殊属性,用于**控制按键的唤醒功能**。以下是深度解析: --- ### 一、核心作用 `gpio-key,wakeup` 属性标记该GPIO按键是否具备**唤醒系统**的能力。当系统处于以下低功耗状态时: - **挂起到内存**(Suspend-to-RAM) - **挂起到空闲**(Suspend-to-Idle) - **深度休眠状态** 配置了该属性的按键被按下后,会触发**唤醒中断**,将系统恢复到正常工作状态。 --- ### 二、技术实现原理 1. **中断配置** GPIO按键驱动会将该GPIO配置为**边沿触发中断**(上升沿/下降沿),具体由 `GPIO_ACTIVE_LOW/HIGH` 决定。 2. **电源管理集成** 内核电源管理子系统(PM)会为带有 `wakeup` 标志的GPIO注册**唤醒源**(Wakeup Source),例如: ```c device_init_wakeup(dev, true); // 驱动中实际调用的API ``` 3. **硬件要求** - GPIO控制器必须支持**保持唤醒能力**(部分SoC只有特定GPIO引脚支持) - 需要正确配置GPIO复用器(Pinmux)的**低功耗状态保持** --- ### 三、典型应用场景 1. **手机/平板电源键** 通过`gpio-key,wakeup`实现按电源键唤醒屏幕 2. **嵌入式设备遥控唤醒** 红外遥控器通过GPIO按键唤醒休眠设备 3. **工业设备快速恢复** 在低功耗模式下通过物理按键立即唤醒系统 --- ### 四、配置示例与注意事项 #### 设备树片段 ```dts vol_up { ... gpios = <&portb 16 GPIO_ACTIVE_LOW>; gpio-key,wakeup; // 关键配置 ... }; ``` #### 必须配合的配置 1. **内核配置** 确保启用以下选项: ```kconfig CONFIG_PM=y CONFIG_PM_SLEEP=y CONFIG_INPUT_KEYBOARD=y CONFIG_KEYBOARD_GPIO=y ``` 2. **硬件验证** 使用 `cat /sys/kernel/debug/wakeup_sources` 查看已注册唤醒源: ```bash # 示例输出 name active_count event_count wakeup_count active_since gpio-keys 3 5 2 0 ``` 3. **调试命令** 查看唤醒事件统计: ```bash dmesg | grep "wakeup by IRQ" ``` --- ### 五、常见问题排查 1. **无法唤醒系统** - 检查GPIO是否支持唤醒功能(查阅SoC手册) - 确认系统实际进入了支持唤醒休眠状态 - 使用示波器测量GPIO电平是否达到触发条件 2. **意外唤醒** 添加 `debounce-interval` 去抖配置(推荐30-50ms): ```dts debounce-interval = <50>; ``` 3. **唤醒后系统不稳定** 检查GPIO中断处理函数是否包含`IRQF_NO_SUSPEND`标志: ```c // 正确的中断注册应包含此标志 request_irq(irq, handler, IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND, ...); ``` --- ### 六、进阶开发提示 1. **多级唤醒控制** 通过 `wakeup-source` 属性与 `/sys/power/wake_lock` 配合,实现分级唤醒策略 2. **功耗优化** 唤醒后及时调用 `pm_relax()` 释放唤醒锁,避免阻止系统再次休眠 3. **安全机制** 对关键唤醒按键实施**防误触算法**(如长按1秒才触发唤醒) 通过合理使用 `gpio-key,wakeup`,可以构建高效的电源管理系统,典型场景下可将设备待机功耗降低至微安级。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值