目录
一、zephyr驱动结构分析
1. 驱动模型及其实现
1.1 驱动模板代码
1.2 对外API接口subsystem_do_this/subsystem_do_that
1.3 各硬件平台实现该驱动,填充struct api结构
2. 驱动设备注册
3. 设备初始化
4. 系统初始化
5. 获取设备
二、zephyr驱动自定义添加
1. 驱动代码目录添加
2. 设备树绑定文件目
3. 设备驱动API头文件目录
三、GPIO子系统分析(驱动举例)
1. 子系统对外API
2. 设备注册
一、zephyr驱动结构分析
1. 驱动模型及其实现
Zephyr驱动五要素如下:
1) 内核对象结构体,struct device;
2)设备申明与定义的宏,DEVICE_DEFINE/DEVICE_DT_DEFINE、;
3)设备相关的结构体,struct xxx_device_config,struct xxx_device_data;
4)设备初始化接口,int (*init)(struct device *device);
5)设备接口的结构体及其成员函数的实现,struct xxx_driver_api ;
Zephyr设备模型为配置作为系统一部分的驱动程序提供了一致的设备模型。设备模型负责初始化配置到系统中的所有驱动程序,每种类型的驱动程序(例如UART、SPI、I2C)都由通用子系统API支持的。其定义如下:
C /** * @brief Runtime device structure (in ROM) per driver instance */ struct device { /** Name of the device instance */ const char *name; /** Address of device instance config information */ const void *config; /** Address of the API structure exposed by the device instance */ const void *api; /** Address of the common device state */ struct device_state * const state; /** Address of the device instance private data */ void * const data; /** optional pointer to handles associated with the device. * * This encodes a sequence of sets of device handles that have * some relationship to this node. The individual sets are * extracted with dedicated API, such as * device_required_handles_get(). */ Z_DEVICE_HANDLES_CONST device_handle_t * const handles; #ifdef CONFIG_PM_DEVICE /** Reference to the device PM resources. */ struct pm_device * const pm; #endif }; |
name:设备实例名称。
config:用于驱动配置数据,比如I2C驱动,配置包括寄存器地址,时钟源或者设备其他物理数据等。该指针是在使用DEVICE_DEFINE()或者相关宏时传递的。
api:该结构体将通用子系统api映射到驱动程序中用于设备驱动的实现。它通常是只读的,并在构建时填充。
data:可为设备管理指定一个数据结构,比如存放一个锁或者信号,用于API中write/read的阻塞模式实现等。
大多数设备驱动模型将实现一个具备通用设备驱动API的子系统,该子系统可以构造多个设备驱动实例,每个实例中的device->api再由具体的底层驱动实现。
1.1 驱动模板代码
1.2 对外API接口subsystem_do_this/subsystem_do_that
C typedef int (*subsystem_do_this_t)(const struct device *dev, int foo, int bar); typedef void (*subsystem_do_that_t)(const struct device *dev, void *baz); struct subsystem_api { subsystem_do_this_t do_this; subsystem_do_that_t do_that; }; static inline int subsystem_do_this(const struct device *dev, int foo, int bar) { struct subsystem_api *api; api = (struct subsystem_api *)dev->api; return api->do_this(dev, foo, bar); } static inline void subsystem_do_that(const struct device *dev, void *baz) { struct subsystem_api *api; api = (struct subsystem_api *)dev->api; api->do_that(dev, baz); } |
1.3 各硬件平台实现该驱动,填充struct api结构
C static int my_driver_do_this(const struct device *dev, int foo, int bar) { ... } static void my_driver_do_that(const struct device *dev, void *baz) { ... } static struct subsystem_api my_driver_api_funcs = { .do_this = my_driver_do_this, .do_that = my_driver_do_that }; |
2. 驱动设备注册
内核在路径zephyr\include\device.h下提供了宏接口,用于设备驱动的注册,并且按照注册的参数自动进行初始化,下面介绍几个常用的宏。
DEVICE_DEFINE
C #define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_device, \ data_ptr, cfg_ptr, level, prio, api_ptr) \ Z_DEVICE_STATE_DEFINE(DT_INVALID_NODE, dev_name) \ Z_DEVICE_DEFINE(DT_INVALID_NODE, dev_name, drv_name, init_fn, \ pm_device, \ data_ptr, cfg_ptr, level, prio, api_ptr, \ &Z_DEVICE_STATE_NAME(dev_name)) |
Z_DEVICE_STATE_DEFINE
C #define Z_DEVICE_STATE_DEFINE(node_id, dev_name) \ static struct device_state Z_DEVICE_STATE_NAME(dev_name) \ __attribute__((__section__(".z_devstate"))); |
Z_DEVICE_DEFINE
C #define Z_DEVICE_DEFINE(node_id, dev_name, drv_name, init_fn, pm_device,\ data_ptr, cfg_ptr, level, prio, api_ptr, state_ptr, ...) \ Z_DEVICE_DEFINE_PRE(node_id, dev_name, __VA_ARGS__) \ COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static)) \ const Z_DECL_ALIGN(struct device) \ DEVICE_NAME_GET(dev_name) __used \ __attribute__((__section__(".z_device_" #level STRINGIFY(prio)"_"))) = { \ .name = drv_name, \ .config = (cfg_ptr), \ .api = (api_ptr), \ .state = (state_ptr), \ .data = (data_ptr), \ COND_CODE_1(CONFIG_PM_DEVICE, (.pm = pm_device,), ()) \ Z_DEVICE_DEFINE_INIT(node_id, dev_name) \ }; \ BUILD_ASSERT(sizeof(Z_STRINGIFY(drv_name)) <= Z_DEVICE_MAX_NAME_LEN, \ Z_STRINGIFY(DEVICE_NAME_GET(drv_name)) " too long"); \ Z_INIT_ENTRY_DEFINE(DEVICE_NAME_GET(dev_name), init_fn, \ (&DEVICE_NAME_GET(dev_name)), level, prio)
#ifdef __cplusplus } |
参数含义:
dev_name - 设备名称,用户可通过该名称使用device_get_binding()获取设备
drv_name - 驱动名称
inif_fn - 初始化函数指针
pm-action-cb - 电源管理回调函数指针,如果NULL表明不使用PM
data_ptr - 设备私有数据指针
cfg_ptr - 该驱动实例的配置数据指针
level,prio - 初始化等级,后面会具体介绍
api_ptr - 提供该设备驱动的struct api结构指针
可以看到是通过宏定义来声明一个struct dev,并保存至段.z_device_。不难猜出,用户调用device_get_binding()时,就会从段.z_device_通过dev_name遍历到对应的struct dev。同时通过Z_INIT_ENTRY_DEFINE宏,将初始化函数和初始化等级注册进内核,在内核启动时会相应完成自动初始化。
DEVICE_DT_DEFINE
C #define DEVICE_DT_DEFINE(node_id, init_fn, pm_device, \ data_ptr, cfg_ptr, level, prio, \ api_ptr, ...) \ Z_DEVICE_STATE_DEFINE(node_id, Z_DEVICE_DT_DEV_NAME(node_id)) \ Z_DEVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_NAME(node_id), \ DEVICE_DT_NAME(node_id), init_fn, \ pm_device, \ data_ptr, cfg_ptr, level, prio, \ api_ptr, \ &Z_DEVICE_STATE_NAME(Z_DEVICE_DT_DEV_NAME(node_id)), \ __VA_ARGS__) |
Z_DEVICE_STATE_DEFINE
C #define Z_DEVICE_STATE_DEFINE(node_id, dev_name) \ static struct device_state Z_DEVICE_STATE_NAME(dev_name) \ __attribute__((__section__(".z_devstate"))); |
Z_DEVICE_DEFINE、
C #define Z_DEVICE_DEFINE(node_id, dev_name, drv_name, init_fn, pm_device,\ data_ptr, cfg_ptr, level, prio, api_ptr, state_ptr, ...) \ Z_DEVICE_DEFINE_PRE(node_id, dev_name, __VA_ARGS__) \ COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static)) \ const Z_DECL_ALIGN(struct device) \ DEVICE_NAME_GET(dev_name) __used \ __attribute__((__section__(".z_device_" #level STRINGIFY(prio)"_"))) = { \ .name = drv_name, \ .config = (cfg_ptr), \ .api = (api_ptr), \ .state = (state_ptr), \ .data = (data_ptr), \ COND_CODE_1(CONFIG_PM_DEVICE, (.pm = pm_device,), ()) \ Z_DEVICE_DEFINE_INIT(node_id, dev_name) \ }; \ BUILD_ASSERT(sizeof(Z_STRINGIFY(drv_name)) <= Z_DEVICE_MAX_NAME_LEN, \ Z_STRINGIFY(DEVICE_NAME_GET(drv_name)) " too long"); \ Z_INIT_ENTRY_DEFINE(DEVICE_NAME_GET(dev_name), init_fn, \ (&DEVICE_NAME_GET(dev_name)), level, prio)
#ifdef __cplusplus } |
DEVICE_DT_DEFINE和DEVICE_DEFINE一样的作用,传入的参数是节点标识符,dev_name和drv_name是通过节点标识符node_id转换而来。dev_name就是Z_DEVICE_DT_DEV_NAME(node_id),drv_name是DEVICE_DT_NAME(node_id)。
该设备声明是外部可见的(即COND_CODE_1(DT_NODE_EXISTS部分),除了调用device_get_binding(),也可以直接通过DEVICE_DT_GET()获取。
DEVICE_DECLARE
C * @param name Device name*/ #define DEVICE_DECLARE(name) static const struct device DEVICE_NAME_GET(name) |
name -设备名称,声明一个静态设备对象,这个宏可以在顶层使用来声明一个设备,这样DEVICE_GET()可以在DEVICE_DEFINE()的完整声明之前使用。
3. 设备初始化
前面分析过DEVICE_DEFINE()不仅完成设备的注册,同时也完成设备初始化函数以及初始化等级的注册,最终是通过Z_INIT_ENTRY_DEFINE()来实现的,下面是具体代码实现,声明结构体struct init_entry,其成员init为注册的初始化函数,dev为该设备结构体指针,将做为初始化函数入参传递。将init_entry放到段.z_init中,同时附带上优先级参数。
C #define Z_INIT_ENTRY_DEFINE(_entry_name, _init_fn, _device, _level, _prio) \ static const Z_DECL_ALIGN(struct init_entry) \ _CONCAT(__init_, _entry_name) __used \ __attribute__((__section__(".z_init_" #_level STRINGIFY(_prio)"_"))) = { \ .init = (_init_fn), \ .dev = (_device), \ } |
C struct init_entry { /** Initialization function for the init entry which will take * the dev attribute as parameter. See below. */ int (*init)(const struct device *dev); /** Pointer to a device driver instance structure. Can be NULL * if the init entry is not used for a device driver but a services. */ const struct device *dev; }; |
优先级参数列表:
level:
PRE_KERNEL_1
用于没有依赖关系的设备,这些设备在配置期间不能使用任何内核服务,因为内核服务还不可用。然而,中断子系统将被配置,因此可以设置中断。这个级别的Init函数在中断堆栈上运行。
PRE_KERNEL_2
用于依赖于作为PRE_KERNEL_1级别一部分的初始化设备的设备。这些设备在配置期间不能使用任何内核服务,因为内核服务还不可用。这个级别的Init函数在中断堆栈上运行
POST_KERNEL
用于配置过程中需要内核服务的设备。这个级别的Init函数在内核主任务的上下文中运行
APPLICATION
用于需要自动配置的应用程序组件(即非内核组件)。这些设备可以在配置期间使用内核提供的所有服务。这个级别的Init函数在内核主任务上运行。
prio:
用于level等级相同时,该值越小越早初始化,范围为0~99。
4. 系统初始化
某些场景,只需要在启动时执行某个函数初始化,而不需要像设备注册一样传递各种参数,此时就需要使用下面两个API。其中_init_fn为初始化函数(无入参)
C #define SYS_INIT(_init_fn, _level, _prio) \ Z_INIT_ENTRY_DEFINE(Z_SYS_NAME(_init_fn), _init_fn, NULL, _level, _prio) |
C #define SYS_DEVICE_DEFINE(drv_name, init_fn, level, prio) \ __DEPRECATED_MACRO SYS_INIT(init_fn, level, prio) |
5. 获取设备
device_get_binding
通过设备名称或者通过DT_LABEL(节点标识符)获取struct device *,从段地址_device_start~_device_end从查询。
C const struct device *z_impl_device_get_binding(const char *name) { const struct device *dev;
/* A null string identifies no device. So does an empty * string. */ if ((name == NULL) || (name[0] == '\0')) { return NULL; }
/* 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. */ for (dev = __device_start; dev != __device_end; dev++) { if (z_device_is_ready(dev) && (dev->name == name)) { return dev; } }
for (dev = __device_start; dev != __device_end; dev++) { if (z_device_is_ready(dev) && (strcmp(name, dev->name) == 0)) { return dev; } }
return NULL; } |
DEVICE_DT_GET()
通过节点标识符获取设备指针,该设备是由DEVICE_DT_DEFINE(node_id, ...)注册的,代码如下:
C #define DEVICE_DT_GET(node_id) (&DEVICE_DT_NAME_GET(node_id)) #define DEVICE_DT_NAME_GET(node_id) DEVICE_NAME_GET(Z_DEVICE_DT_DEV_NAME(node_id)) |
二、zephyr驱动自定义添加
Zephyr驱动的添加可以分为3个级别:
有驱动API抽象,有设备树绑定:只用添加驱动代码
有驱动API抽象,无设备树绑定:添加设备树绑定文件和驱动代码
无驱动API抽象,无设备树绑定:添加抽象API头文件,添加设备树绑定文件,添加驱动代码
1. 驱动代码目录添加
在app/目录下添加drivers目录,在drivers/zephyr下的将要添加的驱动类型分类:
如上图所示,有各种类型的驱动文件夹,例如gpio、sensor、spi等驱动类型文件,在sensor类型驱动里又有具体的如ak8975、bma280等sensor驱动。
逐层级CMakeLists.txt添加add_subdirectory()来将新增内容完成添加构建子目录:
逐层级添加Kconfig配置文件
2. 设备树绑定文件目
为了硬件上的灵活性,Zephyr引入了设备树,通过设备树绑定的方式将设备树转换为C宏来使用。Zephyr的设备树绑定文件可能不包含需要用的硬件设备,这就需要自己添加。
3. 设备驱动API头文件目录
对于个人项目开发来说,设备驱动API一般是项目内使用,API抽象的普遍覆盖性并不一定要非常全,此外使用的人员也不需要大范围讨论,根据需求进行自定义就可以, 所形成的头文件放到对应的驱动目录即可,例如zephyr/drivers/sensor/ak8975/ak8975.h。
三、GPIO子系统分析(驱动举例)
以内核GPIO子系统来分析完整的设备驱动模型,包括应用层如何使用,底层驱动如何移植适配。
1. 子系统对外API
在include\drivers\gpio.h头文件中,定义了应用层可以调用的API,以及这些API的实现。下面以gpio_pin_configure分析,应用层可以调用gpio_pin_configure(...),其实现由z_impl_gpio_pin-configure(...)来完成。
C __syscall int gpio_pin_configure(const struct device *port, gpio_pin_t pin, gpio_flags_t flags);
static inline int z_impl_gpio_pin_configure(const struct device *port, gpio_pin_t pin, gpio_flags_t flags) { const struct gpio_driver_api *api = (const struct gpio_driver_api *)port->api; const struct gpio_driver_config *const cfg = (const struct gpio_driver_config *)port->config; struct gpio_driver_data *data = (struct gpio_driver_data *)port->data; |
struct gpio_driver_api:内核统一规范了GPIO子系统对外的API接口,不同硬件平台只需适配struct gpio_driver_api中的功能即可。
C __subsystem struct gpio_driver_api { int (*pin_configure)(const struct device *port, gpio_pin_t pin, gpio_flags_t flags); int (*port_get_raw)(const struct device *port, gpio_port_value_t *value); int (*port_set_masked_raw)(const struct device *port, gpio_port_pins_t mask, gpio_port_value_t value); int (*port_set_bits_raw)(const struct device *port, gpio_port_pins_t pins); int (*port_clear_bits_raw)(const struct device *port, gpio_port_pins_t pins); int (*port_toggle_bits)(const struct device *port, gpio_port_pins_t pins); int (*pin_interrupt_configure)(const struct device *port, gpio_pin_t pin, enum gpio_int_mode, enum gpio_int_trig); int (*manage_callback)(const struct device *port, struct gpio_callback *cb, bool set); uint32_t (*get_pending_int)(const struct device *dev); }; |
2. 设备注册
分析drivers\gpio\gpio_stm32.c代码和dts文件,看下stm32驱动是如何适配到GPIO子系统中
举例设备树中子节点信息如下:
C gpioa: gpio@48000000 { compatible = "st,stm32-gpio"; gpio-controller; #gpio-cells = < 0x2 >; reg = < 0x48000000 0x400 >; clocks = < &rcc 0x1 0x1 >; label = "GPIOA"; phandle = < 0x22 >; }; |
如果gpioa节点存在,则执行GPIO_DEVICE_INIT_STM32(a, A)
C #if DT_NODE_HAS_STATUS(DT_NODELABEL(gpioa), okay) GPIO_DEVICE_INIT_STM32(a, A); #endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpioa), okay) */ |
通过设备树节点标识符DT_NODELABEL(gpioa),获取相关属性:reg,clocks等
C #define GPIO_DEVICE_INIT_STM32(__suffix, __SUFFIX) \ GPIO_DEVICE_INIT(DT_NODELABEL(gpio##__suffix), \ __suffix, \ DT_REG_ADDR(DT_NODELABEL(gpio##__suffix)), \ STM32_PORT##__SUFFIX, \ DT_CLOCKS_CELL(DT_NODELABEL(gpio##__suffix), bits), DT_CLOCKS_CELL(DT_NODELABEL(gpio##__suffix), bus)) |
GPIO_DEVICE_INIT()做3件事:生成gpio_stm32_config,gpio_stm32_data,再完成设备注册
C #define GPIO_DEVICE_INIT(__node, __suffix, __base_addr, __port, __cenr, __bus) \ //4.1 将设备树获取的数据,生成自定义的gpio_stm32_config static const struct gpio_stm32_config gpio_stm32_cfg_## __suffix = { \ .common = { \ .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_NGPIOS(16U), \ }, \ .base = (uint32_t *)__base_addr, \ .port = __port, \ .pclken = { .bus = __bus, .enr = __cenr } \ }; /*4.2 声明一个私有数据gpio_stm32_data*/ \ static struct gpio_stm32_data gpio_stm32_data_## __suffix; \ PM_DEVICE_DT_DEFINE(__node, gpio_stm32_pm_action); \ /* 4.3 完成设备注册,包括初始化函数gpio_stm32_init,电源管理gpio_stm32_pm_device_ctrl, 私有数据&gpio_stm32_data_## __suffix, 私有配置&gpio_stm32_cfg_## __suffix, 初始化等级PRE_KERNEL_1,CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, 以及子系统需要适配的struct gpio_driver_api:&gpio_stm32_driver */ DEVICE_DT_DEFINE(__node, \ gpio_stm32_init, \ PM_DEVICE_DT_GET(__node), \ &gpio_stm32_data_## __suffix, \ &gpio_stm32_cfg_## __suffix, \ PRE_KERNEL_1, \ CONFIG_GPIO_INIT_PRIORITY, \ &gpio_stm32_driver) |
DEVICE_DT_DEFINE()将初始化函数gpio_stm32_init()注册进系统,在内核启动阶段会按优先级自动执行。看代码主要完成一些时钟和电源管理的配置,gpio_stm32_driver完成底层驱动需适配的功能函数。
3. 应用层如何使用
前面已介绍了stm32-gpio通过设备树子节点gpioa: gpio@48000000将GPIOA注册进子系统,下面介绍用户如何使用。
3.1 如何获取设备
device_get_binding(DT_LABEL(节点标识符))
C const struct device *gpioa_dev; static int pins_stm32_init(void) { gpioa = device_get_binding(DT_LABEL(DT_NODELABEL(gpioa_dev))); if (!gpioa) { return -ENODEV; } } |
DEVICE_DT_GET(节点标识符)
C struct device *gpioa_dev; static int pins_stm32_init(void) { gpioa = DEVICE_DT_GET(DT_NODELABEL(gpioa_dev)); if (!gpioa) { return -ENODEV; } } |
3.2 如何使用设备
直接通过获取的struct device调用子系统API控制驱动设备
C gpio_pin_set(gpioa, PIN_INX, 1); gpio_pin_configure(gpioa, PIN_INX, GPIO_OUTPUT_ACTIVE | FLAGS); |