当前位置:首页>Linux>Linux 内核功耗子系统(十六):wakeup source唤醒源框架梳理

Linux 内核功耗子系统(十六):wakeup source唤醒源框架梳理

  • 2026-10-11 05:51:09
Linux 内核功耗子系统(十六):wakeup source唤醒源框架梳理

全景介绍

前面十五篇写完了功耗子系统进入低功耗这一侧的完整链路:CCF 关时钟、regulator 掉电源、genpd 收电源域、Runtime PM 收单设备、DPM 收整机设备、CPUFreq/CPUIdle 收 CPU、system sleep 收整机、CPU Hotplug 收 CPU 视野、PM QoS 收约束、thermal 收温度。

这些机制回答的都是同一个方向的问题,怎么把系统推到低功耗。

反过来的问题也需要一套机制回答:低功耗状态下,谁被允许把系统抬回来?抬起来之后,用户态怎么知道是谁抬的?如果多个唤醒源同时活跃,autosleep 什么时候允许再次推进 suspend?

Linux 里承担这个职责的框架叫 wakeup source,源码集中在 drivers/base/power/wakeup.c。它是 PM Core 里一个专门的登记与计数系统:

  • • 每个可能产生唤醒事件的设备,都注册一个 struct wakeup_source
  • • 每次事件到来,驱动通过 pm_wakeup_event() / __pm_stay_awake() 更新这个 ws
  • • PM Core 维护全局 wakeup_sources 链表和 combined_event_count,一旦发现有活跃 ws 或事件计数增长,pm_wakeup_pending()就返回 true,正在进行的 suspend 会被中止
  • • 用户态从 /sys/kernel/debug/wakeup_sources 读到每个源的 active_count / event_count / expire_count / max_time / total_time,据此定位是哪个源把系统抬起来的

wakeup source 和 IRQ wakeup 不是同一件事

一个常被混淆的边界:enable_irq_wake只是允许这条IRQ 在 suspend 后仍然能被 GIC/PMIC 唤醒 CPU,它作用在irq_desc.wake_depth和底层 irqchip->irq_set_wake上,与 wakeup source 无关。

wakeup source 关心的是更高一层:这条IRQ 唤醒 CPU 后,PM Core是否知道发生了一次唤醒事件。如果 ISR 里没有调用pm_wakeup_event(),系统被 CPU 抢占中断唤醒了,但wakeup_count没变,autosleep 会立刻把它推回suspend。

所以完整的唤醒路径是两层协作:enable_irq_wake决定能不能被抬起,pm_wakeup_event决定抬起后PM Core 是否记账。缺一层,autosleep 平台就会陷入 suspend → 被中断打断 → 立刻再 suspend 的抖动。

与 system sleep 的对接点

在 system sleep(第十四篇)里,我们看到了这两个关键节点:

  • • suspend_devices_and_enter()在dpm_suspend_noirq()后、suspend_ops->enter()前会调pm_wakeup_pending(),检查有没有最后一刻发生的唤醒事件;有就直接终止此次 suspend
  • • suspend_ops->enter() 返回后,resume 路径的第一个动作也不是 dpm_resume,而是让 PM Core 记录本次唤醒事件

这两个点看似独立,实际上都建立在wakeup source 的记账机制之上。理解了 wakeup source,才能把 system sleep 那条进入平台低功耗态的链路闭合。

实际情况

从源码角度看,wakeup source 子系统里有三个设计决策决定了驱动怎么用它。

第一,wakeup source 是事件级别的抽象,不是中断级别。

一个物理按键在按下、抬起时可能各触发一次 GPIO 中断,但从用户角度只发生了一次按键事件。wakeup source 记录的是后者。所以pm_wakeup_event() 的语义是通知 PM Core:设备产生了一个可能持续一段时间的唤醒事件,请在这个时间段内不要 suspend。

框架为此提供了两组 API,语义完全不同:

  • • __pm_stay_awake(ws) / __pm_relax(ws):显式 stay/relax,active_count从 0 变 1 表示进入活跃期,从 1 变 0 表示离开。中间可以嵌套多次,ws->active_count记录进入次数。适合事件持续时间不确定的场景(如网络包接收)。
  • • pm_wakeup_event(dev, msec) / __pm_wakeup_event(ws, msec):一次性事件带过期时间。内部先__pm_stay_awake(),然后启动一个expire_timer,超时自动__pm_relax()。适合事件很短但需要用户态有时间处理的场景(如按键、RTC alarm)。

误用最常见的问题:本来该用pm_wakeup_event(dev, 500)的一次性事件,写成了__pm_stay_awake()但忘了配对的__pm_relax(),结果 ws 永远 active,系统再也进不去 suspend。

第二,wakeup 能力是设备属性,不是驱动能力。

设备是否能唤醒系统,最终由device->power.can_wakeup 和 device->power.should_wakeup 两个 bit 决定:

  • • can_wakeup:硬件能力,通常在 device_init_wakeup(dev, true) 里由驱动置位
  • • should_wakeup:用户策略,通过 /sys/devices/.../power/wakeup 的 enabled/disabled 切换

device_may_wakeup(dev) 返回can_wakeup && should_wakeup。驱动在 suspend 回调里必须查一次这个函数,才决定是否 enable_irq_wake()、是否给硬件写 wakeup 寄存器,用户 disable 掉 wakeup 后,就算硬件能唤,也应该按不能唤处理。

第三,wakeup 事件计数是全局同步的,不是每源独立。

drivers/base/power/wakeup.c里有两个全局计数器:

1
2
3
4
5
6
7
8
9
10
11

static atomic_t combined_event_count = ATOMIC_INIT(0);#define IN_PROGRESS_BITS  (sizeof(int) * 4)#define MAX_IN_PROGRESS   ((1U << IN_PROGRESS_BITS) - 1)static void split_counters(unsigned int *cnt, unsigned int *inpr){    unsigned int comb = atomic_read(&combined_event_count);    *cnt = comb >> IN_PROGRESS_BITS;   // 累计事件数    *inpr = comb & MAX_IN_PROGRESS;    // 当前 in-progress 数}

combined_event_count用一个atomic_t编码了两个值:高 16 位是累计事件数、低 16 位是当前 in-progress 的事件数。任何 ws 上的__pm_stay_awake 都会让低 16 位+1,__pm_relax让低 16 位 -1 同时高 16 位 +1。

这个设计的关键是:一次原子操作既反映了有没有正在处理的事件,又反映了从上次抢占检查到现在新产生了多少事件。suspend 路径上的pm_wakeup_pending() 用 split_counters()一次读两个值,对比就知道要不要中止。

抽象对象

理解 wakeup source 前,先看内核里的核心对象。

struct wakeup_source

每个 wakeup 源的登记单元,源码里的核心结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// include/linux/pm_wakeup.hstruct wakeup_source {    const char          *name;    int                 id;struct list_head    entry;               // 挂到全局 wakeup_sources 链表    spinlock_t          lock;struct wake_irq     *wakeirq;            // 关联的 wakeirq(可选)struct timer_list   timer;               // expire timer    unsigned long       timer_expires;    ktime_t             total_time;          // 累计活跃时长    ktime_t             max_time;            // 单次最长活跃时长    ktime_t             last_time;           // 上次活跃开始时间    ktime_t             start_prevent_time;    ktime_t             prevent_sleep_time;    unsigned long       event_count;         // 累计触发次数    unsigned long       active_count;        // 累计进入 active 次数    unsigned long       relax_count;         // 累计离开 active 次数    unsigned long       expire_count;        // expire timer 触发次数    unsigned long       wakeup_count;        // 抢占 suspend 的次数    bool                active:1;            // 当前是否 active    bool                autosleep_enabled:1;};

字段作用很直白,一句话:一个 ws 就是一台事件计数器 + 活跃期计时器 + 一根挂链。所有对外可见的诊断字段(event_count / active_count / expire_count / max_time / total_time)都是 sysfs / debugfs 里的一列。

struct dev_pm_info 的 wakeup 部分

每个 struct device 都嵌了一个 dev_pm_info,其中和 wakeup 相关的字段是:

1
2
3
4
5
6
7
8
9
10
11

// include/linux/pm.hstruct dev_pm_info {    ...    unsigned int        can_wakeup:1;    unsigned int        wakeup_path:1;    unsigned int        syscore:1;    unsigned int        no_pm_callbacks:1;struct wakeup_source *wakeup;   // 该设备关联的 ws(可以为 NULL)    ...};

can_wakeup 由 device_set_wakeup_capable()设置;wakeup指针由device_wakeup_enable() 分配 ws 并挂上,device_wakeup_disable() 拆下。wakeup_path是 suspend 遍历时 PM Core 打的标记:如果一个设备本身 can_wakeup 或者它的子设备 can_wakeup,那么它到 root 的整条路径都要标 wakeup_path,这些设备在 suspend 时要保留唤醒通路(比如上级 I2C 控制器不能掉电)。

wakeup_sources 全局链表

1
2
3
4

// drivers/base/power/wakeup.cLIST_HEAD(wakeup_sources);static DECLARE_WAIT_QUEUE_HEAD(wakeup_count_wait_queue);

所有通过 wakeup_source_register()注册的 ws 都挂到 wakeup_sources 链表上。这条链表配合wakeup_srcu(sleepable RCU)实现无锁遍历,pm_get_active_wakeup_sources()遍历它拿到当前所有 active ws 的名字,供 sysfs 输出。

combined_event_count 与 saved_count

combined_event_count 用一个 atomic_t 编码了两个值:高 16 位是累计事件数、低 16 位是当前 in-progress 的事件数。任何 ws 上的 __pm_stay_awake 都会让低 16 位 +1,__pm_relax 让低 16 位 -1 同时高 16 位 +1。

1
2
3

static unsigned int saved_count;static DEFINE_RAW_SPINLOCK(events_lock);

pm_wakeup_count()(对应 sysfs /sys/power/wakeup_count)读到当前 combined_event_count 后,把高 16 位存到 saved_count。用户态先读 wakeup_count 拿到快照 X,然后把 X 写回 wakeup_count,如果期间没有新事件,写回成功;否则内核返回 EBUSY,用户态知道从我读到写之间又发生了新事件,不能进 suspend。这就是用户态 autosleep 的准入协议。

autosleep_ws

kernel/power/autosleep.c 里有一个特殊 ws:

1
2

staticstruct wakeup_source *autosleep_ws;

它不是某个具体设备的 ws,而是 autosleep 机制自己的仲裁 ws:当用户态显式请求我在处理事件,先别 suspend(通过 /sys/power/wake_lock),autosleep 就__pm_stay_awake(autosleep_ws);处理完 /sys/power/wake_unlock 写回时 __pm_relax(autosleep_ws)。这让内核的 wakeup source 框架能被用户态显式驱动。

wakeup_stats sysfs 组

drivers/base/power/wakeup_stats.c为每个 ws 在/sys/class/wakeup/wakeupN/下暴露一组属性:

1
2
3
4
5
6
7
8
9
10

active_countevent_countexpire_countwakeup_countactive_time_mstotal_time_msmax_time_mslast_change_msprevent_suspend_time_ms

/sys/kernel/debug/wakeup_sources 是同一批数据的表格化 dump,格式对齐、包含 name,是 shell 分析首选。

模型

把上面这些对象串起来,wakeup source 子系统的模型可以拆成三层。

三层唤醒模型

从物理事件抵达 SoC 到用户态得知系统被唤醒,一共有三层机制在协作。任何一层缺失,唤醒都不完整:

层次
关键机制
关键源码
缺失后果
L1 中断层
enable_irq_wake
 → irqchip->irq_set_wake
kernel/irq/pm.c
、irq_set_irq_wake()
suspend 后 CPU 不会被唤醒,系统睡死
L2 设备层
device_may_wakeup
 → dev_pm_ops 保留唤醒通路
drivers/base/power/wakeup.c
、驱动 suspend 回调
设备被 suspend 掉,中断线没被激活
L3 事件层
pm_wakeup_event
 → combined_event_count
drivers/base/power/wakeup.c
CPU 被抢起来了但 PM Core 不记账,autosleep 立刻推回 suspend

三层的关系可以这样理解:L1 决定能不能被抬起,L2 决定抬起时硬件通路是否留着,L3 决定抬起后 PM Core 是否知道该记账给谁。

事件计数模型

单个 ws 内部的状态迁移由三个计数器和一个 active bit 描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

初始:active=false, active_count=0, event_count=0, relax_count=0, expire_count=0__pm_stay_awake(ws):  ┌────────────────────────────────────┐  │ active==false?                     │  │   active = true                    │  │   active_count++                   │  │   event_count++                    │  │   last_time = now                  │  │   combined_event_count: inpr += 1  │  └────────────────────────────────────┘pm_wakeup_ws_event(ws, msec):  __pm_stay_awake(ws)  mod_timer(&ws->timer, jiffies + msecs_to_jiffies(msec))Timer 到期:  wakeup_source_timer_fn()  __pm_relax(ws)  expire_count++__pm_relax(ws):  ┌────────────────────────────────────┐  │ active==true?                      │  │   active = false                   │  │   relax_count++                    │  │   total_time += now - last_time    │  │   max_time = max(max_time, delta)  │  │   combined_event_count:            │  │     inpr -= 1                      │  │     event_count(high 16b) += 1     │  └────────────────────────────────────┘

三个计数器 active_count / relax_count / event_count 在稳态下应该是接近的,expire_count 反映了有多少事件是被 timer 强行 relax 的。ws->wakeup_count 是另一个维度:这个 ws 造成了多少次 suspend 抢占(在 pm_wakeup_pending() 检测到 active ws 时递增)。

阻塞窗口模型

suspend 路径上,pm_wakeup_pending() 在两个关键点被调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// drivers/base/power/wakeup.cbool pm_wakeup_pending(void){    unsigned long flags;    bool ret = false;    raw_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;    }    raw_spin_unlock_irqrestore(&events_lock, flags);    if (ret) {        pm_pr_dbg("Wakeup pending, aborting suspend\n");        pm_print_active_wakeup_sources();    }    return ret || atomic_read(&pm_abort_suspend) > 0;}

这个函数在 suspend 路径上有两个调用点:suspend_devices_and_enter() 的 dpm_suspend_noirq() 之后,以及 s2idle_loop() 的每次循环里。它构成了一个阻塞窗口:从用户态写 wakeup_count(保存 saved_count)到 suspend 实际进入 suspend_ops->enter(),任何 ws 上的 event 都会让这次 suspend 抢占失败。

窗口关闭点是 syscore_suspend(),此时 IRQ 已经全屏蔽,只有 wakeup IRQ 能进来,pm_wakeup_event 也不再被检查(因为再也没机会检查了)。窗口打开点是用户态的 wakeup_count 读回操作。

数据流

从驱动注册到事件传递到用户态感知,wakeup source 的完整数据流分成四段。

阶段一:注册

驱动初始化时调用:

1
2

device_init_wakeup(&pdev->dev, true);

这个宏展开后做了三件事:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

// drivers/base/power/wakeup.cint device_init_wakeup(struct device *dev, bool enable){    int ret = 0;    if (enable) {        device_set_wakeup_capable(dev, true);        ret = device_wakeup_enable(dev);        // 分配 ws    } else {        device_wakeup_disable(dev);        device_set_wakeup_capable(dev, false);    }    return ret;}

device_wakeup_enable() 会:

  1. 1. 调 wakeup_source_register(dev, dev_name(dev)) 分配 ws,name 用设备名
  2. 2. 把 ws 挂到 wakeup_sources 全局链表
  3. 3. 把 ws 指针写入 dev->power.wakeup
  4. 4. 在 /sys/devices/.../power/wakeup 下暴露 enabled/disabled 属性除了 device_init_wakeup() 这条路径,PM Core 还提供两种更细的注册方式:
  • • wakeup_source_register:不绑定 device,只登记一个 ws。适合子系统级的 wakeup source(如 autosleep_ws)
  • • dev_pm_set_wake_irq/ dev_pm_set_dedicated_wake_irq 绑定一根专用 wakeup IRQ。PM Core 会在 suspend 时自动 enable_irq_wake(),resume 时 disable_irq_wake(),驱动不再需要手写这套代码

阶段二:事件触发

设备驱动在 ISR / workqueue / 网络接收路径里,产生这个事件需要唤醒或保持系统的信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// 一次性事件,允许用户态 500ms 时间处理static irqreturn_t mydev_wakeup_isr(int irq, void *data){struct mydev *d = data;    pm_wakeup_event(&d->pdev->dev, 500);    schedule_work(&d->event_work);    return IRQ_HANDLED;}// 持续性事件,显式 stay/relaxstatic void mydev_rx_start(struct mydev *d){    __pm_stay_awake(d->rx_ws);}static void mydev_rx_done(struct mydev *d){    __pm_relax(d->rx_ws);}

pm_wakeup_event() 内部的原子操作序列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

// drivers/base/power/wakeup.cvoid __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec){    unsigned long flags;    unsigned long expires;    if (!ws)        return;    spin_lock_irqsave(&ws->lock, flags);    wakeup_source_report_event(ws, false);   // active_count++, event_count++, combined_event_count.inpr++    if (!msec) {        wakeup_source_deactivate(ws);        // 立刻 relax        goto unlock;    }    expires = jiffies + msecs_to_jiffies(msec);    if (!expires)        expires = 1;    if (!ws->timer_expires || time_after(expires, ws->timer_expires)) {        mod_timer(&ws->timer, expires);        ws->timer_expires = expires;    }unlock:    spin_unlock_irqrestore(&ws->lock, flags);}

关键点是 mod_timer 只在过期时间需要延长时才改,如果一个 ws 短时间内被多次 pm_wakeup_event(dev, 500),timer 只会指向最晚那次的过期时刻,避免了 timer 抖动。

阶段三:suspend 路径的记账检查

系统进入 suspend 前,用户态 autosleep daemon 会:

1
2
3
4
5

// 用户态 pseudo coderead("/sys/power/wakeup_count", &count);   // 内核 saved_count = combined_event_count.event_countwrite("/sys/power/wakeup_count", count);   // 若 saved_count 仍等于当前 event_count 且 inpr==0,通过write("/sys/power/state", "mem");           // 触发 pm_suspend()

内核在 pm_suspend() 走到 suspend_devices_and_enter(),最后要调 pm_wakeup_pending():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// drivers/base/power/wakeup.cbool pm_save_wakeup_count(unsigned int count){    unsigned int cnt, inpr;    unsigned long flags;    events_check_enabled = false;    raw_spin_lock_irqsave(&events_lock, flags);    split_counters(&cnt, &inpr);    if (cnt == count && inpr == 0) {        saved_count = count;        events_check_enabled = true;    }    raw_spin_unlock_irqrestore(&events_lock, flags);    return events_check_enabled;}

从用户态写回 wakeup_count 成功到 suspend 进入 suspend_ops->enter(),中间任何 ws 上的 __pm_stay_awake 都会让 pm_wakeup_pending() 返回 true,suspend 被中止,控制权回到 suspend_devices_and_enter() 的错误路径,反向 resume 所有已经 suspend 的设备。

阶段四:用户态感知

用户态感知系统被唤醒有三条路:

  1. 1. /sys/power/wakeup_count 变化:autosleep daemon 下一次读会拿到新的 count
  2. 2. /sys/kernel/debug/wakeup_sources 表格:active_count / event_count / wakeup_count / max_time_ms 每一列都是诊断输入
  3. 3. /sys/class/wakeup/wakeupN/*:每个 ws 一个 kobject,方便按名字定点查

第三条在 Android 上被 SystemServer 直接消费,它会周期性读 wakeup source stats,把哪个 ws 阻止了 doze 记录到 battery stats 里。

运行

驱动实现三条正确姿势

姿势一:一次性事件(最常见)

按键、RTC alarm、传感器阈值等短事件,用 pm_wakeup_event(dev, msec):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

static int mykey_probe(struct platform_device *pdev){    device_init_wakeup(&pdev->dev, true);    request_threaded_irq(irq, NULL, mykey_isr, IRQF_ONESHOT, "mykey", pdev);    ...}static irqreturn_t mykey_isr(int irq, void *data){struct platform_device *pdev = data;    /* 通知 PM Core:产生了一个事件,请给用户态 500ms 处理 */    pm_wakeup_event(&pdev->dev, 500);    input_report_key(...);    input_sync(...);    return IRQ_HANDLED;}static int mykey_suspend(struct device *dev){    if (device_may_wakeup(dev))        enable_irq_wake(to_platform_device(dev)->irq);    return 0;}static int mykey_resume(struct device *dev){    if (device_may_wakeup(dev))        disable_irq_wake(to_platform_device(dev)->irq);    return 0;}

姿势二:持续性事件

网络接收、大文件写入这类事件时长不确定的场景,显式配对 stay/relax:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

static void net_rx_pending(struct mydev *d){    __pm_stay_awake(d->rx_ws);        /* 进入活跃期 */    napi_schedule(&d->napi);}static int mydev_poll(struct napi_struct *napi, int budget){    ...    if (rx_done_and_queue_empty) {        napi_complete(napi);        __pm_relax(d->rx_ws);         /* 离开活跃期 */    }    return work_done;}

姿势三:dedicated wake irq

有些 SoC 提供一根专用的 wakeup 中断线(比如 PMIC 的 mux 输出),suspend 时主 IRQ 被关掉,wakeup IRQ 保留。这类场景用 dev_pm_set_dedicated_wake_irq():

1
2
3
4
5
6
7
8
9

static int mydev_probe(struct platform_device *pdev){    int wake_irq = platform_get_irq_byname(pdev, "wakeup");    device_init_wakeup(&pdev->dev, true);    dev_pm_set_dedicated_wake_irq(&pdev->dev, wake_irq);    ...}

PM Core 会在 dpm_suspend_noirq 阶段自动 enable_irq_wake(),dpm_resume_noirq 阶段自动 disable_irq_wake(),驱动完全不用管。

与 Runtime PM 的交互

Runtime PM 和 wakeup source 是正交的:Runtime PM 关心设备 idle 时能不能 suspend,wakeup source 关心设备事件能不能抢占 system sleep。但驱动实现时经常要同时考虑:

  • • Runtime PM suspend 时,如果 device_may_wakeup(dev),要保留唤醒通路(不能关那根 IRQ)
  • • System suspend 回调里通常先 pm_runtime_force_suspend(dev) 把 Runtime PM 状态推到 suspended,再走 system sleep 特有的操作

Runtime PM 自己也会用到 wakeup source:pm_runtime_get_sync() 触发 resume 时,如果发现设备是被 wakeup 事件叫醒的(比如 USB 上游端口检测到设备插入),会通过 pm_wakeup_event(dev, 100) 抢占正在进行的 system sleep。

autosleep 的完整循环

Android 和部分 embedded Linux 用 autosleep 让系统在空闲时自动进 suspend:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

// kernel/power/autosleep.cstatic void try_to_suspend(struct work_struct *work){    unsigned int initial_count, final_count;    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;    /*     * 如果 suspend 期间产生的 wakeup events 数超过 saved_count,     * 说明有活跃事件在处理,暂缓下一次尝试     */    if (final_count == initial_count)        schedule_timeout_uninterruptible(HZ / 2);out:    queue_up_suspend_work();}

这个循环把 wakeup source 的所有机制都用上了:

  • • pm_get_wakeup_count() 拿快照
  • • pm_save_wakeup_count() 提交快照,失败说明有事件正在处理
  • • pm_suspend() 进入 suspend,路径上再次 pm_wakeup_pending()
  • • suspend 返回后再拿一次 count,如果没变说明这次唤醒是被中断本身触发的,不是被 event 触发的,退避半秒再试

总结

wakeup source 子系统做的三件事:登记谁可能唤醒系统、记账事件发生了多少次、抢占正在进行的 suspend。这三件事分别对应三层模型:

  • • 登记 = wakeup_source_register / device_init_wakeup 建立 ws → 挂 wakeup_sources 链表 → sysfs 暴露 kobject
  • • 记账 = pm_wakeup_event / __pm_stay_awake 更新 combined_event_count → sysfs/debugfs 表格化输出
  • • 抢占 = pm_wakeup_pending 对比 saved_count、in_progress → 让 suspend_devices_and_enter 走错误路径

按调试目标划分,每一层都有对应的排查手段:

症状
关注点
关键源码
suspend 后叫不醒
L1/L2:IRQ wake、dedicated wake irq
irq_set_wake
 / dev_pm_set_dedicated_wake_irq
suspend 进不去
L3:谁在 stay awake
/sys/kernel/debug/wakeup_sources
 的 active_since
autosleep 反复失败
L3:谁在抢占
wakeup_count
 列,pm_save_wakeup_count 失败频次
抖动、频繁 wake
事件粒度过粗
是否给非交互事件也调了 pm_wakeup_event
wakelock 泄漏
stay/relax 不配对
__pm_stay_awake
 之后是否补 __pm_relax 或用 timer 版

wakeup source 的复杂度不在于任何一个函数,而在于它是内核里唯一同时被中断、驱动、PM Core 遍历、用户态 daemon 四方消费的对象。任何一方对语义理解偏差,都会体现为 suspend 进不去或进去了叫不醒,两个方向都可能,而且现象容易互相掩盖。

到这一篇为止,功耗子系统从进入低功耗到低功耗态里被唤醒回来这一条闭环终于合上了:DPM + system sleep 讲了怎么睡下去、wakeup source 讲了怎么醒来。

最新文章

随机文章