openharmony中HDF驱动框架源码梳理-驱动加载流程

要想大概了解一个公司,我们可能只需要知道它的运行逻辑即可,例如我们只需要知道它有财务有研发有运营等,财务报销、研发负责产品等即可,但是如果想深入具体的了解的话我们就要了解都有什么部门(对象)、各部门都包含哪些职责(对象方法)以及各部门都包含哪些关键人员(子对象)以及他们的职责(子对象方法),根据这个逻辑我大概整理了openharmony 5.0的HDF框架中包含的关键对象以及对应的方法,便于更深的理解HDF的实现。

一、源码目录

仓库路径 仓库内容
drivers/hdf_core/framework HDF框架、平台驱动框架、驱动模型等平台无关化的公共框架。 - framework/core目录:驱动框架 - 提供驱动框架能力,主要完成驱动加载和启动功能。 - 通过对象管理器方式可实现驱动框架的弹性化部署和扩展。 - framework/model目录:驱动模型 提供了模型化驱动能力,如网络设备模型。 - framework/ability目录:驱动能力库 提供基础驱动能力模型,如IO通信能力模型。 - framework/tools目录:驱动工具 提供HDI接口转换、驱动配置编译等工具。 - framework/support目录:Support 提供规范化的平台驱动接口和系统接口抽象能力。
drivers/hdf_core/adapter 包含所有LiteOS-M和LiteOS-A内核以及用户态接口库等相关适配代码以及编译脚本。
drivers/hdf_core/adapter/khdf/linux 包含所有Linux内核相关适配代码以及编译脚本。
drivers/peripheral Display、Input、Sensor、WLAN、Audio、Camera等外设模块硬件抽象层。
drivers/interface Display、Input、Sensor、WLAN、Audio、Camera等外设模块HDI接口定义。

二、硬件驱动框架(HDF)

1. 驱动开发流程

驱动加载 驱动服务管理 驱动消息机制 配置管理 驱动模型 HDF驱动开发流程 按需加载,加载策略由配置文件中的preload字段来控制
preload为0:则系统启动过程中默认加载
preload为01:当系统支持快速启动的时候,则在系统完成之后再加载这一类驱动
preload为2:则系统启动过程中默认不加载,支持后续动态加载 按序加载(默认)
配置文件中的priority(整数0到200)是用来表示host(驱动容器)和驱动的优先级的
host和驱动都是priority值越小,加载优先级越高 驱动需要以接口的形式对外提供能力时使用
包含驱动服务的发布和获取
驱动对外发布服务的策略,由配置文件中的policy字段来控制
0:驱动不提供服务
1:驱动对内核态发布服务
2:驱动对内核态和用户态都发布服务
3:驱动服务不对外发布服务,但可以被订阅
4:驱动私有服务不对外发布服务,也不能被订阅
5:错误的服务策略 当用户态应用和内核态驱动需要交互时使用:
1.用户态应用发送消息到驱动
2.用户态应用接收驱动主动上报事件
HCS(HDF Configuration Source)是HDF驱动框架的配置描述源码。
它实现了配置代码与驱动代码解耦,便于开发者进行配置管理 HDF框架将一类设备驱动放在同一个Host(设备容器)里面,用于管理一组设备的启动加载等过程
划分Host时,驱动程序是部署在一个Host还是部署在不同的Host,主要考虑驱动程序之间是否存在耦合性

下面以驱动开发流程为主线,一步一步分析每个节点的代码实现,以求完全剖析实现逻辑。本篇为驱动加载流程的说明

2. 驱动加载

上图整理的驱动开发流程主要是从驱动应用的角度来绘制的,在聊驱动加载之前我们肯定要问谁来加载驱动呢?答案是驱动框架,所以这时就要先看下驱动框架的加载了。

驱动框架在不同的平台上(linux/liteos等)是不同的,在linux中是直接使用late_initcall内核的函数(hdf_core\adapter\khdf\linux\manager\src\devmgr_load.c)来进行实现的,对linux内核比较熟悉的码友应该比较清楚(不清楚的可以自行查阅,网络上内容应该比较多),不论什么平台大家最终的目的是调用函数DeviceManagerStart,下面就从此函数开始分析:

由于不同系统有不同的差异,所以码友们需要注意甄别了哈,我下面主要针对linux系统来进行说明的:

c 复制代码
late_initcall(DeviceManagerInit);
|-> DeviceManagerInit()
    |-> DeviceManagerStart()
        | // (1)创建DevMgr单实例对象
        |-> instance = DevmgrServiceGetInstance();
        | // (2)发布DevMgr,"/dev/hdf/"+"dev_mgr"
        |-> ioService = HdfIoServicePublish(DEV_MGR_NODE, DEV_MGR_NODE_PERM);
        | // (3)启动DevMgr服务
        |-> instance->StartService(instance);
	    | //(4) 电源管理的初始化
	    |-> HdfPowerManagerInit()
2.1 创建DevMgr单实例对象

通过对代码的梳理可得代码流程如下图所示,由此可知此函数DevmgrServiceGetInstance的作用为创建DevMgr单实例对象,并为此对象设置好了具体的实现方法。

为了更好的分析DevMgr实例的具体方法我们可以参看IDevmgrService对象的类图,由图中标号①与标号②中我们可以发现实际创建的对象为单实例的DevmgrService类型,而接收的对象为IDevmgrService类型,由下面类图中的结构可知返回IDevmgrService类型相当于返回的单实例对象DevmgrService的super成员,可直接使用对应的接口函数。

在驱动框架需要使用DevmgrService提供对应的服务时,可通过设备服务管理客户端(DevmgrServiceClnt)的实例,该实例包含一个指向IDevmgrService对象的指针,通过该指针可以使用DevmgrService提供的服务,即设备管理接口。

2.2 发布用户态服务dev_mgr

在HdfIoServicePublish函数中直接调用的HdfIoServiceAdapterPublish()来发布用户态服务dev_mgr,具体的流程可参考下图:

其中涉及到了OSAL操作系统层,此层主要的功能是能够适配不同的操作系统(linux、liteos_a等),上图是使用linux操作系统举例的。

相关类图关系如下:

dev_mgr服务的使用

在用户态程序通过标准的文件操作接口open()打开/dev/hdf/dev_mgr设备节点时,会进入内核态驱动框架绑定的HdfVNodeAdapterOpen()中,然后会通过 struct HdfVNodeAdapter *adapter = (struct HdfVNodeAdapter *)OsalGetCdevPriv(cdev)这条语句将cdev中的priv字段强制类型转换,以转换为HdfVNodeAdapter 类型。

2.3 启动DevMgr服务

启动DevMgr服务(instance->StartService(instance))实际调用如下的设备管理服务的启动入口函数(DevmgrServiceStartService)

C 复制代码
//初始化设备管理服务,并启动设备主机(Device Hosts)以及设备服务管理器(DevSvcManager)
int DevmgrServiceStartService(struct IDevmgrService *inst)
	|-->struct DevmgrService *dmService = (struct DevmgrService *)inst;//1.数据类型的强制转换
	|-->ret = DevmgrServiceStartDeviceHosts(dmService);//启动所有设备主机,加载设备驱动并初始化设备
		|-->if (!HdfAttributeManagerGetHostList(&hostList)) {//2.3.1初始化主机设备列表
         |-->while (HdfSListIteratorHasNext(&it)) {//遍历主机设备列表,启动每个主机设备
         |-->ret = DevmgrServiceStartDeviceHost(inst, hostAttr);//2.3.2启动主机设备
         |-->HdfSListFlush(&hostList, HdfHostInfoDelete);//清理主机设备列表
	|-->int startServiceRet = DevSvcManagerStartService();//2.3.3启动设备服务管理器,负责设备服务的管理和发布
         |-->struct IDevSvcManager *svcmgr = DevSvcManagerGetInstance()//获取设备管理服务的实例
         |--> ret = svcmgr->StartService(svcmgr)//启动设备管理器服务
	|-->return ret;

在进行后续启动流程的了解,建议先看下后续驱动模型章节的内容:

2.3.1初始化主机设备列表

从下图的代码流程图中可看出当前流程主要实现对配置文件hcs的解析,并根据解析的信息创建对应的host,并将host信息存储到hostlist链表中

初始化主机设备列表主要是将解析的配置文件信息存储到DeviceResourceNode类型的hdfManagerNode指针中,从 hdfManagerNode 的子节点(child)开始,逐个遍历主机节点,每一个主机节点都会新创建一个新的主机信息实例hostInfo(struct HdfHostInfo),并从当前主机节点(hostNode)中提取主机信息,并填充到 hostInfo 中,最后将新创建的主机信息实例hostInfo添加到链表(hostList)中。参考以下为类图可辅助分析

2.3.2启动主机设备

启动主机设备即启动host,通过对代码的分析可主要分为三个关键步骤:创建设备主机客户端实例、获取当前主机下的设备列表和启动host进程,下面分别说明。

c 复制代码
static int DevmgrServiceStartDeviceHost(struct DevmgrService *devmgr, struct HdfHostInfo *hostAttr)
    |-->struct DevHostServiceClnt *hostClnt = DevHostServiceClntNewInstance(hostAttr->hostId, hostAttr->hostName);//2.3.2.1.创建设备主机客户端实例
    |-->if (HdfAttributeManagerGetDeviceList(hostClnt) != HDF_SUCCESS) {return HDF_FAILURE;} //2.3.2.2.获取当前主机下的设备列表
    |-->DListInsertTail(&hostClnt->node, &devmgr->hosts);//新创建的主机客户端实例(hostClnt)插入到设备管理服务(devmgr)的主机链表(hosts)中
    |-->if (HdfSListIsEmpty(&hostClnt->unloadDevInfos)) {return HDF_SUCCESS;//检查主机客户端的 unloadDevInfos 链表是否为空
    |-->if (DevmgrServiceStartHostProcess(hostClnt, false, false) != HDF_SUCCESS) { //2.3.2.3.启动host进程
    |-->return HDF_SUCCESS;

首先根据从配置文件中获取的host(hostAttr)的id和名称去创建host的客户端实例 ,然后将此hostClnt实例传入到HdfAttributeManagerGetDeviceList中去获取此host下的设备device(实际也是从配置文件中去获取),然后将新创建的主机客户端实例(hostClnt)插入到设备管理服务(devmgr)的主机链表(hosts)中并判断一下是否包含静态启动的设备,为了节省资源,当没有静态启动的设备便不再启动host。当有静态启动设备时会启动host进程。

  1. 静态设备(Static Devices)

    • 静态设备是指在系统启动时需要立即加载和初始化的设备。
    • 它们的配置通常在 HDF 的配置文件(如 .hcs 文件)中明确指定,并且会在系统启动时自动加载。
    • 静态设备通常与系统的核心功能密切相关,需要在系统启动时就可用。
  2. 动态设备(Dynamic Devices)

    • 动态设备是指在系统运行过程中,根据需要动态加载和初始化的设备。
    • 它们通常由外部事件触发(例如,设备的插入或某个服务的请求)。
    • 动态设备不需要在系统启动时立即加载,因此可以节省资源,并且可以根据实际需求动态分配资源。

    为了便于理解可参考以下相关类图 :

2.3.2.1.创建设备主机客户端实例

根据上图的类图可知DevHostServiceClnt类型的成员变量都包含哪些,下图的创建设备host客户端实例的主要作用就是分配内存并赋初值

💙创建时传入的参数只有hostid(在解析配置文件时自动递增添加的唯一标识)和hostname(来自配置文件) ,即此实例除了具备内存之外便只具备这两个有效属性

2.3.2.2.获取当前主机下的设备列表

HdfAttributeManagerGetDeviceList函数主要实现获取指定主机(Host)下的设备列表,并将设备信息存储到hostClnt结构中,具体的代码流程如下:

由以上代码流程可看出代码首先从配置文件中根据hostname查找对应的主机(host)(①处),并将找到的主机赋值到hostNode,然后对hostNode的设备列表(device)做遍历操作再对设备实例(device)做遍历设备节点(devicenode)的操作 (注意此时是遍历两次),操作时会新建设备节点信息实例(deviceNodeInfo),并将配置文件中的设备信息存储到新建的设备节点deviceNodeInfo中,最后根据配置的加载策略添加到不同的链表(unloadDevInfos(需要预加载)、dynamicDevInfos(动态加载))中(③处)。

  • 获取配置文件的根节点信息(②处)不属于此节内容所以忽略了。
  • 注意在创建设备节点信息实例(deviceNodeInfo)前遍历了两次,可根据上图的类图分析。
2.3.2.3.启动host进程

启动host进程的过程涉及的过程比较复杂,主要涉及主机服务安装器(DriverInstaller)的创建、启动设备host、关联设备管理服务与主机客户端、安装设备驱动、设备服务发布,下面分别说明

1.主机服务安装器(DriverInstaller)的创建

DriverInstaller 是一个关键组件,主要负责设备主机(Host)的启动和管理 ,此组件的为单实例创建,调用StartDeviceHost 接口启动设备主机进程。

由下图的类图可知主机服务安装器DriverInstaller实现了对IDriverInstaller类的继承,即获取了启动host和暂停host这两个方法,传入的参数即为hostid和hostName这两个有效属性。

2.启动设备host

对于每个主机,调用 DriverInstallerStartDeviceHost 函数启动主机进程

c 复制代码
hostClnt->hostPid = installer->StartDeviceHost(hostAttr->hostId, hostAttr->hostName);
  • StartDeviceHost 会创建一个设备主机服务(DevHostService),并启动主机进程
  • 主机服务会调用 DevHostServiceStartService进一步初始化主机服务

新建非单实例设备每次会新分配内存,并赋值相关的devHostId和hostName。

  1. 关联设备管理服务与主机客户端

主机服务启动后,会通过 DevmgrServiceClntAttachDeviceHost 函数将设备管理服务(DevmgrServiceClnt)与主机客户端(DevHostServiceClnt)关联。详细如下图:

设备管理服务为单实例对象,获取后通过调用此对象的AttachDeviceHost接口。注意其中传入的参数包含IDevmgrService的实例,在匹配主机客户端时是通过hostid来查找对应的主机客户端的。

  1. 安装设备驱动

DevHostServiceClntInstallDriver 函数会遍历主机下的所有设备,调用 AddDevice 方法安装设备驱动。

c 复制代码
DevHostServiceClntInstallDriver()
|-> HdfSListIteratorInit(&it, hostClnt->deviceInfos); // 初始化设备信息迭代器
|-> while (HdfSListIteratorHasNext(&it)) {
    deviceInfo = (struct HdfDeviceInfo *)HdfSListIteratorNext(&it);
    devHostSvcIf->AddDevice(devHostSvcIf, deviceInfo); // 安装设备驱动
}

具体的安装流程如下:

DevHostServiceAddDevice函数的核心功能是将设备信息与驱动程序关联起来,并完成设备的初始化和注册,首先会创建驱动加载器实例,然后确认设备主机(HdfDevice)是否已经存在,并根据设备模块名通过调用驱动加载器获取对应的驱动程序,最后创建一个新的设备节点实例(HdfDeviceNode),并调用设备对象(device)的 Attach 方法,将设备节点(HdfDeviceNode)绑定到设备对象。

  1. 设备服务发布

每个设备驱动安装完成后,会发布为一个设备服务节点,供其他模块使用.

设备服务发布主要实现启动设备节点(HdfDeviceNode)并完成设备驱动加载、初始化和服务发布。它的作用是将设备节点与驱动程序绑定,并确保设备服务能够被系统识别和使用。此过程会首先调用驱动中设置的bind函数实现将设备节点与驱动程序绑定,然后调用驱动程序的初始化函数(driverEntry->Init),最后将设备节点的服务发布到设备服务管理器并将节点的令牌挂载到设备管理服务。

2.3.3启动设备服务管理器

设备服务管理器(DevSvcManager)扩展模块的启动函数,其主要作用是初始化和启动设备服务管理器。主要实现初始化设备服务管理器的设备对象和服务接口、发布设备服务管理器的 I/O 服务以及初始化服务状态监听器。

2.4 电源管理的初始化

电源管理的初始化,主要作用是注意一个电源管理的回调函数,当有电源状态变化时进行回调。

2.5 驱动加载总结

1)在系统启动时,DeviceManagerInit通过late_initcall先启动

  1. Device Manager 根据 Device Information (deviceInfo.hcs)信息,解析配置文件中的 Host 列表 ,根据 Host表中的信息来实例化对应的 Host 对象

3)Host遍历设备列表去获取与之匹配的驱动程序名称,然后基于驱动程序名称遍历.hdf.driver secon 获得驱动程序地址。

4)设备与驱动匹配成功之后,获取指定驱动的入口地址,加载对应的设备驱动程序

5)调用指定驱动的 Bind 接口,用于关联设备和服务实例。

6)调用指定驱动的 Init 接口,用于完成驱动的相关初始化工作。

7)如果驱动被卸载或者因为硬件等原因 Init 接口返回失败,Release 将被调用,用于释放驱动申谈的各类资源。

相关推荐
林钟雪9 分钟前
HarmonyNext实战案例:基于ArkTS的实时多人协作白板应用开发
harmonyos
轻口味2 小时前
【每日学点HarmonyOS Next知识】获取资源问题、软键盘弹起、swiper更新、C给图片设置位图、读取本地Json
c语言·json·harmonyos·harmonyosnext
林钟雪3 小时前
HarmonyNext 实战:基于 ArkTS 的高级跨设备数据同步方案
harmonyos
陈无左耳、5 小时前
HarmonyOS学习第18天:多媒体功能全解析
学习·华为·harmonyos
IT乐手6 小时前
2.6、媒体查询(mediaquery)
harmonyos
麦田里的守望者江6 小时前
Kotlin/Native 给鸿蒙使用(二)
kotlin·harmonyos
IT乐手6 小时前
2.5、栅格布局(GridRow/GridCol)
harmonyos
小时代的大玩家6 小时前
鸿蒙系统下使用AVPlay播放视频,封装播放器
harmonyos
Harmony培训部小助手6 小时前
HarmonyOS NEXT Grid 组件性能优化指南
性能优化·harmonyos
Harmony培训部小助手6 小时前
HarmonyOS NEXT 瀑布流性能优化指南
性能优化·harmonyos