Zephyr Kernel 设备驱动和设备模型(一)

介绍

       Zephyr 内核支持大量的设备驱动程序。应用程序板级配置的可用设备驱动集合随着所关联的硬件组件和设备驱动软件的变化而变化。Zephyr 的设备模型为配置驱动程序提供了一致的设备模型。设备模型负责初始化配置到系统中的所有驱动。每种类型的驱动程序(UAR,SPI,I2C)都有一个通用类型的API。在这个模型中,当驱动程序初始化时,驱动中会填充指向包含指向它的API 函数的结构的指针。这些结构体被按照初始化等级放到RAM区。


标准驱动
所有的板级配置中都支持的设备驱动包括:
• 中断控制器:用于内核的中断管理子系统。
• 定时器:用于内核的系统时钟和硬件时钟子系统。
• 串行通信:用于内核的系统控制台子系统。
• 随机数生产器:提供数据数源。

同步调用

Zephyr 对多种板子提供了一系列的设备驱动程序。除非硬件不支持,否则每个驱动都应当提供基于中断(而不是轮询)的实现。


驱动程序API
device.h中提供了如下关于设备驱动程序的API。这些API只能用于设备驱动程序中,不能应用于应用程
序中。.
DEVICE_INIT()
创建设备对象,并在启动的初始化阶段调用其初始化函数。
DEVICE_AND_API_INIT()
创建设备对象,并在启动的初始化阶段调用其初始化函数。此外,它还用一个指针指向驱动程
序的API。
DEVICE_NAME_GET()
获得一个全局设别对象的全名。
DEVICE_GET()
通过名字获取一个执行设备对象的指针。
DEVICE_DECLARE()
声明一个设备对象。


驱动的数据结构
设备驱动宏中的某些数据结构分为只读部分和运行时可变部分。在最顶层包括:
struct device {
struct device_config *config;
void *driver_api;
void *driver_data;
};
成员config 是只读配置数据的集合,它在编译时就确定了,例如IO 地址映射的内存、IRQ 号或者设备的其
它固定物理特性。这是传递给宏DEVICE_*INIT()的config_info结构体。
driver_data 结构被置于ARM 中,它是每个实例在运行时的驱动程序所使用的数据。例如,引用计数、信号
量、scratch 缓冲等。
driver_api 结构是驱动程序中实现的设备相关的通用子系统API。它通常是只读的,并在编译时就确定了。
在下一节中将详细描述这一点。
子系统以及API 结构
大多数驱动程序的主要目标是提供一个与设备独立的子系统API。应用程序只需要简单地使用这些通用
API,而不需要了解驱动实现的细节。
子系统API的定义通常是这样的:
typedef int (*subsystem_do_this_t)(struct device *device, int foo, int bar);
typedef void (*subsystem_do_that_t)(struct device *device, void *baz);
struct subsystem_api {
subsystem_do_this_t do_this;
subsystem_do_that_t do_that;
};
static inline int subsystem_do_this(struct device *device, int foo, int bar)
{
struct subsystem_api *api;
api = (struct subsystem_api *)device->driver_api;
return api->do_this(device, foo, bar);
}
static inline void subsystem_do_that(struct device *device, void *baz)
{
struct subsystem_api *api;
api = (struct subsystem_api *)device->driver_api;
api->do_that(device, foo, bar);
}
通常,在遇到错误时,除非在某个常规操作中需要返回值(例如存储设备满了),否则最好使用宏
__ASSERT() 进行断言。参数错误、编程错误、一致性检查、不可恢复的错误等都需要使用断言进行处理。
当需要返回错误状态给调用者检查时,如果成功则返回0,如果失败则返回POSIX errno.h 代码。更多细节
请参考https://wiki.zephyrproject.org/view/Coding_conventions#Return_Codes。
当实现一个具体的子系统时,驱动程序需要定义这些API,并将它与子系统API 结构绑定在一起:

static int my_driver_do_this(struct device *device, int foo, int bar)
{
...
}
static void my_driver_do_that(struct device *device, void *baz)
{
...
}
static struct subsystem_api my_driver_api_funcs = {
.do_this = my_driver_do_this,
.do_that = my_driver_do_that
};
然后,驱动程序需要将my_driver_api_funcs 作为api 参数传递给宏DEVICE_AND_API_INIT(),或者在驱动
的初始化函数中手动地将其赋值给device->driver_api。
注解: 由于指向API 函数的指针是通过driver_api 结构引用的,这些指针将始终被包含到二进制文件中(即
使未使用)。链接选项gc-sections 至少能看到对它们的一个引用。Providing for link-time size optimizations
with driver APIs in most cases requires that the optional feature be controlled by a Kconfig option.

初始化等级
驱动程序可能会依赖其它先初始化的驱动或者需要使用内核服务。DEVICE_INIT() 允许用户指定在系统
启动的哪个时间段执行设备驱动的初始化函数。所有的驱动程序都需要在如下的五个初始化等级中指定一
个:
PRE_KERNEL_1
用于那些没有任何依赖的设备,例如那些纯粹只需要处理器/SoC 上的硬件的设备。这些设备在
配置期间不需要使用任何内内核服务,因此此时内核服务还未启动。不过,中断子系统会被配
置,因此可以设置中断。在这个等级上的初始化函数运行在中断栈上面。
PRE_KERNEL_2
用于那些依赖于已被初始化的PRE_KERNEL_1 等级的设备的设备。这些设备在配置期间不使用
任何内核服务,因此此时内核服务还未启动。在这个等级上的初始化函数运行在中断栈上面。
POST_KERNEL
用于那些在配置期间需要依赖内核服务的设备。在这个等级上的初始化函数运行在内核主栈的
上下文中。
APPLICATION


用于需要自动配置的应用程序组件(即非内核组件)。这些设别在配置期间可以使用内核提供
的所有服务。在这个等级上的初始化函数运行在内核主栈的上下文中。
在每个初始化等级,您还需要指定一个优先级,用于区分相同初始化等级的其它设备。这个优先级是0 到
99 之间的整数值。优先级越低表示越早被初始化。优先级必须是一个前面没有补零的或者没有符号的十
进制整数字面量或者一个对等的符号(例如#define MY_INIT_PRIO 32)。符号表达式是不被允许的(例如
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT + 5)。
系统驱动
在某些情况下,您可以只需要在启动时运行某个函数。宏SYS_INIT 被映射为DEVICE_INIT() 或DEVICE_
INIT_PM()。对于SYS_INIT(),它不存在配置或者运行时的数据结构,因此也不能再随后通过名字获
取到设备指针。它的初始化等级和优先级与普通设备是一样的。
对于SYS_INIT_PM(),您可以通过名字获得指针。参考power management 一节。
SYS_INIT()
SYS_INIT_PM()

你可能感兴趣的:(物联网操作系统--zephyr)