文章大纲
- 引言
- 一、HDF的驱动加载(驱动安装)方式
- 1、动态加载(主要是uhdf)
- 2、静态加载(主要是khdf)
- 2.1、驱动入口实现
- 2.1.1、Bind接口
- 2.1.2、Init接口
- 2.1.3、Release接口
- 2.2、HDF_INIT 驱动入口符号
- 2.3、获取驱动列表
- 2.4、获取设备列表
- 2.5、设备与驱动的匹配
- 2.6、HDF驱动框架的启动
- 2.7、驱动静态加载的全流程总结
- 二、驱动框架交互流程
- 三、驱动的开发
- 3.1、实现驱动的业务功能逻辑的三个接口
- 3.2、定义用于注册驱动入口对象的结构体DriverEntry
- 3.3、驱动配置信息
- 3.3.1、驱动设备描述(必选)
- 3.3.2、驱动私有配置信息(可选)
- 3.3.3、配置SeLinux
引言
前面HDF 驱动框架基础理论入门文章简单的总结了HDF最基础的理论知识,接下来好好学习下HDF的一些核心工作原理。
一、HDF的驱动加载(驱动安装)方式
1、动态加载(主要是uhdf)
传统so方式,先dlopen打开指定的so,驱动程序通过指定symbol方式找到驱动函数入口进行加载。
2、静态加载(主要是khdf)
OpenHarmony驱动主要部署在内核态,当前主要采用静态链接方式,随内核子系统编译和系统镜像打包。HDF驱动加载包括按需加载和按序加载。按需加载是HDF框架支持驱动在系统启动过程中默认加载,或者在系统启动之后动态加载;按序加载是HDF框架支持驱动在系统启动的过程中按照驱动的优先级进行加载。HDF框架定义的驱动按需加载方式的策略是由配置文件中的 preload 字段来控制,preload 字段的取值范围以及含义如下:
驱动的按序加载是通过配置文件中的 priority(取值范围为整数 0 到 200)来决定的,priority 值越小,表示的优先级越高。驱动的加载顺序,优先根据 host 的 priority 决定,如果host 的 priority 相同,再根据 host 内的驱动 priority 值来决定加载顺序。
采用将驱动程序通过Scatter编译到指定的Section,再通过访问指定Section的地址找到驱动函数入口进行加载,如下是内核态驱动静态加载具体流程:
2.1、驱动入口实现
在HDF驱动框架中,HdfDriverEntry对象用于描述驱动入口的实现:
struct HdfDriverEntry {int32_t moduleVersion;const char *moduleName;int32_t (*Bind) (struct HdfDeviceObject *device0bject) ;int32_t (*Init) (struct HdfDeviceObject *device0bject) ;void (*Release) (struct HdfDevice0bject *device0bject) ;
}
2.1.1、Bind接口
用于驱动接口实例化绑定,若需要发布驱动接口,会在驱动加载过程中被调用,实例化该接口的驱动服务和DeviceObject绑定。驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架。该接口的作用主要是完成驱动设备和设备服务接口的bind动作。
在HdfDriverEntry的Bind方法中,必须完成全部驱动服务接口的绑定,禁止将服务接口未定义或定义为空,因为驱动定义的服务接口,均是对外暴露的,如果未定义或定义为空,可能会导致外部调用时产生异常,从而降低驱动的可靠性。
2.1.2、Init接口
当框架完成设备绑定动作后,就开始调用驱动初始化接口,初始化成功后,驱动框架根据配置文件决定是对外创建设备服务接口,还是接口只对当前服务可见。如果Init初始化失败,驱动框架就会主动释放创建的设备接口等信息。
2.1.3、Release接口
用于驱动的卸载,在该接口中需要释放驱动实例的软硬件资源。当需要卸载驱动时,驱动框架先通过该接口通知驱动程序释放资源,然后再释放其他内部资源。
2.2、HDF_INIT 驱动入口符号
系统启动时会执行HDF_INIT宏,
int SampleDriverBind(struct HdfDevice0bject *device0bject)
int SampleDriverInit (struct HdfDeviceObject *device0bject)
void SampleDriverRelease(struct HdfDeviceObject *device0bject)struct HdfDriverEntry g_sampleDriverEntry = {.moduleVersion = 1,.moduleName = "sample_driver",.Bind = SampleDriverBind, // 职责:绑定驱动对外提供的服务接口到HDF.Init = SampleDriverInit, // 职责:初始化驱动自身的业务.Release = SampleDriverRelease, // 职责:释放驱动资源,发生异常时也会调用
};
HDF_INIT (g_sampleDriverEntry) ;
宏展开后,在宏里定义了一个驱动的模块名+HdfEntry的符号并放到.hdf.section,即在编译时通过Scatter文件把HDF_INIT为开头的模块module的指针全部放到.hdf.section,放的Driver Entry的指针。
#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 将用于开机启动时查找设备驱动。
2.3、获取驱动列表
HDF 驱动框架通过将驱动程序入口符号的地址集中存放到一个特殊的 section 来实现对驱动的索引,这个 section 的开头和末尾插入了_hdf_drivers_start、_hdf_drivers_end 两个特殊符号,用于标记这个 section 的范围,两个特殊符号之间的数据即为驱动实现指针。
2.4、获取设备列表
系统启动时会根据Device Info创建对应的Host 节点。配置文本编译后会变成二进制格式的配置文件,其中设备相关信息被存放在一个用“hdf_manager”标记的 device_info 配置块中,host的内容以块的形式在 device_info 块中依次排列,host 块中记录了 host 名称、启动优先级和设备列表信息。设备信息中的 moduleName 字段将用于和驱动程序入口中的 moduleName 进行匹配,从而为设备匹配到正确的驱动程序。从而为设备匹配到正确的驱动程序,完成设备与驱动的匹配,具体流程图如下:
2.5、设备与驱动的匹配
系统启动时Device Manager和Device Host 即HDF框架首先启动,在系统启动时,驱动框架先启动(如何先启动?),通过解析配置文件获取到设备列表,通过读取”.hdf
drivers"段读取到驱动程序(Driver Entry)列表,然后遍历设备列表与驱动程序列表进行匹配,并加
载匹配成功的驱动。驱动框架有两大核心管理者:
- DeviceManager 负责设备的管理,包括设备加载、卸载和查询等设备相关功能。
- DeviceServiceManager 负责管理设备发布的接口服务,提供接口服务的发布、查询等功能。
驱动加载主要由 DeviceManager 主导,首先 DeviceManager 要解析配置文件中的Host 列表,根据Host 列表中的信息来实例化对应的Host 对象。Host 解析配置文件获取到关联的设备列表,遍历设备列表去获取与之匹配的驱动程序名称,然后基于驱动程序名称遍历前面提到的.hdf.driver section 获得驱动程序地址。
2.6、HDF驱动框架的启动
HDF驱动框架通过late_initcall 启动
2.7、驱动静态加载的全流程总结
- 在系统启动时,DeviceManagerInit通过late_initcall先启动。
- Device Manager 根据 Device Information 信息,解析配置文件中的Host 列表,根据 Host 歹
表中的信息来实例化对应的 Host 对象。 - Host遍历设备列表去获取与之匹配的驱动程序名称,然后基于驱动程序名称遍历.hdf.driver sec
on 获得驱动程序地址 - 设备与驱动匹配成功之后,获取指定驱动的入口地址,加载对应的设备驱动程序。
- 调用指定驱动的 Bind 接口,用于关联设备和服务实例。
- 调用指定驱动的 Init 接口,用于完成驱动的相关初始化工作。
- 如果驱动被卸载或者因为硬件等原因Init 接口返回失败,Release 将被调用,用于释放驱动申请
的各类资源。
二、驱动框架交互流程
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(Hardware Driver interface )提供标准化的接口定义和实现,驱动框架提供 IO Service和IO Dispatcher 机制,使得不同部署形态下驱动接口趋于形式一致。
驱动框架完成大部分驱动加载的动作,用户只需注册自己所需的接口和配置,然后驱动框架就会解析配置的内容,完成驱动加载和初始化动作。
三、驱动的开发
基于HDF(Hardware Driver Foundation开发驱动,开发者只需注册所需接口和配置,驱动框架就会解析配置内容,完成驱动加载和初始化动作:
3.1、实现驱动的业务功能逻辑的三个接口
HDF_INIT(g_sampleDriverEntry); 系统内核自动调用的一个宏,驱动入口对象注册到HDF框架,会调用HDF_INIT函数将驱动入口地址注册到HDF框架。
当调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。所以首先就是驱动入口部分相关的Bind, Init 和Release三个接口:
- Bind接口,驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
该接口的作用主要是完成驱动设备和设备服务接口的bind动作。
int32_t SampleDriverBind(struct HdfDeviceObject *deviceObject)
{return HDF_SUCCESS;
}
- Init接口
在HdfDriverEntry的Init方法中,应当调用HdfDeviceSetClass接口,对驱动的类型进行定义。驱动的类型可以用于归类当前设备的驱动程序,也可以用来查询当前设备的驱动能力。为了便于后续驱动的统一管理,建议通过HdfDeviceSetClass接口来设置当前驱动的类型。
int32_t SampleDriverInit(struct HdfDeviceObject *deviceObject)
{// 设置驱动的类型为DISPLAYif (!HdfDeviceSetClass(deviceObject, DEVICE_CLASS_DISPLAY)) {HDF_LOGE("HdfDeviceSetClass failed");return HDF_FAILURE;}return HDF_SUCCESS;
}
- Release接口
当用户需要卸载驱动时,驱动框架先通过该接口通知驱动程序释放资源,然后再释放其他内部资源
void SampleDriverRelease(struct HdfDeviceObject *deviceObject)
{//驱动资源释放的接口 Release all resources.return;
}
3.2、定义用于注册驱动入口对象的结构体DriverEntry
定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量,主要是把三个接口赋值,相当于是完成映射。
struct HdfDriverEntry g_deviceSample = {.moduleVersion = 1,.moduleName = "sample_driver", .Bind = SampleDriverBind,.Init = SampleDriverInit,.Release = SampleDriverRelease,
};
3.3、驱动配置信息
驱动配置包含两部分,HDF框架定义的驱动设备描述和驱动的私有配置信息。HDF使用HCS作为配置描述源码,内容以 Key-Value 键值对为主要形式。它实现了配置代码与驱动代码解耦,便于开发者进行配置管理。HC-GEN (全称 HDF Configuration Generator) 是 HCS 配置转换工具,可以将 HDF 配置文件转换为软件可读取的文件格式:在弱性能环境中,转换为配置树源码,驱动可直接调用 C代码获取配置;在高性能环境中,转换为 HCB(HDF Configuration Binary)二进制文件,驱动可使用 HDF框架提供的配置解析接口获取配置。HCS经过HC-GEN编译生成HCB文件,HDF驱动框架中的HCS Parser模块会从HCB文件中重建配置树,HDF驱动模块使用HCS Parser提供的配置读取接口获取配置内容。
3.3.1、驱动设备描述(必选)
HDF框架加载驱动所需要的信息来源于HDF框架定义的驱动设备描述,因此基于HDF框架开发的驱动必须要在HDF框架定义的device_info.hcs配置文件中添加对应的设备描述,驱动的设备描述填写如下所示:
sample_host :: host{hostName = "host0"; //host名称,host节点是用来存放某一类驱动的容器。priority = 100; //host启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证host的加载顺序。device_sample :: device { //sample设备节点。device0 :: deviceNode { //sample驱动的DeviceNode节点。policy = 1; //驱动服务发布的策略priority = 100; //驱动启动优先级(0-200),值越大优先级越低,建议默认配 100,优先级相同则不保证 device 的加载顺序preload = 0; //驱动按需加载字段permission = 0664;//驱动创建设备节点权限moduleName = "sample_driver"; //驱动名称,该字段的值必须和驱动入口结构的moduleName值一致serviceName = "sample_service"; //驱动对外发布服务的名称,必须唯一deviceMatchAttr = "sample_config";//驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等。}}
}
3.3.2、驱动私有配置信息(可选)
如果驱动有私有配置,则可以添加一个驱动的配置文件,用来填写一些驱动的默认配置信息,HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject 中的property里面,通过Bind和Init传递给驱动,驱动的配置信息示例如下:
root { SampleDriverConfig {sample_version = 1; sample_bus = "I2C_0"; match_attr = "sample_config"; //该字段的值必须和device_info.hcs中的deviceMatchAttr值一致}
}
3.3.3、配置SeLinux
在4.0后必须配置SeLinux 否则即使加载了So也无法正常被拉起,具体配置规则根据业务自行配置。
未完待续…