一、概述
- 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个基本元素的实现。
对于驱动的学习,还有中断、设备树、初始化、系统调用等概念掺杂其中,后续将进一步探讨。