一、概述
- zephyr内核支持很多种驱动,但是在zephyr应用中所支持的驱动,则是在zephyr应用编译时通过CONFIG配置来选择的。以此来达到控制生成文件大小、内核功能、驱动裁剪的效果。
- 与Linux设备驱动不同,zephyr上对于不同类型的设备,定义了不同类型的驱动接口(或称:系统调用),这些接口都定义在include文件夹内的头文件中,如:./zephyr/include/driver/iic.c
- 虽然不同类型的设备驱动接口不同,但是其基本模型是一样的,本文仅分析基本模型,并实现一个较为完整的驱动。
zephyr如今的代码已经更新到V2.6,在驱动中包含了大量的设备树的使用,为更好地分析原理,我们采用V1.14版本的代码来学习。后续有机会在单独学习一下zephyr中设备树的使用。
二、相关文件:
/zephyr/include/device.h
/zephyr/kernel/device.c
三、zephyr驱动的基本要素
先说重点,一个zephyr的驱动,需要包含以下五个基本元素:
- 两个重要的内核对象结构体,struct device, struct device_config
- 一个设备申明与定义的宏,DEVICE_AND_API_INIT
- 两个重要的设备相关的结构体,struct xxx_device_config,struct xxx_device_data
- 一个设备初始化接口,int (*init)(struct device *device)
- 一个设备接口的结构体及其成员函数的实现,struct xxx_driver_api?
四、重要的数据结构之一 ? ? 对于一个驱动设备来说,一方面需要能够被内核管理,另一方面又需要保留一些设备特有信息,因此在zephyr提供了几个重要的数据结构来满足二者的需求。
/**device.h 所有驱动通用,zephyr内核对象,用于管理所有的设备,**/
struct device
/**device.h 所有驱动通用,zephyr内核对象,属于struct device的成员**/
struct device_config
????????这两个对象是zephyr的内核对象,结构体的定义在device.h文件中。可以使用宏:DEVICE_AND_API_INIT来定义,也是应用代码通过系统调用来访问设备驱动的关键对象。其定义如下:
/** Version:V1.14, directory:/zephyr/include/device.h **/
struct device_config {
const char *name; /*驱动名称*/
int (*init)(struct device *device); /*设备初始化接口*/
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
int (*device_pm_control)(struct device *device, u32_t command,
void *context, device_pm_cb cb, void *arg);
struct device_pm *pm;
#endif
const void *config_info; /*驱动设备私有配置,struct xxx_device_config*/
};
struct device {
struct device_config *config; /** **/
const void *driver_api; /** 驱动的api接口,共有代码**/
void *driver_data; /** 驱动设备的私有数据 struct xxx_device_data**/
#if defined(__x86_64) && __SIZEOF_POINTER__ == 4
/* The x32 ABI hits an edge case. This is a 12 byte struct,
* but the x86_64 linker will pack them only in units of 8
* bytes, leading to alignment problems when iterating over
* the link-time array.
*/
void *padding;
#endif
};
? ? ? ? 从这两个结构体的成员可以看出:驱动基本元素中的三个都被包含在里面,从而可以实现对驱动设备私有数据的访问;而对于内核管理驱动设备,则可以通过宏来体现。 五、基本元素之——重要的宏
? ? ? ? 先来看一下宏的定义。通过该宏定义了两个结构体变量,并向其中填充了驱动api、以及驱动设备的data、cfg_info。宏定义中的其他传参的作用,详见注释。
/** Version:zephyrV1.14, Directory:/zephyr/include/device.h **/
/**
* @param dev_name input,设备名,一般没啥作用;
drv_name input,重要参数,设备的驱动名,用于在device_get_binding中查找设备
init_fn input,设备的初始化接口,在内核启动的驱动设备初始化阶段被调用
data input,其实就是struct xxx_device_data 类型的变量
cfg_info input,其实就是struct xxx_device_config 类型的变量
level input,重要参数,与prio一起确定设备的初始化顺序
prio input,重要参数,与level一起驱动设备的初始化顺序
api input,stuct xxx_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 \
}
? ? ? ? 再来看一下struct device类型的变量定义。通过__attribut__ 设置了变量 __device_dev_name在编译后所属的段为(".init_" #level STRINGIFY(prio)),这是一个与level&prio相关的段,在zephyr应用完成编译之后,会根据level & prio 参数将所有的驱动设备汇聚在一起。
????????在zephyr运行时,会将所有的device变量拷贝到内存中,并根据level & prio参数完成设备的初始化(详见:zephyr如何运行到main)。
static struct device _CONCAT(__device_, dev_name) __used ? ? ? ? ?\ __attribute__((__section__(".init_" #level STRINGIFY(prio)))) = {? ?........ }
zephyr的驱动设备是通过struct device结构体来管理的。在内核启动过程中,驱动设备会根据 DEVICE_AND_API_INIT 宏中所传规定的level & prio 参数在内核启动的不同阶段(详见:zephyr如何运行到main)完成初始化。
/** 不同类型设备驱动特有对象,用于保存驱动设备中特有的一些不可更改的重要数据,如IRQ号**/
strcut xxx_device_config
/**驱动特有对象,用于保存驱动设备中一些可供用户访问、修改的参数,如波特率等。**/
struct xxx_device_data
/**驱动特有对象,用于向应用层提供的接口**/
struct xxx_driver_api
五、zephyr驱动的实现
? ? ? ? 从前文可以看到,要实现一个zephyr的驱动,并实例化相应的驱动设备,需要调用DEVICE_AND_API_INIT宏来定义device、device_config类型的变量。为了定义变量则需要定义变量所需的一些成员变量。这些成员变量与驱动类型有关,下面以IIC驱动来简单的分析一下驱动的实现。
未完待续。。。
|