本文首发于: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框架对外提供的能力接口获取驱动相关的服务。
当前服务有三种:
驱动服务结构的定义
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;
}
当明确驱动已经加载完成时,获取该驱动的服务可以通过HDF框架提供的能力接口直接获取,如下所示:
const struct ISampleDriverService *sampleService =
(const struct ISampleDriverService *)DevSvcManagerClntGetService("sample_driver");
if (sampleService == NULL) {
return -1;
}
sampleService->ServiceA();
sampleService->ServiceB(5);
服务订阅也是服务获取的一种,只是是通过订阅的方式进行实现,因为上一小节服务获取的前提条件是已经明确驱动已经加载完成了。但是这个条件不一定已经实现,通过服务订阅的方式,当被订阅的驱动加载完成后,系统会自己调用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);
用户态可以感知到,接着通过用户态注册的回调函数来处理内核态发送过来的消息。具体操作如下:
用户态编写驱动上报消息的处理函数。
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;
}
用户态注册接收驱动上报消息的操作方法。
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;
}
内核态驱动上报事件。
int32_t SampleDriverDispatch(struct HdfDeviceObject *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
{
... // process api call here
return HdfDeviceSendEvent(deviceObject, cmdCode, data);
}
可以看到,鸿蒙的这几个接口基于一套service机制实现了用户态和内核态的之间的相互通信,这就是驱动的大致框架了,开发者只需要用户态写写业务逻辑,通过上述框架将指令传递到内核,然后内核根据下发的指令完成相应的功能,这样一个驱动的功能就可以完整实现了。