相关文章
上一篇: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