OpenHarmonyOs / LiteOs-a 驱动开发

相关文章

上一篇:OpenHarmonyOs / LiteOs-a 开发环境搭建
下一篇:OpenHarmonyOs / LiteOs-a 应用开发


前言

本文瞎逼介绍一下在 Ubuntu 20.04 系统进行 OpenHarmonyOs / LiteOs-a 驱动开发。内容有很多搬官方文档。
开发板用的是润和的 ipcamera_hispark_taurus,芯片是 HI3516DV300。

一、LiteOs-a 内核框架

主要可以分成三大块:Host、Manager、Support。
就是内核框架图面积最大的三块:
在这里插入图片描述
Host 是管理同类设备的驱动的框架,为同一类设备提供统一的构建设备 Node,绑定驱动,监听设备电源状态信息,发布驱动服务,订阅驱动服务等的功能。用户空间或者内核空间需要获取驱动的服务时,可以调用 Host 的接口,然后 Host 再从 Manager 的服务列表中获取驱动服务。

Manager 是内核的管理者,负责内核内大小事务管理。比如 Host 管理,驱动发布的服务管理(Device Service Manage),设备的电源管理(Power Manager),以及其他。

Support 提供内核的基础功能。Host 和 Manager 都可以用 Support 里的 Platform 和 OSAL (Operating System Abstraction Layer) 提供的驱动接口和基础功能,如 GPIO 控制,I2C 读写,Mutex,Thread 等。而 Platform 则直接跟底层硬件打交道。例如,触摸屏驱动复位控制芯片的时候需要拉低 reset GPIO,则需要调 Platform 里的 GPIO 接口;读取触摸屏坐标的时候需要调用 Platform 里的 I2C 接口。再如驱动中需要进行多线程开发或和线程同步机制,则需要用到 OSAL 中的 Thread 和 Mutex。

各个模块在代码中的位置:

host      //drivers/framework/core/host
manager   //drivers/framework/core/manager
          //drivers/adapter/khdf/liteos/manager
osal      //drivers/framework/support/posix
          //drivers/adapter/khdf/liteos/osal
platform  //drivers/framework/support/platform
          //drivers/adapter/khdf/liteos/platform

二、HDF(Hardware Driver Foundation)驱动框架

HDF(Hardware Driver Foundation)驱动框架为驱动开发者提供驱动框架能力,包括驱动加载、驱动服务管理和驱动消息机制。

HDF 驱动加载包括按需加载和按序加载。按需加载:HDF 框架支持驱动在系统启动过程中默认加载,或者在系统启动之后动态加载。按序加载:HDF 框架支持驱动在系统启动的过程中按照驱动的优先级进行加载。

HDF 框架可以集中管理驱动服务,开发者可直接通过 HDF 框架对外提供的接口获取驱动相关的服务;并提供统一的驱动消息机制,支持用户态应用向内核态驱动发送消息,也支持内核态驱动向用户态应用发送消息。

1. 驱动模型

HDF 框架将一类设备驱动放在同一个 Host 里面,开发者也可以将驱动功能分层独立开发和部署,支持一个驱动多个 Node,每个 Node 对应一个物理设备。有看到文章说每个 Host 为一个进程,但是没有跟过代码,不能确定。驱动模型如下图所示:
在这里插入图片描述

2. HDF驱动结构

前两节内容大部分来自官方文档。
1) 主要三个函数和一个 Entry
Bind(),Init(),Release() 分别实现绑定驱动服务,初始化,释放资源的功能。

#include "hdf_device_desc.h"  // HDF框架对驱动开发相关能力接口的头文件
#include "hdf_log.h"          // HDF框架提供的日志接口头文件

#define HDF_LOG_TAG "sample_driver"   // 打印日志所包含的标签,如果不定义则用默认定义的HDF_TAG标签

// 驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
int32_t HdfSampleDriverBind(struct HdfDeviceObject *deviceObject)
{
   
   
    HDF_LOGD("Sample driver bind success");
    return 0;
}

// 驱动自身业务初始的接口
int32_t HdfSampleDriverInit(struct HdfDeviceObject *deviceObject)
{
   
   
    HDF_LOGD("Sample driver Init success");
    return 0;
}

// 驱动资源释放的接口
void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
   
   
    HDF_LOGD("Sample driver release success");
    return;
}

驱动入口注册到 HDF 框架,实际上是把 g_sampleDriverEntry 内存某个区域中。
加载驱动的时候从这块区域中把各个 Entry 取出来,先跑各驱动的 Bind(),然后 Init(),驱动加载出了问题会跑 Release()。

// 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量
struct HdfDriverEntry g_sampleDriverEntry = {
   
   
    .moduleVersion = 1,
    .moduleName = "sample_driver",
    .Bind = HdfSampleDriverBind,
    .Init = HdfSampleDriverInit,
    .Release = HdfSampleDriverRelease,
};

// 调用HDF_INIT将驱动入口注册到HDF框架中,在加载驱动时HDF框架会先调用Bind函数
// 再调用Init函数加载该驱动,当Init调用异常时,HDF框架会调用Release释放驱动资源并退出。
HDF_INIT(g_sampleDriverEntry);

2) 配置文件
HDF 用 .hcs 文件作为描述设备的参数表。这个跟 Android / Linux 的设备树的概念差不多。语法参考:这里
包含两部分,HDF框架定义的驱动设备描述和驱动的私有配置信息。
驱动设备描述:

root {
    device_info {
        match_attr = "hdf_manager";
        template host {     // host 模板,继承这个模板的节点(比如下面的 sample_host)如果底下属性使用默认值,则节点字段可缺省
            hostName = "";
            priority = 100;
            uid = "";       // 用户态进程 uid,缺省为空,会被配置为 hostName 的定义值,即普通用户
            gid = "";       // 用户态进程 gid,缺省为空,会被配置为 hostName 的定义值,即普通用户组
            caps = [""];    // 用户态进程 Linux capabilities 配置,缺省为空,需要业务模块按照业务需要进行配置
            template device {
                template deviceNode {
                    policy = 0;
                    priority = 100;
                    preload = 0;
                    permission = 0664;
                    moduleName = "";
                    serviceName = "";
                    deviceMatchAttr = "";
                }
            }
        }
        sample_host :: host{
            hostName = "host0";              // host 名称
            priority = 100;                  // host 启动优先级(0-200),值越大优先级越低,建议默认配100,优先级同则不保证加载顺序
            caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"];   // 用户态进程 Linux capabilities 配置
            device_sample :: device {        // sample 设备节点
                device0 :: deviceNode {      // sample 驱动的 DeviceNode 节点
                    policy = 1;              // 驱动服务发布的策略
                    priority = 100;          // 驱动启动优先级(0-200),值越大优先级越低,建议默认配100,优先级同则不保证加载顺序
                    preload = 0;             // 驱动按需加载字段
                    permission = 0664;       // 驱动创建设备节点权限
                    moduleName = "sample_driver";         // 驱动名称,该字段的值必须和驱动入口结构的 moduleName 值一致
                    serviceName = "sample_service";       // 驱动对外发布服务的名称,须唯一
                    deviceMatchAttr = "sample_config";    // 驱动私有数据匹配的关键字,须和驱动私有数据配置表中的 match_attr 值相等
                }
            }
        }
    }
}

说明:
a)uid、gid、caps 等配置项是用户态驱动的启动配置,内核态不用配置。本文介绍的是内核态驱动,暂时先不讨论这几个参数。

b)policy: 驱动发布服务的策略。0,驱动不提供服务;1,驱动对内核态发布服务; 2,对内核态和用户态都发布服务; 3,驱动服务不对外发布服务,但可以被订阅; 4,驱动私有服务不对外发布服务,也不能被订阅。

c)preload:加载选项。0,系统启动过程中默认加载。1,当系统支持快启的时候,则在系统完成之后再加载这一类驱动,否则和0相同。2,默认不加载,支持后续动态加载;当用户态获取驱动服务(参考消息机制)时,如果驱动服务不存在,HDF 框架会尝试动态加载该驱动。

d)priority:加载的优先级。范围0-200,值越大优先级越低。

驱动私有配置可放在驱动的配置 hcs,HDF 框架在加载驱动时,会获取对应配置信息并保存在 HdfDeviceObject 中的 property 里面,通过 Bind() 和 Init() 传递给驱动。驱动的配置信息:

root {
    SampleDriverConfig {
        sample_version = 1;
        sample_bus = "I2C_0";
        gpio = 20;
        match_attr = "sample_config";   //该字段的值必须和 device_info.hcs 中的 deviceMatchAttr 值一致
    }
}

配置信息定义之后,需要将该配置文件包含到板级配置入口文件 hdf.hcs。

#include "sample/sample_config.hcs"

做了个驱动模型对应配置文件的图:
请添加图片描述
host node 内是同一类设备
device 内的设备用同一个驱动
device node 对应一个硬件(同一个 moduleName 加载同一个驱动)

3)获取私有配置信息
以下面的配置为例

root {                                                                                                                                   
    SampleDriverConfig {
        boardConfig {
            match_attr = "Sample_config";
            SampleVal1 = 50; 
            SampleNode {
                SampleVal2 = true;
            }
        }
    }   
}

首先包含头文件

#include "utils/device_resource_if.h"

驱动被加载后, Init() 函数的参数 struct HdfDeviceObject *device 的指针成员 property 指向了驱动私有配置。

int32_t InitSampleDriver(struct HdfDeviceObject *device)
{
   
   
   uint32_t sampleVal1;
   bool sampleVal2;
   struct DeviceResourceNode *node = device->property;
   struct DeviceResourceIface *parser = NULL;
   const struct DeviceResourceNode *sampleNode = NULL;
   int ret = 0;
   ...
   parser = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE); // 获取一个parser
   ...
   ret = parser->GetUint32(node, "sampleVal1", &sampleVal1, 0); // 解析boardConfig节点内uint32类型的参数SampleVal1
   ...
   sampleNode = parser->GetChildNode(node, "sampleNode"); // 获取子节点SampleNode
   ...
   sampleVal2 = parser->GetBool(sampleNode, "sampleVal2"); // 解析子节点SampleNode内bool类型的参数SampleVal2
   ...
}

其他类型的参数解析可以参考

//drivers/framework/include/utils/device_resource_if.h

4)获取 Support 模块的功能
a)获取 Platform 接口
以 GPIO 为例,需要包含头文件

#include "gpio_if.h"

然后直接调接口就可以了

int ret = GpioSetDir(20, GPIO_DIR_OUT);
if (ret) {
   
   
    HDF_LOGE("%s: gpio%d setting output failed", __func__, 20);                                                              
    return HDF_FAILURE;
}
if (GpioWrite(20, GPIO_VAL_HIGH) != HDF_SUCCESS) {
   
   
    HDF_LOGE("%s: pull gpio%d to %d level failed", __func__, 41, GPIO_VAL_HIGH);
    return HDF_FAILURE;
} 

在路径

//drivers/framework/include/platform/

下运行命令

find -name "*if.h"
./sdio_if.h
./mmc_if.h
./emmc_if.h
./i2c_if.h
./spi_if.h
./gpio_if.h
./rtc_if.h
./i2s_if.h
./mipi_dsi_if.h
./timer_if.h
./uart_if.h
./platform_if.h
./pwm_if.h
./regulator_if.h
./pin_if.h
./pcie_if.h
./adc_if.h
./i3c_if.h
./watchdog_if.h
./mipi_csi_if.h
./dac_if.h
./hdmi_if.h

可以找到提供接口的 Platform 模块

b)获取 OSAL 接口
以 Mutex 为例,添加头文件

#include "osal_mutex.h"

然后调用接口

(void)OsalMutexLock(&drvData->mutex);
data = 1;
(void)OsalMutexUnlock(&drvData->mutex);

在路径

//drivers/framework/include/platform/

下运行命令

find -name "osal*.h"
./osal.h
./osal/osal_firmware.h
./osal/osal_atomic.h
./osal/osal_irq.h
./osal/osal_timer.h
./osal/osal_cdev.h
./osal/osal_mutex.h
./osal/osal_io.h
./osal/osal_mem.h
./osal/osal_thread.h
./osal/osal_sem.h
./osal/osal_spinlock.h
./osal/osal_file.h
./osal/osal_time.h

可以找到提供接口的 OSAL 模块

5)发布服务
在 hcs 文件配置好适当的驱动服务发布策略,驱动里可以对外发布服务。
驱动服务结构体:

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

驱动服务接口的实现

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

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

驱动服务绑定到 HDF 框架中,实现 HdfDriverEntry 中的 Bind() 指针函数。

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

驱动服务的获取有两种方式,HDF 框架提供接口直接获取和 HDF 框架提供订阅机制获取。
注意驱动服务的获取(目前)只能在内核驱动

a)通过 HDF 接口直接获取
当明确驱动已经加载完成时,驱动服务可通过 HDF 框架接口直接获取

const struct ISampleDriverService *sampleService =
        (const struct ISampleDriverService *)DevSvcManagerClntGetService("sample_driver");
if (sampleService == NULL) {
   
   
    return -1;
}
sampleService
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值