Zephyr电源管理-设备电源管理

Creative Commons
本作品采用知识共享署名

本文描述Zephyr设备电源管理的实现

概述

本文是Zephyr电源管理系列文章的最终篇,之前的Tickless和系统电源管理都是由Soc提供的提供省电的功能,由Zephyr电源管理框架进行调用。设备电源管理和前两者一样也是Zephyr电源管理框架调用设备省电功能,但设备电源管理的省电功能更多的是实现驱动的作者在决定,例如关闭Soc外设的clock或者供电,也可以是其它动作,这部分的可扩展性很大。
Zephyr电源管理框架大体可以分Zephyr设备驱动电源管理接口及使用这些接口的框架两部分。

接口使用框架

Zephyr设备电源管理接口使用框架提供分布式和集中式两种电源管理方法

分布式

在这种方法下应用程序或者组件直接和设备交互,了解设备合适要处于何种状态,可以根据设备的工作需要让设备进入或者退出睡眠状态达到省电的目的。即使是在SOC处于激活状态,也可以让设备进入睡眠达到省电的目的。该方法下使用设备的应用或组件需要了解设备电源情况,并能够决策设备电源的开关。由于分布式方法已经将设备设置为适当的电源状态,因此使用sys_suspend()让SOC进入某种电源状态时就不需要再花时间对设备电源进行设置,而能够更快速进入对应的电源状态。

集中式

在几种式管理方法中,设备电源管理是在sys_suspend()内完成,在系统电源管理一文中的”系统电源管理工作流程”章节已经有提到,当要进入深度睡眠时会让设备进入省电状态,这里只是列出代码,前后调用流程不再做说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum power_states _sys_suspend(s32_t ticks)
{
if (deep_sleep) {
#if CONFIG_DEVICE_POWER_MANAGEMENT
//device挂起,达到省电的功能
if (sys_pm_suspend_devices()) {
LOG_ERR("System level device suspend failed!");
sys_pm_notify_power_state_exit(pm_state);
pm_state = SYS_POWER_STATE_ACTIVE;
return pm_state;
}
#endif
_sys_pm_idle_exit_notification_disable();
}

#if CONFIG_DEVICE_POWER_MANAGEMENT
//对于深度睡眠,在有device电源管理的情况下device要退出深度睡眠
if (deep_sleep) {
//device从挂起中恢复
sys_pm_resume_devices();
}
#endif
}

这里我们姑且先记下sys_pm_suspend_devices和sys_pm_resume_devices,之后再来看其如何和device驱动对接。

device驱动电源管理模型

device驱动电源管理模型提供了抽象的设备驱动电源管理API,不规定具体的实施。曾经在zephyr驱动模型一文中分析了device驱动模型,当时并没有提到电源管理部分,其实设备驱动的电源管理框架和设备驱动框架类似,在device.h中定义了统一的接口,在实现驱动的时候实现了其电源管理接口。这里从API介绍,架构实现,驱动示例几部分介绍。

设备驱动电源管理API

设备驱动电源管理状态

在zephyr/include/device.h中定义了4种电源管理状态,如下:
DEVICE_PM_ACTIVE_STATE: 激活状态,device正常工作,所有上下文被保持
DEVICE_PM_LOW_POWER_STATE:低功耗状态,上下文在硬件中保持,恢复时不需要驱动参与
DEVICE_PM_SUSPEND_STATE:挂起状态,硬件丢失大部分上下文,驱动需要协助保存和恢复device上下文,或者重新初始化硬件
DEVICE_PM_OFF_STATE:掉电状态,device掉电,丢失上下文,重新上电后需要重新初始化硬件

设备驱动电源管理操作

在zephyr/include/device.h定义了电源管理操作的API用于执行下面两种动作
DEVICE_PM_SET_POWER_STATE 设置电源状态
DEVICE_PM_GET_POWER_STATE 获取电源状态
对应下面两个API

1
2
3
static inline int device_set_power_state(struct device *device,
u32_t device_power_state,
device_pm_cb cb, void *arg)

设置device电源状态,各参数和返回值的定义:
device: device驱动handle
device_power_state: 要设置的状态,也就是打算让device进入的状态
cb: 在device设置状态完成后,调用该callback
arg:callback的参数
返回为0表示成功,其它是标准的errno code

1
2
static inline int device_get_power_state(struct device *device,
u32_t *device_power_state)

获取device电源状态,各参数和返回值的定义:
device: device驱动handle
device_power_state: device当前的电源状态
返回为0表示成功,其它是标准的errno code

设备驱动电源管理架构实现

带电源管理的设备驱动一般使用下面宏进行定义

1
2
#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn,	  \
data, cfg_info, level, prio, api)

可以看到和普通的device设备驱动多了一个pm_control_fn用于设备驱动电源管理,我们来查看代码

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
#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn,	  \
data, cfg_info, level, prio, api) \
//定义并初始化电源管理信息
static struct device_pm _CONCAT(__pm_, dev_name) __used \
= { \
.usage = ATOMIC_INIT(0), \
.lock = Z_SEM_INITIALIZER( \
_CONCAT(__pm_, dev_name).lock, 1, 1), \
.signal = K_POLL_SIGNAL_INITIALIZER( \
_CONCAT(__pm_, dev_name).signal), \
.event = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, \
K_POLL_MODE_NOTIFY_ONLY, \
&_CONCAT(__pm_, dev_name).signal), \
}; \
static struct device_config _CONCAT(__config_, dev_name) __used \
__attribute__((__section__(".devconfig.init"))) = { \
.name = drv_name, .init = (init_fn), \
.device_pm_control = (pm_control_fn), \ //设置电源管理函数
.pm = &_CONCAT(__pm_, dev_name), \ //设置电源管理信息
.config_info = (cfg_info) \
}; \
static Z_DECL_ALIGN(struct device) _CONCAT(__device_, dev_name) __used \
__attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \
.config = &_CONCAT(__config_, dev_name), \
.driver_api = api, \
.driver_data = data, \
}

可以看到和无电源管理的设备驱动比,多了定义并初始化了一个电源管理结构体struct device_pm,并将电源管理函数和结构体放置到struct device_config的 device_pm_control和pm中。

电源管理函数

因此一个驱动实现电源管理时只用实现device_pm_control电源管理函数和相关流程,然后在使用DEVICE_DEFINE将其注册进入struct device_config的device_pm_control即可。
设备驱动电源管理操作接口就是将device_pm_control以统一接口的方式透露给驱动使用者,也就是前面提到的set/get函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static inline int device_set_power_state(struct device *device,
u32_t device_power_state,
device_pm_cb cb, void *arg)
{
return device->config->device_pm_control(device,
DEVICE_PM_SET_POWER_STATE,
&device_power_state, cb, arg);
}

static inline int device_get_power_state(struct device *device,
u32_t *device_power_state)
{
return device->config->device_pm_control(device,
DEVICE_PM_GET_POWER_STATE,
device_power_state,
NULL, NULL);
}

可见其set和get都是使用的device_pm_control,只是传递的参数不一样而已,我们再来看下其原型

1
2
int (*device_pm_control)(struct device *device, u32_t command,
void *context, device_pm_cb cb, void *arg);

定义驱动时也可以和普通的device驱动一样使用DEVICE_AND_API_INIT定义不待电源管理的设备驱动:

1
2
3
4
5
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
level, prio, api) \
DEVICE_DEFINE(dev_name, drv_name, init_fn, \
device_pm_control_nop, data, cfg_info, level, \
prio, api)

此时并不需要驱动实现device_pm_control,电源管理函数直接注册为device_pm_control_nop, 该函数什么事都不做,因此也不会进行电源管理。

电源管理信息

前面提到的struct device_config内的pm,其结构体如下,用于device idle电源管理,会被设备电源管理框架zephyr/subsys/power/device_pm.c使用。device_pm提供的API再被设备驱动使用,关于设备idle电源管理本文不做分析,可见最后的其它说明

1
2
3
4
5
6
7
8
9
10
struct device_pm {
struct device *dev;
struct k_sem lock;
bool enable;
atomic_t usage;
atomic_t fsm_state;
struct k_work work;
struct k_poll_event event;
struct k_poll_signal signal;
};

驱动示例

下面是一个nrf uart设备驱动电源管理的实现示例,分析见代码

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
42
43
44
45
46
47
48
49
50
51
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
//设置设备进入指定电源状态的函数,和设备硬件相关,这里可以看到,起始只会出现两种状态激活和关闭
static void uart_nrfx_set_power_state(u32_t new_state)
{
if (new_state == DEVICE_PM_ACTIVE_STATE) {
nrf_uart_enable(uart0_addr);
nrf_uart_task_trigger(uart0_addr, NRF_UART_TASK_STARTRX);
} else {
assert(new_state == DEVICE_PM_LOW_POWER_STATE ||
new_state == DEVICE_PM_SUSPEND_STATE ||
new_state == DEVICE_PM_OFF_STATE);
nrf_uart_disable(uart0_addr);
}
}

//实现电源管理函数,包含电源状态的记录和管理
static int uart_nrfx_pm_control(struct device *dev, u32_t ctrl_command,
void *context, device_pm_cb cb, void *arg)
{
static u32_t current_state = DEVICE_PM_ACTIVE_STATE;

if (ctrl_command == DEVICE_PM_SET_POWER_STATE) {//设置电源状态
u32_t new_state = *((const u32_t *)context);

if (new_state != current_state) {
uart_nrfx_set_power_state(new_state); //调用实际的电源状态设置函数
current_state = new_state;
}
} else {//获取电源状态
assert(ctrl_command == DEVICE_PM_GET_POWER_STATE);
*((u32_t *)context) = current_state;
}

if (cb) { //电源状态设置完备后,调用驱动使用者的callback
cb(dev, 0, context, arg);
}

return 0;
}

//将uart_nrfx_pm_control注册为uart_nrfx_uart0驱动的电源管理函数
DEVICE_DEFINE(uart_nrfx_uart0,
DT_NORDIC_NRF_UART_UART_0_LABEL,
uart_nrfx_init,
uart_nrfx_pm_control,
&uart_nrfx_uart0_data,
&uart_nrfx_uart0_config,
/* Initialize UART device before UART console. */
PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&uart_nrfx_uart_driver_api);

如果应用binding DT_NORDIC_NRF_UART_UART_0_LABEL后拿到device handle对其进行device_set_power_state和
device_get_power_state操作,实际执行的就是uart_nrfx_pm_control

框架驱动对接

分散式方法下,应用会自己决定何时调用设备驱动的device_set_power_state和device_get_power_state。在集中方法下前文已经分析了如何叫到sys_pm_suspend_devices和sys_pm_resume_devices,这里分析看这两个API如何和驱动对接

初始化

zephyr/subsys/power/power.c中执行系统电源初始化时,会创建device list

1
2
3
4
5
6
7
8
9
static int sys_pm_init(struct device *dev)
{
ARG_UNUSED(dev);

sys_pm_create_device_list(); //创建device list
return 0;
}

SYS_INIT(sys_pm_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);

zephyr/subsys/power/device.c中实现了sys_pm_create_device_list 简化主要代码如下

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
//定义核心device
static const char core_devices[NUM_CORE_DEVICES][MAX_DEV_NAME_LEN] = {
"CLOCK_32K",
"CLOCK_16M",
"sys_clock",
"UART_0",
};

void sys_pm_create_device_list(void)
{
//获取device list
device_list_get(&pm_device_list, &count);

/* Reserve for 32KHz, 16MHz, system clock, etc... */
device_count = NUM_CORE_DEVICES;

//循环查找,将核心的device放在device_ordered_list的最前面
for (i = 0; (i < count) && (device_count < MAX_PM_DEVICES); i++) {

/* Check if the device is core device */
for (j = 0, is_core_dev = false; j < NUM_CORE_DEVICES; j++) {
if (!strcmp(pm_device_list[i].config->name,
&core_devices[j][0])) {
is_core_dev = true;
break;
}
}

if (is_core_dev) {
device_ordered_list[j] = i;
} else {
device_ordered_list[device_count++] = i;
}
}
}

初始化完成后就得到一张device的list,最前面放着的是核心的device, 后面我们再看为什么核心的device要放到最前面

挂起device

从下面的代码分析可以看见,最后挂起的是核心设备,因为核心设备会为系统提供时钟和一些基础驱动服务(例如uart输出),所以会放到最后挂起,这也就是为什么要把核心设备放到list的最前面,遍历的时候从最后向前遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int sys_pm_suspend_devices(void)
{
//挂起时,从deivce list里面从后向前逐个让device进入挂起状态
for (int i = device_count - 1; i >= 0; i--) {
int idx = device_ordered_list[i];

//调用驱动接口让设备进入挂起状态
device_retval[i] = device_set_power_state(&pm_device_list[idx],
DEVICE_PM_SUSPEND_STATE,
NULL, NULL);
if (device_retval[i]) {
LOG_ERR("%s suspend operation failed\n",
pm_device_list[idx].config->name);
return device_retval[i];
}
}

return 0;
}

恢复device

恢复时和挂起相反,需要先恢复核心的device

1
2
3
4
5
6
7
8
9
10
11
12
13
void sys_pm_resume_devices(void)
{
int i;
//恢复时,从deivce list里面从后向前逐个让device进入恢复状态
for (i = 0; i < device_count; i++) {
if (!device_retval[i]) {
int idx = device_ordered_list[i];
//调用驱动接口让设备恢复
device_set_power_state(&pm_device_list[idx],
DEVICE_PM_ACTIVE_STATE, NULL, NULL);
}
}
}

核心device并不是必须的,一个系统是否具有核心device,有多少核心deivce是由SOC和实际应用的外设决定的。这里列出的起始是nrf系列的,并且zephyr目前的代码也只有nrf系列的

设备驱动支援电源管理框架

zephyr对于设备驱动的电源管理框架还支持busy管理和设备idle电源管理两项,这两项的框架需要由设备驱动调用实现,由于目前zephyr的已实现的驱动并未完整的使用这两项功能,因此本文只做简要介绍不再详细分析,有兴趣可以阅读代码zephyr/samples/subsys/power/device_pm。

busy管理

busy管理是指:只有当设备驱动没有处于无法中断的事物中时才会进入睡眠状态(busy check),该实施有赖于驱动实现,驱动通过驱动框架zephyr/kernel/device.c提供的以下api对busy状态进行记录和管理
int device_any_busy_check(void): 检查是有device处于busy状态
int device_busy_check(struct device chk_dev): 检查是否由device处于busy状态
void device_busy_set(struct device
busy_dev) : 设置指定device为busy
void device_busy_clear(struct device *busy_dev) : 清除指定device busy状态

设备idle电源管理

设备idle电源管理. 是一种主动的电源管理框架,通过挂起系统未使用的设备达到省电的目的。该实施有赖于驱动实现,驱动代码通过idle电源驱动管理框架zephyr/subsys/power/device_pm.c提供的api对device空闲状态进行更新和监控,当device_pm的usage为0时会自动调用device_set_power_state让其挂起或者恢复
int device_pm_get(struct device dev): 异步请求device(增加usage count, 说明device还在使用)
int device_pm_get_sync(struct device
dev): 同步请求device
int device_pm_put(struct device dev): 异步释放device(减少usage count, 为0时说明device没有被使用了,就会自动执行挂起)
int device_pm_put_sync(struct device
dev) : 同步释放device
void device_pm_enable(struct device dev): 允许device使用idle电源管理功能
void device_pm_disable(struct device
dev):禁止device使用idle电源管理功能

总结

device电源管理说了这么多,感觉有点乱,下面一张图展示该代码框架
device_pm

参考

http://docs.zephyrproject.org/latest/reference/power_management/index.html