鸿蒙驱动框架

本文首发于:LHM’s notes 欢迎关注我的新博客

鸿蒙驱动是基于HDF(Harmony Driver Foundation)驱动框架,为开发者提供了一系列统一接口供其调用,包括驱动加载、驱动服务管理和驱动消息机制。我们要做的,是学习如何使用这些接口,并基于这些接口的使用实现某些业务功能。

设备驱动概述

相信每个人都有给电脑安装驱动的经历,驱动的使用就是去某个官网去下载个软件包,然后一路点击安装就行了。这里可以明确一个定义:驱动是一段程序代码。那么设备呢? 鼠标、键盘、显示器、这些都叫做设备,但是和我们这个里面的驱动设备里面的设备不一样,设备也是一段代码,而这段代码可以描述这个设备的各种信息,比如设备版本号、mac地址等等各种关于这个设备的信息。而驱动的作用是让这个设备能够正常运行起来,因此从模型的意义上来说,设备和驱动是两个模块,一个驱动可以对应多个设备,比如你电脑上插入两个鼠标,那么分别是鼠标设备1和鼠标设备2,但是他们的驱动是同一个,这也就是每个驱动只用安装一次的道理。

驱动和设备如何绑定在一起,在linux内核的源码中,是通过bind将一个设备和某个驱动 进行捆绑。同时考虑到驱动的丰富多样性,A电脑只需要一个打印机的驱动,不需要显示器驱动;而B电脑可能只需要显示器的驱动。各个用户对驱动的需求不一致,如果都放到内核里面,会导致内核的代码越来越大,且有大量冗余,因此驱动的加载自己可配置。在Windows上,除了常见功能的驱动是自带之外,其他设备的驱动一般都要自己安装。

驱动加载

在linux上,驱动的自我加载就是在内核上有选择的插入某个驱动模块(.ko文件),而鸿蒙也是基于这个原理。HDF驱动加载包括按需加载和按序加载。

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

1、按需加载

​ HDF框架支持驱动在系统启动过程中默认加载,或者在系统启动之后动态加载。

2、按序加载

​ HDF框架支持驱动在系统启动过程中按照驱动的优先级进行加载

需要注意的是:我们在此之后所提到的所有驱动,其实是包含设备和驱动两部分,设备和驱动的代码实现整体称之为驱动加载

驱动服务管理

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

当前服务有三种:

1、服务绑定
驱动服务结构的定义
struct ISampleDriverService {
    struct IDeviceIoService ioService;   // 服务结构的首个成员必须是IDeviceIoService类型的成员
    int32_t (*ServiceA)(void);               // 驱动的第一个服务接口
    int32_t (*ServiceB)(uint32_t inputCode); // 驱动的第二个服务接口,有多个可以依次往下累加
};

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 SampleDriverServiceA(void)
{
    // 驱动开发者实现业务逻辑
    return 0;
}

int32_t SampleDriverServiceB(uint32_t inputCode)
{
    // 驱动开发者实现业务逻辑
    return 0;
}

ServiceA 和ServiceB都是开发者可以随意修改定制的类型,而第一个ioService的类型是固定的,这个在驱动消息机制小节中会讲到为啥是固定的。

由开发者实现的驱动服务接口都会注册到ISampleDriverService 结构体中,如下第9、10行; 接着将ISampleDriverService结构体注册到deviceObject中; 这样,内核就可以通过deviceObject调用开发者实现的服务。

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

当明确驱动已经加载完成时,获取该驱动的服务可以通过HDF框架提供的能力接口直接获取,如下所示:

const struct ISampleDriverService *sampleService =
        (const struct ISampleDriverService *)DevSvcManagerClntGetService("sample_driver");
if (sampleService == NULL) {
    return -1;
}
sampleService->ServiceA();
sampleService->ServiceB(5);
3、服务订阅

服务订阅也是服务获取的一种,只是是通过订阅的方式进行实现,因为上一小节服务获取的前提条件是已经明确驱动已经加载完成了。但是这个条件不一定已经实现,通过服务订阅的方式,当被订阅的驱动加载完成后,系统会自己调用callback函数从而可以调用开发者实现的服务函数。

// 订阅回调函数的编写,当被订阅的驱动加载完成后,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框架提供统一的驱动消息机制,支持用户态应用向内核态驱动发送消息,也支持内核态驱动向用户态应用发送消息。在linux内核中,常用的指令ioctl用来用户态传递指令到内核态, 但是ioctl不能主动将内核态消息传递到用户态。内核态与用户态可以相互传递消息可以使用netlink机制。

鸿蒙系统中也有固定的一套接口,在服务绑定小节中讲到第一个服务的函数指针类型是固定的,如下

int32_t SampleDriverDispatch(struct HdfDeviceObject *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply);

这个固定类型的服务其实是一个基本的通信接口,类似于linux中的ioctl,有比较固定的使用方法,第一个参数是设备对象,第二个是读写指令,第三个是发送数据地址,第四个参数是回复消息地址。

个人感觉这就是个模板,供开发者去参考,当然开发者也可以定义像ServiceA、ServiceB这种没有参数或者一个参数的服务,但是远没有这种模板的功能齐全。

用户态通过服务获取或者服务订阅定制机制获取到该服务集,接着调用里面的dispatcher触发第一个服务。

 int ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply);
    if (ret != HDF_SUCCESS) {

用户态给内核态写数据时:将第二个参数置为 WRITE; 将要发送的数据指针传到第三个参数(data);再调用上述接口即可进行消息下发操作。

用户态获取内核态数据时:将第二个参数置为READ; Dispatch执行成功后,读取reply中的数据即可。

WRITE和READ指令可以同时使用。

但是这还没有解决一个问题,就是内核态主动给用户态发送消息。这个linux的ioctl机制也不支持,鸿蒙采用的解决方式是用户态采用监听的方式,如果内核态调用了HdfDeviceSendEvent(deviceObject, cmdCode, data);用户态可以感知到,接着通过用户态注册的回调函数来处理内核态发送过来的消息。具体操作如下:

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

    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;
    }
    
  2. 用户态注册接收驱动上报消息的操作方法。

    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;
    }
    
  3. 内核态驱动上报事件。

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

可以看到,鸿蒙的这几个接口基于一套service机制实现了用户态和内核态的之间的相互通信,这就是驱动的大致框架了,开发者只需要用户态写写业务逻辑,通过上述框架将指令传递到内核,然后内核根据下发的指令完成相应的功能,这样一个驱动的功能就可以完整实现了。

你可能感兴趣的:(鸿蒙驱动框架)