Zephyr-驱动设备模型

目录

1 介绍

2 数据结构

3 系统框架

3.1 基本代码介绍

4 注册设备

4.1 DEVICE_DEFINE

4.2 DEVICE_DT_DEFINE

4.3 DEVICE_DECLARE

5 设备初始化

5.1 代码实现

5.2 优先级

6 系统初始化

7 如何获取设备

7.1 device_get_binding

7.2 DEVICE_DT_GET()

8 GPIO子系统分析

8.1 子系统对外API

8.2 struct gpio_driver_api

8.3 注册设备

8.4 应用层如何使用


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值