目录
一、zephyr驱动结构分析
1. 驱动模型及其实现
1.1 驱动模板代码
1.2 对外API接口subsystem_do_this/subsystem_do_that
1.3 各硬件平台实现该驱动,填充SEO靠我struct api结构
2. 驱动设备注册
3. 设备初始化
4. 系统初始化
5. 获取设备
二、zephyr驱动自定义添加
1. 驱动代码目录添加
2. 设备树绑定文件目
3. 设备驱动API头文件目录
三、GSEO靠我PIO子系统分析(驱动举例)
1. 子系统对外API
2. 设备注册
Zephyr驱动五要素如下:
1) 内核对象结构体,struct device;
2)设SEO靠我备申明与定义的宏,DEVICE_DEFINE/DEVICE_DT_DEFINE、;
3)设备相关的结构体,struct xxx_device_config,struct xxx_device_data;SEO靠我
4)设备初始化接口,int (*init)(struct device *device);
5)设备接口的结构体及其成员函数的实现,struct xxx_driver_api ;
Zephyr设备模型为配SEO靠我置作为系统一部分的驱动程序提供了一致的设备模型。设备模型负责初始化配置到系统中的所有驱动程序,每种类型的驱动程序(例如UART、SPI、I2C)都由通用子系统API支持的。其定义如下:
C
/**
* @bSEO靠我rief Runtime device structure (in ROM) per driver instance
*/
struct device {
/** Name of the device inSEO靠我stance */
const char *name;
/** Address of device instance config information */
const void *config;
/**SEO靠我 Address of the API structure exposed by the device instance */
const void *api;
/** Address of the coSEO靠我mmon device state */
struct device_state * const state;
/** Address of the device instance private datSEO靠我a */
void * const data;
/** optional pointer to handles associated with the device.
*
* This encodes a sSEO靠我equence of sets of device handles that have
* some relationship to this node. The individual sets areSEO靠我
* extracted with dedicated API, such as
* device_required_handles_get().
*/
Z_DEVICE_HANDLES_CONST deviSEO靠我ce_handle_t * const handles;
#ifdef CONFIG_PM_DEVICE
/** Reference to the device PM resources. */
strucSEO靠我t pm_device * const pm;
#endif
};name:设备实例名称。
config:用于驱动配置数据,比如I2C驱动,配置包括寄存器地址,时钟源或者设备其他物理数据等。该指针是在使用DSEO靠我EVICE_DEFINE()或者相关宏时传递的。
api:该结构体将通用子系统api映射到驱动程序中用于设备驱动的实现。它通常是只读的,并在构建时填充。
data:可为设备管理指定一个数据结构,比如存放一SEO靠我个锁或者信号,用于API中write/read的阻塞模式实现等。
大多数设备驱动模型将实现一个具备通用设备驱动API的子系统,该子系统可以构造多个设备驱动实例,每个实例中的device->api再由具体SEO靠我的底层驱动实现。
C
typedef int (*subsystem_do_this_t)(coSEO靠我nst struct device *dev, int foo, int bar);
typedef void (*subsystem_do_that_t)(const struct device *dSEO靠我ev, void *baz);
struct subsystem_api {
subsystem_do_this_t do_this;
subsystem_do_that_t do_that;
};
statiSEO靠我c inline int subsystem_do_this(const struct device *dev, int foo, int bar)
{
struct subsystem_api *apiSEO靠我;
api = (struct subsystem_api *)dev->api;
return api->do_this(dev, foo, bar);
}
static inline void subsySEO靠我stem_do_that(const struct device *dev, void *baz)
{
struct subsystem_api *api;
api = (struct subsystem_SEO靠我api *)dev->api;
api->do_that(dev, baz);
}C
static int my_driver_do_this(consSEO靠我t struct device *dev, int foo, int bar)
{
...
}
static void my_driver_do_that(const struct device *dev, SEO靠我void *baz)
{
...
}
static struct subsystem_api my_driver_api_funcs = {
.do_this = my_driver_do_this,
.do_tSEO靠我hat = my_driver_do_that
};内核在路径zephyr\include\device.h下提供了宏接口,用于设备驱动的注册,并且按照注册的参数自动进行初始化,下面介绍SEO靠我几个常用的宏。
DEVICE_DEFINE
C
#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_device, \
dSEO靠我ata_ptr, cfg_ptr, level, prio, api_ptr) \
Z_DEVICE_STATE_DEFINE(DT_INVALID_NODE, dev_nSEO靠我ame) \
Z_DEVICE_DEFINE(DT_INVALID_NODE, dev_name, drv_name, init_fn, \
pm_device, SEO靠我 \
data_ptr, cfg_ptr, level, prio, api_ptr, \
&Z_DEVICE_STATE_NAME(devSEO靠我_name))Z_DEVICE_STATE_DEFINE
C
#define Z_DEVICE_STATE_DEFINE(node_id, dev_name) SEO靠我 \
static struct device_state Z_DEVICE_STATE_NAME(dev_name) \
__attribute__((__section__(".z_deSEO靠我vstate")));Z_DEVICE_DEFINE
C
#define Z_DEVICE_DEFINE(node_id, dev_name, drv_name, init_fn, pm_device,\SEO靠我
data_ptr, cfg_ptr, level, prio, api_ptr, state_ptr, ...) \
Z_DEVICE_DEFINE_PRE(node_id, dev_naSEO靠我me, __VA_ARGS__) \
COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static)) \SEO靠我
const Z_DECL_ALIGN(struct device) \
DEVICE_NAME_GET(dev_name) __used SEO靠我 \
__attribute__((__section__(".z_device_" #level STRINGIFY(prio)"_"))) = { \
.name = drvSEO靠我_name, \
.config = (cfg_ptr), SEO靠我 \
.api = (api_ptr), \
.state = (state_ptr), SEO靠我 \
.data = (data_ptr), \
COND_CODE_1(SEO靠我CONFIG_PM_DEVICE, (.pm = pm_device,), ()) \
Z_DEVICE_DEFINE_INIT(node_id, dev_name) SEO靠我 \
}; \
BUILD_ASSERT(sizeofSEO靠我(Z_STRINGIFY(drv_name)) <= Z_DEVICE_MAX_NAME_LEN, \
Z_STRINGIFY(DEVICE_NAME_GET(drv_name)) " too longSEO靠我"); \
Z_INIT_ENTRY_DEFINE(DEVICE_NAME_GET(dev_name), init_fn, \
(&DEVICE_NAME_GET(dev_nSEO靠我ame)), level, prio)
#ifdef __cplusplus
}参数含义:
dev_name - 设备名称,用户可通过该名称使用device_get_binding()获取设备
drv_namSEO靠我e - 驱动名称
inif_fn - 初始化函数指针
pm-action-cb - 电源管理回调函数指针,如果NULL表明不使用PM
data_ptr - 设备私有数据指针
cfg_ptr - 该驱动实例的配SEO靠我置数据指针
level,prio - 初始化等级,后面会具体介绍
api_ptr - 提供该设备驱动的struct api结构指针
可以看到是通过宏定义来声明一个struct dev,并保存至段.z_devSEO靠我ice_。不难猜出,用户调用device_get_binding()时,就会从段.z_device_通过dev_name遍历到对应的struct dev。同时通过Z_INIT_ENTRY_DEFINESEO靠我宏,将初始化函数和初始化等级注册进内核,在内核启动时会相应完成自动初始化。
DEVICE_DT_DEFINE
C
#define DEVICE_DT_DEFINE(node_id, init_fn, pm_SEO靠我device, \
data_ptr, cfg_ptr, level, prio, \
api_ptr, ...) SEO靠我 \
Z_DEVICE_STATE_DEFINE(node_id, Z_DEVICE_DT_DEV_NAME(node_id)) \
Z_DSEO靠我EVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_NAME(node_id), \
DEVICE_DT_NAME(node_id), init_fSEO靠我n, \
pm_device, \
data_ptr, cfg_ptr, level, prioSEO靠我, \
api_ptr, \
&Z_DEVICE_STATE_NAME(Z_DESEO靠我VICE_DT_DEV_NAME(node_id)), \
__VA_ARGS__)Z_DEVICE_STATE_DEFINE
C
#define Z_DEVICE_STATE_DEFINE(SEO靠我node_id, dev_name) \
static struct device_state Z_DEVICE_STATE_NAME(dev_name) SEO靠我 \
__attribute__((__section__(".z_devstate")));Z_DEVICE_DEFINE、
C
#define Z_DEVICE_DEFINE(node_idSEO靠我, dev_name, drv_name, init_fn, pm_device,\
data_ptr, cfg_ptr, level, prio, api_ptr, state_ptr, ...) SEO靠我 \
Z_DEVICE_DEFINE_PRE(node_id, dev_name, __VA_ARGS__) \
COND_CODE_1(DT_NODE_EXISTSEO靠我S(node_id), (), (static)) \
const Z_DECL_ALIGN(struct device) \SEO靠我
DEVICE_NAME_GET(dev_name) __used \
__attribute__((__section__(".z_device_" #leSEO靠我vel STRINGIFY(prio)"_"))) = { \
.name = drv_name, \
.config = (SEO靠我cfg_ptr), \
.api = (api_ptr), SEO靠我 \
.state = (state_ptr), \
.data = (data_ptr), SEO靠我 \
COND_CODE_1(CONFIG_PM_DEVICE, (.pm = pm_device,), ()) \
Z_DEVICESEO靠我_DEFINE_INIT(node_id, dev_name) \
}; SEO靠我 \
BUILD_ASSERT(sizeof(Z_STRINGIFY(drv_name)) <= Z_DEVICE_MAX_NAME_LEN, \
Z_STRINSEO靠我GIFY(DEVICE_NAME_GET(drv_name)) " too long"); \
Z_INIT_ENTRY_DEFINE(DEVICE_NAME_GET(dev_name), init_fSEO靠我n, \
(&DEVICE_NAME_GET(dev_name)), level, prio)
#ifdef __cplusplus
}DEVICE_DT_DEFINE和DEVSEO靠我ICE_DEFINE一样的作用,传入的参数是节点标识符,dev_name和drv_name是通过节点标识符node_id转换而来。dev_name就是Z_DEVICE_DT_DEV_NAME(nodeSEO靠我_id),drv_name是DEVICE_DT_NAME(node_id)。
该设备声明是外部可见的(即COND_CODE_1(DT_NODE_EXISTS部分),除了调用device_get_bindSEO靠我ing(),也可以直接通过DEVICE_DT_GET()获取。
DEVICE_DECLARE
C
* @param name Device name*/
#define DEVICE_DECLARE(nameSEO靠我) static const struct device DEVICE_NAME_GET(name)name -设备名称,声明一个静态设备对象,这个宏可以在顶层使用来声明一个设备,这样DEVICE_GSEO靠我ET()可以在DEVICE_DEFINE()的完整声明之前使用。
前面分析过DEVICE_DEFINE()不仅完成设备的注册,同时也完成设备初始化函数以及初始化等级的注册,最终是通过Z_ISEO靠我NIT_ENTRY_DEFINE()来实现的,下面是具体代码实现,声明结构体struct init_entry,其成员init为注册的初始化函数,dev为该设备结构体指针,将做为初始化函数入参传递。将SEO靠我init_entry放到段.z_init中,同时附带上优先级参数。
C
#define Z_INIT_ENTRY_DEFINE(_entry_name, _init_fn, _device, _levelSEO靠我, _prio) \
static const Z_DECL_ALIGN(struct init_entry) \
_CONCAT(__initSEO靠我_, _entry_name) __used \
__attribute__((__section__(".z_init_" #_level STRINGISEO靠我FY(_prio)"_"))) = { \
.init = (_init_fn), \
.dev = (_device), SEO靠我 \
}C
struct init_entry {
/** Initialization function for the initSEO靠我 entry which will take
* the dev attribute as parameter. See below.
*/
int (*init)(const struct device SEO靠我*dev);
/** Pointer to a device driver instance structure. Can be NULL
* if the init entry is not used SEO靠我for a device driver but a services.
*/
const struct device *dev;
};优先级参数列表:
level:
PRE_KERNEL_1
用于没有依赖关系的设SEO靠我备,这些设备在配置期间不能使用任何内核服务,因为内核服务还不可用。然而,中断子系统将被配置,因此可以设置中断。这个级别的Init函数在中断堆栈上运行。
PRE_KERNEL_2
用于依赖于作为PRE_KESEO靠我RNEL_1级别一部分的初始化设备的设备。这些设备在配置期间不能使用任何内核服务,因为内核服务还不可用。这个级别的Init函数在中断堆栈上运行
POST_KERNEL
用于配置过程中需要内核服务的设备。这SEO靠我个级别的Init函数在内核主任务的上下文中运行
APPLICATION
用于需要自动配置的应用程序组件(即非内核组件)。这些设备可以在配置期间使用内核提供的所有服务。这个级别的Init函数在内核主任务上运SEO靠我行。
prio:
用于level等级相同时,该值越小越早初始化,范围为0~99。
某些场景,只需要在启动时执行某个函数初始化,而不需要像设备注册一样传递各种参数,此时就需要使用下面两个API。SEO靠我其中_init_fn为初始化函数(无入参)
C
#define SYS_INIT(_init_fn, _level, _prio) SEO靠我 \
Z_INIT_ENTRY_DEFINE(Z_SYS_NAME(_init_fn), _init_fn, NULL, _level, _prio)C
#define SYS_DEVICE_DEFISEO靠我NE(drv_name, init_fn, level, prio) \
__DEPRECATED_MACRO SYS_INIT(init_fn, level, prio)SEO靠我device_get_binding
通过设备名称或者通过DT_LABEL(节点标识符)获取struct device *,从段地址_device_start~_device_end从查询。SEO靠我
C
const struct device *z_impl_device_get_binding(const char *name)
{
const struct device *dev;
/* A nullSEO靠我 string identifies no device. So does an empty
* string.
*/
if ((name == NULL) || (name[0] == \0)) {
reSEO靠我turn NULL;
}
/* Split the search into two loops: in the common scenario, where
* device names are storeSEO靠我d in ROM (and are referenced by the user
* with CONFIG_* macros), only cheap pointer comparisons willSEO靠我 be
* performed. Reserve string comparisons for a fallback.
*/
for (dev = __device_start; dev != __deviSEO靠我ce_end; dev++) {
if (z_device_is_ready(dev) && (dev->name == name)) {
return dev;
}
}
for (dev = __deviceSEO靠我_start; dev != __device_end; dev++) {
if (z_device_is_ready(dev) && (strcmp(name, dev->name) == 0)) {SEO靠我
return dev;
}
}
return NULL;
}DEVICE_DT_GET()
通过节点标识符获取设备指针,该设备是由DEVICE_DT_DEFINE(node_id, ...)注册的,代码如下:
#define DEVICE_DT_GET(node_id) (&DEVICE_DT_NAME_GET(node_id))
#define DEVICE_DT_NAME_GET(node_id) DEVSEO靠我ICE_NAME_GET(Z_DEVICE_DT_DEV_NAME(node_id))Zephyr驱动的添加可以分为3个级别:
有驱动API抽象,有设备树绑定:只用添加驱动SEO靠我代码
有驱动API抽象,无设备树绑定:添加设备树绑定文件和驱动代码
无驱动API抽象,无设备树绑定:添加抽象API头文件,添加设备树绑定文件,添加驱动代码
在app/目录下添加driveSEO靠我rs目录,在drivers/zephyr下的将要添加的驱动类型分类:
如上图所示,有各种类型的驱动文件夹,例如gpio、sensor、spi等驱动类型文件,在sensor类型驱动里又有具体的如ak897SEO靠我5、bma280等sensor驱动。
逐层级CMakeLists.txt添加add_subdirectory()来将新增内容完成添加构建子目录:
逐层级添加Kconfig配置文件
为了硬SEO靠我件上的灵活性,Zephyr引入了设备树,通过设备树绑定的方式将设备树转换为C宏来使用。Zephyr的设备树绑定文件可能不包含需要用的硬件设备,这就需要自己添加。
对于个人项目SEO靠我开发来说,设备驱动API一般是项目内使用,API抽象的普遍覆盖性并不一定要非常全,此外使用的人员也不需要大范围讨论,根据需求进行自定义就可以, 所形成的头文件放到对应的驱动目录即可,例如zephyr/SEO靠我drivers/sensor/ak8975/ak8975.h。
以内核GPIO子系统来分析完整的设备驱动模型,包括应用层如何使用,底层驱动如何移植适配。
在include\drivers\gpio.h头文件中,定义了应用层可以调用的API,以及这些API的实现。下面以gpio_pin_configure分析,应用层可以调用gpio_pin_confSEO靠我igure(...),其实现由z_impl_gpio_pin-configure(...)来完成。
C
__syscall int gpio_pin_configure(const struct deviSEO靠我ce *port,
gpio_pin_t pin,
gpio_flags_t flags);
static inline int z_impl_gpio_pin_configure(const structSEO靠我 device *port,
gpio_pin_t pin,
gpio_flags_t flags)
{
const struct gpio_driver_api *api =
(const struct gpSEO靠我io_driver_api *)port->api;
const struct gpio_driver_config *const cfg =
(const struct gpio_driver_confSEO靠我ig *)port->config;
struct gpio_driver_data *data =
(struct gpio_driver_data *)port->data;struct gpio_dSEO靠我river_api:内核统一规范了GPIO子系统对外的API接口,不同硬件平台只需适配struct gpio_driver_api中的功能即可。
C
__subsystem struct gpio_driSEO靠我ver_api {
int (*pin_configure)(const struct device *port, gpio_pin_t pin,
gpio_flags_t flags);
int (*poSEO靠我rt_get_raw)(const struct device *port,
gpio_port_value_t *value);
int (*port_set_masked_raw)(const strSEO靠我uct device *port,
gpio_port_pins_t mask,
gpio_port_value_t value);
int (*port_set_bits_raw)(const strucSEO靠我t device *port,
gpio_port_pins_t pins);
int (*port_clear_bits_raw)(const struct device *port,
gpio_portSEO靠我_pins_t pins);
int (*port_toggle_bits)(const struct device *port,
gpio_port_pins_t pins);
int (*pin_intSEO靠我errupt_configure)(const struct device *port,
gpio_pin_t pin,
enum gpio_int_mode, enum gpio_int_trig);
iSEO靠我nt (*manage_callback)(const struct device *port,
struct gpio_callback *cb,
bool set);
uint32_t (*get_peSEO靠我nding_int)(const struct device *dev);
};分析drivers\gpio\gpio_stm32.c代码和dts文件,看下stm32驱动是如何适配到GPIOSEO靠我子系统中
举例设备树中子节点信息如下:
C
gpioa: gpio@48000000 {
compatible = "st,stm32-gpio";
gpio-controller;
#gpio-cells = SEO靠我< 0x2 >;
reg = < 0x48000000 0x400 >;
clocks = < &rcc 0x1 0x1 >;
label = "GPIOA";
phandle = < 0x22 >;
};如果SEO靠我gpioa节点存在,则执行GPIO_DEVICE_INIT_STM32(a, A)
C
#if DT_NODE_HAS_STATUS(DT_NODELABEL(gpioa), okay)
GPIO_DEVISEO靠我CE_INIT_STM32(a, A);
#endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpioa), okay) */通过设备树节点标识符DT_NODELABELSEO靠我(gpioa),获取相关属性:reg,clocks等
C
#define GPIO_DEVICE_INIT_STM32(__suffix, __SUFFIX) SEO靠我 \
GPIO_DEVICE_INIT(DT_NODELABEL(gpio##__suffix), \
__suffix, SEO靠我 \
DT_REG_ADDR(DT_NODELABEL(gpio##__suffix)), \
STM32_PORT##__SUFFIX, SEO靠我 \
DT_CLOCKS_CELL(DT_NODELABEL(gpio##__suffix), bits),
DT_CLOCKS_CELL(DT_NODELABEL(gpio##__sSEO靠我uffix), bus))GPIO_DEVICE_INIT()做3件事:生成gpio_stm32_config,gpio_stm32_data,再完成设备注册
C
#define GPIO_DEVICE_SEO靠我INIT(__node, __suffix, __base_addr, __port, __cenr, __bus) \
//4.1 将设备树获取的数据,生成自定义的gpio_stm32_config
sSEO靠我tatic const struct gpio_stm32_config gpio_stm32_cfg_## __suffix = { \
.common = { SEO靠我 \
.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_NGPIOS(16U), \
}, SEO靠我 \
.base = (uint32_t *)__base_addr, SEO靠我 \
.port = __port, SEO靠我 \
.pclken = { .bus = __bus, .enr = __cenr } \
};
/*4.2 声明一个私有数据gpio_stm32_data*/SEO靠我 \
static struct gpio_stm32_data gpio_SEO靠我stm32_data_## __suffix; \
PM_DEVICE_DT_DEFINE(__node, gpio_stm32_pm_action); SEO靠我 \
/*
4.3 完成设备注册,包括初始化函数gpio_stm32_init,电源管理gpio_stm32_pm_device_ctrl,
私有数据&gpio_stm32_data_SEO靠我## __suffix,
私有配置&gpio_stm32_cfg_## __suffix,
初始化等级PRE_KERNEL_1,CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
以及SEO靠我子系统需要适配的struct gpio_driver_api:&gpio_stm32_driver
*/
DEVICE_DT_DEFINE(__node, SEO靠我 \
gpio_stm32_init, \
PM_DEVICE_DT_GET(__noSEO靠我de), \
&gpio_stm32_data_## __suffix, \
&gpio_stm32SEO靠我_cfg_## __suffix, \
PRE_KERNEL_1, \
CONFIGSEO靠我_GPIO_INIT_PRIORITY, \
&gpio_stm32_driver)DEVICE_DT_DEFINE()将初始化函数gpio_SEO靠我stm32_init()注册进系统,在内核启动阶段会按优先级自动执行。看代码主要完成一些时钟和电源管理的配置,gpio_stm32_driver完成底层驱动需适配的功能函数。
3. 应用层如何使用
前面已介SEO靠我绍了stm32-gpio通过设备树子节点gpioa: gpio@48000000将GPIOA注册进子系统,下面介绍用户如何使用。
3.1 如何获取设备
device_get_binding(DT_LABESEO靠我L(节点标识符))
C
const struct device *gpioa_dev;
static int pins_stm32_init(void)
{
gpioa = device_get_bindingSEO靠我(DT_LABEL(DT_NODELABEL(gpioa_dev)));
if (!gpioa) {
return -ENODEV;
}
}DEVICE_DT_GET(节点标识符)
C
struct deviceSEO靠我 *gpioa_dev;
static int pins_stm32_init(void)
{
gpioa = DEVICE_DT_GET(DT_NODELABEL(gpioa_dev));
if (!gpiSEO靠我oa) {
return -ENODEV;
}
}3.2 如何使用设备
直接通过获取的struct device调用子系统API控制驱动设备
C
gpio_pin_set(gpioa, PIN_INX, 1);
gSEO靠我pio_pin_configure(gpioa, PIN_INX, GPIO_OUTPUT_ACTIVE | FLAGS);网站备案号:浙ICP备17034767号-2