本文介绍zephyr驱动模型,分析zephyr驱动架构,原理和使用方法。本文只分析了无user mode和无电源管理情况下Zephyr的驱动模型。
概述
Zephyr支持各种驱动,根据板子来选择编译使用哪些驱动可用。为了缩小image,驱动是采用配置buildin的形式,不使用的驱动是不会被编译的。
Zephyr提供的模型主要是完成注册驱动和驱动初始化,按照驱动类型提供通用的驱动接口(不同于Linux,所有驱动都是open,close,write,read),并提供注册系统调用的宏,具体的驱动由驱动开发者编写。
Zephyr的驱动都需要采用中断,除非是device硬件不支持中断。
注册驱动
include/device.h中提供了一组数据结构和宏,让驱动开发者按照该模型进行device的注册, Zephyr根据注册的信息可以自动完成驱动初始化
数据结构
1 | struct device_config { |
以drivers/serial/uart_stellaris.c为例:
device的配置包含了uart的基地址,时钟频率和irq配置函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16struct 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
9static 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.h1
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() \
注意
在使用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)
同一个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
21static 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.h1
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.c1
_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
28struct 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
5extern 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和使用可总结为下图和后续说明
driver编写和编译
- driver开发者编写好driver代码,并配置和定义好
- driver name
- driver init函数
- driver各种操作函数driver_api
- driver配置信息driver_cfg
- driver的数据信息driver_data
- 用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