OpenHarmony HDF 框架介绍
OpenHarmony HDF 框架介绍
OpenHarmony 系统 HDF 驱动框架采用 C 语言面向对象编程模型构建,通过平台解耦、内核解耦,来达到兼容不同内核,统一平台底座的目的,从而帮助开发者实现驱动一次开发,多系统部署到的效果。
为了达成这样一个目标,OpenHarmony 系统 HDF 驱动框架提供了:
操作系统适配层(OSAL):对内核操作系统相关接口进行统一封装,屏蔽不同系统的操作接口;
平台驱动接口:提供 Board 部分驱动(例如:i2c、spi、uart 总线等平台资源)支持,同时对 Board 硬件操作进行统一的适配接口抽象,方便开发者只需要开发新硬件抽象接口,即可获得新增 Board 部分驱动支持
驱动模型:面向器件驱动,提供常见的驱动抽象模型,主要打成两个目的:
提供标准化的器件驱动模型,开发者无需独立开发,通过配置即可完成驱动部署
提供驱动模型抽象,屏蔽驱动与不同系统组件之间的交互,使得驱动更具备通用性。
OpenHarmony 系统 HDF 驱动框架主要由:
- 驱动基础框架
- 驱动程序
- 驱动配置文件
- 驱动接口
这四部分组成。
- HDF 驱动基础框架提供统一的硬件资源管理,驱动加载管理以及设备节点管理等功能。驱动框架采用的是主从模式设置,由 device manager 和 device host 组成。device manager 提供了统一的驱动管理,device manager 启动时根据 device information 提供驱动设备信息加载相应的驱动 device host,并控制 host 完成驱动的加载。 device host 提供驱动运行的环境,同时预置 host framework 与 device manager 进行协同,完成驱动加载和调用。根据业务的需求 device host 可以有多个实例。
- 驱动程序实现驱动具体的功能,每个驱动由一个或者多个驱动程序组成,每个驱动程序都对应着一个 driver entry。driver entry 主要完成驱动的初始化和驱动接口绑定功能;
- 驱动配置文件(.hcs),主要由设备信息(device information)和设备资源(device resource)组成,device information 完成设备信息的配置(如配置接口发布策略,驱动加载方式等)。device resource 完成设备资源的配置(如 GPIO 管脚、寄存器等资源信息配置等);
- 驱动接口(HDI),提供标准化的接口定义和实现,驱动框架提供 IO services 和 IO dispatcher 机制,是的不同部署形态下驱动接口趋于形式一致。
HDF 驱动框架框图
HDF 框架将一类设备驱动放在同一个 host 里面,开发者也可以将驱动功能分层独立开发和部署,支持一个驱动多个 node, HDF 框架管理驱动模型如下图所示:
HDF 驱动框架工作原理
device manager 提供了统一的驱动加载管理机制和驱动接口发布机制。
当 device host 环境加载完成时,device manager 根据 device information 信息,请求 host 加载相应的驱动程序,device host 在收到请求时,进行以下操作:
- 根据请求加载设备信息,查找并加载指定路径下驱动镜像或从指定段地址(section)查找驱动程序入口
- 查找驱动设备描述符,匹配对应的设备驱动
- 当驱动匹配成功时,加载指定驱动程序镜像
- host framework 在驱动程序镜像加载成功后,调用驱动程序(driver entry)的绑定接口和初始化接口,实现与驱动程序的服务对象绑定,同时初始化设备驱动程序
- 当 device information 配置中的服务策略要求对外暴露驱动接口时,驱动框架就将驱动程序的服务对象添加到对外发布的服务对象列表中,外部客户端程序就可以通过此列表来查询并访问相应的服务接口。
HDF 驱动框架工作原理框图:
HDF 驱动加载过程分析
OpenHarmony 系统驱动根据驱动程序部署的不同方式,存在两种驱动加载方式:
- 动态加载方式:采用传统的 so(共享库)加载方式,驱动程序通过指定 symbol 方式找到驱动函数入口进行加载
- 静态加载方式:采用将驱动程序通过 scatter 编译到指定的 section,再通过访问指定 section 对应的地址,找到驱动函数入口进行加载。
下面结合一个 sample 示例代码,讲解驱动加载过程,重点分析静态加载方式下内核态驱动加载过程。
HDF 驱动加载过程分析——驱动实现1
在 HDF 驱动框架中,HdfDriverEntry 对象被用来描述一个驱动实现:
struct HdfDriverEntry{
int32_t moduleVersion;
const char *moduleName;
int32_t (*Bind)(struct HdfDeviceObject *deviceObject);
int32_t (*Init)(struct HdfDeviceObject *deviceObject);
void (*Release)(struct HdfDeviceObject *deviceObject);
}
- Bind 接口:实现驱动接口实例化绑定,如果需要发布驱动接口,会在驱动加载过程中被调用,实例化该接口的驱动服务并和 deviceobject 绑定;
- Init 接口:实现驱动的初始化,返回错误将中止驱动加载流程;
- Release 接口:实现驱动的卸载,在该接口中释放驱动实例的软硬件资源。
HDF 驱动加载过程分析——驱动实现2
int SampleDriverBind(struct HdfDeviceObject *deviceObject)
intSampleDriverInit(struct HdfDeviceObject *deviceObject)
intSampleDriverRelease(struct HdfDeviceObject *deviceObject)
struct HdfDriverEntry g_sampleDriverEntry = {
.moduleVersion = 1,
.moduleName = "sample_driver",
.Bind = SampleDriverBind,
.Init = SampleDriverInit,
.Release = SampleDriverRelease
};
HDF_INIT(g_sampleDriverEntry);
将 HDF_INIT 宏展开:
#define HDF_SECTION __attribute__((section(".hdf.driver")))
#define HDF_DRIVER_INIT(module) \
const size_t USED ATTR module##HdfEntry HDF SECTION = (size_t)(&(module))
可以看到 HDF_INIT
宏是定义了一个 “驱动模块名+HdfEntry”的符号放到 “.hdf.driver”
所在的 section,该符号指向的内存地址即为驱动程序入口结构体的地址。这个特殊的 section 将用于开机启动时查找设备驱动。
HDF 驱动加载过程分析——获取驱动列表
HDF 驱动框架通过将驱动程序入口符号的地址集中存放到一个特殊的 section 来实现对驱动的索引,这个 section 的开头和结尾插入了 _hdf_drivers_start、_hdf_drivers_end 两个特殊符号,用于标记这个 section 的范围,两个特殊符号之间的数据即为驱动实现指针。
HDF 驱动加载过程分析——获取设备列表
配置文本编译后会变成二进制格式的配置文件,其中设备相关信息被存放在一个用 “hdf_manager” 标记的 device_info 配置块中,host 的内容以块的形式在 device_info 块中依次排列,host 块中记录了 host 名称、启动优先级和设备列表信息。设备信息中的 moduleName 字段将用于和驱动程序入口中的 moduleName 进行匹配,从而为设备匹配到正确的驱动程序。
HDF 驱动加载过程分析——设备与驱动的匹配
在系统启动时,驱动框架先启动(device manager、device host),通过解析配置文件获取到设备列表,通过读取“.hdf.drivers” 段读取到驱动程序列表,然后遍历设备列表与驱动程序列表进行匹配,并加载匹配成功的驱动。驱动框架有两大核心管理者:
device manager
负责设备的管理,包括设备加载、卸载和查询等设备相关功能device service manager
负责管理设备发布的接口服务,提供接口服务的发布、查询等功能
驱动加载主要有 device manager 主导,首先 device manager 要解析配置文件中的 host 列表,根据 host 列表中的信息来实例化对应的 host 对象,host 解析文件获取到关联的设备列表,遍历设备列表去获取与之匹配的驱动程序名称,然后基于驱动程序名称遍历前面提到的 hdf.driver section 获取驱动程序地址。
HDF 驱动加载过程分析——加载过程流程图
device manager 遍历设备列表,当查找到对应驱动时,为设备创建 device 对象实例,如果设备配置中的 policy 字段为需要对外发布的驱动接口,那么驱动的 bind 接口将首先被调用,用于关联设备和服务实例。然后驱动的 Init 接口将被调用,用于完成驱动的相关初始化工作。如果驱动被卸载或者因为硬件等原因 Init 接口返回失败,Release 将被调用,用于释放驱动申请的各种资源。
HDF 驱动加载过程分析——驱动框架启动
驱动框架通过 late_initcall 启动
static init __init DeviceManagerInit(void)
{
int ret;
ret = DeviceManagerStart();
return ret;
}
late_initcall(DeviceManagerInit);
late_initcall
宏展开
宏含义:
- 声明一个类型为 initcall)_t,名称为 __initcall_DeviceManagerInit 的函数指针
- 将这个函数指针初始化为 DeviceManagerInit
- 编译的时候需要把这个函数指针变量放置到名称为 “.initcall7.init”的 section 中,其实质就是将这个函数DeviceManagerInit的首地址放置到这个“.initcall7.init”的 section 中
HDF 驱动加载过程分析——总结
- 在系统启动时,devicemanagerInit 通过 late_initcall 先启动
- device manager 根据 device information 信息,解析配置文件中的 host 列表,根据 host 列表中的信息来实例化对应的 host 对象
- host 变量设备列表去获取与之匹配的驱动程序名称,然后基于驱动程序名称遍历 .hdf.driver section 获取驱动程序地址
- 设备与驱动匹配成功后,获取指定驱动的入口地址,加载对应的设备驱动程序
- 调用指定驱动的 bind 接口,用于关联设备和服务实例
- 调用指定驱动的 init 接口,拥有完成驱动的相关初始化工作
- 如果驱动被卸载或者因为硬件等原因 init 接口返回失败,release 将被调用用于释放驱动申请的各类资源。