开源操作系统中系统驱动框架做的最好的无疑是Linux系统,Linux系统中设计精良的驱动框架、设备树等功能会硬件厂商对接驱动系统的成本降到很低,并且通过接入VFS系统统一了Linux系统驱动对应用程序提供的接口,为操作系统中使用驱动相关的应用程序在不同的Linux系统间的移植扫平了障碍。反观传统的RTOS系统大多数比较注重在内核功能的开发上,欠缺对驱动架构方面的设计。前文曾经提到过物联网操作系统大多是从传统RTOS系统发展而来的,所以大多数的物联网操作系统在驱动框架上面的没有经过太多设计,总结下来可以分为以下几类:
- 完全没有对设备驱动进行任何定义
- 简单定义了硬件接口层后完全由芯片厂实现
- 实现了简单的设备驱动,提供统一的应用程序访问接口
第1类和第2类系统对应用程序不太友好,即使是相同的操作系统上面写的应用程序移植到不同平台后也会因为接口不一致,或者是虽然接口形式一致但接口底层实现的差别导致应用程序大概率没办法跨平台移植或跨平台无需调试即可运行。第3类对应用程序的移植性比较友好,但有一个问题是,很多系统实现的通用驱动实现的都“过于简单”,这就导致有些硬件的总线的特性没办法被发挥出来,随着物联网应用场景越来越复杂,其缺点也是越来越明显。
在总结了物联网领域设备框架的问题之后,AliOS Things从“易用性”的角度出发进行设计,设计了下图所示的驱动框架。
驱动框架对应用程序统一提供两种类型的接口:
- AOS API,如aos_gpioc_get/ aos_gpioc_set等
- VFS API,即open/close/read/write/ioctl/poll等
AOS API对进行传统RTOS应用开发的人比较友好。对于RAM/ROM要求非常严格的应用场景,可以直接呼叫AOS形式的API,减少对VFS驱动子系统的依赖,从而减少固件大小。
VFS API对Linux开发者比较友好,遵循POSIX接口定义,基于POSIX标准实现的应用程序在AliOS Things和其它遵循POSIX接口的操作系统之间相互移植就会简便很多。
驱动框架定义了HAL API的标准,一般是由芯片厂商来实现的。HAL API的定义的过程中主要考虑了以下两点:
- 硬件强相关:只有和硬件强相关的功能才会被定义到HAL API中
- 原子功能:所有HAL API功能都很独立,很多API只需要操作硬件的几个寄存器就可以实现了
在HAL API和AOS API之间则是设备驱动子系统的实现层。设计设备驱动子系统的主要目的有两个:
- 隔离性:将应用程序和HAL API隔离开,彻底解决应用和芯片厂之间的耦合关系
- 统一性:驱动程序只依赖与驱动子系统提供的API,驱动子系统保证对应用层提供的API行为在不同平台之间是一致的。
除了提供AOS或VFS API之外和定义HAL API的功能之外,驱动框架还提供了以下功能,下面讲分别介绍
1、驱动自动分级加载机制
随着现代物联网系统的应用场景越来越复杂,同一个物联网硬件设备对外设的需求也越来越多。不同驱动程序之间可能会存在依赖关系。为了能让驱动开发者比较方便的将自己的驱动以模块的方式添加到系统中和尽量减少添加/删除设备驱动过程中所需要修改的代码,设计了驱动自动加载机制。AliOS Things将设备驱动一共分为9个级别,每个级别的驱动初始化声明宏定义及其在系统启动过程中的启动顺序如下表所示。
启动顺序 |
宏定义 |
段名称定义 |
1 |
CORE_DRIVER_ENTRY(driver_entry_api_name) |
core_driver_entry |
2 |
BUS_ DRIVER_ENTRY(driver_entry_api_name) |
bus_driver_entry |
3 |
EARLY_DRIVER_ENTRY(driver_entry_api_name) |
early_driver_entry |
4 |
VFS_ DRIVER_ENTRY(driver_entry_api_name) |
vfs_driver_entry |
5 |
LEVEL0_DRIVER_ENTRY(driver_entry_api_name) |
level0_driver_entry |
6 |
LEVEL1_DRIVER_ENTRY(driver_entry_api_name) |
level1_driver_entry |
7 |
LEVEL2_DRIVER_ENTRY(driver_entry_api_name) |
level2_driver_entry |
8 |
LEVEL3_DRIVER_ENTRY(driver_entry_api_name) |
level3_driver_entry |
9 |
POST_DRIVER_ENTRY(driver_entry_api_name) |
post_driver_entry |
采用上面的宏定义进行初始化函数声明之后,用同样的宏定义声明的函数指针会被分到一组中,相同组会在链接阶段放到固件特定的代码段中。系统启动的时候,驱动框架会依次从这些特定的代码段去读取函数指针并呼叫这些函数指针指向的函数,每个段被编译进的段名称如上表所示。这套机制可以保证用不同宏定义声明的多个驱动初始化函数会严格按照表格中从1到9的顺序被呼叫,用相同宏定义声明的多个驱动初始化函数则是随机的。
除此之外,驱动框架还提供了设备后台初始化方式的宏定义(VFS_DRIVER_BG_ENTRY(driver_entry_api_name)),可以用于低优先级驱动初始化函数的声明。被声明做低优先级驱动的初始化函数会在被低优先级的线程在后台运行,通过这样的方式可以提高整个系统启动的速度。假设将将蓝牙驱动初始化过程声明成后台初始化(VFS_DRIVER_BG_ENTRY(bluetooth_drv_init, NULL, 4096)),常规加载过程和驱动分级加载过程的软件流程的差异可以参考下图所示的流程图。
2、驱动线程/消息/事件模型
AliOS Things在3.3版版本中支持的弹性内核对驱动框架提出了比较高的要求,驱动框架既要能运行在宏内核架构下,又要能运行在微内核架构下,甚至还需要同时运行在内核态和用户态。针对这种需求,AliOS Things设计了在适用于弹性内核架构下线程/消息/事件模型。
当设备驱动运行在内核态的时候,内核功能可以通过直接函数调用的方式来调用驱动提供的服务;用户态应用程序可以通过VFS接口或这是通过系统调用的方式访问AOS接口。
但当驱动运行在用户态的时候,其它应用程序访问驱动提供的服务则需要通过RPC(Remote Procure Call,远程过程调用)的方式。在这种情况下,AliOS Things只提供VFS的服务访问方式,如上图所示。这样同一套硬件的驱动程序无需修改就可以运行在用户态或内核态,跟RPC和VFS相关的逻辑全部由驱动框架完成,从而简化硬件驱动程序的设计。微内核架构下驱动线程/消息模型的流程图如下图所示,详细代码可以查看aos_device_register函数的实现,这里就不进行详细展开。
3、设备驱动子系统
下表是各个设备驱动子系统源代码位置,接口定义头文件、使用案例说明以及芯片厂对接接口头文件所在位置。读者可以根据自己的需求进行详细解读。
其中每个设备驱动子系统都有对应的AOS API模块和VFS API模块,VFS API模块是依赖于AOS API的。用户可以根据需求选择使用哪种类型的API。
项目 |
说明 |
源代码位置 |
components/drivers/peripheral |
接口头文件说明 |
components/drivers/peripheral/<subsystem>/include/aos/driver |
使用案例说明 |
components/drivers/peripheral/<subsystem>/example |
芯片厂对接接口头文件 |
components/csi/csi2/include/drv/<subsystem>.h |
其中各驱动子系统在设计实现过程中可以满足下表的场景需求。
驱动子系统类型 |
适用的场景 |
I2C |
同时和多个I2C从设备使用不同的Clock频率进行数据通信 |
SPI |
同时和多个SPI从设备使用不同的参数设定(Clock频率、有无CS及CS极性有效性)进行数据通信 |
MTD |
支持多分区,Nand Flash和Nor Flash |
UART |
兼容POSIX的UART操作 |