4. Linux 驱动基础回顾:字符设备驱动框架、platform驱动、设备树匹配机制

在将驱动从 Linux 移植到 TrustZone 安全环境之前,必须牢固掌握 Linux 内核驱动的三大核心基础:字符设备驱动框架、platform 驱动模型以及设备树(Device Tree)匹配机制。这三者构成了 Linux 驱动与硬件解耦、动态匹配的基石,也是安全驱动移植中需要重新实现或适配的关键部分。

核心观点:Linux 驱动通过“设备-总线-驱动”三层模型实现硬件与驱动的分离;设备树提供硬件描述,platform 总线负责匹配,字符设备框架提供用户空间访问接口。TrustZone 环境通常需要将这三层逻辑重新实现在安全世界(Secure World)中。

4.1 字符设备驱动框架

字符设备是 Linux 中最基本的设备类型,以字节流方式访问,典型代表包括串口、GPIO、I2C 控制器等。字符设备驱动的核心是 struct file_operations 结构体,它定义了用户空间通过系统调用(open、read、write、ioctl 等)访问设备的具体实现。

4.1.1 核心数据结构

数据结构 作用 关键字段
struct cdev 内核中表示字符设备的对象 ops(file_operations 指针)、dev(设备号)
struct file_operations 定义设备操作函数表 open、release、read、write、unlocked_ioctl
dev_t 设备号(主设备号 + 次设备号) MAJOR(dev)、MINOR(dev)

4.1.2 字符设备注册流程

  1. 分配设备号:使用 alloc_chrdev_region() 动态分配,或 register_chrdev_region() 静态指定。
  2. 初始化 cdev:调用 cdev_init() 绑定 file_operations。
  3. 添加设备:调用 cdev_add() 将设备注册到内核。
  4. 创建设备节点:通过 device_create() 在 sysfs 中生成设备文件。
// 字符设备驱动典型框架代码
static int __init my_driver_init(void)
{
    dev_t dev_num;
    struct device *dev;

    // 1. 分配设备号
    alloc_chrdev_region(&dev_num, 0, 1, "my_device");

    // 2. 初始化 cdev
    cdev_init(&my_cdev, &my_fops);
    my_cdev.owner = THIS_MODULE;

    // 3. 添加设备
    cdev_add(&my_cdev, dev_num, 1);

    // 4. 创建设备节点
    dev = device_create(my_class, NULL, dev_num, NULL, "my_device");

    return 0;
}
module_init(my_driver_init);
移植要点:在 TrustZone 安全世界中,没有 Linux 的 VFS 层和 sysfs。字符设备的“设备节点”概念需要替换为安全监控调用(SMC)或共享内存通信机制。但 file_operations 中的操作语义(如 read/write/ioctl)可以直接映射为安全服务的 API 接口。
字符设备驱动框架结构 用户空间应用程序 系统调用层 (VFS) file_operations struct cdev 硬件设备 用户空间通过设备节点 访问内核驱动

4.2 platform 驱动模型

platform 驱动是 Linux 内核中用于管理非即插即用设备(如 SoC 内部外设、中断控制器、GPIO 控制器)的虚拟总线模型。它通过 platform_driver 和 platform_device 的匹配机制,将驱动代码与硬件描述分离。

4.2.1 核心数据结构

结构体 职责 关键字段
struct platform_driver 描述驱动逻辑,包含 probe/remove 回调 driver.name、probe、remove、id_table
struct platform_device 描述设备硬件资源(内存、中断、DMA) name、id、num_resources、resource
struct resource 描述单个硬件资源(IO 地址、中断号) start、end、flags(IORESOURCE_MEM/IRQ)

4.2.2 匹配机制

platform 总线通过以下顺序匹配设备和驱动:

  1. 名称匹配:比较 platform_driver.driver.name 与 platform_device.name。
  2. ID 表匹配:通过 platform_driver.id_table(struct of_device_id 数组)进行设备树兼容性匹配。
  3. ACPI 匹配:在支持 ACPI 的平台上使用 ACPI 表匹配。
// platform 驱动典型框架
static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);

static int my_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *base;

    // 获取 IO 内存资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    base = devm_ioremap_resource(&pdev->dev, res);

    // 获取中断资源
    int irq = platform_get_irq(pdev, 0);

    return 0;
}

static struct platform_driver my_driver = {
    .probe  = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_device",
        .of_match_table = my_of_match,
    },
};
module_platform_driver(my_driver);
移植陷阱:在 TrustZone 安全世界中,没有 platform 总线机制。驱动需要直接操作硬件寄存器,无法依赖 platform_get_resource 等 API。移植时需要将资源获取逻辑改为直接访问安全内存映射区域,并通过安全监控器(Monitor)管理中断。
platform 驱动匹配与 probe 流程 设备树节点 (DT Node) platform_device platform 总线匹配 platform_driver probe() 回调执行 匹配依据:compatible 属性 或 driver.name 字段 匹配成功则调用 probe()

4.3 设备树(Device Tree)匹配机制

设备树是一种描述硬件拓扑结构的数据结构,以 .dts 文件形式存在,编译为 .dtb 二进制文件后由 bootloader 传递给内核。设备树的核心作用是让内核无需硬编码硬件信息,通过解析树状节点自动发现设备。

4.3.1 设备树关键语法

语法元素 示例 说明
compatible "vendor,device" 驱动匹配的核心标识,格式为“厂商,设备型号”
reg <0x10000000 0x1000> 描述设备地址空间(起始地址 + 长度)
interrupts <0 31 4> 描述中断号、触发类型等
status "okay" / "disabled" 控制设备是否启用

4.3.2 匹配流程

当内核启动时,设备树解析流程如下:

  1. Bootloader 加载 DTB:将设备树二进制文件放入内存,传递地址给内核。
  2. 内核解析 DTB:调用 unflatten_device_tree() 将扁平设备树转换为树状结构。
  3. 设备注册:遍历设备树节点,为每个 compatible 属性匹配的节点创建 platform_device。
  4. 驱动匹配:platform 总线根据 of_match_table 查找匹配的驱动,调用 probe()。
// 设备树节点示例(.dts 文件)
/ {
    soc {
        my_device: my-device@10000000 {
            compatible = "vendor,my-device";
            reg = <0x10000000 0x1000>;
            interrupts = <0 31 4>;
            status = "okay";
        };
    };
};

// 驱动中对应的 of_match_table
static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
移植要点:在 TrustZone 环境中,设备树通常由安全启动固件(如 OP-TEE 的 DTS)管理。安全驱动需要从安全设备树中解析硬件资源,但解析逻辑必须精简,避免引入 Linux 庞大的设备树库。常见做法是使用扁平设备树(FDT)的轻量级解析库,如 libfdt。
设备树匹配与驱动加载流程 DTS 源文件 DTC 编译 DTB 二进制 Bootloader 加载 内核解析 DTB platform_device 注册 驱动匹配与 probe() 匹配核心:驱动中的 of_match_table 与设备树节点的 compatible 属性进行字符串匹配 匹配成功后,内核调用驱动的 probe() 函数,传入 platform_device 指针

4.4 三者的关系与移植映射

字符设备、platform 驱动、设备树三者协同工作,构成了 Linux 驱动从硬件描述到用户空间访问的完整链路。下表总结了它们在 Linux 中的角色以及在 TrustZone 环境中的移植映射:

组件 Linux 角色 TrustZone 移植映射
设备树 硬件拓扑描述,提供资源信息 使用安全固件 DTS 或硬编码资源表
platform 驱动 设备-驱动匹配与资源获取 替换为安全世界的手动资源绑定
字符设备框架 用户空间访问接口 替换为 SMC 调用或共享内存通信
file_operations 系统调用到驱动的映射 映射为安全服务的函数指针表