您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 
 订阅
OpenHarmony 驱动框架解读和开发实践
 
作者: yuanbo
   次浏览      
 2022-1-24
 
编辑推荐:
本文主要带领大家解读OpenHarmony驱动框架,并详细介绍HDF驱动开发一般步骤, 希望对您的学习有所帮助。
本文来自于 HarmonyOS开发者社区 ,由火龙果软件Alice编辑、推荐。

在 IoT 时代下,终端设备差异较大、形态各异、尺寸各异、交互方式各异,解决设备适配问题无疑是实现万物互联的一个关键。但是,在驱动框架的开发和部署过程中,由于终端设备对硬件的计算和存储能力的需求不同、设备厂商提供的设备软硬件操作接口不同、内核提供的操作接口不同,这就使得 OEM 厂商部署系统的时候需要投入大量的精力来适配和维护驱动代码。

能否提供了一个跨芯片平台、跨内核的驱动框架,使得设备驱动软件可以在不同的设备上运行?OpenHarmony 作为一个自主研发、全新技术生态的全领域下一代开源操作系统,提供了一套驱动框架来满足此诉求。

下面我们将带着大家解读 OpenHarmony 驱动框架。

一、OpenHarmony 驱动框架解读

1.设计目标

为解决在开发和部署过程中遇到的困难,OpenHarmony 驱动框架设计目标如下:

  • 支持百 K 级~G 级容量的设备部署,如手机、手环等。
  • 提供统一硬件 IO 抽象,屏蔽 SoC 芯片差异,兼容不同内核,如 Linux、LiteOS 等。
  • 屏蔽驱动和系统组件间交互。可动态拆解,满足不同容量设备的部署。
  • 面向不同容量的设备,提供统一的配置界面。

2. 设计思路

OpenHarmony 驱动框架(下面简称为 HDF)通过提供驱动与芯片平台、内核解耦的底座,规范硬件驱动接口,实现驱动软件在不同设备中部署。

HDF 驱动框架架构如下图所示。

图 1 驱动架构

为了达成设计目标,OpenHarmony 驱动框架采用如下核心设计思路:

(1)弹性化架构

● 框架可动态伸缩: 通过对象管理器,多态加载不同容量设备实现方式,实现弹性伸缩部署。

● 驱动可动态伸缩: 支持统一的设备驱动插件管理,实现设备驱动任意分层,积木式组合拼接

(2)组件化设备模型

● 提供设备功能模型抽象,屏蔽设备驱动与系统交互的实现,为开发者提供统一的驱动开发接口

● 提供主流 IC 的公版驱动能力,支持配置化部署

(3)归一化平台底座

提供规范化的内核、SoC 硬件 IO 适配接口,兼容不同内核、SoC 芯片,对外开发规范化的平台驱动接口

(4)统一配置界面

构建全新的配置语言,面向不同容量的设备,提供统一配置界面,支持硬件资源配置和设备信息配置

3.构建策略

面向 Liteos 的轻量级设备,主要基于 HDF 构建主流 IC 驱动,形成公版驱动和通用设备功能模型,支撑不同硬件芯片、不同内核(LiteOS-M/LiteOS-A)部署。

图 2 轻量级设备部署模式

面向标准设备,除了支持内核态驱动,还支持用户态驱动。用户态驱动的重点在于构建设备抽象模型,为系统提供统一的设备接口,兼容 Linux 原生驱动和 HDF 驱动。内核态则使用 Linux 驱动与 HDF 驱动并存的策略,提供端到端的解决方案。

图 3 标准设备部署模式

4.现状与演进

目前 HDF 驱动框架已经支持 Liteos-m、Liteos-a、Linux 内核,以及 OpenHarmony 轻量级、标准级上部署,并且在标准系统上同时支持内核态与用户态部署。

图 4 OpenHarmony 驱动框架演进图

经过开发者的不断努力,OpenHarmony 驱动框架正在不断完善和增强,在 OpenHarmony LTS3.0 中,基础框架新增了对热插拔设备的管理以及 HDI 编译工具 hdi-gen,驱动模型部分新增了 Audio、Camera、Senso、USB DDK 等多个模块的支持。

二、OpenHarmony 驱动开发

OpenHarmony 驱动为了避免与具体内核产生依赖,实现可迁移目标,开发时需要遵循以下约定:

  • 系统相关接口使用 HDF OSAL 接口;
  • 总线和硬件资源相关接口使用平台驱动提供的相关接口。

基于 HDF 框架,驱动开发的通常流程包含驱动代码的实现、编译脚本、配置文件添加、以及用户态程序和驱动交互的流程。下面将详细介绍 HDF 驱动开发一般步骤。

1.实现驱动代码

在 HDF 驱动框架中,HdfDriverEntry 对象被用来描述一个驱动实现。

struct HdfDriverEntry {
int32_t moduleVersion;
<span class="hljs-keyword">const</span> char *moduleName;
int32_t (*Bind)(struct HdfDeviceObject *deviceObject);
int32_t (*Init)(struct HdfDeviceObject *deviceObject);
<span class="hljs-keyword">void</span> (*Release)(struct HdfDeviceObject *deviceObject);
};

编写一个简单的驱动,首先需要实现驱动程序(Driver Entry)入口中的 三个主要接口:

  • >Bind 接口: 实现驱动接口实例化绑定,如果需要发布驱动接口,会在驱动加载过程中被调用,实例化该接口的驱动服务并和 DeviceObject 绑定。当用户态发起调用时,Bind 中绑定的服务对象的 Dispatch 方法将被回调,在该方法中处理用户态调用的消息。
  • Init 接口: 实现驱动或者硬件的初始化,返回错误将中止驱动加载流程。
  • Release 接口: 实现驱动的卸载,在该接口中释放驱动实例的软硬件资源。

一个基于 HDF 框架编写的简单驱动代码如下,其功能是用户态消息回环,即驱动收到用户态发送的消息后将相同内容的消息再发送给用户态:


#include "hdf_base.h"
#include "hdf_device_desc.h"
#include "hdf_log.h"
#define HDF_LOG_TAG "sample_driver"
#define SAMPLE_WRITE_READ 0xFF00
static int EchoString(struct HdfDeviceObject *deviceObject, struct HdfSBuf *data, struct HdfSBuf *reply)
{
const char *readData = HdfSbufReadString(data);
if (readData == NULL) {
HDF_LOGE("%s: failed to read data", __func__);
return HDF_ERR_INVALID_PARAM;
}
if (!HdfSbufWriteInt32(reply, INT32_MAX)) {
HDF_LOGE("%s: failed to reply int32", __func__);
return HDF_FAILURE;
}
return HdfDeviceSendEvent(deviceObject, id, data); // 发送事件到用户态
}
int32_t HdfSampleDriverDispatch(struct HdfDeviceObject *deviceObject, int id, struct HdfSBuf *data, struct HdfSBuf *reply)
{
const char *readData = NULL;
int ret = HDF_SUCCESS;
switch (id) {
switch SAMPLE_WRITE_READ:
ret = EchoString(deviceObject, data, reply);
break;
default:
HDF_LOGE("%s: unsupported command");
ret = HDF_ERR_INVALID_PARAM;
}
return ret;
}
void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
// 在这里释放驱动申请的软硬件资源
return;
}
int HdfSampleDriverBind(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL) {
return HDF_FAILURE
}
static struct IDeviceIoService testService = {
.Dispatch = HdfSampleDriverDispatch,
};
deviceObject->service = &testService;
return HDF_SUCCESS;
}
int HdfSampleDriverInit(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL) {
HDF_LOGE("%s::ptr is null!", __func__);
return HDF_FAILURE;
}
HDF_LOGE("Sample driver Init success");
return HDF_SUCCESS;
}
struct HdfDriverEntry g_sampleDriverEntry = {
.moduleVersion = 1,
.moduleName = "sample_driver",
.Bind = HdfSampleDriverBind,
.Init = HdfSampleDriverInit,
.Release = HdfSampleDriverRelease,
};
HDF_INIT(g_sampleDriverEntry);

2. 配置设备信息

在 HDF 框架的配置文件(例如 vendor/hisilicon/xxx/config/device_info.hcs)中添加该驱动的配置信息,配置目录与具体开发板关联,如下所示:

root {
device_info {
match_attr = "hdf_manager";
template host {
hostName = "";
priority = 100;
template device {
template deviceNode {
policy = 0;
priority = 100;
preload = 0;
permission = 0664;
moduleName = "";
serviceName = "";
deviceMatchAttr = "";
}
}
}
sample_host :: host{
hostName = "host0"; // host名称,host节点是用来存放某一类驱动的容器
priority = 100; // host启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证host的加载顺序
device_sample :: device { // sample设备节点
device0 :: deviceNode { // sample驱动的DeviceNode节点
policy = 1; // policy字段是驱动服务发布的策略,在驱动服务管理章节有详细介绍
priority = 100; // 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序
preload = 0; // 驱动加载策略,参考《5.2 HDF驱动框架章节》
permission = 0664; // 驱动创建设备节点权限
moduleName = "sample_driver"; // 驱动名称,该字段的值必须和驱动入口结构体的moduleName值一致
serviceName = "sample_service"; // 驱动对外发布服务的名称,必须唯一
deviceMatchAttr = "sample_config"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等
}
}
}
}
}

定义设备列表时使用了 HCS 的模板语法,template host 节点下的内容由 HDF 框架定义,新增 host 以及 host 中的 device 只需要继承该模板并填充具体内容即可。

在配置中定义的 device 将在加载过程中产生一个设备实例,配置中通过 moduleName 字段指定设备对应的驱动名称,从而将设备与驱动关联起来。其中,设备与驱动可以是一对多的关系,即可以实现一个驱动支持多个同类型设备。

3.用户态程序与驱动交互

用户态程序和驱动交互基于 HDF IoService 模型实现,该设计屏蔽了具体内核的差异,将驱动接口抽象为 IoService 对象,调用者基于名称获取该对象,并可以使用 IoService 系列接口进行接口调用和事件监听。值得一提的是消息传递时使用了 HDF Sbuf 对象进行参数的序列化和反序列化,这样可以避免不受控的内存访问,也简化了消息传递和分发过程中的内存所有权问题,有利于提升用户态和内核态数据传递的安全性和便利性。HDF Sbuf 相关接口可以参考 HarmonyOS 设备开发官网 API Reference 中头文件 hdf_sbuf.h 部分。

基于 HDF 框架编写的用户态程序和驱动交互的代码如下:

#include "hdf_log.h"
#include "hdf_sbuf.h"
#include "hdf_io_service_if.h"
#define HDF_LOG_TAG "sample_test"
#define SAMPLE_SERVICE_NAME "sample_service"
#define SAMPLE_WRITE_READ 0xFF00
int g_replyFlag = 0;
static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data)
{
const char *string = HdfSbufReadString(data);
int ret = HDF_SUCCESS;
if (string == NULL) {
HDF_LOGE("failed to read string in event data");
ret = HDF_FAILURE;
} else {
HDF_LOGE("%s", string);
}
g_replyFlag = 1;
return ret;
}
static int SendEvent(struct HdfIoService *serv, char *eventData)
{
int ret = 0;
struct HdfSBuf *data = HdfSBufObtainDefaultSize(); // 申请需要发送的序列化对象
if (data == NULL) {
HDF_LOGE("failed to obtain sbuf data");
return 1;
}
struct HdfSBuf *reply = HdfSBufObtainDefaultSize(); // 申请返回数据的序列化对象
if (reply == NULL) {
HDF_LOGE("failed to obtain sbuf reply");
ret = HDF_DEV_ERR_NO_MEMORY;
goto out;
}
if (!HdfSbufWriteString(data, eventData)) { // 准备消息内容
HDF_LOGE("failed to write sbuf");
ret = HDF_FAILURE;
goto out;
}
ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply); // 发起接口调用
if (ret != HDF_SUCCESS) {
HDF_LOGE("failed to send service call");
goto out;
}
int replyData = 0;
if (!HdfSbufReadInt32(reply, &replyData)) { // 反序列化返回数据
HDF_LOGE("failed to get service call reply");
ret = HDF_ERR_INVALID_OBJECT;
goto out;
}
HDF_LOGE("Get reply is: %d", replyData);
out:
HdfSBufRecycle(data);
HdfSBufRecycle(reply);
return ret;
}
int main()
{
struct HdfIoService *serv = HdfIoServiceBind(SAMPLE_SERVICE_NAME); // 通过名称获取IoService对象,与驱动配置中的名称一致
if (serv == NULL) {
HDF_LOGE("failed to get service %s", SAMPLE_SERVICE_NAME);
return HDF_FAILURE;
}
static struct HdfDevEventlistener listener = { // 构造驱动事件监听器对象
.callBack = OnDevEventReceived, // 填充事件处理方法
.priv = NULL;
};
if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS) { // 注册事件监听
HDF_LOGE("failed to register event listener");
return HDF_FAILURE;
}
if (SendEvent(serv, "Hello World, HDF Driver!")) { // 调用驱动接口,样例驱动收到事件
HDF_LOGE("failed to send event");
return HDF_FAILURE;
}
while (g_replyFlag == 0) { // 等待驱动上报事件
sleep(1);
}
HdfDeviceUnregisterEventListener(serv, &listener)); // 去注册事件监听器
HdfIoServiceRecycle(serv); // 回收IoService对象
return 0;
}

该示例执行后会在终端中打印出"Hello World, HDF Driver!"字符串,表明我们的用户态测试程序和驱动成功地进行了一次交互。

三、使用 DevEco Device Tool 进行驱动开发

上一小节介绍了 OpenHarmony 驱动的一般开发方法,那么有没有更简单的方法添加一款驱动呢?答案就是华为南向开发 IDE——DevEco Device Tool。DevEco Device Tool 最新版本已经集成了 HDF 驱动开发功能,下面介绍如何使用 DevEco Device Tool 进行驱动开发。

DevEco Device Tool 下载链接

1. 创建驱动

(1)导入工程

参考 DevEco Device Tool 手册,通过 npm 或网络下载的方式导入 OHOS 工程。

图 5 DevEco Device Tool 启动界面

(2)使用 HDF 页面工具创建新驱动,按照需求填写 Module 名称,工具将根据 Module 名称创建对应驱动代码与。

图 6 Device Eco Tool HDF 插件界面

DevEco Device Tool 将自动生成驱动实现代码:

图 7 Device Eco Tool 生成驱动代码

为源码文件自动生成编译脚本:

图 8 Device Eco Tool 生成驱动编译脚本

DevEco Device Tool 还会在对应单板的驱动配置中生成驱动设备配置信息:

图 9 Device Eco Tool 生成驱动配置信息

2. 修改驱动

图 10 Device Eco Tool 驱动快速编辑界面

DevEco Device Tool 提供了快捷方式直达源码、编译脚本、配置文件,点击链接修改相关文件,实现驱动功能。DevEco Device Tool 自动生成代码已经提供了 DriverEntry 的基础实现,只需填充对应函数的实际功能即可。

3. 编译版本

使用 DevEco Device Tool build 功能一键编译版本,编译输出显示在终端窗口:

图 11 Device Eco Tool 编译界面

4. 烧录验证

DevEco Device Tool 提供了一站式的烧录、调试环境。使用 upload 功能将编译好的镜像烧录进开发板。

图 12 Device Eco Tool 烧写功能界面

烧录过程和进度显示在终端窗口

图 13 Device Eco Tool 烧写输出

四、总结

欢迎大家关注并一起建设 OpenHarmony 驱动生态。

 

   
次浏览       
相关文章

一文了解汽车嵌入式AUTOSAR架构
嵌入式Linux系统移植的四大步骤
嵌入式中设计模式的艺术
嵌入式软件架构设计 模块化 & 分层设计
相关文档

企点嵌入式PHP的探索实践
ARM与STM简介
ARM架构详解
华为鸿蒙深度研究
相关课程

嵌入式C高质量编程
嵌入式操作系统组件及BSP裁剪与测试
基于VxWorks的嵌入式开发、调试与测试
嵌入式单元测试最佳实践

最新活动计划
SysML和EA系统设计与建模 7-26[特惠]
Python、数据分析与机器学习 8-23[特惠]
软件架构设计方法、案例与实践 8-23[特惠]
嵌入式软件架构设计 8-22[线上]
Linux内核编程及设备驱动 7-25[北京]
 
 
最新文章
基于FPGA的异构计算在多媒体中的应用
深入Linux内核架构——简介与概述
Linux内核系统架构介绍
浅析嵌入式C优化技巧
进程间通信(IPC)介绍
最新课程
嵌入式Linux驱动开发
代码整洁之道-态度、技艺与习惯
嵌入式软件测试
嵌入式C高质量编程
嵌入式软件可靠性设计
成功案例
某军工所 嵌入式软件架构
中航工业某研究所 嵌入式软件开发指南
某轨道交通 嵌入式软件高级设计实践
深圳 嵌入式软件架构设计—高级实践
某企业 基于IPD的嵌入式软件开发
更多...