Linux 原生驱动迁移到 OpenHarmony/HDF 的理解

1. 这个问题到底在问什么

这个问题不是简单问“怎么把 Linux 驱动的 .c 文件拿到 OpenHarmony 里编译通过”。

面试官真正想考察的是:

  • 你是否能判断原来的 Linux 驱动应该放在 OpenHarmony 的哪一层。
  • 你是否理解 HDF 的驱动模型。
  • 你是否知道 Linux 驱动和 OpenHarmony/HDF 驱动之间有哪些系统差异。
  • 你是否能处理配置、入口、服务接口、权限、编译和产品裁剪等问题。

一句话理解:

这题考的是“驱动架构迁移能力”,不是“改 Makefile 能力”。

2. 迁移前先确认驱动所在层级

迁移前不能一上来就复制代码,而是要先判断驱动属于哪一类。

常见层级包括:

  • Linux 内核原生驱动
  • 用户态 HAL
  • HDF kernel driver
  • HDF user driver

原 Linux 驱动一般是:

应用
  |
/dev/xxx、sysfs、ioctl
  |
Linux kernel driver
  |
硬件

OpenHarmony/HDF 中可能是:

应用 / 系统服务
  |
HDI / HAL 接口
  |
HDF user driver 或 HDF kernel driver
  |
内核 / 硬件

所以迁移前要先问几个问题:

  • 这个驱动是否必须运行在内核态?
  • 目标 OpenHarmony 系统底层是 Linux 内核,还是 LiteOS 等轻量内核?
  • 上层是继续通过 /dev/xxxioctl 访问,还是通过 HDF/HDI 服务访问?
  • 原驱动是否已经很成熟,是否值得保留?
  • 这个驱动是否适合按 HDF 模型重写?

3. 成熟 Linux 驱动不一定要重写

如果原来的 Linux 驱动很成熟,而且目标 OpenHarmony 产品底层仍然是 Linux 内核,通常可以保留原驱动。

例如:

  • Wi-Fi
  • GPU
  • USB
  • 摄像头
  • 某些复杂外设驱动

这类驱动可以采用:

保留 Linux kernel driver
  +
增加适配层 / HAL / HDF 服务接口

也就是说:

  • 底层驱动仍然使用 Linux 原来的注册方式。
  • 上层通过适配层把能力暴露给 OpenHarmony 系统服务或应用。
  • 不一定要把成熟驱动完全改写成 HDF 驱动。

这类迁移重点通常是:

  • 调整内核配置
  • 适配设备树
  • 适配编译系统
  • 适配设备节点权限
  • 适配 HAL/HDI/HDF 服务接口
  • 确认产品配置中启用了相关功能

4. 新驱动可以按 HDF 模型开发

如果是新驱动,或者是比较简单的平台外设驱动,例如:

  • GPIO 外设
  • I2C 传感器
  • SPI 设备
  • 简单字符设备
  • 板级外设控制驱动

这类驱动可以按 HDF 模型来开发。

HDF 驱动通常要处理以下内容:

HDF 配置
HDF 驱动入口
Bind / Init / Release
服务接口
Dispatch / ioctl-like 调用
OSAL 适配

下面逐个解释。

5. HDF 配置

HDF 配置主要用于告诉系统:

  • 这个驱动叫什么
  • 什么时候加载
  • 是否对外发布服务
  • 服务名是什么
  • 权限是什么
  • 硬件私有参数在哪里

常见配置包括:

device_info.hcs       描述驱动怎么被 HDF 加载
xxx_config.hcs        描述硬件自己的私有参数

device_info.hcs 中常见字段包括:

hostName              驱动属于哪个 host
moduleName            驱动模块名
serviceName           对外发布的服务名
policy                服务是否对外发布
preload               是否开机预加载
priority              加载优先级
permission            设备节点权限
deviceMatchAttr       匹配私有配置

最重要的匹配关系是:

moduleName 要和 HdfDriverEntry 里的 moduleName 对上
deviceMatchAttr 要和私有配置里的 match_attr 对上
serviceName 是上层查找服务时使用的名字

通俗理解:

HDF 配置就像“驱动身份证 + 加载说明书”。系统启动时先看配置,知道这个驱动叫什么、怎么加载、是否发布服务、权限是什么、硬件参数在哪里。

示例:

moduleName = "sample_driver";
serviceName = "sample_service";
deviceMatchAttr = "sample_config";

意思是:

系统要加载一个叫 sample_driver 的驱动,它对外服务名叫 sample_service,硬件参数去找 sample_config

6. HDF 驱动入口

Linux 驱动常见入口是:

module_init(xxx_init);
module_exit(xxx_exit);

HDF 驱动通常使用:

struct HdfDriverEntry g_sampleDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "sample_driver",
    .Bind = SampleDriverBind,
    .Init = SampleDriverInit,
    .Release = SampleDriverRelease,
};

HDF_INIT(g_sampleDriverEntry);

其中:

  • moduleVersion 表示驱动版本。
  • moduleName 表示驱动模块名,需要和 HDF 配置里的 moduleName 对上。
  • Bind 用于绑定服务接口。
  • Init 用于初始化驱动和硬件。
  • Release 用于释放资源。
  • HDF_INIT 用于把这个驱动入口注册到 HDF 框架。

通俗理解:

Linux 驱动是自己通过 module_initmodule_exit 注册模块入口;HDF 驱动是把入口结构交给 HDF 框架,由框架根据配置来调用。

7. Bind / Init / Release

7.1 Bind

Bind 主要负责把驱动的服务接口挂到 HDF 框架中。

示例:

int32_t SampleDriverBind(struct HdfDeviceObject *deviceObject)
{
    deviceObject->service = &g_sampleService.ioService;
    return HDF_SUCCESS;
}

它通常做的事情:

  • 创建或获取驱动服务对象。
  • 把服务对象挂到 deviceObject->service
  • 让 HDF 框架知道这个驱动可以被上层访问。

通俗理解:

Bind 就是“登记窗口”。告诉 HDF:以后别人找这个驱动服务,就通过这个接口来找。

7.2 Init

Init 是真正初始化驱动和硬件的地方。

示例:

int32_t SampleDriverInit(struct HdfDeviceObject *deviceObject)
{
    // 读取 HCS 配置
    // 申请内存
    // 初始化 I2C/SPI/GPIO 等总线资源
    // 注册中断
    // 初始化芯片寄存器
    return HDF_SUCCESS;
}

它通常做的事情:

  • 读取 HCS 私有配置。
  • 解析 GPIO、I2C、SPI、中断号等硬件参数。
  • 申请内存。
  • 初始化锁、线程、队列等软件资源。
  • 初始化硬件。
  • 注册中断。
  • 检查芯片 ID。
  • 设置驱动初始状态。

通俗理解:

Init 就是“正式开工”。比如给传感器上电、读 chip id、初始化寄存器、准备中断和数据结构。

7.3 Release

Release 负责释放资源。

示例:

void SampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
    // 注销中断
    // 停止线程
    // 释放内存
    // 销毁锁
    // 关闭硬件资源
}

它通常做的事情:

  • 注销中断。
  • 停止线程。
  • 释放内存。
  • 销毁锁。
  • 关闭 GPIO、I2C、SPI 等资源。
  • 清理服务对象。
  • 处理初始化失败后的回收。

通俗理解:

Release 就是“收摊”。前面 Init 申请了什么资源,这里就要按相反顺序还回去。

8. 服务接口

HDF 驱动如果要给上层调用,需要定义服务接口。

示例:

struct ISampleService {
    struct IDeviceIoService ioService;
    int32_t (*ReadData)(void);
    int32_t (*SetMode)(uint32_t mode);
};

服务接口的作用是:

  • 定义驱动对外提供哪些能力。
  • 屏蔽驱动内部实现。
  • 让上层通过统一接口访问驱动。

例如一个传感器驱动可以提供:

ReadData        读取传感器数据
SetMode         设置工作模式
Enable          使能设备
Disable         关闭设备
SetRate         设置采样率

通俗理解:

服务接口就像“菜单”。驱动告诉上层:我支持读数据、设置模式、打开、关闭,但你只能按我提供的接口来调用。

9. Dispatch / ioctl-like 调用

Linux 驱动里经常通过 ioctl 处理不同命令:

ioctl(fd, CMD_SET_MODE, arg);

HDF 中常见的方式是通过 Dispatch 处理命令。

基本思路是:

上层发送 command id
参数放到 HdfSBuf
驱动 Dispatch 收到命令
根据 cmd 分发到具体处理函数
结果写回 reply

伪代码:

static int32_t SampleDispatch(
    struct HdfDeviceIoClient *client,
    int32_t cmd,
    struct HdfSBuf *data,
    struct HdfSBuf *reply)
{
    switch (cmd) {
        case CMD_READ_DATA:
            return SampleReadData(data, reply);
        case CMD_SET_MODE:
            return SampleSetMode(data);
        default:
            return HDF_ERR_NOT_SUPPORT;
    }
}

通俗理解:

Dispatch 就是“总接线员”。上层说我要执行 1 号命令、2 号命令,Dispatch 根据命令号转给真正的处理函数。

需要注意:

  • cmd 命令号要定义清楚,不能随意变化。
  • datareply 要检查是否为空。
  • 要检查参数长度和类型。
  • 用户态传来的参数不能直接信任。
  • 非法命令要返回错误。
  • 涉及敏感操作时要做权限校验。

10. OSAL 适配

OSAL 是 Operating System Abstraction Layer,也就是操作系统抽象层。

Linux 驱动中可能直接使用:

kmalloc
kfree
mutex
spinlock
msleep
request_irq
kthread
workqueue
copy_from_user
copy_to_user

HDF 驱动中通常尽量使用 OSAL 提供的统一接口,例如:

OsalMemCalloc
OsalMemFree
OsalMutex
OsalSpinlock
OsalMSleep
OsalThread
OsalRegisterIrq

OSAL 的作用是:

  • 屏蔽不同内核之间的差异。
  • 提高驱动在不同 OpenHarmony 系统形态中的可迁移性。
  • 避免驱动强依赖 Linux 内核 API。

通俗理解:

OSAL 就像“统一插座转换器”。底层可能是 Linux,也可能是 LiteOS,驱动尽量不要直接依赖某个内核的接口,而是通过 HDF 提供的统一接口操作。

除了 OSAL,还有 PAL。

PAL 是 Platform Abstraction Layer,也就是平台抽象层,常见于:

GPIO
I2C
SPI
UART
PWM
RTC
Watchdog

例如写 I2C 传感器驱动时,应尽量通过 HDF/PAL 提供的统一 I2C 接口访问设备,而不是写死某个平台私有的 I2C API。

11. 迁移时常见风险点

迁移过程中要重点检查:

  • OSAL 接口是否适配。
  • 锁机制是否适配。
  • 内存申请和释放是否正确。
  • 线程和工作队列是否需要重写。
  • 中断注册和处理方式是否兼容。
  • 设备节点是否仍然需要保留。
  • 设备节点权限是否正确。
  • 用户态和内核态数据传递是否安全。
  • 编译系统是否从 Makefile/Kconfig 迁移到对应的 GN/产品配置。
  • 产品裁剪配置中是否启用了该驱动。
  • HAL/HDI/HDF 服务名是否和上层调用一致。
  • 初始化失败时是否能正确释放资源。

风险信号:

如果认为迁移只是复制 .c 文件、改一下 Makefile,就说明没有理解 OpenHarmony/HDF 的驱动模型。

12. 面试表达版本

可以这样回答:

如果把 Linux 原生驱动迁移到 OpenHarmony/HDF,我不会一开始就直接复制代码。首先我会确认驱动所在层级,判断它是继续作为 Linux kernel driver 保留,还是改造成 HDF kernel driver、HDF user driver,或者只需要做 HAL/HDI 适配。

如果原来的 Linux 驱动已经很成熟,并且目标系统仍然基于 Linux 内核,我倾向于保留核心驱动,只做设备树、编译配置、权限和上层服务接口适配。这样风险更小,也能复用原来稳定的驱动逻辑。

如果是新驱动,或者需要纳入 HDF 框架统一管理,我会按 HDF 模型来实现。先写 HCS 配置,描述驱动加载信息、服务名、权限、加载优先级和硬件私有参数。然后实现 HdfDriverEntry,通过 BindInitRelease 替代 Linux 驱动中 module_initmodule_exit 的思路。

Bind 负责把服务接口挂到 HDF,Init 负责读取配置、申请资源、初始化总线和硬件、注册中断,Release 负责释放资源。对外调用可以通过服务接口或 Dispatch 机制完成,Dispatch 可以理解为 HDF 版的 ioctl,通过命令号和 HdfSBuf 传递参数。

迁移过程中还要重点处理 OSAL、锁、内存、线程、中断、设备节点、权限、编译依赖和产品裁剪配置,不能只停留在复制 .c 文件和修改 Makefile。

13. 最短记忆版

HCS 配置:
告诉系统驱动叫什么、怎么加载、是否发布服务、权限是什么、硬件参数在哪里。

DriverEntry:
告诉 HDF 驱动入口在哪里。

Bind:
把服务接口挂出去。

Init:
初始化硬件和软件资源。

Release:
释放资源。

Service:
定义上层能调用哪些能力。

Dispatch:
按命令号处理请求,类似 ioctl。

OSAL/PAL:
把 Linux API 换成 OpenHarmony/HDF 的统一抽象接口。

14. 一句话总结

Linux 驱动迁移到 OpenHarmony/HDF,本质是把原来围绕 Linux 内核接口组织的驱动,改造成符合 OpenHarmony 分层、HDF 配置、服务发布和 OSAL/PAL 抽象的驱动形态。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐