目录
1 介绍
Zephyr设备模型为配置作为系统一部分的驱动程序提供了一致的设备模型。设备模型负责初始化配置到系统中的所有驱动程序,每种类型的驱动程序(例如UART、SPI、I2C)都由通用子系统API支持的。
2 数据结构
//只罗列了几个主要成员项
struct device {
const char *name;
const void *config;
const void *api;
void * const data;
}
- name:设备实例名称。
- config:用于驱动配置数据,比如I2C驱动,寄存器地址,时钟源或者设备其他物理数据等等。该指针是在使用DEVICE_DEFINE()或者相关宏时传递的。
- api:该结构体将通用子系统api映射到驱动程序中用于设备驱动的实现。它通常是只读的,并在构建时填充。
- data:可为设备管理指定一个数据结构,比如存放一个锁或者信号,用于API中write/read的阻塞模式实现,等等。
3 系统框架
大多数设备驱动程序将实现一个具备通用设备驱动API的子系统,该子系统可以构造多个设备驱动实例,每个实例中的device->api再由具体的底层驱动实现。
3.1 基本代码介绍
1. 提供了对外API接口subsystem_do_this/subsystem_do_that
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);
}
2. 各硬件平台实现该驱动,填充struct api结构
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
};
4 注册设备
内核在路径zephyr\include\device.h下提供了宏接口,用于设备驱动的注册,并且按照注册的参数自动进行初始化。下面介绍几个常用的宏
4.1 DEVICE_DEFINE
1)参数说明
DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_action_cb, data_ptr, cfg_ptr, level, prio, api_ptr)
- dev_name - 设备名称,用户可通过该名称使用device_get_binding()获取设备
- drv_name - 驱动名称
- inif_fn - 初始化函数指针
- pm-action-cb - 电源管理回调函数指针,如果NULL表明不使用PM
- data_ptr - 设备私有数据指针
- cfg_ptr - 该驱动实例的配置数据指针
- level,prio - 初始化等级,后面会具体介绍
- api_ptr - 提供该设备驱动的struct api结构指针
2)代码实现
- 下面是具体代码实现,可以看到是通过宏定义来声明一个struct dev,并保存至段.z_device_。不难猜出,用户调用device_get_binding()时,就会从段.z_device_通过dev_name遍历到对应的struct dev。同时通过Z_INIT_ENTRY_DEFINE宏,将初始化函数和初始化等级注册进内核,在内核启动时会相应完成自动初始化
//通过Z_DEVICE_DEFINE实现DEVICE_DEFINE
#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_action_cb, \
data_ptr, cfg_ptr, level, prio, api_ptr) \
Z_DEVICE_DEFINE(DT_INVALID_NODE, dev_name, drv_name, init_fn, \
pm_action_cb, \
data_ptr, cfg_ptr, level, prio, api_ptr)
//具体的Z_DEVICE_DEFINE实现
#define Z_DEVICE_DEFINE(node_id, dev_name, drv_name, init_fn, pm_action_cb, \
data_ptr, cfg_ptr, level, prio, api_ptr, ...) \
Z_DEVICE_DEFINE_PRE(node_id, dev_name, pm_action_cb, __VA_ARGS__) \
//声明struct device结构体
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 = &Z_DEVICE_STATE_NAME(dev_name), \
.data = (data_ptr), \
Z_DEVICE_DEFINE_INIT(node_id, dev_name) \
};
//设定了drv_name的最大长度
BUILD_ASSERT(sizeof(Z_STRINGIFY(drv_name)) <= Z_DEVICE_MAX_NAME_LEN, \
Z_STRINGIFY(DEVICE_NAME_GET(drv_name)) " too long"); \
//将初始化函数init_fn,函数入参即dev指针,初始化等级level,prio注册进内核
Z_INIT_ENTRY_DEFINE(DEVICE_NAME_GET(dev_name), init_fn, \
(&DEVICE_NAME_GET(dev_name)), level, prio)
4.2 DEVICE_DT_DEFINE
DEVICE_DT_DEFINE(node_id, init_fn, pm_action_cb,data_ptr, cfg_ptr, level, prio, api_ptr, ...)
- 和DEVICE_DEFINE一样的作用,传入的参数是节点标识符,dev_name和drv_name是通过节点标识符node_id转换而来。dev_name就是Z_DEVICE_DT_DEV_NAME(node_id),drv_name是DEVICE_DT_NAME(node_id)。
#define DEVICE_DT_DEFINE(node_id, init_fn, pm_action_cb, \
data_ptr, cfg_ptr, level, prio, \
api_ptr, ...) \
Z_DEVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_NAME(node_id), \
DEVICE_DT_NAME(node_id), init_fn, \
pm_action_cb, \
data_ptr, cfg_ptr, level, prio, \
api_ptr, __VA_ARGS__)
- 该设备声明是外部可见的,除了调用device_get_binding(),也可以直接通过DEVICE_DT_GET()获取。
a.这句话如何理解?
先看第一句话“声明是外部可见的”,这里就需要重新回顾Z_DEVICE_DEFINE()的实现
//具体的Z_DEVICE_DEFINE实现,只显示本次讨论的关键部分
//注意看COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static))
/*
该宏简单理解就是
if(DT_NODE_EXISTS(node_id))
COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static)) = ()
else
COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static)) = (static)
*/
//我们再来看DEVICE_DEFINE()该参数是DT_INVALID_NODE,所以声明的struct device是static,而DEVICE_DT_DEFINE
//传递进来的node_id肯定是有效的,所有声明的struct device是对外可见的。
#define Z_DEVICE_DEFINE(node_id, dev_name, drv_name, init_fn, pm_action_cb, \
data_ptr, cfg_ptr, level, prio, api_ptr, ...) \
//声明struct device结构体
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 = &Z_DEVICE_STATE_NAME(dev_name), \
.data = (data_ptr), \
Z_DEVICE_DEFINE_INIT(node_id, dev_name) \
};
b. 为什么对外可见的设备声明就可以通过DEVICE_DT_GET()获取? 这里见后续DEVICE_DT_GET()分析就知道了,简单说明DEVICE_DT_GET()就是获取一个全局变量的设备,比如&device_A
4.3 DEVICE_DECLARE
DEVICE_DECLARE(name)
- name -设备名称
- 声明一个静态设备对象,这个宏可以在顶层使用来声明一个设备,这样DEVICE_GET()可以在DEVICE_DEFINE()的完整声明之前使用。
5 设备初始化
前面分析过DEVICE_DEFINE()不仅完成设备的注册,同时也完成设备初始化函数以及初始化等级的注册,最终是通过Z_INIT_ENTRY_DEFINE()来实现的。
5.1 代码实现
- 下面是具体代码实现,声明结构体struct init_entry,其成员init为注册的初始化函数,dev为该设备结构体指针,将做为初始化函数入参传递。将init_entry放到段.z_init中,同时附带上优先级参数。
#define Z_INIT_ENTRY_DEFINE(_entry_name, _init_fn, _device, _level, _prio) \
//声明一个结构体stuct init_entry
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), \
}
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;
};
5.2 优先级
- level
内核规定4个优先级
PRE_KERNEL_1 | 用于没有依赖关系的设备,这些设备在配置期间不能使用任何内核服务,因为内核服务还不可用。然而,中断子系统将被配置,因此可以设置中断。这个级别的Init函数在中断堆栈上运行。 |
PRE_KERNEL_2 | 用于依赖于作为PRE_KERNEL_1级别一部分的初始化设备的设备。这些设备在配置期间不能使用任何内核服务,因为内核服务还不可用。这个级别的Init函数在中断堆栈上运行 |
POST_KERNEL | 用于配置过程中需要内核服务的设备。这个级别的Init函数在内核主任务的上下文中运行 |
APPLICATION | 用于需要自动配置的应用程序组件(即非内核组件)。这些设备可以在配置期间使用内核提供的所有服务。这个级别的Init函数在内核主任务上运行。 |
- prio
用于level等级相同时,该值越小越早初始化,范围为0~99.
PS:该值不能使用符号表达式例如CONFIG_KERNEL_INIT_PRIORITY_DEFAULT + 5
6 系统初始化
某些场景,只需要在启动时执行某个函数初始化,而不需要像设备注册一样传递各种参数,此时就需要使用下面两个API。
- SYS_INIT(_init_fn, _level, _prio)
_init_fn - 初始化函数(无入参)
_level,_prio - 初始化等级,和设备初始化中的等级一样。
- SYS_DEVICE_DEFINE(drv_name, init_fn, level, prio)
和SYS_INIT()一样,多一个驱动名称参数而已
7 如何获取设备
7.1 device_get_binding
- 通过设备名称获取struct device *;或者通过DT_LABEL(节点标识符)
- 从段地址_device_start~_device_end从查询
__syscall const struct device *device_get_binding(const char *name);
const struct device *z_impl_device_get_binding(const char *name)
{
const struct device *dev;
/* 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_ready(dev) && (dev->name == name)) {
return dev;
}
}
for (dev = __device_start; dev != __device_end; dev++) {
if (z_device_ready(dev) && (strcmp(name, dev->name) == 0)) {
return dev;
}
}
return NULL;
}
7.2 DEVICE_DT_GET()
- 通过节点标识符获取设备指针,该设备是由DEVICE_DT_DEFINE(node_id, ...)注册的。
- 来看下代码实现,和device_get_binding()有很大的区别
//就是返回一个宏, 比如一个全局变量&dev_A
#define DEVICE_DT_GET(node_id) (&DEVICE_DT_NAME_GET(node_id))
//接着看下DEVICE_DT_NAME_GET(),返回的就是DEVICE_DT_DEFINE(node_id)创建的设备对象名称
/**
* @def DEVICE_DT_NAME_GET
*
* @return The expanded name of the device object created by
* DEVICE_DT_DEFINE()
*/
#define DEVICE_DT_NAME_GET(node_id) DEVICE_NAME_GET(Z_DEVICE_DT_DEV_NAME(node_id))
8 GPIO子系统分析
以内核GPIO子系统来分析完整的设备驱动模型,包括应用层如何使用,底层驱动如何移植适配。
8.1 子系统对外API
- 在include\drivers\gpio.h头文件中,定义了应用层可以调用的API,以及这些API的实现。下面以gpio_pin_configure分析。
/*
1. 这里的_syscall如何转换为z_impl_暂时无需理会,后续分析syscall会提到。这里简单理解,应用层可以调用
gpio_pin_configure(dev, ...),该函数是通过z_impl_gpio_pin_configure()来实现
2. 从struct device *port获取到struct gpio_driver_api,调用api->pin_configure()实现功能
*/
__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;
...
return api->pin_configure(port, pin, flags);
}
8.2 struct gpio_driver_api
- 内核统一规范了GPIO子系统对外的API接口,不同硬件平台只需适配struct gpio_driver_api中的功能即可
__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);
};
8.3 注册设备
- 分析drivers\gpio\gpio_stm32.c代码和dts文件,看下stm32驱动是如何适配到GPIO子系统中
//1. 设备树中有gpioa子节点,描述了GPIOA bank的硬件信息
gpioa: gpio@48000000 {
compatible = "st,stm32-gpio";
gpio-controller;
#gpio-cells = < 0x2 >;
reg = < 0x48000000 0x400 >;
clocks = < &rcc 0x1 0x1 >;
label = "GPIOA";
phandle = < 0x22 >;
};
//2. 如果存在该节点,则执行GPIO_DEVICE_INIT_STM32(a, A)
#if DT_NODE_HAS_STATUS(DT_NODELABEL(gpioa), okay)
GPIO_DEVICE_INIT_STM32(a, A);
#endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpioa), okay) */
//3. 通过设备树节点标识符DT_NODELABEL(gpioa),获取相关属性:reg,clocks等
#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))
//4. GPIO_DEVICE_INIT()做3件事:生成gpio_stm32_config,gpio_stm32_data,再完成设备注册
#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; \
/*
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, \
gpio_stm32_pm_device_ctrl, \
&gpio_stm32_data_## __suffix, \
&gpio_stm32_cfg_## __suffix, \
PRE_KERNEL_1, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&gpio_stm32_driver)
1)gpio_stm32_init(struct device *dev)
- DEVICE_DT_DEFINE()将初始化函数gpio_stm32_init()注册进系统,在内核启动阶段会按优先级自动执行。看代码主要完成一些时钟和电源管理的配置
static int gpio_stm32_init(const struct device *dev)
{
struct gpio_stm32_data *data = dev->data;
data->dev = dev;
#if defined(PWR_CR2_IOSV) && DT_NODE_HAS_STATUS(DT_NODELABEL(gpiog), okay)
z_stm32_hsem_lock(CFG_HW_RCC_SEMID, HSEM_LOCK_DEFAULT_RETRY);
/* Port G[15:2] requires external power supply */
/* Cf: L4/L5 RM, Chapter "Independent I/O supply rail" */
LL_PWR_EnableVddIO2();
z_stm32_hsem_unlock(CFG_HW_RCC_SEMID);
#endif
#ifdef CONFIG_PM_DEVICE_RUNTIME
pm_device_enable(dev);
return 0;
#else
return gpio_stm32_clock_request(dev, true);
#endif
}
2)gpio_stm32_driver
- gpio_stm32_driver完成底层驱动需适配的功能函数
static const struct gpio_driver_api gpio_stm32_driver = {
.pin_configure = gpio_stm32_config,
.port_get_raw = gpio_stm32_port_get_raw,
.port_set_masked_raw = gpio_stm32_port_set_masked_raw,
.port_set_bits_raw = gpio_stm32_port_set_bits_raw,
.port_clear_bits_raw = gpio_stm32_port_clear_bits_raw,
.port_toggle_bits = gpio_stm32_port_toggle_bits,
.pin_interrupt_configure = gpio_stm32_pin_interrupt_configure,
.manage_callback = gpio_stm32_manage_callback,
};
//分析下gpio_stm32_port_set_bits_raw()做了什么
static int gpio_stm32_port_set_bits_raw(const struct device *dev,
gpio_port_pins_t pins)
{
//1. 从dev->config中获取到寄存器地址base
const struct gpio_stm32_config *cfg = dev->config;
GPIO_TypeDef *gpio = (GPIO_TypeDef *)cfg->base;
//2. 直接操作寄存器完成适配
WRITE_REG(gpio->BSRR, pins);
return 0;
}
8.4 应用层如何使用
- 前面已介绍了stm32-gpio通过设备树子节点gpioa: gpio@48000000将GPIOA注册进子系统,下面介绍用户如何使用
1)如何获取设备
- device_get_binding(DT_LABEL(节点标识符))
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(节点标识符)
struct device *gpioa_dev;
static int pins_stm32_init(void)
{
gpioa = DEVICE_DT_GET(DT_NODELABEL(gpioa_dev));
if (!gpioa) {
return -ENODEV;
}
}
2)如何使用设备
- 直接通过获取的struct device调用子系统API控制驱动设备
gpio_pin_set(gpioa, PIN_INX, 1);
gpio_pin_configure(gpioa, PIN_INX, GPIO_OUTPUT_ACTIVE | FLAGS);