专业的编程技术博客社区

网站首页 > 博客文章 正文

OpenHarmony:全流程讲解如何编写简易HDF驱动以及应用程序

baijin 2024-08-26 10:23:09 博客文章 5 ℃ 0 评论

1、案例简介

该程序是基于OpenHarmony标准系统编写的基础外设类:简易HDF驱动。

目前已在凌蒙派-RK3568开发板跑通。

详细资料请参考官网:https://gitee.com/Lockzhiner-Electronics/lockzhiner-rk3568-openharmony

2、基础知识

2.1、OpenHarmony HDF开发简介

HDF(Hardware Driver Foundation)驱动框架,为驱动开发者提供驱动框架能力,包括驱动加载、驱动服务管理、驱动消息机制和配置管理。旨在构建统一的驱动架构平台,为驱动开发者提供更精准、更高效的开发环境,力求做到一次开发,多系统部署。

2.2、OpenHarmony HDF驱动开发

HDF(Hardware Driver Foundation)框架以组件化的驱动模型作为核心设计思路,为开发者提供更精细化的驱动管理,让驱动开发和部署更加规范。HDF框架将一类设备驱动放在同一个Host(设备容器)里面,用于管理一组设备的启动加载等过程。在划分Host时,驱动程序是部署在一个Host还是部署在不同的Host,主要考虑驱动程序之间是否存在耦合性,如果两个驱动程序之间存在依赖,可以考虑将这部分驱动程序部署在一个Host里面,否则部署到独立的Host中是更好的选择。Device对应一个真实的物理设备。DeviceNode是设备的一个部件,Device至少有一个DeviceNode。每个DeviceNode可以发布一个设备服务。驱动即驱动程序,每个DevicdNode唯一对应一个驱动,实现和硬件的功能交互。HDF驱动模型如下图所示:

2.3、OpenHarmony HDF驱动加载

HDF驱动框架提供把和配置的设备列表匹配成功的驱动程序加载起来的功能。

支持按需加载和按序加载两种策略,具体设备的加载策略由配置文件中的preload字段来控制,配置值参考如下:

typedef enum {
       DEVICE_PRELOAD_ENABLE = 0,
       DEVICE_PRELOAD_ENABLE_STEP2 = 1,
       DEVICE_PRELOAD_DISABLE = 2,
       DEVICE_PRELOAD_INVALID
} DevicePreload;

2.3.1、按需加载

  • preload字段配置为0(DEVICE_PRELOAD_ENABLE),则系统启动过程中默认加载。
  • preload字段配置为1(DEVICE_PRELOAD_ENABLE_STEP2),当系统支持快速启动的时候,则在系统完成之后再加载这一类驱动,否则和DEVICE_PRELOAD_ENABLE含义相同。
  • preload字段配置为2(DEVICE_PRELOAD_DISABLE),则系统启动过程中默认不加载,支持后续动态加载,当用户态获取驱动服务时,如果驱动服务不存在,HDF框架会尝试动态加载该驱动。

2.3.2、按序加载(默认加载策略)

配置文件中的priority(取值范围为整数0到200)是用来表示host(驱动容器)和驱动的优先级。不同的host内的驱动,host的priority值越小,驱动加载优先级越高;同一个host内驱动的priority值越小,加载优先级越高。

2.4、OpenHarmony HDF驱动服务管理

驱动服务是HDF驱动设备对外提供能力的对象,由HDF框架统一管理。驱动服务管理主要包含驱动服务的发布和获取。

2.4.1、驱动服务的发布策略

HDF框架定义了驱动对外发布服务的策略,由配置文件中的policy字段来控制,policy字段的取值范围以及含义如下:

typedef enum {
       /* 驱动不提供服务 */
       SERVICE_POLICY_NONE = 0,
       /* 驱动对内核态发布服务 */
       SERVICE_POLICY_PUBLIC = 1,
       /* 驱动对内核态和用户态都发布服务 */
       SERVICE_POLICY_CAPACITY = 2,
       /* 驱动服务不对外发布服务,但可以被订阅 */
       SERVICE_POLICY_FRIENDLY = 3,
       /* 驱动私有服务不对外发布服务,也不能被订阅 */
       SERVICE_POLICY_PRIVATE = 4,
       /* 错误的服务策略 */
       SERVICE_POLICY_INVALID
}  ServicePolicy;

2.4.2、驱动服务的接口说明

针对驱动服务管理功能,HDF框架开放了以下接口供开发者调用,如下表所示:

2.5、驱动消息机制管理

当用户态应用和内核态驱动需要交互时,可以使用HDF框架的消息机制来实现。

消息机制的功能主要有以下两种:

  • 用户态应用发送消息到驱动。
  • 用户态应用接收驱动主动上报事件。

消息机制接口如下所示:

2.6、配置树配置

HCS(HDF Configuration Source)是HDF驱动框架的配置描述源码,内容以Key-Value为主要形式。它实现了配置代码与驱动代码解耦,便于开发者进行配置管理。

HC-GEN(HDF Configuration Generator)是HCS配置转换工具,可以将HDF配置文件转换为软件可读取的文件格式:

  • 在弱性能环境中,转换为配置树源码或配置树宏定义,驱动可直接调用C代码或宏式APIs获取配置。
  • 在高性能环境中,转换为HCB(HDF Configuration Binary)二进制文件,驱动可使用HDF框架提供的配置解析接口获取配置。

以下是使用HCB模式的典型应用场景:

HCS经过HC-GEN编译生成HCB文件,HDF驱动框架中的HCS Parser模块会从HCB文件中重建配置树,HDF驱动模块使用HCS Parser提供的配置读取接口获取配置内容。

3、代码解析

3.1、配置文件

3.1.1、device_info.hcs

创建config/device_info.hcs,用于驱动设备描述,具体内容如下:

#include "config.hcs"

root  {
       device_info {
              platform :: host {                                                        // led设备节点归类于platform这个host
            	      hostName = "platform_host";
            	      priority = 50;
            	      device_rk3568_sample :: device {                         // rk3568下的sample类设备
                           device0 :: deviceNode {                                // led类设备下的具体某个设备节点的配置
                                  policy = 2;                                             // 驱动服务发布策略
                                  priority = 100;                                      // 驱动启动优先级
                                  preload = 0;                                         // 驱动按需加载字段
                                  permission = 0666;                             // 驱动创建设备节点权限
                                  moduleName = "rk3568_sample_driver";      // 驱动名称,必须和驱动入口结构的moduleName值一致
                                  serviceName = "rk3568_sample_service";      // 驱动对外发布服务的名称,必须唯一
                                  deviceMatchAttr = "rk3568_sample_config"; // 驱动私有数据匹配关键字,必须和驱动私有数据配置节点的match_attr匹配
                           }
            	      }
              }
       }
}

注意:

device_rk3568_sample:为配置树的类设备结点。

deviceMatchAttr:关键字必须与config.hcs的match_attr匹配。

3.1.2、config.hcs

创建config/config.hcs,用于定义私有变量,具体内容如下:

root  {
       platform {
             rk3568_sample_config {
                    match_attr = "rk3568_sample_config";   //该字段的值必须和device_info.hcs中的deviceMatchAttr值一致
             }
       }
}

由于我们没有用到其他私有变量,故不做定义。

3.1.3、参与配置树编译

编辑//vendor/lockzhiner/rk3568/hdf_config/khdf/hdf.hcs,将device_info.hcs添加配置树中。具体内容如下所示:

#include "../../samples/b01_hdf_sample/config/device_info.hcs"

3.2、HDF驱动

3.2.1、driver_hdf_sample.c

3.2.1.1、头文件

#include "hdf_log.h"                       // HDF框架提供的日志接口头文件
#include "hdf_base.h"                     // HDF架构提供的基础接口头文件
#include "hdf_device_desc.h"         // HDF架构提供的驱动开发相关能力接口的头文件
#include "device_resource_if.h"      // 声明用于查询配置树的API

3.2.1.2、定义打印标签

// 打印信息的标签
#define HDF_LOG_TAG                      rk3568_sample_driver
#define PRINT_INFO(fmt, args...)       printk("%s, %s, %d, info: "fmt, __FILE__, __func__, __LINE__, ##args)
#define PRINT_ERROR(fmt, args...)    printk("%s, %s, %d, error: "fmt, __FILE__, __func__, __LINE__, ##args)

建议读者用HDF_LOGI、HDF_LOGE等打印信息。

3.2.1.3、驱动初始化

/***************************************************************
* 函数名称: HdfSampleDriverInit
* 说        明: 驱动自身业务初始化的接口
* 参        数:
*           @deviceObject    hdf设备
* 返 回 值: HDF_SUCCESS为成功,反之为失败
***************************************************************/
static int HdfSampleDriverInit(struct HdfDeviceObject *deviceObject)
{
      if (deviceObject == NULL) {
          HDF_LOGE("%s::ptr is null!", __func__);
          PRINT_ERROR("deviceObject is null\n");
          return HDF_FAILURE;
      }
      HDF_LOGI("Sample driver Init success");
      PRINT_INFO("Sample driver Init success");
      return HDF_SUCCESS;
}

3.2.1.4、驱动释放

/***************************************************************
* 函数名称: HdfSampleDriverRelease
* 说        明: 驱动资源释放的接口
* 参        数:
*           @deviceObject   hdf设备
* 返 回 值: HDF_SUCCESS为成功,反之为失败
***************************************************************/
static void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
      // release resources here
      HDF_LOGI("Sample driver release success");
      return;
}

3.2.1.5、驱动绑定

将驱动对外提供的服务能力接口绑定到HDF框架,通过struct IDeviceIoService设置Dispatch函数。

/***************************************************************
* 函数名称: HdfSampleDriverBind
* 说         明: 将驱动对外提供的服务能力接口绑定到HDF框架
* 参         数:
*            @deviceObject   hdf设备
* 返 回 值: HDF_SUCCESS为成功,反之为失败
***************************************************************/
static int HdfSampleDriverBind(struct HdfDeviceObject *deviceObject)
{
      if (deviceObject == NULL) {
           return HDF_FAILURE;
      }
      static struct IDeviceIoService testService = {
            .Dispatch = HdfSampleDriverDispatch,
      };
      deviceObject->service = &testService;
      return HDF_SUCCESS;
}

HdfSampleDriverDispatch()挂载载struct IDeviceIoService的函数指针成员Dispatch,它相当于Linux的ioctl,可与应用程序进行数据交互。

/***************************************************************
* 函数名称: HdfSampleDriverDispatch
* 说        明: 通过服务中的Dispatch方法向驱动发送消息。
* 参        数:
*           @client      client指向服务的客户端对象的指针
*           @id            cmdId业务接口的命令字
*           @data        data指向调用程序传递的数据的指针
*           @reply       reply指向需要返回给调用者的数据的指针
* 返 回 值: HDF_SUCCESS为成功,反之为失败
***************************************************************/
static int32_t HdfSampleDriverDispatch(struct HdfDeviceIoClient *client,
                                                                 int id,
                                                                 struct HdfSBuf *data,
                                                                 struct HdfSBuf *reply)
{
      int32_t ret = HDF_SUCCESS;
  
      HDF_LOGI("%s: received cmd %d", __func__, id);
      PRINT_INFO("received cmd %d\n", id);
  
      switch (id) {
      case SAMPLE_WRITE_READ: {
             const char *readData = HdfSbufReadString(data);
             if (readData != NULL) {
                 HDF_LOGE("%s: read data is: %s", __func__, readData);
                 PRINT_ERROR("read data is %s\n", readData);
             }
             if (!HdfSbufWriteInt32(reply, INT32_MAX)) {
                 HDF_LOGE("%s: reply int32 fail", __func__);
                 PRINT_ERROR("reply int32 failed\n");
             }
        
             ret = HdfDeviceSendEvent(client->device, id, data);
             if (ret != HDF_SUCCESS) {
                 PRINT_ERROR("HdfDeviceSendEvent failed and ret = %d\n", ret);
                 return ret;
             }
        
             return HDF_SUCCESS;
      }
      break;
          
      default:
              HDF_LOGE("id %d is out of the range\n", id);
              return HDF_ERR_INVALID_PARAM;
              break;
      }
  
out:
      	return HDF_FAILURE;
}

3.2.1.6、驱动注册

// 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量。
struct HdfDriverEntry g_rk3568SampleDriverEntry = {
       .moduleVersion = 1,
       .moduleName = "rk3568_sample_driver",
       .Bind = HdfSampleDriverBind,
       .Init = HdfSampleDriverInit,
       .Release = HdfSampleDriverRelease,
};

// 调用HDF_INIT将驱动入口注册到HDF框架中
HDF_INIT(g_rk3568SampleDriverEntry);

3.2.2、Makefile

include drivers/hdf/khdf/platform/platform.mk
obj-y += driver_hdf_sample.o

3.2.3、参与Linux内核编译

编辑//drivers/hdf_core/adapter/khdf/linux/Makefile,添加一段代码,将sample驱动参与Linux内核编译中。具体如下所示:

obj-$(CONFIG_DRIVERS_HDF) += ../../../../../vendor/lockzhiner/rk3568/samples/b01_hdf_sample/driver/

3.3、应用程序

3.3.1、sample_test.c

int main()
{
      char *sendData = "default event info";
      struct HdfIoService *serv = NULL;
      static struct HdfDevEventlistener listener = {
            .callBack = OnDevEventReceived,
            .priv ="Service0"
      };
  
      // 绑定HDF驱动
      serv = HdfIoServiceBind(SAMPLE_SERVICE_NAME);
      if (serv == NULL) {
           PRINT_ERROR("fail to get service %s\n", SAMPLE_SERVICE_NAME);
           return HDF_FAILURE;
      }
  
      // 用户态程序注册接收驱动上报事件的操作方法
      if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS) {
          PRINT_ERROR("fail to register event listener\n");
          return HDF_FAILURE;
      }
      if (SendEvent(serv, sendData)) {
           PRINT_ERROR("fail to send event\n");
           return HDF_FAILURE;
      }
  
      // 等待
      while (g_replyFlag == 0) {
             sleep(1);
      }
  
      // 注销驱动上报事件
      if (HdfDeviceUnregisterEventListener(serv, &listener)) {
           PRINT_ERROR("fail to unregister listener\n");
           return HDF_FAILURE;
      }
  
      // 释放驱动服务
      HdfIoServiceRecycle(serv);
      return HDF_SUCCESS;
}

3.3.2、BUILD.gn

import("//build/ohos.gni")
import("//drivers/hdf_core/adapter/uhdf2/uhdf.gni")

print("samples: compile rk3568_sample_test")
ohos_executable("rk3568_sample_test") {
    sources = [ "sample_test.c" ]
    include_dirs = [
       "$hdf_framework_path/include",
       "$hdf_framework_path/include/core",
       "$hdf_framework_path/include/osal",
       "$hdf_framework_path/include/platform",
       "$hdf_framework_path/include/utils",
       "$hdf_uhdf_path/osal/include",
       "$hdf_uhdf_path/ipc/include",
       "//base/hiviewdfx/hilog/interfaces/native/kits/include",
       "//third_party/bounds_checking_function/include",
]
  
deps = [
       "$hdf_uhdf_path/platform:libhdf_platform",
       "$hdf_uhdf_path/utils:libhdf_utils",
       "//base/hiviewdfx/hilog/interfaces/native/innerkits:libhilog",
]
  
cflags = [
       "-Wall",
       "-Wextra",
       "-Werror",
       "-Wno-format",
       "-Wno-format-extra-args",
]
  
subsystem_name = "applications"
part_name = "product_rk3568"
install_enable = true
}

3.3.3、参与应用程序编译

编辑//vendor/lockzhiner/rk3568/samples/BUILD.gn,开启sample编译。具体如下:

"b01_hdf_sample/app:rk3568_sample_test",

4、编译说明

建议使用docker编译方法,运行如下:

hb set -root .
hb set
# 选择lockzhiner下的rk3568编译分支。
hb build -f

5、运行结果

该程序运行结果如下所示:

# rk3568_sample_test
../../vendor/lockzhiner/rk3568/samples/b01_hdf_sample/app/sample_test.c, SendEvent, 94, info: Get reply is: 9999999
../../vendor/lockzhiner/rk3568/samples/b01_hdf_sample/app/sample_test.c, OnDevEventReceived, 41, info: Service0: dev event received: 123 default event info
#

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表