【FFH】HDF驱动开发之编写驱动代码 原创 精华
HDF驱动开发之编写驱动代码
HDF的基本概念
HDF(Hardware Driver Foundation)是OpenHarmony的驱动程序框架,为驱动开发者提供驱动框架能力,包括驱动加载、驱动服务管理和驱动消息机制。旨在构建统一的驱动架构平台,为驱动开发者提供更精准、更高效的开发环境,力求做到一次开发,多系统部署。
HDF的整体架构如图:
图源《深入浅出OpenHarmony》
自下而上看,其中:
- OSAL层:是操作系统的抽象层,封装了驱动相关的系统调用。
- Adapter层:适配层,厂家需要具体实现的IO操作,与Core一起构成了驱动程序。
- Core 层:平台驱动层,是随OpenHarmony一起发布的平台类驱动程序框架。
- Device层:具体的设备对象。
- Host 层:各种Device按照一定的关系关联在一起,形成Host的概念。
- Manager:OpenHarmony设备管理服务。
- HDI:OpenHarmony驱动接口,供上层框架层调用,作用类似于安卓的HAL。
- 能力库,驱动模型,HCS 等:驱动框架相关的工具。
本文以点亮一个LED程序为例,编写相关驱动代码。
编写驱动代码
一. 在./device/st/drivers下新建led文件夹(存放驱动源码文件)
1. 在led下新建一个led.c文件——存放驱动源码文件
led.c的主要作用是定义驱动程序入口函数,在HDF加载时读取配置信息并初始化GPIO,定义dispatch函数。
接下来,让我们在小熊派官方已有注释的基础上,深入剖析上述代码,理解其实现内容。
首先我们先来了解一下驱动消息机制。
- 驱动消息机制:
HDF框架提供统一的驱动消息机制,支持用户态应用向内核态驱动发送消息,也支持内核态驱动向用户态应用发送消息。
接口说明
- 用户态应用发送消息到驱动
- 用户台应用接受驱动主动上报事件
以下代码对应业务代码my_led_app.c中“通过Dispatch发送驱动”步骤:
其中object为订阅者的私有数据,service为被订阅的服务对象
接下来,实现服务基类成员IDeviceIoService中的Dispatch方法
Dispatch函数,是用来处理用户态发下来的信息的函数。
驱动作为内核态的内容能够处理用户态发下来的消息,同时返回数据,达到处理与用户层的交互逻辑的目的。
此处的LedDriverDispatch是最重要的消息分发处理函数,在这个函数中我们针对预先定义的不同的命令码进行不同的处理。
第一步:发送LED_WRITE_READ命令到驱动,此处只编写了一个命令,后续可以增加更多命令选项。
第二步:发送data的值到驱动。
第三步:对led灯进行操作后,读取IO口电平状态并写入reply,然后通过reply携带到用户程序。
- 读取驱动私有配置
如果驱动有私有配置,则可以添加一个驱动的配置文件,用来填写一些驱动的默认配置信息,HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject 中的property里面,通过Bind和Init传递给驱动。而上述代码的作用就是读取驱动私有配置。
我们可以看看device\st\bearpi_hm_micro\liteos_a\hdf_config\led\led_config.hcs下的LED私有配置描述。
上述代码采用了HCS文件格式。简单来说,HCS文件就是将设备相关的资源等配置信息加入到系统镜像中的一种方式。
root是每个HCS文件的缺省根节点,且每个HCS文件有且只有一个,但可以在根节点下面定义子节点,子节点可以嵌套包含子节点。所有的节点都称为node。
HCS定义了一组保留字,用来实现各种语法。其中,上述代码中的match_attr就是保留字之一,用于查找节点的主键。
- 驱动对外提供的服务能力,将相关服务接口绑定到HDF框架
Bind(绑定)函数:
此函数把一个驱动函数的外部接口绑定到HDF上。在这个函数中,驱动需要定义自己的IDeviceloService 接口的实现,并赋值给 deviceObject->service 。
我们先来初步了解一下IDeviceIoService:
IDeviceIoService是所有驱动服务的接口基类,其继承自HdfObject。IDeviceIoService对外提供了三个标准的接口方法————Open、Dispatch、Release。
其类的定义如下:
本案例代码应用如下:
此HdfLedDriverBind函数名在之后需要添加进HdfDriverEntry。
上述代码仅在第9行至第11行使用了Dispatch方法构造了自身的IDeviceloService的接口实现,并在第12行将其赋值给了 deviceObject->service,返回IDeviceIoService实例。
在第10行,采用了之前定义好的LedDriverDispatch函数,此时再结合IDeviceIoService类中dispatch函数参数定义就能理解之前为什么那样给LedDriverDispatch函数传参了。
- 驱动自身业务初始的接口
Init(初始化)函数:初始化一个驱动程序。一般是对驱动程序的特有数据进行初始化,如果需要分配新的内存,也需要在这个函数中完成。
第11行即利用了之前定义好的用来读取驱动私有配置的Stm32LedReadDrs函数。
- 驱动资源释放的接口
Release(释放)函数:销毁一个驱动程序。如果有,需要释放在Init当中申请的内存。
- 定义驱动入口的对象
必须为HdfDriverEntry(hdf_device_desc.h中定义)类型的全局变量
HdfDriverEntry是所有驱动程序入口的HDF虚基类,编写驱动程序必须实现此接口。且驱动开发者所实现的子类必须实现Bind、Init、Release这三个虚函数,这些函数都会被HDF自动调用。
该类的定义如下:
device_info.hcs文件中的moduleName必须要和以上驱动文件led.c中的moduleName字段匹配,这样驱动才会加载起来
- 调用HDF_INIT将驱动入口注册到HDF框架中
每个驱动程序在编写完成后,必须通过HDF_INIT这个宏注册驱动程序,即调用HDF_INIT将驱动入口注册到HDF框架中。获得驱动的入口地址后,在加载驱动时HDF框架会按照定义先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。(此说法来源于OpenHarmony文档https://docs.openharmony.cn/pages/v3.1/zh-cn/device-dev/driver/driver-hdf-development.md/,但在《深入浅出OpenHarmony》一书中P145却写的是依次调用驱动Init函数和Bind函数,所以…)
2.在led下新建一个BUILD.gn文件——创建驱动源码编译脚本
在创建完成led.c后,还要把它加入OpenHarmony的构建系统,因而需要在led.c同目录下创建BUILD.gn。
BUILD.gn文件是一种编译构建文件,类似于Cmake。相较于Cmake,当工程规模增大到难以想象的量级时,编译速度和工程模块的划分变得尤为重要,而gn(Gernerate ninja的缩写,用于产生ninja文件,ninja是一个专注于速度的小型构建系统)便很好解决了这两个问题。
具体操作为:
在led/BUILD.gn文件中添加以下代码
● 导入hdf.gni组件
● 定义hdf驱动:将驱动的源文件led.c编译成hdf_led
3. 修改drivers的编译脚本使之编译进内核
在/device/st/drivers/BUILD.gn文件中的deps中添加"led"目录,将hdf_led编译进内核
后记
参考文献:
1.《深入浅出OpenHarmony》
2.https://gitee.com/bearpi/bearpi-hm_micro_small/blob/master/applications/BearPi/BearPi-HM_Micro/docs/device-dev/编写一个点亮LED灯程序.md
3.https://docs.openharmony.cn/pages/v3.1/zh-cn/device-dev/driver/driver-hdf-overview.md/
作为一名南向设备开发小白,在上手学习BearPi-HM_Micro开发板的前期实在是非常痛苦。接触鸿蒙的这几个月以来,环境配置失误重装导致的一系列问题,读不懂代码,理解不了复杂的编译架构…种种困难让我轻易就中断了OpenHarmony的学习。我曾将这归咎于小熊派已有的教程不全面,让我的学习没有继续的头绪。但其实,最大的问题在于自己的功底不够扎实,对OpenHarmony系统没有理解透彻,没有打好C语言面向对象编程的基础,也没有去广泛的搜索学习资料。当你下定决心要把某个东西搞懂的时候,其实是很容易从外界获得帮助的。
这两天我收到了李传钊老师的《深入浅出OpenHarmony》这本书,一拿到手就赶紧去读了自己之前学习过的HDF驱动程序框架这章,然后在一些疑惑消除后给自己之前写的学习笔记补充了很多内容。
在此,十分感谢李老师给我们学校带来的讲座以及这本书带给我的帮助。
所以,就把这作为我发的第一篇51CTO博客吧。本代码解析可能还有不完善的地方,仅供参考。如有问题,欢迎指正~
感谢楼主解读,很详细的讲解