平台总线platform 设备注册流程分析
文章目录
前言
之前知识点:
- 了解数据模型
keyObject、keyset;了解总线模型:设备、驱动、总线 - 了解了 注册自己的总线并创建属性文件
- 驱动总线
bus注册流程分析 platform平台总线注册流程分析- 在总线下注册设备及设备注册流程分析
这里 就开始分析platform 平台总线设备注册流程分析。 其实 就是以总线设备、总线驱动、总线为知识点展开理论知识,以platform 平台总线为例展开进一步讨论研究知识点,深化理解。
参考资料
设备模型基本框架-kobject-kset
驱动-设备模型kobject实现属性文件读写功能
驱动-设备模型kobject实现属性文件读写终篇
驱动-注册自己的总线并创建属性文件
驱动-总线bus注册流程分析
platform总线注册流程分析
在总线下注册设备及设备注册流程分析
以前platform 平台总线相关知识点:
驱动-平台总线-platform设备注册platform驱动注册篇
驱动-平台总线-probe
一、platform 总线设备注册
这个实验和基本知识点我们在 驱动-平台总线-platform设备注册platform驱动注册篇 了解过,这里我先把代码直接贴出来,之前知识点里面其实都有的。
platform 总线设备注册 实验
参考 驱动-平台总线-platform设备注册platform驱动注册篇 ,具体带阿米如下
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#define MEM_START_ADDR 0xFDD60000
#define MEM_END_ADDR 0xFDD60004
#define IRQ_NUMBER 101
static struct resource my_resources[] = {
{
.start = MEM_START_ADDR, // 内存资源起始地址
.end = MEM_END_ADDR, // 内存资源结束地址
.flags = IORESOURCE_MEM, // 标记为内存资源
},
{
.start = IRQ_NUMBER, // 中断资源号
.end = IRQ_NUMBER, // 中断资源号
.flags = IORESOURCE_IRQ, // 标记为中断资源
},
};
static void my_platform_device_release(struct device *dev)
{
// 释放资源的回调函数
}
static struct platform_device my_platform_device = {
.name = "my_platform_device", // 设备名称
.id = -1, // 设备ID
.num_resources = ARRAY_SIZE(my_resources), // 资源数量
.resource = my_resources, // 资源数组
.dev.release = my_platform_device_release, // 释放资源的回调函数
};
static int __init my_platform_device_init(void)
{
int ret;
ret = platform_device_register(&my_platform_device); // 注册平台设备
if (ret)
{
printk(KERN_ERR "Failed to register platform device\n");
return ret;
}
printk(KERN_INFO "Platform device registered\n");
return 0;
}
static void __exit my_platform_device_exit(void)
{
platform_device_unregister(&my_platform_device); // 注销平台设备
printk(KERN_INFO "Platform device unregistered\n");
}
module_init(my_platform_device_init);
module_exit(my_platform_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fangchen");
这里我们需要梳理的核心代码就是: ret = platform_device_register(&my_platform_device)
API分析- platform_device_register(&my_platform_device)
直接上源码,如下:
/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
arch_setup_pdev_archdata(pdev);
return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);
如注释说明,这个方法就是把平台总线设备,添加到platform 总线上去
步骤 - device_initialize(&pdev->dev);
这个API 不就是 在总线下注册设备及设备注册流程分析 里面重点分析的 设备初始化部分内容嘛,这里暂不分析。
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset; // 设置设备所属的kset
kobject_init(&dev->kobj, &device_ktype); // 初始化kobject
INIT_LIST_HEAD(&dev->dma_pools); // 初始化DMA池链表
mutex_init(&dev->mutex); // 初始化互斥锁
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock); // 初始化自旋锁
INIT_LIST_HEAD(&dev->devres_head); // 初始化资源链表
device_pm_init(dev); // 初始化电源管理
set_dev_node(dev, -1); // 设置NUMA节点
}
二、核心步骤 platform_device_add
先上源码,供后面内容分析、理解:
// 参考 drivers/base/platform.c
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
// 1. 参数有效性检查
if (!pdev)
return -EINVAL;
// 2. 设置设备的父设备(如果未指定,则默认为平台总线)
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; // 默认父设备是平台总线:cite[3]:cite[5]
// 3. 将设备绑定到平台总线类型
pdev->dev.bus = &platform_bus_type; // 指定设备所属的总线类型:cite[3]:cite[5]
// 4. 根据设备ID设置设备的全名
if (pdev->id != -1)
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); // 例如 "serial0"
else
dev_set_name(&pdev->dev, "%s", pdev->name); // 例如 "my-device":cite[3]:cite[5]
// 5. 遍历并处理设备提供的所有资源(resources)
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i]; // 获取第i个资源
// 如果资源没有名称,使用设备名作为资源名
if (r->name == NULL)
r->name = dev_name(&pdev->dev);
// 为资源寻找合适的父资源树(iomem_resource 或 ioport_resource)
p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM) // 内存资源
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO) // I/O端口资源
p = &ioport_resource;
// 对于IRQ等资源,可能不需要父资源
}
// 将资源插入到全局资源树中,防止冲突
if (p && insert_resource(p, r)) { // 插入资源失败
printk(KERN_ERR "%s: failed to claim resource %d\n",
dev_name(&pdev->dev), i);
ret = -EBUSY;
goto failed; // 跳转到错误处理
}
}
// 6. 最终通过设备模型的核心函数将设备添加到内核
ret = device_add(&pdev->dev); // 这会创建sysfs条目,触发热插拔事件,并尝试绑定驱动:cite[3]:cite[4]:cite[5]
if (ret == 0)
return ret;
failed: // 错误处理路径
// 逆向操作,释放之前已经申请的资源
while (--i >= 0) {
struct resource *r = &pdev->resource[i];
unsigned long type = resource_type(r);
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r); // 释放资源
}
return ret;
}
核心功能
platform_device_add 主要完成以下工作:
- 参数检查:确保传入的
platform_device结构体有效。 - 设置设备父总线和总线类型:若无指定父设备,则默认设置为平台总线(
platform_bus),并将设备总线类型设置为platform_bus_type。 - 设备命名:根据设备ID(
pdev->id)设置设备的名称。 - 资源管理:将设备提供的资源(如内存、I/O、中断等)注册到内核全局资源树中,防止冲突并由内核统一管理。
- 设备添加:最终通过
device_add函数将设备添加到设备模型中,使其对用户空间和驱动程序可见。
函数执行流程
platform_device_add 函数的执行流程大致如下,你可以通过下面的流程图来快速了解其整体运作过程:

下面是流程中几个关键步骤的详细说明:
设置设备父总线和总线类型
若设备的父设备(pdev->dev.parent)未指定,则将其父设备设置为全局变量platform_bus,并将设备的总线类型设置为platform_bus_type,这表明该设备挂载在平台总线上。
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;
我们看看platform_bus 是如何定义的,如下:
struct device platform_bus = {
.init_name = "platform",
};
哈哈,这说明,默认情况下就是绑定到:/system/bus/platorm 下面,当前文件作为父设备目录。
对于 platform_bus_type 理解,可以参考之前文章 驱动-注册自己的总线并创建属性文件
中的 bus_type 结构体详解 内容。
设备命名
根据设备ID(pdev->id)的不同,采用两种方式为设备命名:
- 如果id不是-1,设备名称格式为"name.id"(例如"serial.0")。
- 如果id是-1,则直接使用pdev->name作为设备名称(例如"my-device")。
这个名称是驱动和设备匹配的关键依据之一。
这里首先看一下平台设备注册代码中的platform_device 结构体定义内容:
static struct platform_device my_platform_device = {
.name = "my_platform_device", // 设备名称
.id = -1, // 设备ID
.num_resources = ARRAY_SIZE(my_resources), // 资源数量
.resource = my_resources, // 资源数组
.dev.release = my_platform_device_release, // 释放资源的回调函数
};
static int __init my_platform_device_init(void)
{
int ret;
ret = platform_device_register(&my_platform_device); // 注册平台设备
对于 platform_device 设备定义里面有个id ,设备id 定义。
源码里面是这样设计的,如下:
switch (pdev->id) {
default:
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
break;
case PLATFORM_DEVID_NONE:
dev_set_name(&pdev->dev, "%s", pdev->name);
break;
case PLATFORM_DEVID_AUTO:
/*
* Automatically allocated device ID. We mark it as such so
* that we remember it must be freed, and we append a suffix
* to avoid namespace collision with explicit IDs.
*/
ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
if (ret < 0)
goto err_out;
pdev->id = ret;
pdev->id_auto = true;
dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
break;
}
那么实际结果,可以看看如下:

资源管理(核心步骤)
这是platform_device_add相较于通用device_add的特有且关键步骤3。
-
函数遍历
pdev->resource数组(资源数量由pdev->num_resources指定)。 -
为每个资源(
struct resource *r)设置名称(若未设置,则使用设备名称)。 -
根据资源类型(
flags,如IORESOURCE_MEM, IORESOURCE_IO, IORESOURCE_IRQ)为其指定父资源域(parent),例如iomem_resource用于IORESOURCE_MEM。 -
调用
insert_resource(p, r)将资源插入内核的全局资源树。这一步确保了设备资源的唯一性,避免了资源冲突。如果插入失败(例如资源已被占用),则跳转到错误处理路径。
添加到设备模型
最终,通过调用device_add(&pdev->dev)将设备添加到设备模型层次结构中35。这会:
- 将设备注册到内核的设备列表(
kobject)。 - 在
sysfs中创建对应的设备入口点。 - 触发内核尝试为该设备寻找并绑定匹配的驱动(如果驱动已注册,则会调用驱动的probe函数)。
对于添加逻辑可以参考:在总线下注册设备及设备注册流程分析 中的 device_add 添加设备到系统里面去 知识点
关键数据结构
要理解这些函数,需要了解它们操作的核心结构体: 之前 驱动-平台总线-platform设备注册platform驱动注册篇 里面描述过结构体:struct platform_device 和 struct resource,这是核心结构体,方便理解api 和 代码业务分析的。
struct platform_device (在 include/linux/platform_device.h 中定义):
struct platform_device {
const char *name; // 设备名称,用于与驱动匹配
int id; // 实例ID(如同一类型设备多个实例),-1表示唯一
struct device dev; // 内嵌的通用设备结构体,包含大量核心信息
u32 num_resources; // 资源数量
struct resource *resource; // 指向资源数组的指针,描述设备使用的硬件资源(内存区域、中断号等):cite[1]:cite[2]
// ... 其他字段如 archdata, of_node, driver_override 等
};
struct resource (在 include/linux/ioport.h 中定义):
struct resource {
resource_size_t start; // 资源的起始地址(物理地址、中断号)
resource_size_t end; // 资源的结束地址
const char *name; // 资源名称
unsigned long flags; // 资源类型和属性标志(如下)
// ... 资源树链表指针
};
flags 的重要标志:
- IORESOURCE_MEM:内存映射的资源。
- IORESOURCE_IO:I/O端口映射的资源。
- IORESOURCE_IRQ:中断资源。
- IORESOURCE_DMA:DMA资源1。
要点与注意事项
设备与驱动的匹配:
设备注册后,内核会在合适的时机(设备注册后或驱动注册后)通过平台总线 (platform_bus_type) 的匹配函数 (platform_match) 来尝试将设备与已注册的驱动进行匹配。匹配策略通常优先比较设备树兼容性 (of_driver_match_device),然后是ID表 (id_table),最后是设备名称和驱动名称的字符串比较 (strcmp(pdev->name, drv->name))。
注册顺序:
虽然Linux设备模型在一定程度上能处理设备和驱动注册的先后顺序(后注册的一方会触发匹配),但通常推荐先注册设备 (platform_device_register),再注册驱动 (platform_driver_register)。这在某些初始化阶段或依赖资源的场景下更为可靠。
三、注册总线之前要先注册设备实例-platform_bus_init-device_register
之前我们了解 platform总线注册流程分析,里面为什么需要创建一个device,如下源码: 就是在注册平台总线的时候,为什么执行 device_register(&platform_bus); 创建设备一次。
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error) {
put_device(&platform_bus);
return error;
}
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
原因:
device_register(&platform_bus) 里面的 platform_bus 定义如下:
struct device platform_bus = {
.init_name = "platform",
};
那么执行的结果就是 创建了 /sys/devices/platform 目录,在 /sys/devices/ 目录下创建了platform目录。 继续看platform_device 注册流程源码:
// 参考 drivers/base/platform.c
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
// 1. 参数有效性检查
if (!pdev)
return -EINVAL;
// 2. 设置设备的父设备(如果未指定,则默认为平台总线)
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; // 默认父设备是平台总线
这里要讲解的就是这个,如下:
pdev->dev.parent = &platform_bus; // 默认父设备是平台总线:cite[3]:cite[5]
所以所有的子设备都放在了 /sys/devices/platform 目录下作为子目录统一管理了。
总结
- Linux驱动之
platform总线设备注册流程分析流程,对比总线下设备注册,思路其实都是一样的。platform_device只是作为一类来具体说明,典型应用而已。 - 看数据结构,
platform_device里面有成员变量struct device dev;platform_device_add添加platform设备最终也是走到了device_add,所以平台平台总线设备其实就是总线设备的一次封装。无论结构体还是注册都是封装了一层而已。
195

被折叠的 条评论
为什么被折叠?



