Linux平台总线
时间轴
2025-11-15
init
平台总线
在 Linux 内核中, 平台总线(Platform Bus) 是一个用于管理和连接 平台设备(platform device) 和 平台驱动(platform driver) 的抽象层。它充当了平台设备(platform device)和平台驱动(platform driver)之间的桥梁,负责将它们进行匹配和绑定。
它主要用于那些 不通过标准总线(如 PCI、USB、I²C、SPI)发现设备 的设备,而是直接集成在 SoC(System on Chip)或者主板上的设备。
通过使用平台总线模型,将设备驱动和平台设备进行了分离。这样一来,我们只需编写一份通用的驱动代码即可,然后针对不同的平台设备进行配置,这就大大减少了重复编写代码的工作量,并提高了驱动代码的重用性。
当我们需要将驱动移植到不同的平台时,只需对硬件相关的部分进行适配即可,其他部分可以保持不变。
平台总线下,设备(platform_device)和驱动(platform_driver)是两个独立对象。
- 设备来自 DT/板级文件/平台代码
- 驱动来自模块加载/编译进内核
- 驱动卸载不等于设备消失
注册platform设备
platform_device结构体
1 | // include/linux/platform_device.h |
const char *name:设备的名称,用于唯一标识设备。必须提供一个唯一的名称,以便内核能够正确识别和管理该设备。int id:设备的 ID,可以用于区分同一种设备的不同实例。这个参数是可选的,如果不需要使用 ID 进行区分,可以将其设置为-1struct device dev:表示平台设备对应的 struct device 结构体,用于设备的基本管理和操作。必须为该参数提供一个有效的 struct device 对象,该结构体的 release 方法必须要实现,否则在编译的时候会报错。u32 num_resources:设备资源的数量。如果设备具有资源(如内存区域、中断等),则需要提供资源的数量。struct resource *resource:指向设备资源的指针。如果设备具有资源,需要提供一个指向资源数组的指针,会在下个小节对该结构体进行详细的讲解。
resource结构体
1 | //include/linux/ioport.h |
中最重要的是前四个参数,每个参数的具体介绍如下所示:
resource_size_t start:资源的起始地址。它表示资源的起始位置或者起始寄存器的地址。resource_size_t end:资源的结束地址。它表示资源的结束位置或者结束寄存器的地址。const char *name:资源的名称。它是一个字符串,用于标识和描述资源。unsigned long flags:资源的标志位。它包含了一些特定的标志,用于表示资源的属性或者特征。例如,可以用标志位来指示资源的可用性、共享性、缓存属性等。flags 参数的具体取值和含义可以根据系统和驱动的需求进行定义和解释,但通常情况下,它用于表示资源的属性、特征或配置选项。下面是一些常见的标志位及其可能的含义
| 分类 | 标志位 | 说明 |
|---|---|---|
| 资源类型相关标志位 | IORESOURCE_IO |
资源是 I/O 端口资源 |
IORESOURCE_MEM |
资源是内存资源 | |
IORESOURCE_REG |
资源是寄存器偏移量 | |
IORESOURCE_IRQ |
资源是中断资源 | |
IORESOURCE_DMA |
资源是 DMA(直接内存访问)资源 | |
| 资源属性与特征相关标志位 | IORESOURCE_PREFETCH |
资源是无副作用的可预取资源 |
IORESOURCE_READONLY |
资源只读 | |
IORESOURCE_CACHEABLE |
资源支持缓存 | |
IORESOURCE_RANGELENGTH |
资源的范围长度 | |
IORESOURCE_SHADOWABLE |
资源可被影子资源替代 | |
IORESOURCE_SIZEALIGN |
资源大小字段对齐 | |
IORESOURCE_STARTALIGN |
起始地址字段对齐 | |
IORESOURCE_MEM_64 |
资源是 64 位内存资源 | |
IORESOURCE_WINDOW |
资源由桥接器转发 | |
IORESOURCE_MUXED |
资源被软件复用 | |
IORESOURCE_SYSRAM |
资源是系统 RAM(修饰符) | |
| 状态与控制相关标志位 | IORESOURCE_EXCLUSIVE |
用户空间无法映射此资源 |
IORESOURCE_DISABLED |
资源当前被禁用 | |
IORESOURCE_UNSET |
地址尚未分配给资源 | |
IORESOURCE_AUTO |
地址由系统自动分配 | |
IORESOURCE_BUSY |
驱动程序标记资源为繁忙 |
platform_device_register()
| 项目 | 说明 |
|---|---|
| 函数定义 | int platform_device_register(struct platform_device *pdev); |
| 头文件 | #include <linux/platform_device.h> |
| 参数 pdev | 指向 platform_device 结构体的指针,描述要注册的平台设备,包括设备名称、资源、设备 ID 等信息。 |
| 功能 | 将平台设备注册到内核,使其能够参与设备资源分配和驱动匹配。 |
| 返回值 | 成功:返回 0; 失败:返回负数错误码 |
1 | // include/linux/platform_device.h |
device_initialize(&pdev->dev)对pdev->dev进行初始化。pdev->dev是struct platform_device结构体中的一个成员,它表示平台设备对应的struct device结构体。通过调用device_initialize函数,对pdev->dev进行一些基本的初始化工作,例如设置设备的引用计数、设备的类型等。setup_pdev_dma_masks根据平台设备的架构数据来设置 pdev 的架构相关数据。这个函数的具体实现可能与具体的架构相关,它主要用于在不同的架构下对平台设备进行特定的设置。platform_device_add函数 , 将平台设备 pdev 添加到内核中 。platform_device_add函数会完成平台设备的添加操作,包括将设备添加到设备层级结构中、添加设备的资源等。它会返回一个 int 类型的结果,表示设备添加的结果。
platform_device_register 函数的主要作用是将 platform_device 结构体描述的平台设备注册到内核中,包括设备的初始化、添加到 platform 总线和设备层级结构、添加设备资源等操作。
通过该函数,平台设备被注册后,就能够参与设备的资源分配和驱动的匹配过程。函数的返回值可以用于判断设备注册是否成功。
platform_device_unregister()
| 项目 | 说明 |
|---|---|
| 函数定义 | void platform_device_unregister(struct platform_device *pdev); |
| 头文件 | #include <linux/platform_device.h> |
| 参数 pdev | 指向要取消注册的平台设备的 platform_device 结构体指针 |
| 功能 | 取消注册已注册的平台设备,从内核中移除设备,并进行资源清理。 |
| 返回值 | 无返回值 |
1 | // drivers/base/platform.c |
platform_device_del函数,用于将设备从 platform 总线的设备列表中移除。它会将设备从设备层级结构中移除,停止设备的资源分配和驱动的匹配。platform_device_put函数,用于减少对设备的引用计数。这个函数会检查设备的引用计数,如果引用计数减为零,则会释放设备结构体和相关资源。通过减少引用计数,可以确保设备在不再被使用时能够被释放。
platform_device_unregister 函数的作用是取消注册已经注册的平台设备,从内核中移除设备 。它先调用 platform_device_del 函 数 将设备从设层级结构中移除 , 然后调用platform_device_put函数减少设备的引用计数,确保设备在不再被使用时能够被释放。
示例
在较低内核版本 platform_device 的 release 回调函数必须实现,否则可能编译不过
1 |
|
加载后在/sys/bus/platform/devices 目录下,可以看到我们创建的 my_platform_device 设备
1 | ~ # insmod platform_device_test.ko |
注册platform驱动
struct platform_driver
1 | // include/linux/platform_device.h |
probe:平台设备的探测函数指针。当系统检测到一个平台设备与该驱动程序匹配时,该函数将被调用以初始化和配置设备。remove:平台设备的移除函数指针。当平台设备从系统中移除时,该函数将被调用以执行清理和释放资源的操作。shutdown:平台设备的关闭函数指针。当系统关闭时,该函数将被调用以执行与平台设备相关的关闭操作。suspend:平台设备的挂起函数指针。当系统进入挂起状态时,该函数将被调用以执行与平台设备相关的挂起操作。resume:平台设备的恢复函数指针。当系统从挂起状态恢复时,该函数将被调用以执行与平台设备相关的恢复操作。driver:包含了与设备驱动程序相关的通用数据,它是struct device_driver类型的实例。其中包括驱动程序的名称、总线类型、模块拥有者、属性组数组指针等信息。id_table:指向struct platform_device_id结构体数组的指针,用于匹配平台设备和驱动程序之间的关联关系,只有匹配成功了才能进入probe函数,但是优先级低于platform_driver.driver.of_match_tableprevent_deferred_probe:一个布尔值,用于确定是否阻止延迟探测。如果设置为 true,则延迟探测将被禁用。
struct device_driver
struct device_driver 是 设备模型(Device Model)层) 的抽象,它描述的是 驱动程序本身的身份和行为,例如:
- 驱动名称、所属总线、模块信息
- 设备匹配表(设备树、ACPI)
- 驱动操作回调:
probe/remove/suspend/resume - sysfs 属性和电源管理
核心思想:它关注的是驱动和设备之间的匹配与生命周期管理。
1 | // include/linux/device/driver.h |
platform_driver_register()
| 项目 | 说明 |
|---|---|
| 函数定义 | int platform_driver_register(struct platform_driver *driver); |
| 头文件 | #include <linux/platform_device.h> |
| 参数 driver | 指向 platform_driver 结构体的指针,描述要注册的平台驱动程序,包括属性和回调函数。 |
| 功能 | 将平台驱动程序注册到内核,使内核能够与特定平台设备匹配并调用相应回调函数。 |
| 返回值 | 成功:返回 0; 失败:返回负数错误码 |
1 |
|
这个宏用于简化平台驱动程序的注册过程。它将实际的注册函数 __platform_driver_register 与当前模块(驱动程序)关联起来。宏的参数 drv 是一个指向 struct platform_driver 结构体的指针,描述了要注册的平台驱动程序的属性和回调函数。THIS_MODULE 是一个宏,用于获取当前模块的指针。
1 | // drivers/base/platform.c |
通过这些操作,__platform_driver_register 函数将平台驱动程序与内核关联起来,并确保内核能够正确识别和调用驱动程序的各种回调函数,以实现与平台设备的交互和管理。函数的返回值表示注册过程的执行状态,以便在需要时进行错误处理。
platform_driver_unregister()
| 项目 | 说明 |
|---|---|
| 函数定义 | int platform_driver_register(struct platform_driver *driver); |
| 头文件 | #include <linux/platform_device.h> |
| 参数 driver | 指向 platform_driver 结构体的指针,描述要注册的平台驱动程序,包括属性和回调函数。 |
| 功能 | 将平台驱动程序注册到内核,使内核能够与特定平台设备匹配并调用相应回调函数。 |
| 返回值 | 成功:返回 0; 失败:返回负数错误码 |
1 | // include/linux/platform_device.h |
bus_remove_driver函数,用于从总线中移除设备驱动程序。该函数会执行以下操作:- 从总线驱动程序列表中移除指定的设备驱动程序。
- 调用与设备驱动程序关联的 remove 回调函数(如果有定义)。
- 释放设备驱动程序所占用的资源和内存。
- 最终销毁设备驱动程序的数据结构。
通过调用 driver_unregister 函数,可以正确地注销设备驱动程序,并在注销过程中进行必要的清理工作。这样可以避免资源泄漏和其他问题。在调用该函数后,应避免继续使用已注销的设备驱动程序指针,因为该驱动程序已不再存在于内核中
platform_get_resource()
在驱动程序中获取平台设备的资源信息,并根据这些信息进行后续的操作和配置
| 项目 | 说明 |
|---|---|
| 函数定义 | struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num); |
| 头文件 | #include <linux/platform_device.h> |
| 参数 pdev | 指向要获取资源的平台设备(platform_device)结构体指针 |
| 参数 type | 资源类型,如: IORESOURCE_MEM:内存资源IORESOURCE_IO:I/O 资源IORESOURCE_IRQ:中断资源 |
| 参数 num | 资源索引号,用于选择设备中同类型的第 num 个资源 |
| 功能 | 从平台设备的资源数组中获取指定类型和索引的资源信息 |
| 返回值 | 成功:返回指向 struct resource 的指针;失败或资源不存在:返回 NULL |
helper_macro
1 | /* module_platform_driver() - Helper macro for drivers that don't do |
module_platform_driver(driver)
📌 用途
用于可加载内核模块(.ko),且驱动结构体 已完整定义 .probe, .remove 等成员。
🔧 展开效果
1 | static int __init driver_init(void) |
✅ 使用前提
driver是一个完整的struct platform_driver变量;- 已实现
.probe,.remove等回调函数。
1 | static struct platform_driver my_driver = { |
builtin_platform_driver(driver)
📌 用途
用于编译进内核(非模块) 的驱动,且驱动结构体 已完整定义。
🔧 展开效果
1 | static int __init driver_init(void) |
❗ 没有
exit部分(因为内置驱动通常不卸载)。
✅ 适用场景
- 驱动静态链接到内核(
CONFIG_MY_DRIVER=y); - 不需要支持运行时卸载。
module_platform_driver_probe(driver, probe_fn)
📌 用途
用于可加载模块,但你只想提供一个 .probe 函数,不想手动定义完整的 platform_driver 结构体(尤其是当不需要 .remove 等其他回调时)。
🔧 展开效果
1 | static int __init driver_init(void) |
⚠️ 关键点
platform_driver_probe()是一种轻量级注册方式:- 它只支持
.probe,不支持.remove、.shutdown等; - 驱动不能被卸载后重新加载(因为内部会设置
driver->prevent_deferred_probe = true); - 适用于简单、一次性探测的设备(如某些 SoC 内建控制器)。
- 它只支持
📝 示例
1 | static int my_probe(struct platform_device *pdev) |
💡 此时
my_driver中不需要写.probe = my_probe,宏会自动关联。
builtin_platform_driver_probe(driver, probe_fn)
📌 用途
module_platform_driver_probe 的内置版本(编译进内核,不可卸载)。
🔧 展开效果
1 | static int __init driver_init(void) |
同样无 exit 路径,适用于简单内置驱动。
示例
1 |
|
测试:
1 | ~ # insmod platform_device_test.ko |
LED灯平台总线示例
platform_device
1 |
|
platform_driver
1 |
|
一个 platform_driver 可能被多个 platform_device 使用。如果写在module_exit函数中当设备被热拔出(或通过 sysfs 移除)时,资源不会被释放 。因此资源清理逻辑要放在remove函数中
