2. HDF驱动模型核心概念

好,咱们今天聊聊HDF驱动模型里最核心的几个概念。说实话,我刚接触HDF那会儿,也被这些对象绕得有点晕。驱动对象、设备对象、服务对象,还有入口出口函数——它们到底是什么关系?

我个人的理解是:驱动对象是“人”,设备对象是“工具”,服务对象是“接口”。你想想看,一个工人(驱动对象)操作一把电钻(设备对象),通过电钻的开关(服务对象)对外提供打孔服务。嗯,这个比喻虽然糙了点,但道理是通的。

2.1 驱动对象(Driver Object)

驱动对象,说白了就是驱动在HDF框架里的“身份证”。每个驱动在加载时,框架都会为它创建一个HdfDriverObject实例。这个对象里存了什么?驱动的名字、入口函数指针、私有数据,还有它绑定的设备列表。

我记得第一次看HDF源码时,发现驱动对象其实是个很轻量的结构体。它不负责具体硬件操作,更像一个“调度中心”。

核心要点:驱动对象是HDF管理驱动的单元。一个驱动对象对应一个.ko文件(或静态编译的驱动模块)。

// 驱动对象的核心结构(简化版)
struct HdfDriverObject {
    const char *driverName;       // 驱动名字
    int (*bind)(struct HdfDeviceObject *device);    // 绑定函数
    int (*init)(struct HdfDeviceObject *device);    // 初始化函数
    void (*release)(struct HdfDeviceObject *device); // 释放函数
    void *priv;                   // 私有数据
};

这里要注意:bindinitrelease这三个函数指针,就是咱们后面要讲的驱动入口和出口函数。它们由驱动开发者实现,HDF框架在合适的时机调用。

2.2 设备对象(Device Object)

设备对象,代表的是“一个具体的硬件设备实例”。比如你的开发板上有两个I2C控制器,那就会有两个设备对象。每个设备对象都挂载在某个驱动对象下面。

我曾经在项目中踩过一个坑:以为一个驱动对象只能对应一个设备对象。后来调试时发现,HDF支持一个驱动管理多个同类型设备。比如GPIO驱动,可以同时控制多组GPIO控制器。每个控制器就是一个独立的设备对象。

我的习惯:在写驱动时,我会把设备对象的私有数据(priv字段)用来存放硬件资源信息,比如寄存器基地址、中断号、时钟句柄等。这样每个设备实例都有自己的“小仓库”。

// 设备对象的核心结构(简化版)
struct HdfDeviceObject {
    struct HdfDriverObject *driver;  // 所属驱动对象
    struct HdfDeviceObject *next;    // 链表指针,用于挂载同驱动下的其他设备
    const char *deviceName;          // 设备名字
    const char *deviceMatchAttr;     // 匹配属性(用于设备树匹配)
    void *priv;                      // 私有数据
    struct HdfServiceObject *service;// 服务对象(后面讲)
};

你可能会问:驱动对象和设备对象是怎么关联起来的?答案是——通过bind函数。在驱动加载时,HDF框架会调用驱动的bind函数,把设备对象和驱动对象“绑”在一起。

2.3 服务对象(Service Object)

服务对象,是驱动对外暴露的“接口层”。用户态程序不能直接操作设备对象,但可以通过服务对象来调用驱动的能力。说白了,服务对象就是驱动和上层应用之间的“翻译官”。

HDF支持两种服务发布方式:

  • 用户态服务:通过HDF的IPC机制,用户态程序可以像调用本地函数一样调用驱动服务
  • 内核态服务:其他内核模块可以直接获取服务对象指针,调用其方法

避坑指南:我曾经在项目里把服务对象设计得过于复杂,一个服务接口塞了十几个函数。后来发现,服务接口应该尽量精简。我的建议是:每个服务对象只暴露5-8个核心接口,复杂的业务逻辑放在上层封装。

// 服务对象的典型定义
struct HdfServiceObject {
    struct HdfDeviceObject *device;  // 所属设备对象
    const char *serviceName;        // 服务名字
    struct HdfObject *object;       // 服务实例(实际的功能实现)
    int (*dispatch)(struct HdfObject *object, int cmd, ...); // 消息分发函数
};

服务对象和设备对象的关系是一对一的。一个设备对象可以发布一个服务对象,但一个服务对象只能属于一个设备对象。嗯,这个关系要记清楚。

2.4 驱动入口与出口函数

每个HDF驱动都必须实现三个关键函数:bindinitrelease。它们就是驱动的“生老病死”管理函数。

函数 调用时机 主要职责 返回值
bind 驱动加载时,最先调用 绑定设备对象,初始化服务对象 成功返回0,失败返回负数
init bind成功后调用 初始化硬件、申请资源、注册中断 成功返回0,失败返回负数
release 驱动卸载时调用 释放资源、注销中断、关闭硬件 无返回值(void)

我个人习惯的写法是:bind里只做“轻量级”工作,比如分配服务对象、设置回调函数。真正的硬件初始化放到init里。为什么?因为bind失败时,驱动还能尝试重新绑定;但init失败时,驱动就直接退出了。把重试逻辑放在bind里更安全。

// 一个典型的驱动入口出口实现
static int MyDriverBind(struct HdfDeviceObject *device)
{
    // 1. 分配私有数据
    device->priv = kzalloc(sizeof(struct MyDevicePriv), GFP_KERNEL);
    if (!device->priv) {
        return -ENOMEM;
    }
    
    // 2. 创建服务对象(可选)
    // 这里只做准备工作,不操作硬件
    return 0;
}

static int MyDriverInit(struct HdfDeviceObject *device)
{
    struct MyDevicePriv *priv = (struct MyDevicePriv *)device->priv;
    
    // 1. 获取硬件资源(寄存器地址、中断号等)
    // 2. 初始化硬件(复位、配置时钟等)
    // 3. 注册中断处理函数
    // 4. 注册服务到HDF框架
    
    return 0;
}

static void MyDriverRelease(struct HdfDeviceObject *device)
{
    struct MyDevicePriv *priv = (struct MyDevicePriv *)device->priv;
    
    // 1. 注销服务
    // 2. 释放中断
    // 3. 关闭硬件
    // 4. 释放私有数据内存
    
    kfree(priv);
    device->priv = NULL;
}

重要提醒:release函数必须能处理“init只执行了一半”的情况。比如init里申请了三个资源,但在申请第二个时失败了,release要能正确释放第一个资源。我曾经因为没处理好这个,导致内存泄漏,排查了两天才找到原因。

2.5 三个对象的关系总结

咱们用一张图来理清关系(嗯,虽然HTML里不能画图,但我用文字描述一下):

  • 驱动对象(HdfDriverObject):管理驱动的生命周期,包含bind/init/release函数指针
  • 设备对象(HdfDeviceObject):代表具体的硬件实例,挂载在驱动对象下
  • 服务对象(HdfServiceObject):对外暴露的接口,挂载在设备对象下

它们的关系是:1个驱动对象 → N个设备对象 → 每个设备对象可挂载0或1个服务对象

你想想看,这种分层设计的好处是什么?驱动对象负责“管理”,设备对象负责“实例”,服务对象负责“通信”。各司其职,互不干扰。我在做项目时,经常利用这种分层来解耦:驱动层只关心硬件操作,服务层只关心接口协议,上层应用只关心业务逻辑。

我的小技巧:在调试时,可以通过/sys/kernel/hdf目录下的文件查看驱动对象、设备对象和服务对象的注册情况。这个目录是HDF框架自动生成的调试接口,非常有用。

好了,这一章的核心概念就讲到这里。下一章咱们会深入bind函数的实现细节,包括如何解析设备树、如何匹配硬件资源。到时候我会分享一个我在项目里遇到的设备树匹配失败的案例,保证让你印象深刻。