简介

Zephyr支持各种驱动,根据板子来选择编译使用哪些驱动可用。为了缩小image,驱动是采用配置buildin的形式,不使用的驱动是不会被编译的。
Zephyr提供的模型主要是完成:

  • 注册驱动
  • 驱动初始化
  • 注册驱动的系统调用

按照驱动类型提供通用的驱动接口,并提供注册系统调用,具体的驱动由驱动开发者编写。
Zephyr的驱动都需要采用中断,除非是device硬件不支持中断。

注册驱动

include/device.h中提供了一组数据结构,让驱动开发者按照该模型进行device的注册, Zephyr根据注册的信息可以自动完成驱动初始化。

数据结构

  1. struct device {
  2. /** Name of the device instance */
  3. const char *name;
  4. /** Address of device instance config information */
  5. const void *config;
  6. /** Address of the API structure exposed by the device instance */
  7. const void *api;
  8. /** Address of the common device state */
  9. struct device_state * const state;
  10. /** Address of the device instance private data */
  11. void * const data;
  12. /** optional pointer to handles associated with the device.
  13. *
  14. * This encodes a sequence of sets of device handles that have
  15. * some relationship to this node. The individual sets are
  16. * extracted with dedicated API, such as
  17. * device_required_handles_get().
  18. */
  19. const device_handle_t *const handles;
  20. #ifdef CONFIG_PM_DEVICE
  21. /** Power Management function */
  22. int (*pm_control)(const struct device *dev, uint32_t command,
  23. uint32_t *state, pm_device_cb cb, void *arg);
  24. /** Pointer to device instance power management data */
  25. struct pm_device * const pm;
  26. #endif
  27. };
  • config:用于生成时的只读配置数据集。例如,基本内存映射的 IO 地址、IRQ 行号或设备的其他固定物理特征。
  • data: 该结构保存在 RAM 中,并由驱动程序用于每个实例运行时的内务处理。例如,它可能包含引用计数、信号量、暂存缓冲区等。
  • api:主要保存的就是API的接口函数。

drivers/serial/uart_stellaris.c为例:
device的config包含了uart的基地址,时钟频率和irq配置函数

  1. struct uart_device_config {
  2. union {
  3. u32_t port;
  4. u8_t *base;
  5. u32_t regs;
  6. };
  7. u32_t sys_clk_freq;
  8. uart_irq_config_func_t irq_config_func;
  9. };
  10. static const struct uart_device_config uart_stellaris_dev_cfg_0 = {
  11. .base = (u8_t *)TI_STELLARIS_UART_4000C000_BASE_ADDRESS,
  12. .sys_clk_freq = UART_STELLARIS_CLK_FREQ,
  13. .irq_config_func = irq_config_func_0,
  14. };

device的data包含了可改变的波特率和可由应用层设置的irq处理函数

  1. static struct uart_stellaris_dev_data_t uart_stellaris_dev_data_0 = {
  2. .baud_rate = TI_STELLARIS_UART_4000C000_CURRENT_SPEED,
  3. };
  4. struct uart_stellaris_dev_data_t {
  5. u32_t baud_rate; /* Baud rate */
  6. uart_irq_callback_t cb; /**< Callback function pointer */
  7. };

创建设备对象

DEVICE_DEFINE:创建设备对象

  1. #define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn, \
  2. data_ptr, cfg_ptr, level, prio, api_ptr) \
  3. Z_DEVICE_DEFINE(DT_INVALID_NODE, dev_name, drv_name, init_fn, \
  4. pm_control_fn, \
  5. data_ptr, cfg_ptr, level, prio, api_ptr)
  6. /* Like DEVICE_DEFINE but takes a node_id AND a dev_name, and trailing
  7. * dependency handles that come from outside devicetree.
  8. */
  9. #define Z_DEVICE_DEFINE(node_id, dev_name, drv_name, init_fn, pm_control_fn, \
  10. data_ptr, cfg_ptr, level, prio, api_ptr, ...) \
  11. static struct device_state Z_DEVICE_STATE_NAME(dev_name); \
  12. Z_DEVICE_DEFINE_PRE(node_id, dev_name, __VA_ARGS__) \
  13. COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static)) \
  14. const Z_DECL_ALIGN(struct device) \
  15. DEVICE_NAME_GET(dev_name) __used \
  16. __attribute__((__section__(".z_device_" #level STRINGIFY(prio)"_"))) = { \
  17. .name = drv_name, \
  18. .config = (cfg_ptr), \
  19. .api = (api_ptr), \
  20. .state = &Z_DEVICE_STATE_NAME(dev_name), \
  21. .data = (data_ptr), \
  22. Z_DEVICE_DEFINE_INIT(node_id, dev_name, pm_control_fn) \
  23. }; \
  24. BUILD_ASSERT(sizeof(Z_STRINGIFY(drv_name)) <= Z_DEVICE_MAX_NAME_LEN, \
  25. Z_STRINGIFY(DEVICE_NAME_GET(drv_name)) " too long"); \
  26. Z_INIT_ENTRY_DEFINE(DEVICE_NAME_GET(dev_name), init_fn, \
  27. (&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赋值

  1. #define DEVICE_INIT(dev_name, drv_name, init_fn, \
  2. data_ptr, cfg_ptr, level, prio) \
  3. __DEPRECATED_MACRO \
  4. DEVICE_DEFINE(dev_name, drv_name, init_fn, NULL, \
  5. data_ptr, cfg_ptr, level, prio, NULL)

获取设备对象

DEVICE_NAME_GET: 根据名字获取设备对象

  1. #define DEVICE_NAME_GET(name) _CONCAT(__device_, name)
  • name: 与DEVICE_DEFINE()dev_name相同。

返回值为设备对象

DEVICE_GET: 根据名字获取设备对象

  1. #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中定义了驱动初始化函数如下:

  1. void z_sys_init_run_level(int32_t level)
  2. {
  3. static const struct init_entry *levels[] = {
  4. __init_PRE_KERNEL_1_start,
  5. __init_PRE_KERNEL_2_start,
  6. __init_POST_KERNEL_start,
  7. __init_APPLICATION_start,
  8. #ifdef CONFIG_SMP
  9. __init_SMP_start,
  10. #endif
  11. /* End marker */
  12. __init_end,
  13. };
  14. const struct init_entry *entry;
  15. for (entry = levels[level]; entry < levels[level+1]; entry++) {
  16. const struct device *dev = entry->dev;
  17. int rc = entry->init(dev);
  18. if (dev != NULL) {
  19. /* Mark device initialized. If initialization
  20. * failed, record the error condition.
  21. */
  22. if (rc != 0) {
  23. if (rc < 0) {
  24. rc = -rc;
  25. }
  26. if (rc > UINT8_MAX) {
  27. rc = UINT8_MAX;
  28. }
  29. dev->state->init_res = rc;
  30. }
  31. dev->state->initialized = true;
  32. }
  33. }
  34. }

驱动初始化的过程如下:

  • 遍历所有的注册级别
  • 在每个注册级别上遍历优先级
  • 在每个优先级上都执行之前注册的初始化函数

注册驱动系统调用

驱动的系统调用分为两种模式:

  • 全局模式
  • 用户模式

用户模式需要在创建系统调用的时候,提供一个验证函数
在zephyr下已经给了一个创建驱动系统调用的例子,路径:samples/application_development/out_of_tree_driver

  • 在头文件中定义系统调用函数,必须以__syscall返回

    1. __syscall void hello_world_print(const struct device *dev);
    2. #include <syscalls/hello_world_driver.h>
  • 在头文件或者源文件中定义实现系统调用的函数,函数名格式必须为:z_impl_<系统调用函数名>

    1. static inline void z_impl_hello_world_print(const struct device *dev)
    2. {
    3. const struct hello_world_driver_api *api = dev->api;
    4. __ASSERT(api->print, "Callback pointer should not be NULL");
    5. api->print(dev);
    6. }
  • 如果要支持用户模式,那就需要在源文件定义系统调用的验证函数,函数名格式为:z_vrfy_<系统调用函数名>

    1. static inline void z_vrfy_hello_world_print(const struct device *dev)
    2. {
    3. Z_OOPS(Z_SYSCALL_DRIVER_HELLO_WORLD(dev, print));
    4. z_impl_hello_world_print(dev);
    5. }

使用驱动

  • 通过device_get_binding获取设备结构体。
  • 使用驱动的系统调用API来完成设备的操作。

示例

驱动示例

  1. //hello_world_driver.h
  2. /*
  3. * Copyright (c) 2019 Nordic Semiconductor
  4. *
  5. * SPDX-License-Identifier: Apache-2.0
  6. */
  7. #ifndef __HELLO_WORLD_DRIVER_H__
  8. #define __HELLO_WORLD_DRIVER_H__
  9. #ifdef __cplusplus
  10. extern "C" {
  11. #endif
  12. #include <device.h>
  13. /*
  14. * This 'Hello World' driver has a 'print' syscall that prints the
  15. * famous 'Hello World!' string.
  16. *
  17. * The string is formatted with some internal driver data to
  18. * demonstrate that drivers are initialized during the boot process.
  19. *
  20. * The driver exists to demonstrate (and test) custom drivers that are
  21. * maintained outside of Zephyr.
  22. */
  23. __subsystem struct hello_world_driver_api {
  24. /* This struct has a member called 'print'. 'print' is function
  25. * pointer to a function that takes 'struct device *dev' as an
  26. * argument and returns 'void'.
  27. */
  28. void (*print)(const struct device *dev);
  29. };
  30. __syscall void hello_world_print(const struct device *dev);
  31. static inline void z_impl_hello_world_print(const struct device *dev)
  32. {
  33. const struct hello_world_driver_api *api = dev->api;
  34. __ASSERT(api->print, "Callback pointer should not be NULL");
  35. api->print(dev);
  36. }
  37. #ifdef __cplusplus
  38. }
  39. #endif
  40. #include <syscalls/hello_world_driver.h>
  41. #endif /* __HELLO_WORLD_DRIVER_H__ */
  1. //hello_world_driver.c
  2. /*
  3. * Copyright (c) 2019 Nordic Semiconductor
  4. *
  5. * SPDX-License-Identifier: Apache-2.0
  6. */
  7. #include "hello_world_driver.h"
  8. #include <zephyr/types.h>
  9. #include <syscall_handler.h>
  10. /**
  11. * This is a minimal example of an out-of-tree driver
  12. * implementation. See the header file of the same name for details.
  13. */
  14. static struct hello_world_dev_data {
  15. uint32_t foo;
  16. } data;
  17. static int init(const struct device *dev)
  18. {
  19. data.foo = 5;
  20. return 0;
  21. }
  22. static void print_impl(const struct device *dev)
  23. {
  24. printk("Hello World from the kernel: %d\n", data.foo);
  25. __ASSERT(data.foo == 5, "Device was not initialized!");
  26. }
  27. #ifdef CONFIG_USERSPACE
  28. static inline void z_vrfy_hello_world_print(const struct device *dev)
  29. {
  30. Z_OOPS(Z_SYSCALL_DRIVER_HELLO_WORLD(dev, print));
  31. z_impl_hello_world_print(dev);
  32. }
  33. #include <syscalls/hello_world_print_mrsh.c>
  34. #endif /* CONFIG_USERSPACE */
  35. DEVICE_DEFINE(hello_world, "CUSTOM_DRIVER",
  36. init, NULL, &data, NULL,
  37. PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
  38. &((struct hello_world_driver_api){ .print = print_impl }));

使用驱动示例

  1. /*
  2. * Copyright (c) 2019 Nordic Semiconductor
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. #include "hello_world_driver.h"
  7. #include <stdio.h>
  8. #include <zephyr.h>
  9. const struct device *dev;
  10. static void user_entry(void *p1, void *p2, void *p3)
  11. {
  12. hello_world_print(dev);
  13. }
  14. void main(void)
  15. {
  16. printk("Hello World from the app!\n");
  17. dev = device_get_binding("CUSTOM_DRIVER");
  18. __ASSERT(dev, "Failed to get device binding");
  19. printk("device is %p, name is %s\n", dev, dev->name);
  20. k_object_access_grant(dev, k_current_get());
  21. k_thread_user_mode_enter(user_entry, NULL, NULL, NULL);
  22. }