Linux电源管理4(基于Linux6.6)---Generic PM 接口介绍
一、概述
Linux电源管理中,相当多的部分是在处理Hibernate、Suspend、Runtime PM等功能。而这些功能都基于一套相似的逻辑,即“Power management interface”。
Linux 电源管理接口通过 ACPI、CPU 动态调频、设备电源管理、系统挂起/休眠/关机等机制,提供了一整套灵活而高效的电源管理功能。其主要目标是提高系统能效,节省能源并延长设备的使用寿命。
- 系统级管理:通过 ACPI 和内核支持的挂起、休眠等操作,Linux 实现了系统级的电源管理。
- 设备级管理:每个设备都可以通过相应的电源管理接口进入低功耗模式,从而最大限度减少功耗。
- 动态调节:CPU 和其他硬件设备可以根据系统负载动态调整工作频率和电压,平衡性能与能效。
二、Device PM callbacks
在一个系统中,数量最多的是设备,耗电最多的也是设备,因此设备的电源管理是Linux电源管理的核心内容。而设备电源管理最核心的操作就是:在合适的时机,将设备置为合理的状态。这就是device PM callbacks的目的:定义一套统一的方式,让设备在特定的时机,步调一致的进入类似的状态。
include/linux/pm.h
struct dev_pm_ops {
int (*prepare)(struct device *dev);
void (*complete)(struct device *dev);
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_late)(struct device *dev);
int (*resume_early)(struct device *dev);
int (*freeze_late)(struct device *dev);
int (*thaw_early)(struct device *dev);
int (*poweroff_late)(struct device *dev);
int (*restore_early)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
从Linux PM Core的角度来说,这些callbacks并不复杂,因为PM Core要做的就是在特定的电源管理阶段,调用相应的callbacks,例如在suspend/resume的过程中,PM Core会依次调用“prepare—>suspend—>suspend_late—>suspend_noirq-------wakeup--------->resume_noirq—>resume_early—>resume-->complete”。
但由于这些callbacks需要由具体的设备Driver实现,这就要求设计每个Driver时,清晰的知道这些callbacks的使用场景、是否需要实现、怎么实现,这才是struct dev_pm_ops的复杂之处。
Linux kernel对struct dev_pm_ops的注释已经非常详细了,但要弄清楚每个callback的使用场景、背后的思考,并不是一件容易的事情。
三、Device PM callbacks设备模型
Linux设备模型中的很多数据结构,都会包含struct dev_pm_ops变量,具体如下:
struct bus_type {
...
const struct dev_pm_ops *pm;
...
};
struct device_driver {
...
const struct dev_pm_ops *pm;
...
};
struct class {
...
const struct dev_pm_ops *pm;
...
};
struct device_type {
...
const struct dev_pm_ops *pm;
};
struct device {
...
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
...
};
bus_type、device_driver、class、device_type等结构中的pm指针,比较容易理解,和旧的suspend/resume callbacks类似。重点关注一下device结构中的power和pm_domain变量。
◆power变量
include/linux/pm.h
struct dev_pm_info {
pm_message_t power_state;
unsigned int can_wakeup:1;
unsigned int async_suspend:1;
bool in_dpm_list:1; /* Owned by the PM core */
bool is_prepared:1; /* Owned by the PM core */
bool is_suspended:1; /* Ditto */
bool is_noirq_suspended:1;
bool is_late_suspended:1;
bool no_pm:1;
bool early_init:1; /* Owned by the PM core */
bool direct_complete:1; /* Owned by the PM core */
u32 driver_flags;
spinlock_t lock;
#ifdef CONFIG_PM_SLEEP
struct list_head entry;
struct completion completion;
struct wakeup_source *wakeup;
bool wakeup_path:1;
bool syscore:1;
bool no_pm_callbacks:1; /* Owned by the PM core */
unsigned int must_resume:1; /* Owned by the PM core */
unsigned int may_skip_resume:1; /* Set by subsystems */
#else
unsigned int should_wakeup:1;
#endif
#ifdef CONFIG_PM
struct hrtimer suspend_timer;
u64 timer_expires;
struct work_struct work;
wait_queue_head_t wait_queue;
struct wake_irq *wakeirq;
atomic_t usage_count;
atomic_t child_count;
unsigned int disable_depth:3;
unsigned int idle_notification:1;
unsigned int request_pending:1;
unsigned int deferred_resume:1;
unsigned int needs_force_resume:1;
unsigned int runtime_auto:1;
bool ignore_children:1;
unsigned int no_callbacks:1;
unsigned int irq_safe:1;
unsigned int use_autosuspend:1;
unsigned int timer_autosuspends:1;
unsigned int memalloc_noio:1;
unsigned int links_count;
enum rpm_request request;
enum rpm_status runtime_status;
enum rpm_status last_status;
int runtime_error;
int autosuspend_delay;
u64 last_busy;
u64 active_time;
u64 suspended_time;
u64 accounting_timestamp;
#endif
struct pm_subsys_data *subsys_data; /* Owned by the subsystem. */
void (*set_latency_tolerance)(struct device *, s32);
struct dev_pm_qos *qos;
};
power变量主要保存PM相关的状态,如当前的power_state、是否可以被唤醒、是否已经prepare完成、是否已经suspend完成等等。由于涉及的内容非常多,我们在具体使用的时候,顺便说明。
◆pm_domain指针
include/linux/pm.h
struct dev_pm_domain {
struct dev_pm_ops ops;
int (*start)(struct device *dev);
void (*detach)(struct device *dev, bool power_off);
int (*activate)(struct device *dev);
void (*sync)(struct device *dev);
void (*dismiss)(struct device *dev);
};
所谓的PM Domain(电源域),是针对“device”来说的。bus_type、device_driver、class、device_type等结构,本质上代表的是设备驱动,电源管理的操作,由设备驱动负责,是理所应当的。但在内核中,由于各种原因,是允许没有driver的device存在的,那么怎么处理这些设备的电源管理呢?就是通过设备的电源域实现的。
四、Device PM callbacks操作函数
内核在定义device PM callbacks数据结构的同时,为了方便使用该数据结构,也定义了大量的操作API,这些API分为两类。
◆通用的辅助性质的API,直接调用指定设备所绑定的driver的、pm指针的、相应的callback,如下
include/linux/pm.h
extern int pm_generic_prepare(struct device *dev);
extern int pm_generic_suspend_late(struct device *dev);
extern int pm_generic_suspend_noirq(struct device *dev);
extern int pm_generic_suspend(struct device *dev);
extern int pm_generic_resume_early(struct device *dev);
extern int pm_generic_resume_noirq(struct device *dev);
extern int pm_generic_resume(struct device *dev);
extern int pm_generic_freeze_noirq(struct device *dev);
extern int pm_generic_freeze_late(struct device *dev);
extern int pm_generic_freeze(struct device *dev);
extern int pm_generic_thaw_noirq(struct device *dev);
extern int pm_generic_thaw_early(struct device *dev);
extern int pm_generic_thaw(struct device *dev);
extern int pm_generic_restore_noirq(struct device *dev);
extern int pm_generic_restore_early(struct device *dev);
extern int pm_generic_restore(struct device *dev);
extern int pm_generic_poweroff_noirq(struct device *dev);
extern int pm_generic_poweroff_late(struct device *dev);
extern int pm_generic_poweroff(struct device *dev);
extern void pm_generic_complete(struct device *dev);
extern bool dev_pm_skip_resume(struct device *dev);
extern bool dev_pm_skip_suspend(struct device *dev);
以pm_generic_prepare为例,就是查看dev->driver->pm->prepare接口是否存在,如果存在,直接调用并返回结果。
◆和整体电源管理行为相关的API,目的是将各个独立的电源管理行为组合起来,组成一个较为简单的功能,如下:
include/linux/pm.h
#ifdef CONFIG_PM_SLEEP
extern void device_pm_lock(void);
extern void dpm_resume_start(pm_message_t state);
extern void dpm_resume_end(pm_message_t state);
extern void dpm_resume_noirq(pm_message_t state);
extern void dpm_resume_early(pm_message_t state);
extern void dpm_resume(pm_message_t state);
extern void dpm_complete(pm_message_t state);
extern void device_pm_unlock(void);
extern int dpm_suspend_end(pm_message_t state);
extern int dpm_suspend_start(pm_message_t state);
extern int dpm_suspend_noirq(pm_message_t state);
extern int dpm_suspend_late(pm_message_t state);
extern int dpm_suspend(pm_message_t state);
extern int dpm_prepare(pm_message_t state);
extern void __suspend_report_result(const char *function, struct device *dev, void *fn, int ret);
#define suspend_report_result(dev, fn, ret) \
do { \
__suspend_report_result(__func__, dev, fn, ret); \
} while (0)
这些API的功能和动作解析如下。
dpm_prepare,执行所有设备的“->prepare() callback(s)”,内部动作为:
dpm_prepare()
函数会遍历所有正在进行电源管理操作的设备,并调用每个设备的 ->prepare()
回调函数。这个回调函数的作用是在设备进入低功耗状态之前执行一些必要的操作,通常包括暂停某些功能、保存设备状态或处理硬件中断等。
工作流程
- 遍历设备列表:
dpm_prepare()
会遍历所有需要进行电源管理的设备(如正在挂起、休眠等的设备)。 - 调用
->prepare()
:对于每个设备,它会调用设备驱动中实现的->prepare()
回调函数。这个回调函数用于确保设备在电源状态变化前做好准备,避免在进入低功耗模式时丢失数据或导致硬件损坏。 - 执行必要的准备操作:
- 对于某些设备,可能需要暂停设备的功能,或者停止设备的中断处理。
- 对于某些设备,可能需要保存当前的工作状态,以便之后恢复。
- 返回成功或失败:如果任何一个设备的
->prepare()
操作失败,dpm_prepare()
会返回失败,并可能中止后续的电源管理过程。成功的->prepare()
操作则继续进行。
具体操作
- 保存设备状态:对于某些设备,可能需要将当前的状态(如内存缓冲区、寄存器配置等)保存到临时存储区域,以便后续恢复。
- 暂停设备功能:例如,暂停硬盘的 I/O 操作,或者停止设备的中断响应。
- 同步设备的状态:确保设备在进入低功耗状态前,其所有的操作和资源都已经正确同步和管理,以防止数据丢失或操作冲突。
->prepare()
回调函数的作用
->prepare()
回调函数是设备驱动程序中定义的一个函数,它会在设备准备进入低功耗状态之前执行。这个回调函数通常需要做以下几件事:
- 停止设备的活动(如数据传输、I/O 操作)。
- 保存设备的状态,以便可以恢复。
- 关闭设备的中断或通知。
举例
假设有一个网络设备的驱动,在设备准备挂起之前,->prepare()
回调函数可能需要暂停数据包接收和发送,关闭相关的硬件中断,保存必要的硬件状态。
static int net_device_prepare(struct device *dev)
{
struct net_device *ndev = to_net_dev(dev);
/* 停止网络设备的接收和发送操作 */
netif_tx_stop_all_queues(ndev);
/* 禁用中断 */
disable_irq(ndev->irq);
return 0;
}
static const struct dev_pm_ops net_device_pm_ops = {
.prepare = net_device_prepare,
};
在这个例子中,net_device_prepare()
在 dpm_prepare()
调用时被执行,它会暂停设备的发送和接收操作,并禁用中断。这样做是为了确保设备在电源管理过程中不会受到干扰或丢失数据。
dpm_suspend,执行所有设备的“->suspend() callback(s)”,其内部动作和dpm_prepare类似:
dpm_suspend()
函数的内部流程
dpm_suspend()
的主要任务是遍历所有挂起的设备,并调用它们各自的 ->suspend()
回调函数。这个过程通常包括以下几个步骤:
-
遍历设备列表:
dpm_suspend()
函数会遍历系统中所有需要挂起的设备,这些设备包括所有驱动程序已经注册并参与电源管理的设备。 -
调用每个设备的
->suspend()
:对每个设备,内核会调用其驱动程序中实现的->suspend()
函数。这个函数用于将设备的状态保存,并准备设备进入低功耗状态。 -
执行挂起操作:在
->suspend()
回调中,设备驱动会执行各种操作,通常包括:- 停止设备的 I/O 操作。
- 关闭设备中断。
- 保存设备的状态,以便恢复。
- 关闭硬件资源(如时钟、DMA、总线等)。
- 确保设备在挂起时不进行任何未完成的操作。
-
错误处理:如果在调用
->suspend()
回调时,某个设备的挂起操作失败,dpm_suspend()
会记录失败的设备,并决定是否继续执行后续设备的挂起操作。 -
返回挂起状态:如果所有设备的挂起操作都成功,
dpm_suspend()
返回成功,并允许系统进入挂起状态。否则,内核会中止挂起操作,恢复系统的正常工作状态。
->suspend()
回调的作用
每个设备驱动都可以定义一个 ->suspend()
回调函数,通常在该回调中,设备驱动会执行以下操作:
-
保存设备的状态:如果设备有运行状态需要保存(例如设备的寄存器、缓冲区等),则在
->suspend()
中会将这些状态保存到内存或某个持久性存储区域,以便在恢复时使用。 -
停止设备的活动:为了使设备能够安全地进入挂起状态,通常需要停止设备的活动,如:
- 停止数据传输。
- 关闭设备的中断。
- 停止任何其他硬件操作(如网络设备的发送和接收操作)。
-
禁用硬件资源:例如:
- 禁用时钟或系统总线的电源。
- 禁用 DMA 或其他硬件接口,避免系统在设备挂起时还试图访问这些硬件。
-
切换到低功耗状态:在
->suspend()
函数中,驱动程序可能会将设备切换到某种低功耗模式(如睡眠模式、暂停模式等),以节省电力。
dpm_suspend()
的工作流程(简要概述)
-
检查设备是否支持挂起:首先,
dpm_suspend()
会检查设备是否支持挂起操作,以及设备是否需要进入低功耗状态。 -
调用设备的
->suspend()
:接着,内核会调用每个设备驱动的->suspend()
回调函数。这个函数通常会暂停设备的活动,并保存其状态。 -
错误处理:如果设备的
->suspend()
返回错误,dpm_suspend()
会记录这个错误,决定是否继续挂起其它设备,或者完全中止挂起操作。 -
返回挂起操作结果:最终,
dpm_suspend()
会返回挂起的结果,成功的话进入挂起状态,失败则回滚所有已挂起设备的操作。
示例:网络设备的 ->suspend()
实现
假设有一个网络设备驱动程序,它的 ->suspend()
回调函数可能会像这样:
static int net_device_suspend(struct device *dev)
{
struct net_device *ndev = to_net_dev(dev);
/* 停止网络设备的传输操作 */
netif_tx_stop_all_queues(ndev);
/* 停止接收数据 */
netif_stop_queue(ndev);
/* 关闭硬件中断 */
disable_irq(ndev->irq);
/* 保存设备的状态(例如硬件寄存器) */
save_device_state(ndev);
/* 将设备切换到低功耗状态 */
power_down_device(ndev);
return 0;
}
static const struct dev_pm_ops net_device_pm_ops = {
.suspend = net_device_suspend,
};
在这个示例中,net_device_suspend()
实现了以下操作:
- 停止网络设备的发送队列和接收队列。
- 禁用中断。
- 保存设备状态(如硬件寄存器的值)。
- 将设备切换到低功耗状态。