通过一个led点灯的demo来熟悉openharmony驱动编写的过程(附带hdf详细调用过程)
概述
本应用程序(led_rgb)是在上实现直接通过消息机制与内核驱动进行交互,设置RGB三色灯的亮灯行为。我从网上随便找了个demo测试了一下,坑了三天…,整个状态如下图,同时也迫使我深度梳理了一下整个流程框架。直到绝望的时候,翻书找思路的时候找到了答案。。。最绝的是想分享的时候从码云上找了下原作者的demo,发现人家有相关问题的说明。实际经验分享完毕,下面开始说细节~~
源码目录
- 需要详细了解的建议大家从这个目录下载
https://gitee.com/liangkzgitee/led_rgb
./led_rgb/
├── apps #应用代码
│ └── led_rgb
│ ├── BUILD.gn
│ └── led_rgb.c
├── config #配置文件
│ ├── device_info
│ │ └── device_info.hcs
│ └── led
│ ├── led_config_hi3516.hcs
│ └── led_config_rk3568.hcs
├── drv #驱动代码
│ ├── build_linux
│ │ └── Makefile
│ ├── build_liteos
│ │ ├── BUILD.gn
│ │ └── Makefile
│ └── led_drv.c
└── README.md #部署说明
APP部署说明
打开 //build/lite/components/applications.json 文件,找到camera_sample_app组件的信息,
在它的"dirs"和"targets"下,分别添加下面两句语句即可:
"dirs": [
"drivers/framework/sample/led_rgb/apps/led_rgb",
......
],
"targets": [
"//drivers/framework/sample/led_rgb/apps/led_rgb:led_rgb",
......
],
"adapted_kernel": [ "liteos_a", "linux" ],
应用部署在 app/std/hap, 打开 //applications/standard/hap/ohos.build 文件,在其module_list上增加下面一句语句即可:
"//drivers/framework/sample/led_rgb/apps/led_rgb:led_rgb"
配置文件设置
分别将led设备的hcs配置文件加入到对应系统的设备配置树中,一起编译进内核并被使用。
在//vendor/hihope/rk3568/hdf_config/khdf/hdf.hcs文件的 include 部分增加两行代码即可:
#include "../../../../../drivers/framework/sample/led_rgb/config/led/led_config_rk3568.hcs"
#include "../../../../../drivers/framework/sample/led_rgb/config/device_info/device_info.hcs"
驱动部署说明
在//drivers/adapter/khdf/linux/Makefile文件的末尾,添加如下一句语句即可:
obj-$(CONFIG_DRIVERS_HDF) += ../../../framework/sample/led_rgb/drv/build_linux/
把这个Makefile纳入khdf/linux的编译体系中。
配置文件分析
- device_info
创建config/device_info.hcs,用于驱动设备描述,具体内容如下:
root {
device_info {
platform :: host { // led设备节点归类于platform这个host
hostName = "platform_host";
priority = 50;
device_led :: device { // led类设备
device0 :: deviceNode { // led类设备下的具体某个设备节点的配置
policy = 2; // 驱动服务发布策略
priority = 100; // 驱动启动优先级
preload = 0; // 驱动按需加载字段
permission = 0666; // 驱动创建设备节点权限
moduleName = "led_driver"; // 驱动名称,必须和驱动入口结构的moduleName值一致
serviceName = "led_service"; // 驱动对外发布服务的名称,必须唯一
deviceMatchAttr = "led_config"; // 驱动私有数据匹配关键字,必须和驱动私有数据配置节点的match_attr匹配
}
}
}
}
}
- deviceMatchAttr:关键字必须与led_config_rk3568.hcs的match_attr匹配。
config.hcs
创建config/led_config_rk3568.hcs,用于定义私有变量,具体内容如下:
root {
platform {
led_config {
match_attr = "led_config"; //该字段的值必须和device_info.hcs中的deviceMatchAttr值一致
led_version = 2;
led_R = 147;
led_G = 146;
led_B = 149;
}
}
}
驱动代码分析
由驱动代码的注册函数中可得出
struct HdfDriverEntry g_ledDriverEntry = {
.moduleVersion = 1,
.moduleName = "led_driver",
.Bind = HdfLedDriverBind,
.Init = HdfLedDriverInit,
.Release = HdfLedDriverRelease,
};
HDF_INIT(g_ledDriverEntry);
由hdf驱动框架中可知驱动框架会调用init和bind函数具体可参考这篇博文
下面分别分析这两个函数,首先看下init函数
int32_t HdfLedDriverInit(struct HdfDeviceObject *deviceObject)
|-->struct DeviceResourceIface *CfgOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE)//获取设备资源接口对象指针,可以获取到一个设备资源接口的实例,进而使用该接口提供的功能
|--> HcsIfaceConstruct(instance)//构造函数
|-->instance->GetRootNode = HcsGetRootNode;
|-->instance->GetUint16 = HcsGetUint16;
|-->....
|-->if(CfgOps->GetUint32(deviceObject->property, "led_version", &g_ledcfg.led_ver, 0)//将配置文件中的版本信息赋值到驱动的全局变量g_ledcfg.led_ver中
|-->if(CfgOps->GetUint32(deviceObject->property, "led_R", &g_ledcfg.led_R, 0) != HDF_SUCCESS)//将配置文件中led_R的配置信息赋值到驱动的全局变量g_ledcfg.led_R中
|-->....同上会将led_G和led_B赋值到全局变量g_ledcfg中
由以上分析可知此函数主要是将配置文件led_config_rk3568.hcs中的信息配置到驱动缓存中。
- bind函数
一个驱动绑定函数,它的作用是将传入的设备对象(
deviceObject
)与一个设备 I/O 服务接口(IDeviceIoService
)进行绑定。绑定完成后,设备对象就可以通过这个接口提供 I/O 操作服务。
int32_t HdfLedDriverBind(struct HdfDeviceObject *deviceObject)
|-->static struct IDeviceIoService ledDriverServ = {.Dispatch = LedDriverDispatch,};//指向具体的 I/O 操作分发函数。这个函数负责处理设备的 I/O 请求。
|-->deviceObject->service = (struct IDeviceIoService *)(&ledDriverServ);//将设备对象与设备 I/O 服务接口绑定在一起
通过对应用代码的分析可知通过调用Dispatch函数实现对led的控制。
应用代码分析
从应用代码中可以看出此应用的主要功能是将传入的参数传入到驱动代码中,首先是通过HdfIoServiceBind获取驱动的服务即绑定RGB三色灯HDF服务,获取HDF空间缓冲区,然后通过服务中的Dispatch方法向驱动发送消息,实现向缓冲区写入需要控制的RGB三色灯低三位数据。
HdfIoServiceBind函数
//drivers\hdf_core\interfaces\inner_api\core\hdf_io_service_if.h
//drivers\hdf_core\framework\core\shared\src\hdf_io_service.c
//serviceName为led_service
struct HdfIoService *HdfIoServiceBind(const char *serviceName)
|-->return HdfIoServiceAdapterObtain(serviceName)
|-->nodePath = OsalMemCalloc(PATH_MAX);realPath = OsalMemCalloc(PATH_MAX);//分配节点路径内存
|-->if (sprintf_s(nodePath, PATH_MAX - 1, "%s%s", , serviceName) < 0)// "/dev/hdf/led_service"
|-->TrytoLoadIoService(serviceName, nodePath, realPath);//
|-->if (HdfLoadDriverByServiceName(serviceName) != HDF_SUCCESS) {//加载对应的驱动程序
|-->if (serviceName == NULL || strcmp(serviceName, DEV_MGR_NODE) == 0) return ret;//注意当设备节点名称为DEV_MGR_NODE即dev_mgr时会直接返回失败。
|-->data = HdfSbufObtainDefaultSize();//创建一个默认大小的 HDF 数据缓冲区(HdfSBuf)
|-->if (!HdfSbufWriteString(data, serviceName)) {//将服务名称传递给设备管理服务
|-->ret = ioService->dispatcher->Dispatch(&ioService->object, DEVMGR_LOAD_SERVICE, data, NULL);//调用设备管理服务的 Dispatch 方法,发送一个请求,加载指定的服务(serviceName),即后文的HdfSyscallAdapterDispatch函数
|-->while (realpath(devNodePath, realPath) == NULL && waitCount > 0) {//一个标准库函数,用于将 devNodePath 转换为绝对路径,并存储到 realPath 中
|-->adapter = (struct HdfSyscallAdapter *)OsalMemCalloc(sizeof(struct HdfSyscallAdapter))//分配系统适配器
|-->DListHeadInit(&adapter->listenerList);//初始化适配器的监听器列表
|-->adapter->fd = open(realPath, O_RDWR);//使用 open 函数以读写模式(O_RDWR)打开设备文件(realPath)
|-->static struct HdfIoDispatcher dispatch = {//设置服务调度器
.Dispatch = HdfSyscallAdapterDispatch,
};
ioService->dispatcher = &dispatch;
- HdfSyscallAdapterDispatch函数说明
/**
* @brief 系统调用适配器的静态分发函数,用于在某种硬件驱动框架(可能是 OpenHarmony 或类似的系统)中通过 ioctl 系统调用与设备驱动程序进行通信
* @param object 一个 HdfObject 类型的指针,表示调用该函数的对象。HdfObject 是一个通用的对象结构体
* @param code 要执行的操作代码,用于指定具体的设备操作或服务请求
* @param data 指向一个 HdfSBuf 类型的指针,表示发送给设备的数据缓冲区
* @param reply 指向一个 HdfSBuf 类型的指针,表示从设备接收的响应数据缓冲区
* @return int32_t 一个静态函数,返回值类型为 int32_t,表示函数执行的结果状态
*/
static int32_t HdfSyscallAdapterDispatch(
struct HdfObject *object, int32_t code, struct HdfSBuf *data, struct HdfSBuf *reply)
{
if (object == NULL) {
HDF_LOGE("Input object is null");
return HDF_FAILURE;
}
struct HdfSyscallAdapter *ioService = (struct HdfSyscallAdapter *)object;//将 object 转换为 HdfSyscallAdapter 类型
struct HdfWriteReadBuf wrBuf;//用于存储写入和读取缓冲区的信息
if (reply != NULL) {
wrBuf.readBuffer = (uintptr_t)HdfSbufGetData(reply);//如果 reply 不为空,则从 reply 中获取读取缓冲区的地址和容量,并将其赋值给 wrBuf.readBuffer
wrBuf.readSize = HdfSbufGetCapacity(reply);//如果 reply 不为空,则从 reply 中获取读取缓冲区的地址和容量,并将其赋值给wrBuf.readSize
} else {
wrBuf.readBuffer = 0;
wrBuf.readSize = 0;
}
if (data != NULL) {
wrBuf.writeBuffer = (uintptr_t)HdfSbufGetData(data);//如果 data 不为空,则从 data 中获取写入缓冲区的地址和数据大小,并将其赋值给 wrBuf.writeBuffer
wrBuf.writeSize = HdfSbufGetDataSize(data);//如果 data 不为空,则从 data 中获取写入缓冲区的地址和数据大小,并将其赋值给 wrBuf.writeSize
} else {
wrBuf.writeBuffer = 0;
wrBuf.writeSize = 0;
}
wrBuf.readConsumed = 0;
wrBuf.writeConsumed = 0;
wrBuf.cmdCode = code;//设置为传入的 code,表示要执行的操作代码
int32_t ret = ioctl(ioService->fd, HDF_WRITE_READ, &wrBuf);//使用 ioctl 系统调用,通过设备文件描述符 ioService->fd,发送 HDF_WRITE_READ 命令,并将 wrBuf 作为参数传递给设备驱动程序。
if (ret < 0) {
HDF_LOGE("Failed to dispatch serv call ioctl %{public}d", -errno);
ret = -errno;
}
if (reply != NULL) {
HdfSbufSetDataSize(reply, wrBuf.readConsumed);//如果 reply 不为空,则根据 wrBuf.readConsumed 更新 reply 的数据大小
}
return ret;
}
Dispatch方法
ret = serv->dispatcher->Dispatch(&serv->object, LED_RGB_WRITE, data, NULL);//向缓冲区写入需要控制的RGB三色灯低三位数据
测试记录
为了节省大家时间,快速展示效果,我在这里加快了闪烁,实际应该是2s左右变换一次颜色。
润和DAYU200开发板
问题记录
- 操作app没有反应,报device led_service not in configed device list
解决方案:
通过各种尝试还是未解决掉,崩溃的边缘翻书玩,发现如下稻草。。。并成功解决!
只按上图说明删除,不删除out下的目录时,重新编译后,不会重新生成hcb,所以怀疑应该是没有重新生成,
将out目录重新删除后,进行编译。
参考文档
- #DAYU200体验官# RK3568三色灯点灯流程
- zh-cn\device-dev\driver\driver-peripherals-light-des.md
- 《沉浸式剖析OpenHarmony源代码》