3、HDF核心概念:驱动对象、设备对象、服务接口

好,咱们今天来聊聊HDF框架里最核心的三个概念。说实话,我刚接触HDF那会儿,也被这三个东西绕得有点晕——驱动对象、设备对象、服务接口,听起来都差不多,到底谁管谁?

我个人的理解是:驱动对象是“人”,设备对象是“工具”,服务接口是“操作手册”。你想想看,一个工人(驱动)要操作一台机器(设备),总得知道怎么按按钮(接口)吧?HDF就是帮你把这套逻辑理清楚。

3.1 驱动对象(DriverObject)——驱动的“身份证”

驱动对象,说白了就是驱动在HDF框架里的“户口本”。每个驱动加载到系统里,都会对应一个DriverObject结构体。它记录了驱动的名字、初始化函数、绑定函数、以及它要管理哪些设备。

我记得第一次写HDF驱动时,上来就写BindInit,结果忘了注册驱动对象,系统根本找不到我的驱动。嗯,这里要注意:没有DriverObject,驱动就是个“黑户”

核心结构体定义(简化版):

struct DriverObject {
    const char *driverName;        // 驱动名称,必须唯一
    int32_t (*Bind)(struct HdfDeviceObject *device);   // 绑定设备
    int32_t (*Init)(struct HdfDeviceObject *device);   // 初始化设备
    void (*Release)(struct HdfDeviceObject *device);   // 释放设备
    struct DeviceObject **deviceObjects; // 管理的设备对象列表
    uint32_t deviceCount;           // 设备数量
};

你看,这个结构体里最关键的就是三个函数指针:BindInitRelease。它们构成了驱动的生命周期。

  • Bind:驱动和设备“配对”的地方。我习惯在这里做资源预分配。
  • Init:真正的初始化工作,比如注册中断、申请内存。
  • Release:卸载时清理,千万别漏了,否则内存泄漏等着你。

避坑指南:我曾经在Init里申请了DMA缓冲区,但在Release里忘了释放。结果驱动反复加载卸载几次后,系统直接OOM。所以我的习惯是:在写Init的同时,就把Release的对应清理代码写好,成对出现,不容易漏。

3.2 设备对象(DeviceObject)——硬件的“影子”

设备对象,就是硬件设备在软件层的“影子”。每个物理设备(比如一个I2C传感器、一个GPIO按键)在HDF里都对应一个DeviceObject

你可能会问:驱动对象和设备对象,到底谁是谁?我打个比方:

  • 驱动对象 = 一个万能遥控器(可以控制多种设备)
  • 设备对象 = 你家里的电视机、空调、音响(每个设备有自己的属性)

一个驱动对象可以管理多个设备对象。比如一个“通用GPIO驱动”可以控制板子上的十几个GPIO引脚,每个引脚就是一个设备对象。

设备对象结构体:

struct DeviceObject {
    struct DriverObject *driver;     // 所属的驱动对象
    struct HdfDeviceObject *property; // 设备属性(来自HCS配置)
    uint32_t deviceId;              // 设备ID,用于区分同类型设备
    void *priv;                     // 私有数据,我通常放硬件寄存器基址
};

这里有个关键点:priv指针。它是个万能口袋,你可以把任何硬件相关的数据塞进去。我一般用它来存放:

  • 寄存器基地址
  • 中断号
  • 设备状态标志
  • 自定义的锁或信号量

注意:设备对象的创建和销毁,通常由HDF框架自动完成。你只需要在HCS配置文件中描述好设备树,框架会帮你生成对应的DeviceObject。千万别手动去new一个DeviceObject,否则框架管理会乱套。

3.3 服务接口(ServiceInterface)——驱动的“API手册”

服务接口,就是驱动对外暴露的功能函数。上层应用(比如一个APP)想控制硬件,不能直接操作寄存器,必须通过服务接口。

我刚开始做驱动时,总觉得服务接口就是“把寄存器读写封装一下”。后来踩了坑才发现,服务接口的设计直接决定了驱动的易用性和安全性

HDF里定义服务接口的方式很灵活,我常用的是通过HdfDeviceObjectservice指针来暴露

// 定义服务接口结构体
struct GpioService {
    int32_t (*SetDir)(uint32_t gpio, uint16_t dir);
    int32_t (*Write)(uint32_t gpio, uint16_t val);
    int32_t (*Read)(uint32_t gpio, uint16_t *val);
};

// 在Bind函数中注册服务
static int32_t GpioBind(struct HdfDeviceObject *device) {
    static struct GpioService service = {
        .SetDir = GpioSetDirImpl,
        .Write = GpioWriteImpl,
        .Read = GpioReadImpl,
    };
    device->service = &service;  // 关键:把服务挂到设备对象上
    return HDF_SUCCESS;
}

上层调用时,只需要通过设备对象拿到service指针,就能调用驱动功能了。这种设计的好处是:接口与实现分离。你换硬件了?只要接口不变,上层代码一行都不用改。

我的经验:设计服务接口时,尽量做到“原子操作”。比如GPIO驱动,我提供SetDirWriteRead三个接口就够了。别搞什么SetDirAndWrite这种组合接口,上层自己会组合。接口越简单,越不容易出错。

3.4 三者关系——一张图说清楚

说了这么多,咱们用一张图来总结三者的关系。我画了个简单的结构图,你看完应该就全明白了:

HDF核心概念关系图 DriverObject 驱动对象(工人) DeviceObject 1 设备对象(工具1) DeviceObject 2 设备对象(工具2) DeviceObject N 设备对象(工具N) ServiceInterface 服务接口(操作手册) 一个驱动管理多个设备 每个设备暴露一组服务接口

从这张图可以清楚看到:

  1. DriverObject 在最上层,它负责管理一个或多个 DeviceObject
  2. 每个 DeviceObject 对应一个具体的硬件设备,它内部通过 ServiceInterface 向上层提供功能。
  3. 上层应用只跟 ServiceInterface 打交道,不关心底层是哪个驱动、哪个设备。

3.5 实战中的一点体会

最后,分享一个我实际项目中的例子。当时做一款智能家居网关,需要同时控制温湿度传感器、继电器、LED指示灯。这三个硬件完全不同,但我只写了一个“通用IO驱动”。

怎么做到的?

  • 驱动对象只有一个:IoDriverObject
  • 设备对象有三个:TempSensorDeviceRelayDeviceLedDevice
  • 每个设备对象暴露的服务接口不同:传感器提供ReadTemp、继电器提供SwitchOn/Off、LED提供SetBrightness

你看,这就是HDF的灵活之处。驱动对象复用,设备对象独立,服务接口按需定制。代码结构清晰,后期维护也方便。

总结一句话:驱动对象是“管理者”,设备对象是“被管理者”,服务接口是“管理工具”。三者各司其职,共同构成了HDF驱动模型的核心骨架。


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