简介
Zephyr支持各种驱动,根据板子来选择编译使用哪些驱动可用。为了缩小image
,驱动是采用配置buildin
的形式,不使用的驱动是不会被编译的。
Zephyr提供的模型主要是完成:
- 注册驱动
- 驱动初始化
- 注册驱动的系统调用
按照驱动类型提供通用的驱动接口,并提供注册系统调用
的宏
,具体的驱动由驱动开发者编写。
Zephyr的驱动都需要采用中断
,除非是device
硬件不支持中断。
注册驱动
include/device.h
中提供了一组数据结构
和宏
,让驱动开发者按照该模型进行device的注册
, Zephyr根据注册
的信息可以自动完成驱动初始化。
数据结构
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().
*/
const device_handle_t *const handles;
#ifdef CONFIG_PM_DEVICE
/** Power Management function */
int (*pm_control)(const struct device *dev, uint32_t command,
uint32_t *state, pm_device_cb cb, void *arg);
/** Pointer to device instance power management data */
struct pm_device * const pm;
#endif
};
config
:用于生成时的只读配置数据集。例如,基本内存映射的 IO 地址、IRQ 行号或设备的其他固定物理特征。data
: 该结构保存在 RAM 中,并由驱动程序用于每个实例运行时的内务处理。例如,它可能包含引用计数、信号量、暂存缓冲区等。api
:主要保存的就是API的接口函数。
以drivers/serial/uart_stellaris.c
为例:
device的config
包含了uart
的基地址,时钟频率和irq配置函数
struct uart_device_config {
union {
u32_t port;
u8_t *base;
u32_t regs;
};
u32_t sys_clk_freq;
uart_irq_config_func_t irq_config_func;
};
static const struct uart_device_config uart_stellaris_dev_cfg_0 = {
.base = (u8_t *)TI_STELLARIS_UART_4000C000_BASE_ADDRESS,
.sys_clk_freq = UART_STELLARIS_CLK_FREQ,
.irq_config_func = irq_config_func_0,
};
device的data
包含了可改变的波特率和可由应用层设置的irq处理函数
static struct uart_stellaris_dev_data_t uart_stellaris_dev_data_0 = {
.baud_rate = TI_STELLARIS_UART_4000C000_CURRENT_SPEED,
};
struct uart_stellaris_dev_data_t {
u32_t baud_rate; /* Baud rate */
uart_irq_callback_t cb; /**< Callback function pointer */
};
宏
创建设备对象
DEVICE_DEFINE:创建设备对象
#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn, \
data_ptr, cfg_ptr, level, prio, api_ptr) \
Z_DEVICE_DEFINE(DT_INVALID_NODE, dev_name, drv_name, init_fn, \
pm_control_fn, \
data_ptr, cfg_ptr, level, prio, api_ptr)
/* Like DEVICE_DEFINE but takes a node_id AND a dev_name, and trailing
* dependency handles that come from outside devicetree.
*/
#define Z_DEVICE_DEFINE(node_id, dev_name, drv_name, init_fn, pm_control_fn, \
data_ptr, cfg_ptr, level, prio, api_ptr, ...) \
static struct device_state Z_DEVICE_STATE_NAME(dev_name); \
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 = &Z_DEVICE_STATE_NAME(dev_name), \
.data = (data_ptr), \
Z_DEVICE_DEFINE_INIT(node_id, dev_name, pm_control_fn) \
}; \
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)
DEVICE_DEFINE的参数如下:
dev_name
: 设备名称。drv_name
: 驱动名字,暴露到系统的名字,提供给device_get_binding()
的名字。init_fn
: 初始化函数pm_control_fn
: 电源管理函数data_ptr
: data数据结构体cfg_ptr
: config数据结构体level
:驱动的注册级别prio
: 注册级别的优先级api_ptr
: api的数据结构体
DEVICE_INIT: 初始化设备对象,但不给api
赋值,稍后才给api
赋值
#define DEVICE_INIT(dev_name, drv_name, init_fn, \
data_ptr, cfg_ptr, level, prio) \
__DEPRECATED_MACRO \
DEVICE_DEFINE(dev_name, drv_name, init_fn, NULL, \
data_ptr, cfg_ptr, level, prio, NULL)
获取设备对象
DEVICE_NAME_GET: 根据名字获取设备对象
#define DEVICE_NAME_GET(name) _CONCAT(__device_, name)
name
: 与DEVICE_DEFINE()
的dev_name
相同。
返回值为设备对象
DEVICE_GET: 根据名字获取设备对象
#define DEVICE_GET(name) (&DEVICE_NAME_GET(name))
name
: 与DEVICE_DEFINE()
的dev_name
相同。
返回值为设备对象
驱动注册级别
在通过DEVICE_DEFINE
来进行驱动注册的时候,有一个level
来指定注册级别,其实也就是指定驱动在什么阶段进行注册。
PRE_KERNEL_1
:该阶段在初始化完成中断控制器
,内核尚未初始化
, 因此该Level的驱动可以使用中断但不能用内核服务,也不依赖其它设备驱动。该Level的驱动初始化函数执行在中断堆栈
上。PRE_KERNEL_2
:该阶段已经完成了PRE_KERNEL_1初始化
,但内核尚未初始化
,因此该Level的驱动可以使用中断
和PRE_KERNEL_1的驱动
,但不能使用内核服务
。该Level的驱动初始化函数执行在中断堆栈
上。POST_KERNEL
:该阶段已完成PRE_KERNEL_2初始化
和内核初始化
,因此该Level的驱动可以使用其它驱动和内核服务。该Level驱动初始化函数执行在main thread的堆栈
上。- APPLICATION:为了应用组件使用(例如shell),可以使用
其它驱动
和所有的内核服务
。该Level驱动初始化函数执行在main thread的堆栈
上。
驱动注册级别优先级
针对每个Level
内的驱动定义优先级用于对驱动初始化
的顺序进行排序, 优先级取值在0~99
,数字越低该驱动就越先初始化
。
驱动初始化
从DEVICE_DEFINE
的定义可以看出,我们将每个驱动结构体根据注册级别的不同存储在一个不同的段内。
在/kernel/device.c
中定义了驱动初始化函数如下:
void z_sys_init_run_level(int32_t level)
{
static const struct init_entry *levels[] = {
__init_PRE_KERNEL_1_start,
__init_PRE_KERNEL_2_start,
__init_POST_KERNEL_start,
__init_APPLICATION_start,
#ifdef CONFIG_SMP
__init_SMP_start,
#endif
/* End marker */
__init_end,
};
const struct init_entry *entry;
for (entry = levels[level]; entry < levels[level+1]; entry++) {
const struct device *dev = entry->dev;
int rc = entry->init(dev);
if (dev != NULL) {
/* Mark device initialized. If initialization
* failed, record the error condition.
*/
if (rc != 0) {
if (rc < 0) {
rc = -rc;
}
if (rc > UINT8_MAX) {
rc = UINT8_MAX;
}
dev->state->init_res = rc;
}
dev->state->initialized = true;
}
}
}
驱动初始化的过程如下:
- 遍历所有的注册级别
- 在每个注册级别上遍历优先级
- 在每个优先级上都执行之前注册的初始化函数
注册驱动系统调用
驱动的系统调用分为两种模式:
- 全局模式
- 用户模式
用户模式需要在创建系统调用的时候,提供一个验证函数
。
在zephyr下已经给了一个创建驱动系统调用的例子,路径:samples/application_development/out_of_tree_driver
在头文件中定义系统调用函数,必须以
__syscall
返回__syscall void hello_world_print(const struct device *dev);
#include <syscalls/hello_world_driver.h>
在头文件或者源文件中定义实现系统调用的函数,函数名格式必须为:
z_impl_<系统调用函数名>
static inline void z_impl_hello_world_print(const struct device *dev)
{
const struct hello_world_driver_api *api = dev->api;
__ASSERT(api->print, "Callback pointer should not be NULL");
api->print(dev);
}
如果要支持用户模式,那就需要在源文件定义系统调用的验证函数,函数名格式为:
z_vrfy_<系统调用函数名>
static inline void z_vrfy_hello_world_print(const struct device *dev)
{
Z_OOPS(Z_SYSCALL_DRIVER_HELLO_WORLD(dev, print));
z_impl_hello_world_print(dev);
}
使用驱动
- 通过
device_get_binding
获取设备结构体。 - 使用驱动的系统调用API来完成设备的操作。
示例
驱动示例
//hello_world_driver.h
/*
* Copyright (c) 2019 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __HELLO_WORLD_DRIVER_H__
#define __HELLO_WORLD_DRIVER_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <device.h>
/*
* This 'Hello World' driver has a 'print' syscall that prints the
* famous 'Hello World!' string.
*
* The string is formatted with some internal driver data to
* demonstrate that drivers are initialized during the boot process.
*
* The driver exists to demonstrate (and test) custom drivers that are
* maintained outside of Zephyr.
*/
__subsystem struct hello_world_driver_api {
/* This struct has a member called 'print'. 'print' is function
* pointer to a function that takes 'struct device *dev' as an
* argument and returns 'void'.
*/
void (*print)(const struct device *dev);
};
__syscall void hello_world_print(const struct device *dev);
static inline void z_impl_hello_world_print(const struct device *dev)
{
const struct hello_world_driver_api *api = dev->api;
__ASSERT(api->print, "Callback pointer should not be NULL");
api->print(dev);
}
#ifdef __cplusplus
}
#endif
#include <syscalls/hello_world_driver.h>
#endif /* __HELLO_WORLD_DRIVER_H__ */
//hello_world_driver.c
/*
* Copyright (c) 2019 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "hello_world_driver.h"
#include <zephyr/types.h>
#include <syscall_handler.h>
/**
* This is a minimal example of an out-of-tree driver
* implementation. See the header file of the same name for details.
*/
static struct hello_world_dev_data {
uint32_t foo;
} data;
static int init(const struct device *dev)
{
data.foo = 5;
return 0;
}
static void print_impl(const struct device *dev)
{
printk("Hello World from the kernel: %d\n", data.foo);
__ASSERT(data.foo == 5, "Device was not initialized!");
}
#ifdef CONFIG_USERSPACE
static inline void z_vrfy_hello_world_print(const struct device *dev)
{
Z_OOPS(Z_SYSCALL_DRIVER_HELLO_WORLD(dev, print));
z_impl_hello_world_print(dev);
}
#include <syscalls/hello_world_print_mrsh.c>
#endif /* CONFIG_USERSPACE */
DEVICE_DEFINE(hello_world, "CUSTOM_DRIVER",
init, NULL, &data, NULL,
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&((struct hello_world_driver_api){ .print = print_impl }));
使用驱动示例
/*
* Copyright (c) 2019 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "hello_world_driver.h"
#include <stdio.h>
#include <zephyr.h>
const struct device *dev;
static void user_entry(void *p1, void *p2, void *p3)
{
hello_world_print(dev);
}
void main(void)
{
printk("Hello World from the app!\n");
dev = device_get_binding("CUSTOM_DRIVER");
__ASSERT(dev, "Failed to get device binding");
printk("device is %p, name is %s\n", dev, dev->name);
k_object_access_grant(dev, k_current_get());
k_thread_user_mode_enter(user_entry, NULL, NULL, NULL);
}