zephyr的驱动模型及其实现

您所在的位置:网站首页 iphone浏览器怎么删除搜索记录 zephyr的驱动模型及其实现

zephyr的驱动模型及其实现

2023-08-23 13:55| 来源: 网络整理| 查看: 265

一、概述

zephyr内核支持很多种驱动,但是在zephyr应用中所支持的驱动,则是在zephyr应用编译时通过CONFIG配置来选择的。以此来达到控制生成文件大小、内核功能、驱动裁剪的效果。与Linux设备驱动不同,zephyr上对于不同类型的设备,定义了不同类型的驱动接口(或称:系统调用),这些接口都定义在include文件夹内的头文件中,如:./zephyr/include/driver/iic.c虽然不同类型的设备驱动接口不同,但是其基本模型是一样的,本文仅分析基本模型,并实现一个较为完整的驱动。

zephyr如今的代码已经更新到V2.6,在驱动中包含了大量的设备树的使用,为更好地分析原理,我们采用V1.14版本的代码来学习。后续有机会在单独学习一下zephyr中设备树的使用。

二、相关文件:

/zephyr/include/device.h /zephyr/kernel/device.c

三、zephyr驱动的基本要素

先说重点,一个zephyr的驱动,需要包含以下五个基本元素:

两个重要的内核对象结构体,struct device, struct device_config一个设备申明与定义的宏,DEVICE_AND_API_INIT两个重要的设备相关的结构体,struct xxx_device_config,struct xxx_device_data一个设备初始化接口,int (*init)(struct device *device)一个设备接口的结构体及其成员函数的实现,struct xxx_driver_api 

四、重要的数据结构之一     对于一个驱动设备来说,一方面需要能够被内核管理,另一方面又需要保留一些设备特有信息,因此在zephyr提供了几个重要的数据结构来满足二者的需求。

/**device.h 所有驱动通用,zephyr内核对象,用于管理所有的设备,**/ struct device /**device.h 所有驱动通用,zephyr内核对象,属于struct device的成员**/ struct device_config

        这两个对象是zephyr的内核对象,结构体的定义在device.h文件中。可以使用宏:DEVICE_AND_API_INIT来定义,也是应用代码通过系统调用来访问设备驱动的关键对象。其定义如下:

/** Version:V1.14, directory:/zephyr/include/device.h **/ struct device_config { const char *name; /*驱动名称*/ int (*init)(struct device *device); /*设备初始化接口*/ #ifdef CONFIG_DEVICE_POWER_MANAGEMENT int (*device_pm_control)(struct device *device, u32_t command, void *context, device_pm_cb cb, void *arg); struct device_pm *pm; #endif const void *config_info; /*驱动设备私有配置,struct xxx_device_config*/ }; struct device { struct device_config *config; /** **/ const void *driver_api; /** 驱动的api接口,共有代码**/ void *driver_data; /** 驱动设备的私有数据 struct xxx_device_data**/ #if defined(__x86_64) && __SIZEOF_POINTER__ == 4 /* The x32 ABI hits an edge case. This is a 12 byte struct, * but the x86_64 linker will pack them only in units of 8 * bytes, leading to alignment problems when iterating over * the link-time array. */ void *padding; #endif };

        从这两个结构体的成员可以看出:驱动基本元素中的三个都被包含在里面,从而可以实现对驱动设备私有数据的访问;而对于内核管理驱动设备,则可以通过宏来体现。 五、基本元素之——重要的宏

        先来看一下宏的定义。通过该宏定义了两个结构体变量,并向其中填充了驱动api、以及驱动设备的data、cfg_info。宏定义中的其他传参的作用,详见注释。

/** Version:zephyrV1.14, Directory:/zephyr/include/device.h **/ /** * @param dev_name input,设备名,一般没啥作用; drv_name input,重要参数,设备的驱动名,用于在device_get_binding中查找设备 init_fn input,设备的初始化接口,在内核启动的驱动设备初始化阶段被调用 data input,其实就是struct xxx_device_data 类型的变量 cfg_info input,其实就是struct xxx_device_config 类型的变量 level input,重要参数,与prio一起确定设备的初始化顺序 prio input,重要参数,与level一起驱动设备的初始化顺序 api input,stuct xxx_driver_api,提供系统调用的接口 **/ #define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \ level, prio, api) \ static struct device_config _CONCAT(__config_, dev_name) __used \ __attribute__((__section__(".devconfig.init"))) = { \ .name = drv_name, .init = (init_fn), \ .config_info = (cfg_info) \ }; \ static struct device _CONCAT(__device_, dev_name) __used \ __attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \ .config = &_CONCAT(__config_, dev_name), \ .driver_api = api, \ .driver_data = data \ }

        再来看一下struct device类型的变量定义。通过__attribut__ 设置了变量 __device_dev_name在编译后所属的段为(".init_" #level STRINGIFY(prio)),这是一个与level&prio相关的段,在zephyr应用完成编译之后,会根据level & prio 参数将所有的驱动设备汇聚在一起。

        在zephyr运行时,会将所有的device变量拷贝到内存中,并根据level & prio参数完成设备的初始化(详见:zephyr如何运行到main)。

static struct device _CONCAT(__device_, dev_name) __used          \ __attribute__((__section__(".init_" #level STRINGIFY(prio)))) = {   ........ }

zephyr的驱动设备是通过struct device结构体来管理的。在内核启动过程中,驱动设备会根据 DEVICE_AND_API_INIT 宏中所传规定的level & prio 参数在内核启动的不同阶段(详见:zephyr如何运行到main)完成初始化。

/** 不同类型设备驱动特有对象,用于保存驱动设备中特有的一些不可更改的重要数据,如IRQ号**/ strcut xxx_device_config /**驱动特有对象,用于保存驱动设备中一些可供用户访问、修改的参数,如波特率等。**/ struct xxx_device_data /**驱动特有对象,用于向应用层提供的接口**/ struct xxx_driver_api

五、zephyr驱动的实现

        从前文可以看到,要实现一个zephyr的驱动,并实例化相应的驱动设备,需要调用DEVICE_AND_API_INIT宏来定义及初始化设备,即device、device_config类型的变量。为了定义设备则需要定义设备所需的一些成员,也就是xxx_device_data、xxx_device_config、xxx_driver_api等类型的变量。这些类型的变量都与具体的驱动类型有关,所以下面以GPIO的驱动来分析一下驱动的实现。

        文件包括:

/zephyr/include/gpio.h /zephyr/drivers/gpio/gpio_stm32.c /zephyr/dirvers/gpio/gpio_stm32.h

        在熟悉了zephyr的基本的驱动模型之后,对于具体的某一类型的驱动的学习, 可以按照以下过程:

首先学习include目录下的头文件,了解该类型驱动的框架、底层API、可提供的系统调用;其次学习driver目录下的驱动的具体实现。

        在include目录下的头文件对于该类型的驱动框架做出了明确,例如api的定义、系统调用的定义以及驱动应用所需的一些数据结构等。对于/zephyr/include/gpio.h文件,代码分析详见注释:

/* * Copyright (c) 2017 ARM Ltd * Copyright (c) 2015-2016 Intel Corporation. * * SPDX-License-Identifier: Apache-2.0 */ /** * @file * @brief Public APIs for GPIO drivers */ #ifndef ZEPHYR_INCLUDE_GPIO_H_ #define ZEPHYR_INCLUDE_GPIO_H_ #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * @brief GPIO Driver APIs * @defgroup gpio_interface GPIO Driver APIs * @ingroup io_interfaces * @{ */ /** @cond INTERNAL_HIDDEN */ #define GPIO_ACCESS_BY_PIN 0 #define GPIO_ACCESS_BY_PORT 1 /** * @endcond */ /* 声明回调函数相关的变量及函数类型 * 回调函数一般都是用于中断处理,回调函数可以接口进行注册及注销。 */ struct gpio_callback; /** * @typedef gpio_callback_handler_t * ...... */ typedef void (*gpio_callback_handler_t)(struct device *port, struct gpio_callback *cb, u32_t pins); /** * @brief GPIO callback structure * ...... */ struct gpio_callback { /** This is meant to be used in the driver and the user should not * mess with it (see drivers/gpio/gpio_utils.h) */ sys_snode_t node; /** Actual callback function being called when relevant. */ gpio_callback_handler_t handler; /** A mask of pins the callback is interested in, if 0 the callback * will never be called. Such pin_mask can be modified whenever * necessary by the owner, and thus will affect the handler being * called or not. The selected pins must be configured to trigger * an interrupt. */ u32_t pin_mask; }; /* 声明系统调用的函数类型*/ /** * @cond INTERNAL_HIDDEN * * GPIO driver API definition and system call entry points * * (Internal use only.) */ typedef int (*gpio_config_t)(struct device *port, int access_op, u32_t pin, int flags); typedef int (*gpio_write_t)(struct device *port, int access_op, u32_t pin, u32_t value); typedef int (*gpio_read_t)(struct device *port, int access_op, u32_t pin, u32_t *value); typedef int (*gpio_manage_callback_t)(struct device *port, struct gpio_callback *callback, bool set); typedef int (*gpio_enable_callback_t)(struct device *port, int access_op, u32_t pin); typedef int (*gpio_disable_callback_t)(struct device *port, int access_op, u32_t pin); typedef u32_t (*gpio_api_get_pending_int)(struct device *dev); /* 定义xxx_driver_api,此结构体中的接口,是驱动中需要考虑实现的。*/ struct gpio_driver_api { gpio_config_t config; gpio_write_t write; gpio_read_t read; gpio_manage_callback_t manage_callback; gpio_enable_callback_t enable_callback; gpio_disable_callback_t disable_callback; gpio_api_get_pending_int get_pending_int; }; /** 定义系统调用接口**/ __syscall int gpio_config(struct device *port, int access_op, u32_t pin, int flags); /* 定义系统调用的实现接口,或者说是隐式调用接口,后续会专门对系统调用的实现过程进行分析,在 *本文中仅作陈述,调用接口gpio_config后会调用接口z_impl_gpio_config。 *在开启用户空间后,这个过程还会有另一个中间的verify函数。 */ static inline int z_impl_gpio_config(struct device *port, int access_op, u32_t pin, int flags) { const struct gpio_driver_api *api = (const struct gpio_driver_api *)port->driver_api; return api->config(port, access_op, pin, flags); } /** 其他的系统调用的定义及实现 **/ /** * @endcond */ /** 对系统调用进行封装之后的一些更为方便使用的用户接口**/ /** * @brief..... */ static inline int gpio_pin_configure(struct device *port, u32_t pin, int flags) { return gpio_config(port, GPIO_ACCESS_BY_PIN, pin, flags); } static inline int gpio_pin_write(struct device *port, u32_t pin, u32_t value) { return gpio_write(port, GPIO_ACCESS_BY_PIN, pin, value); } /** 这些用户接口,可以在用户代码中通过包含gpio.h头文件后直接使用。 **/ /** * @brief Function to get pending interrupts * * The purpose of this function is to return the interrupt * status register for the device. * This is especially useful when waking up from * low power states to check the wake up source. * * @param dev Pointer to the device structure for the driver instance. * * @retval status != 0 if at least one gpio interrupt is pending. * @retval 0 if no gpio interrupt is pending. */ __syscall int gpio_get_pending_int(struct device *dev); /** * @internal */ static inline int z_impl_gpio_get_pending_int(struct device *dev) { const struct gpio_driver_api *api = (const struct gpio_driver_api *)dev->driver_api; if (api->get_pending_int == NULL) { return -ENOTSUP; } return api->get_pending_int(dev); } /** 定义一些方便使用的宏 **/ struct gpio_pin_config { char *gpio_controller; u32_t gpio_pin; }; #define GPIO_DECLARE_PIN_CONFIG_IDX(_idx) \ struct gpio_pin_config gpio_pin_ ##_idx #define GPIO_DECLARE_PIN_CONFIG \ GPIO_DECLARE_PIN_CONFIG_IDX() #define GPIO_PIN_IDX(_idx, _controller, _pin) \ .gpio_pin_ ##_idx = { \ .gpio_controller = (_controller),\ .gpio_pin = (_pin), \ } #define GPIO_PIN(_controller, _pin) \ GPIO_PIN_IDX(, _controller, _pin) #define GPIO_GET_CONTROLLER_IDX(_idx, _conf) \ ((_conf)->gpio_pin_ ##_idx.gpio_controller) #define GPIO_GET_PIN_IDX(_idx, _conf) \ ((_conf)->gpio_pin_ ##_idx.gpio_pin) #define GPIO_GET_CONTROLLER(_conf) GPIO_GET_CONTROLLER_IDX(, _conf) #define GPIO_GET_PIN(_conf) GPIO_GET_PIN_IDX(, _conf) /** * @} */ #include #ifdef __cplusplus } #endif #endif /* ZEPHYR_INCLUDE_GPIO_H_ */

        而对于驱动的实现,以说stm32系列的GPIO驱动来学习,在文件/zephyr/drivers/gpio/gpio_stm32.c文件中,根据5个基本元素来进行学习stm32是如何实现GPIO驱动的。

        首先找到宏DEVICE_AND_API_INIT,了解一下驱动是如何实行该宏,并利用宏来实现了多少个驱动设备。

#define GPIO_DEVICE_INIT(__name, __suffix, __base_addr, __port, __cenr, __bus) \ static const struct gpio_stm32_config gpio_stm32_cfg_## __suffix = { \ .base = (u32_t *)__base_addr, \ .port = __port, \ .pclken = { .bus = __bus, .enr = __cenr } \ }; \ static struct gpio_stm32_data gpio_stm32_data_## __suffix; \ DEVICE_AND_API_INIT(gpio_stm32_## __suffix, \ __name, \ gpio_stm32_init, \ &gpio_stm32_data_## __suffix, \ &gpio_stm32_cfg_## __suffix, \ POST_KERNEL, \ CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ &gpio_stm32_driver);

        在宏定义中对DEVICE_AND_API_INIT进行了两次封装,每一次封装都是为了可以更简单的使用宏定义。依次为:

DEVICE_AND_API_INIT——>>GPIO_DEVICE_INIT——>>GPIO_DEVICE_INIT_STM32

        在封装成GPIO_DEVICE_INIT(__name, __suffix, __base_addr, __port, __cenr, __bus)的过程中,则涉及到了另外两个私有的数据结构:static const struct gpio_stm32_config,static struct gpio_stm32_data。

        只要填入适当的入参,使用GPIO_DEVICE_INIT宏已经可以实例化出一个GPIO的驱动设备。但是对于MCU来说,类似的设备一般有很多,GPIO管脚就可以有多组,GPIOA、GPIOB......,且每组设备的参数相差不多,因此就有了第二层封装。

#define GPIO_DEVICE_INIT_STM32(__suffix, __SUFFIX) \ GPIO_DEVICE_INIT(DT_GPIO_STM32_GPIO##__SUFFIX##_LABEL, \ __suffix, \ DT_GPIO_STM32_GPIO##__SUFFIX##_BASE_ADDRESS, \ STM32_PORT##__SUFFIX, \ DT_GPIO_STM32_GPIO##__SUFFIX##_CLOCK_BITS, \ DT_GPIO_STM32_GPIO##__SUFFIX##_CLOCK_BUS)

        可以看到,在封装过程中使用了许多DT_GPIO_STM32_xxxx的宏,这些是通过头文件#include 引入的,最终根源是设备树文件。设备树文件经过yaml语言文件的注释,在py脚本的脚本的解析后,最终形成device.h文件供驱动调用,在学习zephyr的构建过程中在进行详细的学习。

        进行二次封装之后,只需要填入两个简单的参数就可以完成驱动设备的实例化,非常便于存在多组功能相似的设备的实例化,例如:

#ifdef CONFIG_GPIO_STM32_PORTA                /** GPIIOA 如果使能**/ GPIO_DEVICE_INIT_STM32(a, A);                      /** 实例化驱动设备GPIOA**/ #endif /* CONFIG_GPIO_STM32_PORTA */

         在DEVICE_AND_API_INIT的传参中,也体现了另外两个基本元素:init函数、驱动API接口。

DEVICE_AND_API_INIT(gpio_stm32_## __suffix, \ __name, \ gpio_stm32_init, \ &gpio_stm32_data_## __suffix, \ &gpio_stm32_cfg_## __suffix, \ POST_KERNEL, \ CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \ &gpio_stm32_driver);

        设备初始化接口是驱动设备最先被调用的接口,调用时机是从系统上电之后——应用层main之前。具体的调用时间点与设备实例化时的入参level、prio有关。如上面的代码中就是:POST_KERNEL 、CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,层级顺序是POST_KERNEL,内核完成初始化之后;同层之间优先级是CONFIG_KERNEL_INIT_PRIORITY_DEFAULT。

static int gpio_stm32_init(struct device *device)

        在初始化函数中,一般会完成以下几件事:

外设时钟配置及使能中断配置。

        设备驱动接口的实现也是驱动文件比不可少的一部分,在include/gpio.h头文件中提供的系统调用,经过层层转换之后,最终都是调用的驱动API。在gpio_stm32.c中,它的实现如下:

static int gpio_stm32_config(struct device *dev, int access_op, u32_t pin, int flags) {/**........**/} /** API接口实现 **/ static const struct gpio_driver_api gpio_stm32_driver = { .config = gpio_stm32_config, .write = gpio_stm32_write, .read = gpio_stm32_read, .manage_callback = gpio_stm32_manage_callback, .enable_callback = gpio_stm32_enable_callback, .disable_callback = gpio_stm32_disable_callback, };

        以上便是对zephyr的驱动模型及其实现的学习,总结一下就是:

了解驱动模型的5个基本元素;对于具体某一类驱动的使用,则需要了解include目录下的对应头文件中提供的系统调用;对于驱动的实现,则需要考虑5个基本元素的实现。

        对于驱动的学习,还有中断、设备树、初始化、系统调用等概念掺杂其中,后续将进一步探讨。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3