zephyr驱动模型

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

本文介绍zephyr驱动模型,分析zephyr驱动架构,原理和使用方法。本文只分析了无user mode和无电源管理情况下Zephyr的驱动模型。

概述

Zephyr支持各种驱动,根据板子来选择编译使用哪些驱动可用。为了缩小image,驱动是采用配置buildin的形式,不使用的驱动是不会被编译的。
Zephyr提供的模型主要是完成注册驱动和驱动初始化,按照驱动类型提供通用的驱动接口(不同于Linux,所有驱动都是open,close,write,read),并提供注册系统调用的宏,具体的驱动由驱动开发者编写。
Zephyr的驱动都需要采用中断,除非是device硬件不支持中断。

注册驱动

include/device.h中提供了一组数据结构和宏,让驱动开发者按照该模型进行device的注册, Zephyr根据注册的信息可以自动完成驱动初始化

数据结构

1
2
3
4
5
6
7
8
9
10
11
struct device_config {
char *name; //驱动名,上层使用驱动时以此来寻找驱动
int (*init)(struct device *device); //驱动初始化函数
const void *config_info; //驱动配置信息,不同类型的驱动其结构不一样,一般用于保存memory IO address,IRQ号或其它物理属性
};

struct device {
struct device_config *config; //device 配置,不可变
const void *driver_api; //device drv的API,由驱动底层提供,不同类型的驱动API结构不一样
void *driver_data; //device drv的数据,可变
};

以drivers/serial/uart_stellaris.c为例:
device的配置包含了uart的基地址,时钟频率和irq配置函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct uart_device_config {
union {
u32_t port;
u8_t *base;
u32_t regs;
};

u32_t sys_clk_freq;
uart_irq_config_func_t irq_config_func;
};

static const struct uart_device_config uart_stellaris_dev_cfg_0 = {
.base = (u8_t *)TI_STELLARIS_UART_4000C000_BASE_ADDRESS,
.sys_clk_freq = UART_STELLARIS_CLK_FREQ,
.irq_config_func = irq_config_func_0,
};

device的数据包含了可改变的波特率和可由应用层设置的irq处理函数

1
2
3
4
5
6
7
8
9
static struct uart_stellaris_dev_data_t uart_stellaris_dev_data_0 = {
.baud_rate = TI_STELLARIS_UART_4000C000_CURRENT_SPEED,
};

struct uart_stellaris_dev_data_t {
u32_t baud_rate; /* Baud rate */
uart_irq_callback_t cb; /**< Callback function pointer */

};

宏DEVICE_AND_API_INIT将驱动drv name, init函数, cfg info, data, level prio和api组合成两个结构体struct device_config和struct device.
DEVICE_DEFINE和DEVICE_INIT都使用DEVICE_AND_API_INIT来完成device drv的注册,区别在于DEVICE_INIT不预先设置drv api,而是在运行时(例如init函数中)设置driver_api.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info,  \
level, prio, api) \
\
static struct device_config _CONCAT(__config_, dev_name) __used \
__attribute__((__section__(".devconfig.init"))) = { \
.name = drv_name, .init = (init_fn), \
.config_info = (cfg_info) \
}; \
static struct device _CONCAT(__device_, dev_name) __used \
__attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \
.config = &_CONCAT(__config_, dev_name), \
.driver_api = api, \
.driver_data = data \
}


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

#define DEVICE_INIT(dev_name, drv_name, init_fn, data, cfg_info, level, prio) \
DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
level, prio, NULL)

Driver Level & 优先级

Level

Zephyr在使用DEVICE_AND_API_INIT注册宏时,需要开发者为驱动指定Level,不同的驱动level初始化的时机不一样,参见Zephyr如何运行到main, Zephyr一共分为4个level:

  • PRE_KERNEL_1
    该阶段只初始化完成中断控制器,内核尚未初始化, 因此该Level的驱动可以使用中断但不能用内核服务,也不依赖其它设备驱动。该Level的驱动初始化函数执行在中断堆栈上。
  • PRE_KERNEL_2
    该阶段已经完成了PRE_KERNEL_1初始化,但内核尚未初始化,因此该Level的驱动可以使用中断和PRE_KERNEL_1的驱动,但不能使用内核服务。该Level的驱动初始化函数执行在中断堆栈上。
  • POST_KERNEL
    该阶段已完成PRE_KERNEL_2初始化和内核初始化,因此该Level的驱动可以使用其它驱动和内核服务。该Level驱动初始化函数执行在main thread的堆栈上。
  • APPLICATION
    为了应用组件使用(例如shell),可以使用其它驱动和所有的内核服务。该Level驱动初始化函数执行在main thread的堆栈上。

优先级

针对每个Level内的驱动定义优先级用于对驱动初始化的顺序进行排序, 优先级取值在0~99,数字越低该驱动就越先初始化。

优先级排序

从DEVICE_AND_API_INIT内的struct device可以看到,定义的struct device变量是被放到根据level和prio生成的段:

1
__attribute__((__section__(".init_" #level STRINGIFY(prio))))

以PRE_KERNEL_1的CONFIG_KERNEL_INIT_PRIORITY_DEVICE和CONFIG_KERNEL_INIT_PRIORITY_DEFAULT为例进行宏展开:
include/toolchain/common.h

1
2
3
4
5
#define _STRINGIFY(x) #x
#define STRINGIFY(s) _STRINGIFY(s)

#define _DO_CONCAT(x, y) x ## y
#define _CONCAT(x, y) _DO_CONCAT(x, y)

include/generated/autoconf.h(CONFIG_KERNEL_INIT_PRIORITY_xxx可以通过prj.conf配置)

1
2
#define CONFIG_KERNEL_INIT_PRIORITY_DEFAULT 40
#define CONFIG_KERNEL_INIT_PRIORITY_DEVICE 50

最后暂开为

1
2
__attribute__((__section__(".init_PRE_KERNEL_1_40)))
__attribute__((__section__(".init_PRE_KERNEL_1_50)))

优先级

在include/linker/linker-defs.h中对段进行排序

1
2
3
4
5
6
7
8
9
10
11
12
13
#define DEVICE_INIT_LEVEL(level)				\
__device_##level##_start = .; \ //某个Level device数组的开始地址
KEEP(*(SORT(.init_##level[0-9]))); \
KEEP(*(SORT(.init_##level[1-9][0-9]))); \

#define DEVICE_INIT_SECTIONS() \
__device_init_start = .; \ //所有device数组的开始地址
DEVICE_INIT_LEVEL(PRE_KERNEL_1) \
DEVICE_INIT_LEVEL(PRE_KERNEL_2) \
DEVICE_INIT_LEVEL(POST_KERNEL) \
DEVICE_INIT_LEVEL(APPLICATION) \
__device_init_end = .; \
DEVICE_BUSY_BITFIELD() \

注意

  1. 在使用DEVICE_AND_API_INIT时,优先级传入不能使用表达式,下面的优先级写法是不允许的:

    1
    DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, level, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT+5, api)
  2. 同一个level的相同优先级下面允许有多个device

驱动初始化

从DEVICE_AND_API_INIT的分析可以看到对于一个device有三点:

  • device的初始化函数的指针放到struct device_config中
  • struct device_config的指针放到struct device
  • 每个struct device被排序后放到自己的段中

当要初始化一个Level的驱动时,找到该Level的device数组开始地址,循环取出device->config->init进行调用即可
在kernel/device.c定义了初始化一个level的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static struct device *config_levels[] = {		//level device起始地址数组
__device_PRE_KERNEL_1_start,
__device_PRE_KERNEL_2_start,
__device_POST_KERNEL_start,
__device_APPLICATION_start,
/* End marker */
__device_init_end,
};

void _sys_device_do_config_level(int level)
{
struct device *info;

for (info = config_levels[level]; info < config_levels[level+1]; //取得指定level device,循环取出struct device
info++) {
struct device_config *device = info->config; //取得device_config

device->init(info); //进行初始化
_k_object_init(info);
}
}

PRE_KERNEL_1驱动初始化实例
include/init.h

1
2
3
4
#define _SYS_INIT_LEVEL_PRE_KERNEL_1	0
#define _SYS_INIT_LEVEL_PRE_KERNEL_2 1
#define _SYS_INIT_LEVEL_POST_KERNEL 2
#define _SYS_INIT_LEVEL_APPLICATION 3

kernel/init.c

1
_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_1);

使用驱动

device drv的使用者通过device_get_binding根据drv name找到struct device ,然后将device作为句柄进行系统调用

获取struct device

从前文的DEVICE_INIT_SECTIONS可以看出所有device的其实地址都是_device_init_start,因此从_device_init_start开始查找name匹配的驱动

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
struct device *device_get_binding(const char *name)
{
struct device *info;

/* Split the search into two loops: in the common scenario, where
* device names are stored in ROM (and are referenced by the user
* with CONFIG_* macros), only cheap pointer comparisons will be
* performed. Reserve string comparisons for a fallback.
*/
//如果传入的name指针和config内的一致表示找到
for (info = __device_init_start; info != __device_init_end; info++) {
if (info->driver_api != NULL && info->config->name == name) {
return info;
}
}

for (info = __device_init_start; info != __device_init_end; info++) {
if (!info->driver_api) {
continue;
}
//如果传入的name保存在其它地方,name的指针不一致,就需要比较string
if (!strcmp(name, info->config->name)) {
return info;
}
}

return NULL;
}

系统调用

Zephyr支援CPU执行在非混合模式(不区分user & kernel mode),Zephyr的系统调用是由开发者根据API规则编写代码,然后由python script生成标准接口。本文不展开说明系统调用,以后会有其它文章分析。对于无user mode的情况下一个系统调用的例子:
由python script产生的系统调用声明宏zephyr_sample/build/zephyr/include/generated/syscall_macros.h定义了syscall的实现形式,这里是无user mode系统调用的函数直接调用实现函数

1
2
3
4
5
6
#define K_SYSCALL_DECLARE2(id, name, ret, t0, p0, t1, p1) \
extern ret _impl_##name(t0 p0, t1 p1); \
static inline ret name(t0 p0, t1 p1) \
{ \
return _impl_##name(p0, p1); \
}

由python script产生的系统调用实现zephyr_sample/build/zephyr/include/generated/syscalls/uart.h实现了系统调用

1
K_SYSCALL_DECLARE2(K_SYSCALL_UART_POLL_OUT, uart_poll_out, unsigned char, struct device *, dev, unsigned char, out_char);

以上宏展开:

1
2
3
4
5
extern unsigned char _impl_uart_poll_out(struct device *dev, unsigned char out_char); \
static inline unsigned char name(struct device *dev, unsigned char out_char)
{
return _impl_uart_poll_out(dev, out_char);
}

在开发者实现的系统调用函数include/uart.h知道对应的drv应该用什么样的driver_api,并利用driver_api来完成系统调用

1
2
3
4
5
6
7
8
9
10
__syscall unsigned char uart_poll_out(struct device *dev,
unsigned char out_char);

static inline unsigned char _impl_uart_poll_out(struct device *dev,
unsigned char out_char)
{
const struct uart_driver_api *api = dev->driver_api;

return api->poll_out(dev, out_char);
}

总结

zephyr的device driver mode和使用可总结为下图和后续说明
mode

driver编写和编译

  1. driver开发者编写好driver代码,并配置和定义好
    • driver name
    • driver init函数
    • driver各种操作函数driver_api
    • driver配置信息driver_cfg
    • driver的数据信息driver_data
  2. 用DEVICE_AND_API_INIT根据level将name,init,driver_api,driver_cfg,driver_data放入指定的段中

driver初始化

在系统上电初始化时按照level的优先级使用_sys_device_do_config_level将所有的device driver进行初始化

driver的使用

1.用device_get_binding通过name获取device
2.以device为句柄呼叫通用的driver接口
3.通用接口进行系统调用
注意:同一类型的driver api可以对应多个device driver,例如同样是UART driver可以通过不同device name使用不同的uart device driver

参考

http://docs.zephyrproject.org/devices/drivers/drivers.html