Hi3516 OpenHarmony_release_v1.1.0 LTS版本led内核态驱动与用户态应用贯通篇
此文档是针对 OpenHarmony 2021年4月10日发布的OpenHarmony_release_v1.1.0 LTS 版本分析。
1. 开发环境
-
HiSpark_AI_Hi3516D300开发板 -
虚拟机安装Ubuntu18.04版本,参考https://device.harmonyos.com/cn/docs/start/introduce/oem_minitinier_environment_lin-0000001105407498搭好建开发环境。
2. 开发目标
在HiSpark_AI_Hi3516D300开发板上实现红外LED灯的亮灭控制,支持内核态驱动程序和用户态应用程序双向通信,实现用户态发送一个控制红外灯亮灭的消息到内核态,同时内核态返回灯的状态消息发送到用户态程序。
3. 开发准备
1)板卡级编译配置文件:device/hisilicon/hispark_taurus/sdk_liteos/config.gni
2)hi3516dv300驱动闭源lib库的路径在目录:device/hisilicon/drivers/libs/ohos/llvm/hi3516dv300
3)Hi3516开发板的红外灯的 IO 口编号为GPIO5_1。
4. 内核态开发
4.1 HDF简介
HDF(OpenHarmony Driver Foundation)驱动框架,为驱动开发者提供驱动框架能力,包括驱动加载、驱动服务管理和驱动消息机制。旨在构建统一的驱动架构平台,为驱动开发者提供更精准、更高效的开发环境,力求做到一次开发,多系统部署。
HDF框架以组件化的驱动模型作为核心设计思路,为开发者提供更精细化的驱动管理,让驱动开发和部署更加规范。HDF框架将一类设备驱动放在同一个host里面,驱动内部实现开发者也可以将驱动功能分层独立开发和部署,支持一个驱动多个node,HDF框架管理驱动模型如下图所示。
4.2 HDF实现
4.2.1 框架驱动实现
在vendor/huawei/hdf/sample/platform目录下新增led目录,新建如下文件夹和文件,其中include文件夹包含led_driver.h,src文件夹包含led_driver.c,led_driver.c文件实现HDF驱动框架代码。
主要代码如下。
#include "hdf_device_desc.h" // HDF框架对驱动开放相关能力接口的头文件
#include "hdf_log.h" // HDF框架提供的日志接口头文件
#include "device_resource_if.h" // HDF框架获取设备配置信息的接口头文件
//驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
int32_t HdfLedDriverBind(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL)
{
HDF_LOGE("Led driver bind failed!");
return HDF_ERR_INVALID_OBJECT;
}
static struct IDeviceIoService ledService = {
.Dispatch = LedDriverDispatch,
};
deviceObject->service = (struct IDeviceIoService *)(&ledService);
HDF_LOGD("Led driver bind success");
return HDF_SUCCESS;
}
// 驱动自身业务初始的接口
int32_t HdfLedDriverInit(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL)
{
HDF_LOGE("Led driver Init failed!");
return HDF_ERR_INVALID_OBJECT;
}
HDF_LOGD("Led driver Init success");
return HDF_SUCCESS;
}
// 驱动资源释放的接口
void HdfLedDriverRelease(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL)
{
HDF_LOGE("Led driver release failed!");
return;
}
HDF_LOGD("Led driver release success");
return;
}
// 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量
struct HdfDriverEntry g_ledDriverEntry = {
.moduleVersion = 1,
.moduleName = "HDF_LED",
.Bind = HdfLedDriverBind,
.Init = HdfLedDriverInit,
.Release = HdfLedDriverRelease,
};
// 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数,再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
HDF_INIT(g_ledDriverEntry);
4.2.2 驱动编译
在vendor/huawei/hdf/sample/platform/led目录下新增Makefile文件。
include $(LITEOSTOPDIR)/config.mk
include $(LITEOSTOPDIR)/../../drivers/adapter/khdf/liteos/lite.mk
MODULE_NAME := hdf_led
LOCAL_CFLAGS += $(HDF_INCLUDE)
LOCAL_SRCS += src/led_driver.c \
LOCAL_INCLUDE := ./include
LOCAL_CFLAGS += -fstack-protector-strong
include $(HDF_DRIVER)
4.2.3 链接到内核镜像
修改device/hisilicon/drivers/lite.mk 文件新增以下内容。
ifeq ($(LOSCFG_DRIVERS_HDF_PLATFORM_LED), y)
LITEOS_BASELIB += -lhdf_led
LIB_SUBDIRS += $(LITEOS_SOURCE_ROOT)/vendor/huawei/hdf/sample/platform/led
endif
4.2.4 驱动配置
驱动配置包含两部分,HDF框架定义的驱动设备描述和驱动的私有配置信息。
(1)驱动设备配置
需要修改device/hisilicon/hispark_taurus/sdk_liteos/config/device_info/device_info.hcs文件,新增以下内容。其中,moduleName、serviceName和deviceMatchAttr 都比较重要,分布链接到源码的不同位置,我这里都分开命名,便于理解,设备级配置信息也需要同步修改,在文件vendor/hisilicon/hispark_taurus/config/device_info/device_info.hcs里面同步以下信息。
device_led :: device { // led设备节点
device0 :: deviceNode { // led驱动的DeviceNode节点
policy = 2; // policy字段是驱动服务发布的策略,在驱动服务管理章节有详细介绍
priority = 40; // 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级相同则不保证device的加载顺序
permission = 0644; // 驱动创建设备节点权限
moduleName = "HDF_LED"; // 驱动名称,该字段的值必须和驱动入口结构的moduleName值一致
serviceName = "led_service"; // 驱动对外发布服务的名称,必须唯一
deviceMatchAttr = "led_config"; // 驱动私有数据匹配的关键字,必须和驱动私有数据配置表中的match_attr值相等
}
}
(2)驱动私有配置信息
如果驱动有私有配置,可以添加一个驱动的配置文件,填写驱动的配置信息,HDF框架在加载驱动的时候,会将对应的配置信息获取并保存在HdfDeviceObject 的property里面传递给驱动。在device/hisilicon/hispark_taurus/sdk_liteos/config/led目录新增led_config.hcs文件,内容如下。
root{
LedDriverConfig {
led_version = 1;
match_attr = "led_config"; //该字段的值必须和device_info.hcs中的deviceMatchAttr值一致
}
}
4.2.5 板级配置
配置信息定义之后,需要将该配置文件添加到板级配置入口文件device/hisilicon/hispark_taurus/sdk_liteos/config/hdf.hcs。
#include "led/led_config.hcs"
4.3 驱动消息机制实现
当用户态应用程序和内核态驱动程序需要交互数据时,可以使用HDF框架的消息机制来实现。消息通信机制可以在用户态和内核态之间架起桥梁,这为我们之后的APP与底层驱动提供了双向通信的能力。本案例在用户态发送一个控制红外灯亮灭的消息到内核态,同时内核态返回灯的状态消息发送到用户态程序。
实现内核态的消息服务接口,编辑 led_driver.c, 实现服务基类成员IDeviceIoService中的Dispatch方法。收到用户态发来的命令后,操作红外LED设备,然后将返回值通过reply传回,最后将收到的命令回传给用户态程序。
// Dispatch是用来处理用户态发下来的消息
int32_t LedDriverDispatch(struct HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
{
int32_t result = HDF_FAILURE;
if (client == NULL || client->device == NULL)
{
HDF_LOGE("Led driver device is NULL");
return HDF_ERR_INVALID_OBJECT;
}
switch (cmdCode)
{
case LED_WRITE_READ:
//读取用户发过来的数据
const char *recv = HdfSbufReadString(data);
if (recv != NULL)
{
HDF_LOGI("recv: %s", recv);
if (strcmp(recv, "on") == 0)
{
LedControl(LED_LIGHT_ON);
result = 1;
}
else if (strcmp(recv, "off") == 0)
{
LedControl(LED_LIGHT_OFF);
result = 0;
}
else
{
HDF_LOGE("recv: %s invalid led mode", recv);
result = -1;
}
//返回值通过reply回传
if (!HdfSbufWriteInt32(reply, result))
{
HDF_LOGE("replay is fail");
}
//返回接收的数据发回给用户程序
return HdfDeviceSendEvent(client->device, cmdCode, data);
}
break;
default:
break;
}
return result;
}
修改 HdfLedDriverBind函数,将服务绑定到框架。
//驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
int32_t HdfLedDriverBind(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL)
{
HDF_LOGE("Led driver bind failed!");
return HDF_ERR_INVALID_OBJECT;
}
static struct IDeviceIoService ledService = {
.Dispatch = LedDriverDispatch,
};
deviceObject->service = (struct IDeviceIoService *)(&ledService);
HDF_LOGD("Led driver bind success");
return HDF_SUCCESS;
}
4.4 业务代码实现
编辑 led_driver.c,实现业务逻辑控制代码。通过mode参数实现LED灯的亮灭功能。其中Hi3516DV300的控制器管理12组GPIO管脚,每组8个。GPIO号 = GPIO组索引(0~11)* 每组GPIO管脚数(8) + 组内偏移,GPIO5_1的GPIO号 = 5 * 8 +1 = 41。
static int32_t LedControl(int mode)
{
int32_t ret;
/* LED的GPIO管脚号 */
uint16_t gpio = 5 * 8 + 1; // 红外补光灯
/* 将GPIO管脚配置为输出 */
ret = GpioSetDir(gpio, GPIO_DIR_OUT);
if (ret != 0)
{
HDF_LOGE("GpioSetDir: failed, ret %d\n", ret);
return ret;
}
//点亮LED
if (mode == LED_LIGHT_ON)
{
ret = GpioWrite(gpio, GPIO_VAL_HIGH);
}
else if (mode == LED_LIGHT_OFF) //关闭LED
{
ret = GpioWrite(gpio, GPIO_VAL_LOW);
}
else
{
HDF_LOGE("LedControl: invalid led mode.\n");
ret = -1;
}
if (ret != 0)
{
HDF_LOGE("LedControl: failed, ret = %d\n", ret);
return ret;
}
return ret;
}
4.5 配置KConfig
drivers/adapter/khdf/liteos/Kconfig添加LED相关的配置项控制编译。
config DRIVERS_HDF_PLATFORM_LED
bool "Enable HDF platform led driver"
default n
depends on DRIVERS_HDF_PLATFORM
help
Answer Y to enable HDF platform led driver.
在kernel/liteos_a目录下输入make menuconfig配置KConfig文件,选择Driver->Enable HDF platform led driver选项。
系统编译配置文件:kernel/liteos_a/tools/build/config/hispark_taurus_clang.config,LiteOS内核对配置进行修改后需要将内核配置的.config文件拷贝到上述路径下,命令如下:
cp .config tools/build/config/debug/hispark_taurus_clang.config
因为hb build -f每次会从kernel/liteos_a/tools/build/config/hispark_taurus_clang.config拷贝一份默认的配置到kernel/litos_a目录下把之前的配置给覆盖掉。
5. 用户态开发
我们开始写个led测试程序通过消息机制来与内核态交互,新建applications/sample/camera/ledApp/my_led_app.c源文件。代码如下:
//设置回调函数,收到内核态发来的消息,打印信息
static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data)
{
const char *string = HdfSbufReadString(data);
if (string == NULL)
{
printf("fail to read string in event data\n");
return HDF_FAILURE;
}
printf("%s received driver data: led %s\n", (char *)priv, string);
return HDF_SUCCESS;
}
//先实现一个发送消息的函数SendEvent,发送字符串命令后,收回内核态reply中操作设备后的返回值,放入replyData中打印出来,操作成功返回0。
static int SendEvent(struct HdfIoService *serv, char *eventData)
{
int ret = 0;
struct HdfSBuf *data = HdfSBufObtainDefaultSize();
if (data == NULL)
{
printf("fail to obtain sbuf data");
return 1;
}
struct HdfSBuf *reply = HdfSBufObtainDefaultSize();
if (reply == NULL)
{
printf("fail to obtain sbuf reply\n");
ret = HDF_DEV_ERR_NO_MEMORY;
goto out;
}
if (!HdfSbufWriteString(data, eventData))
{
printf("fail to write sbuf");
ret = HDF_FAILURE;
goto out;
}
ret = serv->dispatcher->Dispatch(&serv->object, LED_WRITE_READ, data, reply);
if (ret != HDF_SUCCESS)
{
printf("fail to send service call\n");
goto out;
}
int replyData = 0;
if (!HdfSbufReadInt32(reply, &replyData))
{
printf("fail to get service call reply\n");
ret = HDF_ERR_INVALID_OBJECT;
goto out;
}
printf("Get reply is: %d\n", replyData);
out:
HdfSBufRecycle(data);
HdfSBufRecycle(reply);
return ret;
}
//先获取led_service服务,通过服务名称,绑定到对应的驱动。然后设置监听,等待来自内核的消息。用户通过led on/off来控制红外LED灯的亮灭。
int main(int argc, char **argv)
{
if (argc < 2)
{
printf("./ledApp [on/off]");
return HDF_FAILURE;
}
struct HdfIoService *serv = HdfIoServiceBind(LED_SERVICE);
if (serv == NULL)
{
printf("fail to get service %s\n", LED_SERVICE);
return HDF_FAILURE;
}
static struct HdfDevEventlistener listener = {
.callBack = OnDevEventReceived,
.priv = "ledApp"
};
if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS)
{
printf("fail to register event listener\n");
return HDF_FAILURE;
}
SendEvent(serv, argv[1]);
sleep(2); //延时2秒等待回应
if (HdfDeviceUnregisterEventListener(serv, &listener))
{
printf("fail to unregister listener\n");
return HDF_FAILURE;
}
HdfIoServiceRecycle(serv);
printf("exit!\n");
return HDF_SUCCESS;
}
新建applications/sample/camera/ledApp/BUILD.gn,配置BUILD.gn
import("//build/lite/config/component/lite_component.gni")
executable("ledApp") {
sources = [ "my_led_app.c" ]
cflags = [ "-Wall" ]
cflags_cc = cflags
include_dirs = [
"//drivers/framework/include/utils",
"//drivers/adapter/khdf/liteos/osal/include",
"//drivers/framework/ability/sbuf/include",
"//drivers/framework/core/shared/include",
"//drivers/framework/core/host/include",
"//drivers/framework/core/manager/include",
"//drivers/framework/include/core",
"//drivers/framework/include/utils",
"//drivers/framework/utils/include",
"//drivers/framework/include/osal",
]
ldflags = [ "-lstdc++" ]
ldflags += [ "-lpthread" ]
ldflags += [ "-Wl,-rpath-link=$ohos_root_path/$root_out_dir" ]
deps = [
"//drivers/adapter/uhdf/manager:hdf_core",
"//drivers/adapter/uhdf/posix:hdf_posix_osal",
]
output_dir = "$root_out_dir/"
}
lite_component("led_app") {
features = [
":ledApp",
]
}
在build/lite/components/applications.json文件新增如下内容。
{
"component": "led_app",
"description": "Led related samples.",
"optional": "true",
"dirs": [
"applications/sample/camera/ledApp"
],
"targets": [
"//applications/sample/camera/ledApp:ledApp"
],
"rom": "",
"ram": "",
"output": [],
"adapted_kernel": [ "liteos_a" ],
"features": [],
"deps": {
"components": [],
"third_party": []
}
},
6. 系统编译烧写
在系统根目录下执行hb build -f进行系统编译。
系统烧写可以采用TFTP简化烧写过程。
//更新内核
tftp 0x81000000 OHOS_Image.bin
mmc write 0x0 0x81000000 0x800 0x36c8
//更新根文件系统
tftp 0x81000000 rootfs_vfat.img
mmc write 0x0 0x81000000 0x5000 0x7fae
//更新用户数据空间
tftp 0x81000000 userfs_vfat.img
mmc write 0x0 0x81000000 0xf000 0x19000
7. 测试
测试结果符合设计目标。
OHOS # ./ledApp on
OHOS # Get reply is: 1
01-01 00:33:38.741 17 80 I 02500/led_driver: recv: on
ledApp received driver data: led on
exit!
01-01 00:33:40.742 17 81 I 02500/hdf_syscall_adapter: event listener task received exit event
01-01 00:33:40.742 17 81 I 02500/hdf_syscall_adapter: event listener task exit
OHOS #
OHOS #
OHOS # ./ledApp off
OHOS # Get reply is: 0
01-01 00:33:49.654 18 80 I 02500/led_driver: recv: off
ledApp received driver data: led off
exit!
01-01 00:33:51.654 18 81 I 02500/hdf_syscall_adapter: event listener task received exit event
01-01 00:33:51.654 18 81 I 02500/hdf_syscall_adapter: event listener task exit
8. 总结
驱动和应用开发涉及到文件和配置比较多,关系也比较纷繁,而且分散在各个目录,这里列出主要文件再梳理一下:
//应用主目录 用户态
applications
|--sample
|--camera
|--ledApp
|--BUILD.gn
|--my_ledapp.c //用户态主程序
//产品级主目录 内核态
vender
|--huawei
|--hdf
|--sample
|--platform
|--led
|--Makefile
|--src
|--led_driver.c 驱动实现文件
|--include
|--led_driver.h
//板卡级配置文件
drivers/adapter/khdf/liteos/Kconfig
device/hisilicon/drivers/lite.mk
device/hisilicon/hispark_taurus/sdk_liteos/config/device_info/device_info.hcs
device/hisilicon/hispark_taurus/sdk_liteos/config/hdf.hcs
device/hisilicon/hispark_taurus/sdk_liteos/config/led/led_config.hcs
//产品级配置文件
vendor/hisilicon/hispark_taurus/config.json
vendor/hisilicon/hispark_taurus/config/device_info/device_info.hcs
vendor/hisilicon/hispark_taurus/config/hdf.hcs
vendor/hisilicon/hispark_taurus/config/led/led_config.hcs
//应用级配置文件
build/lite/components/applications.json
大致上分三个部分:内核态、用户态和配置文件。
(1) 内核态 首先由led_driver.c生成名为led_service的服务,以g_ledDriverEntry结构注册到HCS框架。编译成hdf_led_driver驱动,通过device/hisilicon/drivers/lite.mk链接到内核镜像中。通过 HdfLedDriverBind函数将led_driver模块绑定到HDF框架,IDeviceIoService中的Dispatch方法来处理来自用户态消息。
(2) 用户态 用户态以HdfIoServiceBind通过服务名led_service来找到相应的驱动,用HdfSbufWriteString来发送消息,serv->dispatcher->Dispatch来接收返回值,通过HdfDeviceRegisterEventListener设置监听,来获取内核态主动发送的消息。
(3)驱动配置 以模块名led_driver找到注册到HCS框架的驱动程序,以服务名led_service暴露给用户态程序或内核态程序调用,以led_config链接驱动私有配置文件,最后通过配置文件来进行耦合,保证了灵活性。这种设计在分布式的场合中,会有比较大的便利性。 cs //应用级配置文件 build/lite/components/applications.json
大致上分三个部分:内核态、用户态和配置文件。
**(1) 内核态**
首先由led_driver.c生成名为led_service的服务,以g_ledDriverEntry结构注册到HCS框架。编译成hdf_led_driver驱动,通过device/hisilicon/drivers/lite.mk链接到内核镜像中。通过 HdfLedDriverBind函数将led_driver模块绑定到HDF框架,IDeviceIoService中的Dispatch方法来处理来自用户态消息。
**(2) 用户态**
用户态以HdfIoServiceBind通过服务名led_service来找到相应的驱动,用HdfSbufWriteString来发送消息,serv->dispatcher->Dispatch来接收返回值,通过HdfDeviceRegisterEventListener设置监听,来获取内核态主动发送的消息。
**(3)驱动配置**
以模块名led_driver找到注册到HCS框架的驱动程序,以服务名led_service暴露给用户态程序或内核态程序调用,以led_config链接驱动私有配置文件,最后通过配置文件来进行耦合,保证了灵活性。这种设计在分布式的场合中,会有比较大的便利性。
|