4. 第一个HDF驱动:创建HDF驱动项目、驱动入口函数与用户态通信

好,咱们今天正式动手写第一个HDF驱动。说实话,很多初学者看到HDF那套框架代码会觉得头大——又是Bind又是Init,还有Release,感觉比普通Linux驱动复杂不少。但你别被吓到,这套流程其实很固定,说白了就是「三段式」生命周期管理。

我个人习惯是先把骨架搭起来,再往里填肉。咱们今天就干这件事:从零创建一个HDF驱动项目,把三个入口函数写明白,然后部署到开发板上,最后让用户态程序跟内核驱动说上话。

4.1 HDF驱动项目的目录结构

先看看一个标准的HDF驱动长什么样。我以Hi3516DV300平台为例,你在其他芯片上做也是一样的套路。

// 驱动源码目录建议
drivers/hdf_core/adapter/khdf/linux/platform/
    └── my_hdf_driver/
        ├── Makefile
        ├── my_hdf_driver.c      // 驱动主文件
        ├── my_hdf_driver.h      // 头文件
        └── hcs/
            └── my_hdf_driver.hcs // 配置文件

嗯,这里要注意:HDF驱动必须配一个.hcs配置文件,这是HDF框架用来做设备匹配和资源管理的核心。没有它,驱动加载不起来。

文件 作用 是否必须
my_hdf_driver.c 驱动主逻辑,包含Bind/Init/Release 是
my_hdf_driver.h 对外接口声明、数据结构定义 推荐
my_hdf_driver.hcs 设备配置、资源描述 是
Makefile 编译脚本,链接到HDF框架 是

4.2 驱动入口函数:Bind、Init、Release

HDF驱动的生命周期就靠这三个函数撑着。我刚开始接触时也觉得奇怪——为什么Linux驱动一个probe就够了,HDF要搞三个?后来踩过坑才明白,这是为了把「资源绑定」和「业务初始化」分开,方便做热插拔和电源管理。

4.2.1 Bind:绑定服务接口

Bind阶段干的事很简单:把你的驱动服务注册到HDF框架的服务管理器中。说白了就是告诉系统「我来了,我能提供这些接口」。

// my_hdf_driver.c
#include "hdf_device_desc.h"
#include "hdf_log.h"

#define HDF_LOG_TAG my_hdf_driver

// 服务接口结构体
struct MyDriverService {
    struct IDeviceIoService ioService;
    int (*DoSomething)(void);
};

// 实际业务函数
static int MyDriverDoSomething(void)
{
    HDF_LOGI("MyDriver: DoSomething called");
    return 0;
}

// Bind函数:注册服务
static int32_t MyDriverBind(struct HdfDeviceObject *device)
{
    struct MyDriverService *service = NULL;

    if (device == NULL) {
        HDF_LOGE("MyDriverBind: device is NULL");
        return HDF_ERR_INVALID_PARAM;
    }

    service = (struct MyDriverService *)OsalMemAlloc(sizeof(struct MyDriverService));
    if (service == NULL) {
        HDF_LOGE("MyDriverBind: failed to alloc service");
        return HDF_ERR_MALLOC_FAIL;
    }

    service->ioService.Dispatch = NULL;  // 后面讲通信时会用到
    service->DoSomething = MyDriverDoSomething;
    device->service = &service->ioService;

    HDF_LOGI("MyDriverBind: success");
    return HDF_SUCCESS;
}
我的经验:Bind里只做资源指针的赋值,别做内存申请以外的操作。我曾经把硬件初始化写在Bind里,结果设备还没上电就崩了。Bind就是「牵线搭桥」,Init才是「干活」。

4.2.2 Init:初始化硬件与资源

Init阶段才是真正的初始化。这时候设备已经就绪,你可以操作寄存器、申请中断、创建内核线程了。

// Init函数:初始化硬件
static int32_t MyDriverInit(struct HdfDeviceObject *device)
{
    (void)device;
    HDF_LOGI("MyDriverInit: enter");

    // 这里做真正的初始化工作
    // 比如:映射寄存器地址、配置GPIO、注册中断
    // int ret = MyDriverHardwareInit();
    // if (ret != HDF_SUCCESS) {
    //     HDF_LOGE("MyDriverInit: hardware init failed");
    //     return ret;
    // }

    HDF_LOGI("MyDriverInit: success");
    return HDF_SUCCESS;
}
避坑指南:我曾经在Init里做了个阻塞等待,结果内核启动卡住了。Init函数必须快速返回,长时间阻塞会导致系统启动超时。如果确实需要等待,用异步方式或者工作队列。

4.2.3 Release:释放资源

Release是驱动的「善后」函数。驱动卸载时调用,释放所有申请的资源。很多新手会忽略这个函数,结果卸载驱动后内存泄漏,系统越来越慢。

// Release函数:释放资源
static void MyDriverRelease(struct HdfDeviceObject *device)
{
    struct MyDriverService *service = NULL;

    if (device == NULL || device->service == NULL) {
        HDF_LOGE("MyDriverRelease: invalid param");
        return;
    }

    service = (struct MyDriverService *)device->service;
    // 释放硬件资源
    // MyDriverHardwareDeinit();

    OsalMemFree(service);
    device->service = NULL;

    HDF_LOGI("MyDriverRelease: success");
}

4.3 驱动入口注册与HCS配置

三个函数写好了,怎么告诉HDF框架「这就是我的驱动」?需要注册一个入口描述符。

// 驱动入口描述符
struct HdfDriverEntry g_myDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "my_hdf_driver",
    .Bind = MyDriverBind,
    .Init = MyDriverInit,
    .Release = MyDriverRelease,
};

// 宏注册
HDF_INIT(g_myDriverEntry);

同时,HCS配置文件里要声明这个设备:

// my_hdf_driver.hcs
root {
    platform {
        my_hdf_driver {
            match_attr = "my_hdf_driver";
            // 可以加自定义属性
            // gpio_num = 10;
            // irq_num = 38;
        }
    }
}

match_attr是HDF用来匹配设备和驱动的关键字段,必须跟驱动代码里的一致。

4.4 驱动编译与部署

编译HDF驱动有两种方式:编译进内核镜像,或者编译成独立模块。我建议前期调试用独立模块,方便快速迭代。

# Makefile
include $(LITEOSTOPDIR)/config.mk

MODULE_NAME := my_hdf_driver

LOCAL_SRCS := my_hdf_driver.c

LOCAL_INCLUDE := \
    -I$(LITEOSTOPDIR)/drivers/hdf_core/framework/include \
    -I$(LITEOSTOPDIR)/drivers/hdf_core/adapter/khdf/linux/include

LOCAL_CFLAGS := -fstack-protector-strong

include $(HDF_DRIVER)

编译命令:

# 编译整个内核(包含驱动)
hb build -f

# 或者单独编译驱动模块
make -C out/kernel/linux-5.10/ M=$(PWD)/drivers/hdf_core/adapter/khdf/linux/platform/my_hdf_driver modules

部署到开发板:

# 通过NFS挂载或scp拷贝
scp my_hdf_driver.ko root@192.168.1.100:/lib/modules/

# 加载驱动
insmod /lib/modules/my_hdf_driver.ko

# 查看驱动是否加载成功
cat /proc/hdf/driver_list

验证方法:执行cat /proc/hdf/driver_list,如果看到my_hdf_driver出现在列表中,说明Bind和Init都执行成功了。如果没出现,检查dmesg看错误日志。

4.5 用户态与内核态通信

驱动写好了,用户态程序怎么调用它?HDF提供了一套标准的IO通信机制——通过Dispatch接口实现。

4.5.1 内核端:实现Dispatch

在Bind函数里,我们留了个service->ioService.Dispatch = NULL,现在把它补上:

// 命令码定义
enum MyDriverCmd {
    CMD_DO_SOMETHING = 0,
    CMD_GET_STATUS,
};

// Dispatch函数:处理用户态请求
static int32_t MyDriverDispatch(struct HdfDeviceIoClient *client,
                                int cmdId,
                                struct HdfSBuf *data,
                                struct HdfSBuf *reply)
{
    (void)client;

    switch (cmdId) {
        case CMD_DO_SOMETHING:
            HDF_LOGI("Dispatch: CMD_DO_SOMETHING");
            // 从data中读取参数
            // 执行操作
            // 结果写入reply
            return HDF_SUCCESS;

        case CMD_GET_STATUS:
            HDF_LOGI("Dispatch: CMD_GET_STATUS");
            // 读取状态并返回
            return HDF_SUCCESS;

        default:
            HDF_LOGE("Dispatch: unknown cmd %d", cmdId);
            return HDF_ERR_NOT_SUPPORT;
    }
}

// 修改Bind,注册Dispatch
static int32_t MyDriverBind(struct HdfDeviceObject *device)
{
    // ... 前面的代码不变 ...
    service->ioService.Dispatch = MyDriverDispatch;  // 注册分发函数
    // ...
}

4.5.2 用户态:打开设备并发送命令

用户态程序通过HDF提供的用户态API来操作驱动:

// user_app.c
#include "hdf_io_service_if.h"

int main(void)
{
    struct HdfIoService *service = NULL;
    struct HdfSBuf *data = NULL;
    struct HdfSBuf *reply = NULL;
    int ret;

    // 1. 打开驱动服务
    service = HdfIoServiceBind("my_hdf_driver");
    if (service == NULL) {
        printf("Failed to bind service\n");
        return -1;
    }

    // 2. 准备数据缓冲区
    data = HdfSBufObtainDefaultSize();
    reply = HdfSBufObtainDefaultSize();

    // 3. 发送命令
    ret = service->dispatcher->Dispatch(
        service->dispatcher,
        CMD_DO_SOMETHING,
        data,
        reply);

    if (ret != HDF_SUCCESS) {
        printf("Dispatch failed: %d\n", ret);
    }

    // 4. 清理
    HdfSBufRecycle(data);
    HdfSBufRecycle(reply);
    HdfIoServiceRecycle(service);

    return 0;
}
调试技巧:用户态程序编译时,需要链接HDF用户态库。在BUILD.gn里加上deps = ["//drivers/hdf_core/framework/model/network/hdf_io_service:libhdf_io_service"]。我第一次编译时忘了加,链接报错找了半天。

4.6 本章知识结构图

下面这张图把整个流程串起来了,从驱动创建到用户态通信,一目了然:

HDF驱动开发全流程 HdfDriverEntry 注册 Bind:注册服务接口 Init:初始化硬件 Release:释放资源 HCS设备配置 编译部署 加载验证 用户态 ↔ 内核态通信(Dispatch) 用户态:HdfIoServiceBind 构造SBuf数据 Dispatch调用内核 内核处理 → 结果返回用户态 关键点:Bind只做服务注册,Init做硬件初始化,Release做资源释放,Dispatch是用户态与内核态的桥梁

4.7 常见问题与避坑

  • 驱动加载失败:检查HCS配置里的match_attr是否跟驱动代码里的moduleName一致。我遇到过两次都是因为拼写错误。
  • 用户态无法绑定服务:确认驱动已经加载成功(cat /proc/hdf/driver_list),并且用户态程序有权限访问HDF设备节点。
  • Dispatch收不到数据:检查SBuf的读写顺序——先写后读,别搞反了。我曾经在用户态先读后写,数据一直为空,排查了半天。
  • Release没被调用:驱动卸载时如果还有用户态程序持有服务引用,Release不会执行。先关掉所有用户态程序再卸载驱动。

好了,第一个HDF驱动就这么写出来了。从Bind到Init再到Release,加上用户态通信,这套流程在后续所有HDF驱动开发中都会反复用到。你把这个骨架跑通了,后面加功能就是往里面填代码的事。