鸿蒙操作系统系列——Hi3516 OpenHarmony_release_v1.1.0 LTS版本led内核态驱动与用户态应用贯通篇

Hi3516 OpenHarmony_release_v1.1.0 LTS版本led内核态驱动与用户态应用贯通篇

此文档是针对 OpenHarmony 2021年4月10日发布的OpenHarmony_release_v1.1.0 LTS 版本分析。

1. 开发环境

  1. HiSpark_AI_Hi3516D300开发板

  2. 虚拟机安装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链接驱动私有配置文件,最后通过配置文件来进行耦合,保证了灵活性。这种设计在分布式的场合中,会有比较大的便利性。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
本期是我们鸿蒙系列课程的第1期,主要目标是让大家先搞清楚鸿蒙的整体架构,开发环境搭建,代码构建和编译烧录测试等玩法。然后就是核心的代码解读和试验了,譬如liteos_a的启动流程,liteos的shell原理和用法,鸿蒙各组件的加载和使用。鸿蒙的ipc和rpc,ace模块的实现和接口,鸿蒙app的sdk介绍和app开发环境搭建,app和hap的区分,注册鸿蒙app开发者,本次测试应用、上传应用等。 本课程是整个第1期的第1个课程,我们基于HI3516DV300开发板+liteos_a / HI3861开发板+liteos_m这2套组合来整个打通做一遍。从源码gitee下载,本地开发环境部署,到解压、配置、编译、烧录、启动运行,在shell上看一些信息,本地bm工具安装hap包运行,本地开发helloworld程序运行。课程目标是让大家整体的体验harmonyos开发全流程。Harmonyos支持Windows和linux两种开发方式,windows下提供了南向ide(基于vscode+deveco studio插件),可以配置编译并烧录,还可以单步调试。而linux下基于命令行只能配置编译不能烧录(但是开发起来很清爽)。这些开发细节在这个课程都会带大家走一遍。老规矩,一边做一边讲。我昨天已经打好了ubuntu20.04上传到核心课程的虚拟机网盘里了,本课程会使用这个ubuntu200401LTS来做。 课程特色*完全零基础,降低学习门槛。*深入浅出,通俗易懂。不怕学不会,就怕你不学习。*思路清晰、语言风趣,对着视频看也不会想睡觉······*视频 + 文档 + 练习题 + 答疑,全方位保证学习质量。*基础知识 + 思路引导的教学方式,授之以鱼更授之以渔。*系列课程。本教程只是入门,后续还有更多更精彩视频更新中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CPUOS2010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值