最近HarmonyOS 2.0Beta出来了,大家伙看了界面,很多人说这不就是Android,尤其是头条上一些人;大家都知道Android是基于Linux内核开发的,HarmonyOS是全新微内核(官方说法,具体啥意思不做探究),下面我们就拿linux驱动和HarmonyOS驱动做个对比,看看到底是不是套用!

a14c5cf9d3c1edcdea420249d6a23a05.png

驱动概述

HarmonyOS Driver Foundation

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

概念性的东西,看不出啥来

驱动加载

HDF驱动加载包括按需加载和按序加载。

  • 按需加载HDF框架支持驱动在系统启动过程中默认加载,或者在系统启动之后动态加载。
  • 按序加载HDF框架支持驱动在系统启动的过程中按照驱动的优先级进行加载。

linux中也可以动态加载驱动模块,也可以设置优先级

驱动服务管理

HDF框架可以集中管理驱动服务,开发者可直接通过HDF框架对外提供的能力接口获取驱动相关的服务。

驱动消息机制

HDF框架提供统一的驱动消息机制,支持用户态应用向内核态驱动发送消息,也支持内核态驱动向用户态应用发送消息。

驱动模型介绍

HDF框架以组件化的驱动模型作为核心设计思路,为开发者提供更精细化的驱动管理,让驱动开发和部署更加规范。HDF框架将一类设备驱动放在同一个host里面,开发者也可以将驱动功能分层独立开发和部署,支持一个驱动多个node,HDF框架管理驱动模型如下图所示:

65aaa1bc0144bc757c4416ce012ce289.png

上面这些概念分别都可以在linux中找到对应的概念或者说法

概念上看不出来,下面我们就看具体代码

驱动开发步骤示例

基于HDF框架进行驱动的开发主要分为两个部分,驱动实现和驱动配置,详细开发流程如下所示:

  1. 驱动实现

驱动实现包含驱动业务代码和驱动入口注册,具体写法如下: 驱动业务代码

#include "hdf_device_desc.h"  // HDF框架对驱动开放相关能力接口的头文件#include "hdf_log.h"          // HDF 框架提供的日志接口头文件 #define HDF_LOG_TAG sample_driver   // 打印日志所包含的标签,如果不定义则用默认定义的HDF_TAG标签 //驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架int32_t HdfSampleDriverBind(struct HdfDeviceObject *deviceObject){    HDF_LOGD("Sample driver bind success");    return 0;} // 驱动自身业务初始的接口int32_t HdfSampleDriverInit(struct HdfDeviceObject *deviceObject){    HDF_LOGD("Sample driver Init success");    return 0;} // 驱动资源释放的接口void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject){    HDF_LOGD("Sample driver release success");    return;}

首先说代码风格,linux代码是这种“hdf_sample_driver_release”小写加下划线的风格,Harmony是驼峰式写法。

驱动入口注册到HDF框架

// 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量struct HdfDriverEntry g_sampleDriverEntry = {    .moduleVersion = 1,    .moduleName = "sample_driver",    .Bind = HdfSampleDriverBind,    .Init = HdfSampleDriverInit,    .Release = HdfSampleDriverRelease,}; // 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。HDF_INIT(g_sampleDriverEntry);

这个注册跟下面这个linux驱动还是有些相似的,虽说条条大路通罗马,但路先让别人走了,我们再走,不能算抄袭。

// linux内核任意摘取的驱动static struct i2c_driver bd2802_i2c_driver = {.driver= {.name= "BD2802",.pm= &bd2802_pm,},.probe= bd2802_probe,.remove= bd2802_remove,.id_table= bd2802_id,};module_i2c_driver(bd2802_i2c_driver);
  1. 驱动编译

编译都是采用的Makefile,这个没啥好说的。

  • 驱动代码的编译必须要使用HDF框架提供的Makefile模板进行编译。
include $(LITEOSTOPDIR)/../../drivers/hdf/lite/lite.mk #导入hdf预定义内容,必需MODULE_NAME :=    #生成的结果文件LOCAL_INCLUDE :=  #本驱动的头文件目录LOCAL_SRCS :=     #本驱动的源代码文件LOCAL_CFLAGS :=  #自定义的编译选项include $(HDF_DRIVER) #导入模板makefile完成编译

编译结果文件链接到内核镜像,添加到vendor目录下的hdf_vendor.mk里面,示例如下:

LITEOS_BASELIB +=  -lxxx  #链接生成的静态库LIB_SUBDIRS    +=         #驱动代码Makefile的目录

3.驱动配置

HDF使用HCS作为配置描述源码,HCS详细介绍参考配置管理介绍。

驱动配置包含两部分,HDF框架定义的驱动设备描述和驱动的私有配置信息,具体写法如下:

  • 驱动设备描述(必选)HDF框架加载驱动所需要的信息来源于HDF框架定义的驱动设备描述,因此基于HDF框架开发的驱动必须要在HDF框架定义的device_info.hcs配置文件中添加对应的设备描述,驱动的设备描述填写如下所示:
root {    device_info {        match_attr = "hdf_manager";        template host {       // host模板,继承该模板的节点(如下sample_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;             // 驱动按需加载字段,在本章节最后的说明有详细介绍                    permission = 0664;       // 驱动创建设备节点权限                    moduleName = "sample_driver";   // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致                    serviceName = "sample_service";    // 驱动对外发布服务的名称,必须唯一                    deviceMatchAttr = "sample_config"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等                }            }        }    }}

下面这个是任意摘录自linux源码

dw-apb-gpio@2000 {compatible = "snps,dw-apb-gpio";reg = < 0x2000 0x80 >;#address-cells = <1>;#size-cells = <0>;ictl_intc: gpio-controller@0 {compatible = "snps,dw-apb-gpio-port";gpio-controller;#gpio-cells = <2>;snps,nr-gpios = <30>;reg = <0>;interrupt-controller;#interrupt-cells = <2>;interrupt-parent = ;interrupts = <25>;};};

无论是概念思路还是代码风格都跟Linux的设备树(DeviceTree)似乎异曲同工;虽说条条大路通罗马,但路先让别人走了,我们再走,不能算抄袭。

驱动私有配置信息(可选)

如果驱动有私有配置,则可以添加一个驱动的配置文件,用来填写一些驱动的默认配置信息,HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject 中的property里面,通过Bind和Init(参考驱动开发)传递给驱动,驱动的配置信息示例如下:

root {    SampleDriverConfig {        sample_version = 1;        sample_bus = "I2C_0";        match_attr = "sample_config";   //该字段的值必须和device_info.hcs中的deviceMatchAttr值一致    }}

>linux设备树中似乎没这个概念

配置信息定义之后,需要将该配置文件添加到板级配置入口文件hdf.hcs(这一块可以通过HarmonyOS驱动子系统在DevEco集成驱动开发套件工具一键式配置,具体使用方法参考驱动开发套件中的介绍),示例如下:

#include "device_info/device_info.hcs"#include "sample/sample_config.hcs"

驱动服务管理

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

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;

这个地方,嗯,似乎LINUX没有这个概念,更像IOT中的一些通信协议比如MQTT中的一些概念,放在操作系统中是比较新的概念,就像华为说的物联网操作系统

使用场景

当驱动以接口的形式对外提供能力时,可以使用HDF框架的驱动服务管理能力。

接口说明

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

0d3dc1d8dbe87027ffb3b99d469ade19.png

开发步骤

驱动服务管理的开发包括驱动服务的编写、绑定、获取或者订阅,详细步骤如下。

  1. 驱动服务发布
驱动服务结构的定义struct ISampleDriverService {    struct IDeviceIoService ioService;   // 服务结构的首个成员必须是IDeviceIoService类型的成员    int32_t (*ServiceA)(void);               // 驱动的第一个服务接口    int32_t (*ServiceB)(uint32_t inputCode); // 驱动的第二个服务接口,有多个可以依次往下累加}; 驱动服务接口的实现int32_t SampleDriverServiceA(void){    // 驱动开发者实现业务逻辑    return 0;} int32_t SampleDriverServiceB(uint32_t inputCode){    // 驱动开发者实现业务逻辑    return 0;}

驱动服务绑定到HDF框架中,实现HdfDriverEntry中的Bind指针函数。

int32_t SampleDriverBind(struct HdfDeviceObject *deviceObject){    // deviceObject为HDF框架给每一个驱动创建的设备对象,用来保存设备相关的私有数据和服务接口    if (deviceObject == NULL) {        HDF_LOGE("Sample device object is null!");        return -1;    }    static struct ISampleDriverService sampleDriverA = {        .ServiceA = SampleDriverServiceA,        .ServiceB = SampleDriverServiceB,    };    deviceObject->service = &sampleDriverA.ioService;    return 0;}

驱动服务获取。

驱动服务的获取有两种方式,HDF框架提供接口直接获取和HDF框架提供订阅机制获取。

  • 通过HDF接口直接获取当明确驱动已经加载完成时,获取该驱动的服务可以通过HDF框架提供的能力接口直接获取,如下所示:
const struct ISampleDriverService *sampleService =        (const struct ISampleDriverService *)DevSvcManagerClntGetService("sample_driver");if (sampleService == NULL) {    return -1;}sampleService->ServiceA();sampleService->ServiceB(5);

通过HDF提供的订阅机制获取

当对驱动(同一个host)加载的时机不感知时,可以通过HDF框架提供的订阅机制来订阅该驱动,当该驱动加载完成时,HDF框架会将被订阅的驱动服务发布给订阅者,实现方式如下所示:

// 订阅回调函数的编写,当被订阅的驱动加载完成后,HDF框架会将被订阅驱动的服务发布给订阅者,通过这个回调函数给订阅者使用// object为订阅者的私有数据,service为被订阅的服务对象int32_t TestDriverSubCallBack(struct HdfDeviceObject *deviceObject, const struct HdfObject *service){    const struct ISampleDriverService *sampleService =        (const struct ISampleDriverService *)service;    if (sampleService == NULL) {        return -1;    }    sampleService->ServiceA();    sampleService->ServiceB(5);}// 订阅过程的实现int32_t TestDriverInit(struct HdfDeviceObject *deviceObject){    if (deviceObject == NULL) {        HDF_LOGE("Test driver init failed, deviceObject is null!");        return -1;    }    struct SubscriberCallback callBack;    callBack.deviceObject = deviceObject;    callBack.OnServiceConnected = TestDriverSubCallBack;    int32_t ret = HdfDeviceSubscribeService(deviceObject, "sample_driver", callBack);    if (ret != 0) {        HDF_LOGE("Test driver subscribe sample driver failed!");    }    return ret;}

驱动消息机制管理

使用场景

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

接口说明

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

  1. 用户态应用发送消息到驱动。
  2. 用户态应用接收驱动主动上报事件。
be15ef76ea8618cbeac7e182c7c83b63.png

开发步骤

  1. 将驱动配置信息中服务策略policy字段设置为2(SERVICE_POLICY_CAPACITY,参考policy定义)。
device_sample :: Device {    policy = 2;    ...}
  1. 配置驱动信息中的服务设备节点权限(permission字段)是框架给驱动创建设备节点的权限,默认是0666,驱动开发者根据驱动的实际使用场景配置驱动设备节点的权限。
  2. 在服务实现过程中,实现服务基类成员IDeviceIoService中的Dispatch方法。
// Dispatch是用来处理用户态发下来的消息int32_t SampleDriverDispatch(struct HdfDeviceObject *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply){    HDF_LOGE("sample driver lite A dispatch");    return 0;}int32_t SampleDriverBind(struct HdfDeviceObject *device){    HDF_LOGE("test for lite os sample driver A Open!");    if (device == NULL) {        HDF_LOGE("test for lite os sample driver A Open failed!");        return -1;    }    static struct ISampleDriverService sampleDriverA = {        .ioService.Dispatch = SampleDriverDispatch,        .ServiceA = SampleDriverServiceA,        .ServiceB = SampleDriverServiceB,    };    device->service = (struct IDeviceIoService *)(&sampleDriverA);    return 0;}
  1. 驱动定义消息处理函数中的cmd类型。
#define SAMPLE_WRITE_READ 1    // 读写操作码1
  1. 用户态获取服务接口并发送消息到驱动。
int SendMsg(const char *testMsg){    if (testMsg == NULL) {        HDF_LOGE("test msg is null");        return -1;    }    struct HdfIoService *serv = HdfIoServiceBind("sample_driver", 0);    if (serv == NULL) {        HDF_LOGE("fail to get service");        return -1;    }    struct HdfSBuf *data = HdfSBufObtainDefaultSize();    if (data == NULL) {        HDF_LOGE("fail to obtain sbuf data");        return -1;    }    struct HdfSBuf *reply = HdfSBufObtainDefaultSize();    if (reply == NULL) {        HDF_LOGE("fail to obtain sbuf reply");        ret = HDF_DEV_ERR_NO_MEMORY;        goto out;    }    if (!HdfSbufWriteString(data, testMsg)) {        HDF_LOGE("fail to write sbuf");        ret = HDF_FAILURE;        goto out;    }    int ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply);    if (ret != HDF_SUCCESS) {        HDF_LOGE("fail to send service call");        goto out;    }out:    HdfSBufRecycle(data);    HdfSBufRecycle(reply);    HdfIoServiceRecycle(serv);    return ret;}
  1. 用户态接收该驱动上报的消息。

a.用户态编写驱动上报消息的处理函数。

static int OnDevEventReceived(void *priv,  uint32_t id, struct HdfSBuf *data){    OsalTimespec time;    OsalGetTime(&time);    HDF_LOGE("%s received event at %llu.%llu", (char *)priv, time.sec, time.usec);     const char *string = HdfSbufReadString(data);    if (string == NULL) {        HDF_LOGE("fail to read string in event data");        return -1;    }    HDF_LOGE("%s: dev event received: %d %s",  (char *)priv, id, string);    return 0;}

b.用户态注册接收驱动上报消息的操作方法。

int RegisterListen(){    struct HdfIoService *serv = HdfIoServiceBind("sample_driver", 0);    if (serv == NULL) {        HDF_LOGE("fail to get service");        return -1;    }    static struct HdfDevEventlistener listener = {        .callBack = OnDevEventReceived,        .priv ="Service0"    };    if (HdfDeviceRegisterEventListener(serv, &listener) != 0) {        HDF_LOGE("fail to register event listener");        return -1;    }    ......    HdfDeviceUnregisterEventListener(serv, &listener);    HdfIoServiceRecycle(serv);    return 0;}

c.驱动上报事件

int32_t SampleDriverDispatch(struct HdfDeviceObject *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply){    ... // process api call here    return HdfDeviceSendEvent(deviceObject, cmdCode, data);}

从驱动的服务管理、消息机制管理可以看出跟Linux驱动是截然不同的。

1fd71e8083d265a0bc9eef5de9676ccb.png

至于配置管理和Linux驱动中的设备树极为相似,以及内核中的shell、adb、api接口的名字相似等,我觉得这个说明不了什么,毕竟linux几十年了,有些比较先进的理念,我们采用了不能说我们就套用了。还是那句话,条条大路通罗马,人家选择一条好的路先走了,我们不能为了避嫌故意选择一条差的路吧。你们说呢?

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐