zephyr的驱动模型及其实现

一、概述

  • 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 <misc/__assert.h>
#include <misc/slist.h>

#include <zephyr/types.h>
#include <stddef.h>
#include <device.h>
#include <dt-bindings/gpio/gpio.h>

#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 <syscalls/gpio.h>

#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 <device.h> 引入的,最终根源是设备树文件。设备树文件经过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个基本元素的实现。

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

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值