ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux DPM框架详解:设备电源管理的suspend/resume机制与排查实践

Linux DPM框架详解:设备电源管理的suspend/resume机制与排查实践 前两篇我们聊了系统睡眠的入口、状态切换流程这一篇终于要进入整个功耗子系统里驱动开发者接触最多的一层了。DPMDevice Power Management框架说白了就是系统 suspend/resume 过程中对设备进行批量电源管理的一套机制。每次你在终端里敲下echo mem /sys/power/state内核就会靠这套框架把成百上千个设备按照严格的顺序拉进低功耗状态等唤醒时再按相反的次序拉回来。如果你曾经被设备挂起失败、唤醒后没反应、noirq 阶段莫名 crash 折磨过这篇就是冲着你写的。我会从 dpm_list 数据结构一路拆到实际驱动接入最后给出一份排查经验清单尽量让看完的人能直接上手排查自己遇到的问题。1. DPM框架到底在管什么1.1 系统睡眠不是拉闸断电很多人第一次接触系统睡眠时会有一个直觉睡眠就是把电断掉嘛设备自然就关闭了。但实际上现代 Linux 系统的睡眠远比断电复杂。以最常见的 Suspend-to-RAM 为例系统进入睡眠前要做的事情包括把 CPU 上下文保存到内存、把当前设备状态保存到寄存器或内存、停止 DMA 传输、关闭外设中断、把总线控制器切换到低功耗模式最后才通过 ACPI 或者其他平台固件指令真正进入睡眠。这个过程中最怕的就是设备之间互相依赖。举个我经常用来跟同事举例的场景一台嵌入式设备上I2C 控制器下面挂着触摸屏和几个传感器硬件上触摸屏的寄存器访问必须经过 I2C 控制器。如果系统睡眠时先把 I2C 控制器关了再去挂起触摸屏那触摸屏驱动里写入某个寄存器让设备进入睡眠模式的操作就根本没发执行因为访问总线的通路已经被切断。所以内核必须知道一个先后顺序对于树状结构的设备拓扑一定是先挂起叶子设备再挂起父设备。恢复的时候反过来父设备先恢复子设备后恢复。DPM 框架最核心的职责就是维护这个顺序并且为每个设备提供一套标准的回调接口让驱动在合适的阶段做合适的事。你可以把它理解成公司下班不是所有人同时冲出办公室就完事了得有人管服务器、有人关灯、有人锁门。每个岗位在下班前有自己负责的最后一步操作而且必须先让最里面的工位关电脑再一层层往外走最后保安切断总电源。DPM 就是那套下班预案。1.2 与 Runtime PM 的分工界限聊 DPM 之前必须把 Runtime PM 和它的关系讲清楚否则很多刚接触内核电源管理的读者会搞混。Runtime PM 解决的是设备在系统运行期间根据使用情况动态开关电源的问题。比如一块 USB 外设长时间空闲系统可以悄悄把它挂起用户感知不到。它管理的粒度是单个设备状态是 rpm_active、rpm_suspended 那一套针对的是运行时的空闲与忙。而 DPM 属于系统级睡眠的一部分解决的是一整套设备在系统休眠时的集体状态迁移。它管理的粒度是全部设备方向也是全局一致的要么全部 suspend要么全部 resume。两者的回调函数虽然都放在struct dev_pm_ops里但执行路径完全不同Runtime PM 走的是rpm_suspend()/rpm_resume()系统睡眠走的是dpm_suspend()/dpm_resume()。实际开发中有一个好消息如果驱动把 Runtime PM 的 suspend/resume 回调写得足够完善在系统睡眠时可以通过pm_runtime_force_suspend()/pm_runtime_force_resume()直接借用让系统睡眠变成一次强制性的 runtime 挂起。很多驱动就是这么省事的后面我会专门讲这个技巧。2. 一切从数据结构开始dpm_list 与 dev_pm_ops2.1 设备链表是怎么排出来的DPM 框架的一切操作都围绕一个全局链表dpm_list展开这个链表定义在drivers/base/power/main.c里。设备驱动模型在添加设备的时候会调用device_pm_add()把设备挂到链表尾部同时通过判断设备注册时的父子关系保证父设备始终排在子设备前面。那这个顺序到底怎么保证核心逻辑在设备模型注册流程里。比如一个平台设备注册时platform bus 会先处理设备与父节点的关系父设备device_add()在前子设备device_add()在后所以dpm_list里父节点天然在前面。PCI、I2C、SPI 等总线枚举流程也类似先有控制器设备再扫描挂载的子设备。当系统进入 suspend 流程时内核从dpm_list尾部向前遍历也就是说先注册的后挂起。这正是我们需要的叶子设备在链表尾部先被挂起父设备在链表头部最后被挂起。resume 时反过来从头部向后遍历父设备先恢复子设备再恢复。这套顺序就是前面说的因果依赖下游设备的操作依赖上游总线背板的可用性。这里有一个值得注意的细节不是所有dpm_list里的设备都真的需要执行挂起。很多设备可能根本没有注册任何dev_pm_ops或者被标记为no_pm_callbacksDPM 框架在遍历时会直接跳过这些设备。检查这一点的函数是device_pm_check_callbacks()它在dpm_prepare()阶段被调用会结合dev-pm_domain、驱动的dev_pm_ops是否定义了相关回调以及总线层的 PM 回调综合判断。2.2 dev_pm_ops每个设备交出的睡眠清单驱动想要让设备正确响应系统睡眠根本途径是填充struct dev_pm_ops。这个结构体定义在include/linux/pm.h里字段很多但核心就是围绕系统睡眠的几组回调。我每次跟别人讲这个结构体时都会先画一张表回调节点调用阶段允许的行为prepare挂起前最早阶段可以睡眠、可以分配内存为后续挂起做准备suspend主挂起阶段可以睡眠停止数据流、关闭设备中断、保存上下文suspend_late中断关闭前仍可睡眠但应尽量只做轻量操作suspend_noirqCPU 中断已关只能原子操作保存必要寄存器、直接操作硬件resume_noirq唤醒后最早阶段恢复必须的中断设置和寄存器禁止睡眠resume_early中断逐步开启恢复 DMA、重新初始化控制器resume主恢复阶段恢复正常运行、释放 prepare 阶段获取的资源complete恢复后收尾做最终确认、通知其他子系统你可能注意到了普通驱动开发里用到最多的就是suspend和resume这一对prepare和complete用于需要提前做资源准备或者事后做清理的场景。至于suspend_late/resume_early/suspend_noirq/resume_noirq一般是电源管理单元、总线控制器这类对时序敏感的驱动才需要碰。除了 suspend/resumedev_pm_ops里还有freeze/thaw/poweroff/restore这一组它们是给休眠Hibernate用的。内核里有一个非常常用的宏SYSTEM_SLEEP_PM_OPS(suspend_fn, resume_fn)如果驱动不关心休眠与睡眠的差异可以用它把同一份 suspend/resume 回调映射到 freeze/poweroff/thaw/restore 上。绝大多数驱动都用SIMPLE_DEV_PM_OPS定义内部就是调用的SYSTEM_SLEEP_PM_OPS。2.3 状态机与回调查找路径DPM 框架内部为每个设备维护了一个电源状态定义在struct dev_pm_info里。主要状态包括DPM_ON、DPM_PREPARING、DPM_SUSPENDING、DPM_SUSPENDED、DPM_RESUMING等。不过现在内核代码里很多状态已经逐步被布尔标志位取代比如is_prepared、is_suspended、is_late_suspended、is_noirq_suspended。这些标志位的作用是确保每个回调在正确阶段只执行一次同时帮助框架判断设备当前处于哪一步。回调的查找路径也值得了解。系统睡眠流程中框架在调用具体回调前并不是一把梭直接调dev-driver-pm-suspend而是存在一个优先级链。大致顺序是如果设备有pm_domain电源域优先用电源域的 ops否则看驱动注册的dev-pm如果驱动没有注册再看设备所属的总线、类型、类上是否有公共的pm_ops。这个设计的好处是总线驱动可以为同一类设备提供默认的电源管理操作个体驱动只需要补充自己特有的部分。比如 I2C 客户端设备的 suspend可以由 i2c-core 统一处理总线相关的操作具体设备驱动再处理自己的寄存器。3. 睡眠dpm_suspend 的五级拆解3.1 dpm_prepare 与 prepare 回调系统睡眠的设备挂起流程入口是dpm_suspend_start(state)它会依次调用dpm_prepare()和dpm_suspend()。dpm_prepare()遍历dpm_list时对每个设备尝试调用prepare回调。这一步的核心目的是让驱动有机会在挂起前做资源准备比如申请 DMA buffer、保存中断号、请求唤醒源等。prepare阶段最重要的特性是框架允许设备在这一阶段返回错误并且返回错误不会导致整个睡眠流程崩溃只会让错误设备被跳过并继续处理其他设备。最终如果有一个或多个设备 prepare 失败dpm_suspend_start()会返回错误整个系统睡眠中止之前所有已经 prepare 的设备会走dpm_complete()收尾。我在实际调试中见过一个典型的 prepare 使用场景某个触摸屏驱动在prepare里写了一个自定义 touch sleep 命令给控制器同时在这阶段把 GPIO 唤醒脚配置好。之所以放在prepare而不是suspend是因为设备此时还完全可用I2C 还没被挂起访问外设没有任何障碍。等到suspend阶段再关掉主电源。这样设备进入睡眠状态会更从容给硬件留足处理时间。3.2 主挂起阶段 dpm_suspenddpm_prepare()完成之后框架进入dpm_suspend()。这是整个 DPM 流程里最核心、驱动开发者也最熟悉的一段。框架从dpm_list尾部开始遍历对每个设备调用suspend回调。很多读者会疑惑为什么 suspend 阶段不直接禁用设备中断实际上对某一个设备来说时序是这样的驱动在suspend回调里应当主动调用devm_free_irq()或者disable_irq()把中断关掉然后停止数据通路比如触发 DMA stop、禁用 FIFO、保存寄存器上下文最后让硬件进入低功耗模式。框架本身不替驱动做这些因为只有驱动才清楚硬件细节。这里有个非常容易踩的坑有的驱动在suspend里调用msleep()或者等待互斥锁。原则上 suspend 阶段是可以睡眠的但时间不能太长尤其是很多总线在 sleep 期间会进入低功耗模式睡眠时间太长会导致整体挂起时间成倍增加。有些嵌入式设备整个 suspend 耗时几十秒排查到最后就是某个驱动在suspend里做了 10 秒的忙等。正常流程中dpm_suspend()结束后所有设备的is_suspended标志会被置位意味着主挂起阶段完成。但此时系统 CPU 的中断还没有完全关闭设备还能响应唤醒事件。3.3 late 与 noirq 阶段主挂起完成后框架继续调用dpm_suspend_late()和dpm_suspend_noirq()。这两个阶段一个比一个硬核。dpm_suspend_late()阶段系统尚未完全关闭中断但此时大多数设备已被挂起剩下的设备主要是总线控制器、时钟控制器这类基础设施。suspend_late回调可以做轻量操作比如关闭 PLL、降电压、锁存少量寄存器。需要注意suspend_late阶段仍然可以调用可睡眠的 API但最好不要依赖任何已被挂起的设备否则很可能触发死锁。dpm_suspend_noirq()是设备挂起链路的最后一步。它发生在系统禁用 CPU 中断、停止调度器之后。到了这个阶段绝对不能调用任何可能睡眠的函数不能kmalloc、不能拿普通互斥锁、不能printk某些环境下可以但也不推荐。这个阶段驱动只做最原子的操作写几个寄存器、拉一根 GPIO、关闭一个硬件块。以 DRM 显示控制器为例noirq 阶段往往只是把显示控制器的时钟 gate 掉、把 AUX 通道断电需要保留的上下文早在 suspend 阶段就保存到内存里了。很多驱动在 noirq 阶段 crash原因基本都是驱动作者把必须在 suspend 阶段做的事放到了 noirq比如访问了 I2C 总线或者等待某个线程。3.4 失败路径怎么走DPM 框架的容错设计也很有意思。任何一个阶段如果某个设备挂起失败框架不会停下来直接放弃而是会记录失败设备然后继续尝试挂起剩余的设备。挂起完成或部分完成后如果发现存在失败就会走dpm_resume()把已经挂起的设备全部恢复回来因为此时系统尚未真正进入睡眠必须恢复到可运行状态。这一过程在 dmesg 里通常体现为PM: Some devices failed to suspend, or early wake event detected后面还会跟具体是哪个设备报错。很多运维新手看到这种日志会以为机器坏了其实这只是内核的保护机制在起作用系统睡眠没有成功它把你安全地拉回了正常状态。这时候应该做的是查看失败设备的具体 dmesg 日志定位驱动报错的函数。4. 唤醒dpm_resume 的逆序恢复4.1 noirq 阶段最早的恢复动作唤醒流程和挂起流程完全镜像。系统从睡眠中醒来的最初阶段CPU 中断还是关闭的此时率先执行的是dpm_resume_noirq()。这个阶段里每个设备的resume_noirq回调负责把最小必需的硬件状态恢复出来重新使能时钟、恢复关键寄存器、重新配置中断控制器中与该设备相关的部分。resume_noirq不能睡眠但它做的事必须让设备在后续阶段可以被安全访问。比如一个 MMC 控制器resume_noirq需要把 SD 卡时钟重新使能、恢复控制器运行状态这样后续resume_early阶段才可以重新初始化 DMA 或发送命令。紧接着是dpm_resume_early()。这个阶段系统开始恢复设备中断但要到dpm_resume()阶段才全部恢复适合做 DMA 控制器重新初始化、恢复需要中断但中断还没完全开放时的硬件状态。很多平台把 USB 控制器的 PHY 重新上电放在resume_early因为 USB Host 控制器依赖 PHY 就绪才能开始工作。4.2 resume 与 complete 收尾中断恢复后框架执行dpm_resume()这是驱动最常实现的恢复入口。在resume回调里驱动重新打开设备中断、恢复完整的寄存器上下文、把数据通路重新打通。到这里设备基本可以正常工作了。最后是dpm_complete()框架调用每个设备的complete回调。complete与prepare相对应最适合做后置清理和跨设备通知比如释放prepare阶段临时申请的缓冲、通知其他子系统本设备已恢复。如果某个设备的prepare做了一些全局性的备份操作complete里可以确认恢复结果。4.3 顺序陷阱resume 时最怕什么恢复顺序是 DPM 框架设计里最容易出错也最容易写错的地方。之前说过resume 时从dpm_list头部开始遍历父设备在前子设备在后。这意味着 I2C 控制器会先于挂载在它上面的触摸屏恢复。这是唯一正确的方式触摸屏驱动在resume里访问 I2C 寄存器之前I2C 控制器必须已经可用。如果一个驱动在 resume 时违反了这套顺序会怎样典型症状是设备恢复时报超时或者 I/O 错误dmesg 里有大量i2c_transfer failed之类的输出。有些经验不足的开发者会试图通过调整dpm_list的顺序解决那是绝对错误的做法。正确做法是检查自己的驱动依赖关系并用设备模型里的device_link_add()显式声明依赖让框架感知到先后顺序。这里有一个系统层面的细节显示控制器一般希望 resume 得越早越好这样用户能尽快看到画面。DPM 框架本身不提供优先级配置设备顺序完全由拓扑和设备注册顺序决定。要调整显示控制器的恢复顺序通常通过电源域PM Domain或调整 probe 顺序间接实现而不是直接去改dpm_list。5. 异步机制与设备依赖处理5.1 async_suspend 与 dpm_waitDPM 框架一开始是全同步的一个设备挂起完才轮到下一个。这在设备数量少的嵌入式平台上问题不大但在 PC 或者服务器平台设备数量可能上百逐个同步挂起会让睡眠时间变得不可接受。为此内核引入了异步挂起/恢复机制。设备可以通过device_enable_async_suspend()标记自己支持异步挂起。框架在dpm_prepare()阶段会为这类设备生成一个异步任务通过async_schedule_domain()调度执行。有异步依赖关系的设备之间用dpm_wait()等待对方完成框架内部用struct completion管理等待状态。异步机制不是万能的。如果两个设备之间存在硬件依赖但驱动没有用device_link_add()建立明确的依赖关系异步执行的结果就是两者完全并行很有可能造成访问冲突。所以内核默认只对明确标记async_suspend的设备启用异步而且要求驱动作者对自己的设备有把握。调试时如果不确定设备是否能异步挂起可以把参数pm_async0传给内核直接关闭异步机制把问题缩小到异步导致还是驱动自身 bug。5.2 device links 是更明确的依赖表达前面提到设备模型里有一个更现代化的依赖机制device_link_add()。它允许驱动显式声明我是 consumer它是 supplier。DPM 框架在遍历设备时会考虑这些链接关系确保 supplier 设备在 consumer 之前恢复、在 consumer 之后挂起。我举一个真实场景某 ARM 平台上有一个 GPU 和它对应的电源管理单元PMUGPU 驱动在 probe 时通过device_link_add()把 PMU 声明为 supplier。那么系统睡眠时框架会先挂起 GPU再挂起 PMU唤醒时先恢复 PMU再恢复 GPU。即使 PMU 在dpm_list里的物理位置和 GPU 不同这种显式依赖也能保证正确顺序。这件事在复杂 SoC 上特别重要因为很多外设的电源和时钟由独立的 PMU/clock 控制器管理单纯靠设备树枚举顺序往往表达不了真实的电路依赖。如果你在开发驱动并且设备之间确实存在硬件依赖我强烈建议把device_link_add()用起来这比靠运气顺序或者加各种 hack 要可靠得多。6. 驱动接入 DPM 的实操指南6.1 标准 dev_pm_ops 模板给驱动正确接入系统睡眠其实并不复杂绝大多数外部设备驱动只需要提供一对 suspend/resume 回调。我自己写驱动时最常用的模板是这样的#include linux/pm.h static int my_dev_suspend(struct device *dev) { struct my_dev *d dev_get_drvdata(dev); disable_irq(d-irq); /* 先关中断防止挂起过程中打扰 */ cancel_work_sync(d-work); /* 停 DMA、停 FIFO、保存上下文寄存器 */ regmap_write(d-regmap, MY_REG_CTRL, 0); d-regs_saved true; return 0; } static int my_dev_resume(struct device *dev) { struct my_dev *d dev_get_drvdata(dev); /* 恢复寄存器上下文 */ if (d-regs_saved) { regmap_write(d-regmap, MY_REG_CTRL, d-ctrl_bak); regmap_write(d-regmap, MY_REG_DMA, d-dma_bak); } enable_irq(d-irq); schedule_work(d-work); return 0; } static const struct dev_pm_ops my_dev_pm_ops { SYSTEM_SLEEP_PM_OPS(my_dev_suspend, my_dev_resume) }; static struct platform_driver my_driver { ... .driver { .name my-dev, .pm my_dev_pm_ops, }, };这里有几个细节值得说明。SYSTEM_SLEEP_PM_OPS宏会让同一个 suspend/resume 也作用于 Hibernate 流程省掉重复定义。如果在某些编译配置里CONFIG_PM_SLEEP没开pm字段可能被忽略通常配合#ifdef CONFIG_PM_SLEEP或者pm_sleep_ptr()宏处理避免编译警告。另一个细节是disable_irq()和devm_free_irq()的选择。如果 suspend 后系统睡眠期间设备中断不会发生直接用disable_irq()最安全resume 时再enable_irq()如果设备可能在睡眠期间收到唤醒信号那就不能简单 disable 它而是要走 wakeup source 机制配置。6.2 何时用 pm_runtime_force_suspend有些驱动的 Runtime PM 回调已经写得很完善它们并不想在系统睡眠时单独维护一套suspend/resume而是希望复用 runtime 的逻辑。Linux 内核为此提供了pm_runtime_force_suspend()和pm_runtime_force_resume()。这套机制的做法是在系统睡眠的 suspend 回调里调用pm_runtime_force_suspend(dev)它会无视设备当前的 runtime 状态强制把设备挂起并调用驱动的runtime_suspend回调resume 时调用pm_runtime_force_resume(dev)强制调用runtime_resume并恢复 runtime 状态。这在管理型驱动里很常见比如 MMC、USB、DRM 等子系统都有类似用法。但这个函数不是银弹。如果驱动在 runtime suspend 里关了时钟和电源但系统睡眠后恰好需要靠设备上的某个寄存器做唤醒判断那用它就会有问题。要结合硬件行为决定设备睡眠状态下还需要保留哪些电源轨道和时钟是驱动正确接入 DPM 的核心考量。6.3 睡眠与唤醒时的中断处理中断是 DPM 接线里最容易被忽略的一环。系统进入suspend_noirq之后中断控制器本身也被挂起此时任何设备的中断都不会被 CPU 响应。问题出在 resume 阶段中断控制器恢复后如果某个设备的中断没有被正确屏蔽它可能立即产生一个 pending 中断而对应驱动还没执行 resume中断处理函数访问的还是未恢复的硬件状态直接导致系统不稳定甚至 panic。标准的做法我在模板里已经体现suspend 里先disable_irq()再停 DMAresume 里先把硬件状态恢复到可处理中断的状态再enable_irq()。顺序千万别反。如果设备支持 wakeup那要走的路径稍有不同不是disable_irq()而是配置中断为唤醒源让它在睡眠期间能唤醒系统等唤醒后再在 resume 里做完整的中断处理流程。7. 常见问题与排查技巧实录7.1 系统无法睡眠suspend 失败定位遇到系统睡眠失败第一步先看 dmesg。DPM 框架在挂起出问题时通常会打印失败设备的名称和返回值。比如PM: Device i2c-0 failed to suspend: error -16-16是-EBUSY说明设备认为自己正忙不让睡眠。常见原因是有 runtime reference 没释放或者设备有 pending 的唤醒事件。排查方向是去驱动里搜pm_runtime_get()和pm_runtime_put()是否配对以及是否有 wakeup source 处于激活状态。如果 dmesg 没给出具体设备可以把内核参数加上pm_debug_messages或者运行时执行echo 1 /sys/power/pm_debug_messages这会让 DPM 打印完整的设备遍历过程包括每个设备处于哪个阶段、回调是否执行、执行结果如何。结合日志定位到具体设备后再配合该设备驱动的 debug 开关看内部状态。7.2 suspend 卡死与超时设备挂起卡死比直接失败难查得多。常见表现是系统执行echo mem /sys/power/state后控制台无输出或者日志停在某个设备再也没动静。DPM 框架对每个设备的回调执行有超时控制吗在内核中dpm_suspend()本身没有为单个设备设置超时但整个 suspend 时间过长平台层的suspend_ops-enter()或者 watchdog 会介入。排查思路是缩小范围。最有效的方法是用内核自带的pm_test功能echo devices /sys/power/pm_test echo mem /sys/power/statepm_test会在设备挂起/恢复流程完成后停止不会真正进入睡眠。如果复现卡死就能确认问题在设备层面。剩下的就是二分法在 dmesg 里看卡在哪个设备然后在该驱动里加打印或者用 ftrace 的 power 事件进一步追踪执行时间。7.3 noirq 阶段访问了可睡眠资源这种问题几乎毫无例外地表现为在suspend_noirq或者resume_noirq阶段出现 oops、调度器警告或者死锁。内核在这类路径上有很多might_sleep()检查一旦在原子上下文调用了可能睡眠的函数就会打印类似BUG: sleeping function called from invalid context at ...典型的违规操作包括在 noirq 阶段调用msleep()、mutex_lock()、kmalloc(..., GFP_KERNEL)、i2c_transfer()等。修法不是去绕过might_sleep()而是把操作挪到更早的阶段。记住一条原则任何可能要等别人的操作都必须在suspend或suspend_late阶段完成noirq阶段只做纯原子的寄存器写入和 GPIO 拉电平。7.4 resume 后设备无响应或触发中断风暴设备唤醒后没反应常见原因是resume回调没有完整恢复硬件状态。很多芯片在睡眠中会丢失一部分寄存器上下文如果驱动只在 suspend 时保存了部分关键寄存器resume 时没恢复全设备就会处于半初始化状态。排查办法是先确认该设备的 resume 确实被调用再对比 suspend 保存的寄存器集合和 resume 恢复的寄存器集合必要时直接 dump 硬件寄存器与正常状态对比。中断风暴也是老问题。设备唤醒后反复触发中断多半是disable_irq()和enable_irq()的时机不对或者是设备在睡眠期间产生了中断事件但没有在 resume 开始时清理 pending 中断位。很多驱动的思路是在 resume 里先读中断状态寄存器、清掉所有 pending 位再恢复业务逻辑这样中断风暴基本能避免。写在最后的一点个人经验DPM 框架这套东西代码读起来并不复杂真正难的是建立设备之间有时序依赖的意识。我调试过不少睡眠唤醒问题最后发现绝大多数都不是内核框架的 bug而是驱动作者没想清楚自己设备的掉电顺序哪些功能必须保持哪些可以在睡眠期间关闭哪些寄存器在上电后必须最先恢复。我的习惯是每接触一个新平台先花半小时把 dmesg 里 suspend/resume 的设备顺序打印出来和原理图上的电源树对照一遍再动手写回调。这个习惯帮我省掉了大量的排查时间。你如果正在被某台机器的睡眠问题折磨不妨也先从这个角度开始。
返回列表