2、HDF驱动模型核心概念:驱动对象、设备对象、服务对象、驱动入口与出口函数

好,咱们今天聊聊HDF驱动模型里最核心的几个概念。说实话,我刚接触HDF的时候,也被这些对象绕得有点晕。驱动对象、设备对象、服务对象,听起来都差不多,到底有什么区别?

我个人的理解是:驱动对象是“代码”,设备对象是“硬件”,服务对象是“接口”。你想想看,一个驱动要跑起来,总得有代码吧?代码得绑定到具体的硬件上吧?绑定之后还得给上层提供调用接口吧?嗯,这三个对象就是干这三件事的。

2.1 驱动对象(Driver Object)

驱动对象,说白了就是驱动程序的“身份证”。它告诉HDF框架:我这个驱动叫什么名字、能管理哪些设备、初始化函数在哪里、卸载函数在哪里。

在HDF里,驱动对象用 HdfDriverEntry 结构体表示。我刚开始写驱动时,总觉得这个结构体字段太多,后来发现其实核心就三个:moduleNameBindInitRelease

struct HdfDriverEntry g_myDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "my_driver",
    .Bind = MyDriverBind,
    .Init = MyDriverInit,
    .Release = MyDriverRelease,
};

这里要注意,moduleName 必须和HCS配置里的名字一致。我曾经因为大小写不匹配,折腾了一下午才找到问题。嗯,这种坑踩过一次就记住了。

核心要点:驱动对象是静态定义的,编译时就确定了。它不负责具体硬件操作,只负责告诉框架“我来了,我该怎么被调用”。

2.2 设备对象(Device Object)

设备对象代表的是真实的硬件实体。比如你板子上挂了一个I2C传感器,那这个传感器就是一个设备对象。

设备对象由 DeviceResourceIfaceHdfDeviceObject 共同描述。前者从HCS配置里读取硬件资源(比如中断号、寄存器地址),后者是运行时设备实例。

我个人习惯把设备对象理解成“硬件资源的容器”。它里面装着:

  • 设备属性(从HCS解析来的键值对)
  • 服务接口(如果有的话)
  • 私有数据(驱动自己用的结构体)
struct HdfDeviceObject {
    uint32_t deviceId;
    const char *serviceName;
    struct DeviceResourceIface *resource;
    void *priv;  // 驱动私有数据
};

这里有个坑:priv 指针是驱动自己管理的,HDF框架不会帮你释放。我记得有一次写GPIO驱动,忘记在卸载时释放 priv 指向的内存,结果每次insmod/rmmod都泄漏几KB。虽然不大,但积累多了系统就卡了。

避坑指南:设备对象的生命周期由HDF框架管理,但 priv 指向的内存必须由驱动在 Release 函数中手动释放。我曾经因为这个问题导致内存泄漏,排查了很久才发现。

2.3 服务对象(Service Object)

服务对象是驱动暴露给上层应用的接口。说白了,用户态程序不能直接操作硬件,得通过服务对象来间接访问。

在HDF里,服务对象通过 IDeviceIoService 基类实现。驱动可以继承这个基类,添加自己的接口方法。

struct MyDriverService {
    struct IDeviceIoService base;
    int32_t (*ReadData)(struct MyDriverService *service, uint8_t *buf, uint32_t len);
    int32_t (*WriteData)(struct MyDriverService *service, uint8_t *buf, uint32_t len);
};

你想想看,为什么要有服务对象?直接让上层调用驱动函数不行吗?嗯,不行。因为内核态和用户态是隔离的,不能直接调用。服务对象就是这座“桥”。

个人经验:设计服务接口时,尽量保持简单。我见过有人把几十个功能都塞到一个服务对象里,结果接口文档比驱动代码还长。其实大部分场景下,读、写、控制(ioctl)三个接口就够了。

2.4 驱动入口与出口函数

每个HDF驱动都有三个关键函数:BindInitRelease。它们就是驱动的“生老病死”。

函数 调用时机 主要职责
Bind 驱动加载时,最先调用 将驱动对象绑定到设备对象,创建服务对象
Init Bind成功后调用 初始化硬件、申请资源、注册中断
Release 驱动卸载时调用 释放资源、注销中断、清理内存

这三个函数的调用顺序是固定的:Bind → Init → Release。如果Bind失败,Init不会被调用。如果Init失败,Release会被调用来做清理。

static int32_t MyDriverBind(struct HdfDeviceObject *deviceObject)
{
    // 创建服务对象并挂到deviceObject上
    struct MyDriverService *service = OsalMemAlloc(sizeof(*service));
    if (service == NULL) return HDF_ERR_MALLOC_FAIL;
    
    service->ReadData = MyDriverRead;
    service->WriteData = MyDriverWrite;
    deviceObject->service = &service->base;
    
    return HDF_SUCCESS;
}

static int32_t MyDriverInit(struct HdfDeviceObject *deviceObject)
{
    // 初始化硬件,比如配置GPIO、使能时钟
    // 从HCS读取资源
    struct DeviceResourceIface *resource = deviceObject->resource;
    uint32_t gpioNum;
    resource->GetUint32(resource, "gpio_num", &gpioNum, 0);
    
    // 注册中断等操作
    return HDF_SUCCESS;
}

static void MyDriverRelease(struct HdfDeviceObject *deviceObject)
{
    // 释放服务对象
    struct MyDriverService *service = (struct MyDriverService *)deviceObject->service;
    if (service != NULL) {
        OsalMemFree(service);
        deviceObject->service = NULL;
    }
}

关键点:Bind里只做“轻量级”工作,比如分配内存、创建服务对象。Init里做“重量级”工作,比如操作硬件寄存器。这样设计的好处是:如果硬件初始化失败,Bind已经分配的资源可以在Release里统一释放,不会遗漏。

2.5 三者关系图

为了让你更直观地理解这三个对象的关系,我画了一张图:

驱动对象 HdfDriverEntry moduleName: "my_driver" Bind / Init / Release 设备对象 HdfDeviceObject resource: HCS配置 priv: 私有数据 服务对象 IDeviceIoService ReadData() WriteData() Bind service Init/Release操作 用户态应用程序 通过服务接口调用

从这张图可以看得很清楚:驱动对象通过Bind关联到设备对象,设备对象持有服务对象的指针,用户态程序通过服务对象来操作硬件。三者各司其职,缺一不可。

2.6 小结

嗯,这一章的内容就这些。总结一下:

  • 驱动对象:驱动的“身份证”,告诉框架我是谁
  • 设备对象:硬件的“代言人”,装着资源和私有数据
  • 服务对象:驱动的“窗口”,让上层能调用底层功能
  • 入口/出口函数:驱动的生命周期管理,Bind→Init→Release

我个人觉得,理解这三个对象的关系,是写好HDF驱动的第一步。刚开始可能会觉得抽象,但多写几个驱动就自然明白了。就像学开车,一开始觉得离合、油门、刹车配合好难,开多了就变成肌肉记忆了。

小技巧:调试时可以在Bind和Init里加打印日志,观察调用顺序。我经常用 HDF_LOGI("Bind called for %s", deviceObject->serviceName); 来确认流程是否正确。


公众号:蓝海资料掘金营,微信deep3321