3. Platform设备注册:设备树节点解析、platform_device结构体、设备注册与注销API
Platform设备注册是驱动与设备绑定的起点。在设备树(Device Tree)成为主流平台描述方式后,内核通过解析设备树节点自动生成platform_device实例,驱动开发者只需关注匹配与探测逻辑。本节深入剖析设备树节点到platform_device的映射过程、核心结构体字段含义以及注册/注销API的使用规范。
3.1 设备树节点解析与platform_device生成
内核在启动阶段遍历设备树中所有compatible属性匹配的节点,为每个节点创建一个platform_device。解析流程涉及以下关键步骤:
- OF(Open Firmware)核心层:读取设备树二进制(DTB),构建
device_node树。 - 平台总线(platform bus)初始化:调用
of_platform_populate()遍历根节点下的子节点。 - 资源提取:从
reg、interrupts、dmas等属性中提取struct resource数组。 - 设备创建:分配
platform_device,填充name、id、num_resources、resource指针,并关联dev.of_node。
关键点:设备树中的
reg属性直接映射为IORESOURCE_MEM类型资源;interrupts映射为IORESOURCE_IRQ;dmas映射为IORESOURCE_DMA。每个资源条目对应一个struct resource。
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()回调。
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);,可快速确认设备注册状态和资源数量。