3. Platform设备注册

Platform设备注册是驱动与设备绑定的起点。在设备树(Device Tree)成为主流平台描述方式后,内核通过解析设备树节点自动生成platform_device实例,驱动开发者只需关注匹配与探测逻辑。本节深入剖析设备树节点到platform_device的映射过程、核心结构体字段含义以及注册/注销API的使用规范。

3.1 设备树节点解析与platform_device生成

内核在启动阶段遍历设备树中所有compatible属性匹配的节点,为每个节点创建一个platform_device。解析流程涉及以下关键步骤:

  1. OF(Open Firmware)核心层:读取设备树二进制(DTB),构建device_node树。
  2. 平台总线(platform bus)初始化:调用of_platform_populate()遍历根节点下的子节点。
  3. 资源提取:从reg、interrupts、dmas等属性中提取struct resource数组。
  4. 设备创建:分配platform_device,填充name、id、num_resources、resource指针,并关联dev.of_node。
关键点:设备树中的reg属性直接映射为IORESOURCE_MEM类型资源;interrupts映射为IORESOURCE_IRQ;dmas映射为IORESOURCE_DMA。每个资源条目对应一个struct resource。
设备树节点解析为platform_device流程图 设备树节点 → platform_device 解析流程 DTB 二进制 of_platform_populate() 资源提取 (reg/irq/dma) platform_device 说明: • 每个设备树节点(compatible匹配)生成一个 platform_device。 • reg 属性 → IORESOURCE_MEM;interrupts → IORESOURCE_IRQ;dmas → IORESOURCE_DMA。 device_node of_match_table匹配 填充 platform_device

3.2 platform_device结构体深度解析

platform_device是平台设备在内核中的核心表示,定义在<linux/platform_device.h>。其关键字段如下表所示:

字段 类型 说明
name const char * 设备名称,用于与驱动id_table或name字段匹配
id int 设备实例编号,-1表示单实例设备;多实例时自动递增
dev struct device 通用设备结构体,包含of_node、release回调等
num_resources u32 资源数组长度
resource struct resource * 指向资源数组的指针,包含内存、IRQ、DMA等资源
id_entry const struct platform_device_id * 匹配成功的id_table条目(非设备树匹配时使用)
driver_override const char * 强制绑定到指定驱动名称(调试或特殊场景使用)
实践建议:在驱动probe()中,通过platform_get_resource()系列函数获取资源,而非直接访问pdev->resource数组。这保证了代码的可移植性和资源类型安全。

3.3 设备注册与注销API

内核提供两套API用于platform_device的注册与注销:一套用于手动创建设备(非设备树场景),另一套用于设备树自动生成的设备。驱动开发者通常只需关注probe()/remove()回调,但理解底层API有助于调试和特殊场景开发。

3.3.1 核心API函数

函数 原型 说明
platform_device_register int platform_device_register(struct platform_device *pdev) 注册一个已初始化的platform_device到内核
platform_device_unregister void platform_device_unregister(struct platform_device *pdev) 注销设备并释放资源
platform_device_alloc struct platform_device *platform_device_alloc(const char *name, int id) 动态分配platform_device结构体
platform_device_add int platform_device_add(struct platform_device *pdev) 将分配好的设备添加到系统(含资源添加)
platform_device_add_resources int platform_device_add_resources(struct platform_device *pdev, const struct resource *res, unsigned int num) 为设备添加资源数组
platform_device_del void platform_device_del(struct platform_device *pdev) 从系统删除设备(不释放结构体)
platform_device_put void platform_device_put(struct platform_device *pdev) 释放设备结构体引用计数

3.3.2 典型注册流程(非设备树场景)

当设备不由设备树描述时(例如旧板级文件或ACPI),驱动需要手动创建并注册platform_device:

// 1. 分配 platform_device 结构体
struct platform_device *pdev;
pdev = platform_device_alloc("my_device", -1);
if (!pdev)
    return -ENOMEM;

// 2. 定义资源(内存、IRQ等)
struct resource res[] = {
    [0] = DEFINE_RES_MEM(0x10000000, 0x1000),
    [1] = DEFINE_RES_IRQ(IRQ_LINE_1),
};

// 3. 添加资源到设备
platform_device_add_resources(pdev, res, ARRAY_SIZE(res));

// 4. 注册设备到内核
ret = platform_device_add(pdev);
if (ret) {
    platform_device_put(pdev);
    return ret;
}

// ... 使用设备 ...

// 5. 注销设备
platform_device_unregister(pdev);
注意:platform_device_register()是platform_device_add()的封装,但前者要求设备已完全初始化(含资源)。推荐使用platform_device_alloc() + platform_device_add_resources() + platform_device_add()三步法,更灵活且易于错误处理。

3.3.3 设备树场景下的自动注册

对于设备树节点,内核在of_platform_populate()中自动完成注册,驱动无需手动调用注册API。设备树节点中的compatible属性与驱动of_match_table匹配成功后,内核自动调用驱动的probe()回调。

platform_device 注册与注销生命周期 platform_device 注册与注销生命周期 platform_device_alloc platform_device_add_resources platform_device_add 设备使用 (probe) platform_device_unregister 设备树场景:of_platform_populate() 自动完成上述流程,驱动无需手动调用注册API。

3.4 设备树节点与platform_device的关联

在驱动probe()中,通过struct platform_device *pdev参数可以反向获取对应的设备树节点:

static int my_driver_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;  // 获取设备树节点

    if (!np) {
        dev_err(dev, "No device tree node found\n");
        return -ENODEV;
    }

    // 读取设备树属性示例
    u32 dma_mask;
    if (of_property_read_u32(np, "dma-mask", &dma_mask) == 0) {
        dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_mask));
    }

    // 获取DMA资源(第0个DMA通道)
    struct resource *dma_res;
    dma_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
    if (dma_res) {
        dev_info(dev, "DMA channel: %llx\n", dma_res->start);
    }

    return 0;
}
最佳实践:在设备树驱动的probe()中,优先使用of_property_read_*系列函数读取设备树自定义属性,使用platform_get_resource获取标准资源(内存、IRQ、DMA)。避免混合使用两种方式读取同一信息,防止资源冲突。

3.5 常见错误与调试技巧

  • 设备未注册:检查设备树节点status属性是否为"okay";确认compatible字符串与驱动of_match_table完全匹配(包括大小写)。
  • 资源获取失败:使用platform_get_resource返回NULL时,检查设备树中reg、interrupts属性格式是否正确;多资源场景注意索引号从0开始。
  • 设备树节点重复:同一compatible的多个节点会生成多个platform_device,驱动需通过id字段区分实例。
  • 调试命令:在用户空间使用ls /sys/devices/platform/查看已注册的平台设备;使用dtc -I fs -O dts /sys/firmware/devicetree/base反编译当前设备树。
调试提示:在驱动probe()中加入dev_info(dev, "probe: name=%s, id=%d, num_resources=%d\n", pdev->name, pdev->id, pdev->num_resources);,可快速确认设备注册状态和资源数量。