zephyr驱动介绍

目录

一、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再由具体的底层驱动实现。

zephyr驱动介绍_第1张图片

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下的将要添加的驱动类型分类:

zephyr驱动介绍_第2张图片

zephyr驱动介绍_第3张图片如上图所示,有各种类型的驱动文件夹,例如gpio、sensor、spi等驱动类型文件,在sensor类型驱动里又有具体的如ak8975、bma280等sensor驱动。

逐层级CMakeLists.txt添加add_subdirectory()来将新增内容完成添加构建子目录:zephyr驱动介绍_第4张图片zephyr驱动介绍_第5张图片zephyr驱动介绍_第6张图片

逐层级添加Kconfig配置文件

zephyr驱动介绍_第7张图片zephyr驱动介绍_第8张图片zephyr驱动介绍_第9张图片2. 设备树绑定文件目

为了硬件上的灵活性,Zephyr引入了设备树,通过设备树绑定的方式将设备树转换为C宏来使用。Zephyr的设备树绑定文件可能不包含需要用的硬件设备,这就需要自己添加。

zephyr驱动介绍_第10张图片zephyr驱动介绍_第11张图片

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);

你可能感兴趣的:(其他)