Zephyr驱动模型(转自Half Coder)

本作品采用知识共享署名(https://lgl88911.gitee.io,转载过程中并未修改原文)

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

概述

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

注册驱动

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

数据结构

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配置函数

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处理函数

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.

#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生成的段:

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

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

#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配置)

#define CONFIG_KERNEL_INIT_PRIORITY_DEFAULT 40
#define CONFIG_KERNEL_INIT_PRIORITY_DEVICE 50

最后展开为

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

优先级

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

#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时,优先级传入不能使用表达式,下面的优先级写法是不允许的:

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的函数

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

#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

_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匹配的驱动

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系统调用的函数直接调用实现函数

#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实现了系统调用

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

以上宏展开:

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来完成系统调用

__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编写和编译

  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