当前位置:首页>Linux>Linux platform 总线

Linux platform 总线

  • 2026-08-28 12:16:22
Linux platform 总线
大家好,我是王鸽,今天这篇文章承接总线驱动设备,具体化实例,好了进入主题。
platform总线
Linux发明了一种虚拟的总线, 称为platform总线,platform 总线是 bus_type 的一个具体实例,相应的设备称为platform_device,而驱动成为platform_driver。

物理总线 vs 虚拟总线

  • 物理总线:实际存在的硬件总线(如 I2C、SPI、PCI、USB),设备和驱动通过物理总线通信;
  • Platform 总线:Linux 内核定义的 虚拟总线(bus_type platform_bus_type),专为 SoC 中没有标准物理总线的外设(如片上定时器、GPIO、UART 控制器)设计,本质是内核的软件抽象。
定义在文件 drivers/base/platform.c, platform 总线定义如下:
struct bus_type platform_bus_type = {    .name = "platform",    .dev_groups = platform_dev_groups,   .match = platform_match,   .uevent = platform_uevent,  .pm = &platform_dev_pm_ops,};
platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。
platform_match 函数定义在文件 drivers/base/platform.c 中,函数内容如下所示:
staticintplatform_match(struct device *dev, struct device_driver *drv){struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;/* Then try to match against the id table */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);}

驱动和设备的匹配有四种方法,

第一种匹配方式:of_driver_match_device(dev, drv),OF 类型的匹配,也就是设备树采用的匹配方式,device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。

第二种匹配方式:ACPI 匹配方式,

Linux ACPI 匹配是内核通过 _HID(硬件 ID)、_CID(兼容 ID) 与驱动的 acpi_match_table 进行字符串比对。

比如:

#include<linux/acpi.h>static const struct acpi_device_id my_acpi_ids[] = {    { "ACME0001", 0 },  // 匹配 _HID    { "ACME0002", 0 },  // 匹配 _CID    { "PRP0001", 0 },   // 兼容 DT 模式    { }                 // 结束标记};MODULE_DEVICE_TABLE(acpi, my_acpi_ids);  // 导出给模块自动加载

另外调试与查看

  • 查看系统 ACPI 设备:cat /sys/bus/acpi/devices/*/hid
  • 查看驱动 ACPI 匹配表:modinfo my_driver.ko | grep alias
  • 内核日志:dmesg | grep -i acpi 查看匹配与 probe 过程

第三种匹配方式, id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。

platform_match_id(pdrv->id_table, pdev) != NULL

具体函数如下:

static const struct platform_device_id *platform_match_id(const struct platform_device_id *id,struct platform_device *pdev){while (id->name[0]) {if (strcmp(pdev->name, id->name) == 0) {			pdev->id_entry = id;return id;		}		id++;	}return NULL;}

第四种匹配方式

直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。

(strcmp(pdev->name, drv->name) == 0);

对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,也就是直接比较驱动和设备的 name 字段,毕竟这种方式最简单了。

platform _driver
platform _driver 结构体表示platform驱动,该结构体定义在文件include/linux/platform_device.h,内容如下:
struct platform_driver {int (*probe)(struct platform_device *);int (*remove)(struct platform_device *);void (*shutdown)(struct platform_device *);int (*suspend)(struct platform_device *, pm_message_t state);int (*resume)(struct platform_device *);struct device_driver driver;const struct platform_device_id *id_table;};

probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行,driver 成员,为 device_driver 结构体变量, Linux 内核里面大量使用到了面向对象的思维, device_driver 相当于基类,提供了最基础的驱动框架。 plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量。device_driver 结构体定义在 include/linux/device.h

of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为 of_device_id 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中.

/* * Struct used for matching a device */struct of_device_id{char	name[32];char	type[32];char	compatible[128];const void *data;};

compatible 非常重要,因为对于设备树而言,就是通过设备节点的 compatible 属性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

const struct platform_device_id *id_table;

另外id_table 表,之前 platform 总线匹配驱动和设备的时候采用的第三种匹配方法, id_table 是个表(也就是数组),每个元素的类型为 platform_device_id,platform_device_id 结构体内容如下:

struct platform_device_id {char name[PLATFORM_NAME_SIZE];kernel_ulong_t driver_data;};

其实platform 驱动还是传统的字符设备驱动、块设备驱动或网络设备驱动,只是套上了一张“platform” 的壳,目的是为了使用总线、驱动和设备这个驱动模型来实现驱动的分离与分层。

典型示例

设备注册(设备树方式,主流)

在设备树(.dts)中定义设备:

my_platform_device {    compatible = "my_driver,platform_dev";  // 匹配关键字    reg = <0x12340000 0x1000>;              // 寄存器地址    interrupts = <IRQ_TYPE_EDGE_RISING 5>;  // 中断号};

内核解析设备树后,自动创建 device 结构体并注册到 platform 总线。

驱动注册(匹配 + probe)

#include<linux/module.h>#include<linux/platform_device.h>// 匹配表(和设备树的compatible对应)static const struct of_device_id my_dev_of_match[] = {    { .compatible = "my_driver,platform_dev" },    { },  // 结束符};MODULE_DEVICE_TABLE(of, my_dev_of_match);// 驱动的probe函数(匹配成功后执行)staticintmy_platform_probe(struct platform_device *pdev){    printk(KERN_INFO "设备匹配成功,初始化硬件!\n");    // 1. 获取设备树参数(寄存器地址、中断号)    struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);    // 2. 映射寄存器地址    void __iomem *reg_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));    // 3. 初始化硬件(比如设置寄存器)    writel(0x1234, reg_base + 0x00);    return 0;}// 驱动移除函数staticintmy_platform_remove(struct platform_device *pdev){    printk(KERN_INFO "驱动移除,释放资源!\n");    return 0;}// 定义platform驱动结构体static struct platform_driver my_platform_driver = {    .driver = {        .name = "my_platform_driver",        .of_match_table = my_dev_of_match,  // 设备树匹配表    },    .probe = my_platform_probe,    .remove = my_platform_remove,};// 模块加载/卸载module_platform_driver(my_platform_driver);MODULE_LICENSE("GPL");
  • 加载驱动模块后,platform 总线调用 match 函数,对比驱动的 of_match_table 和设备树的 compatible;
  • 匹配成功后,调用 my_platform_probe 初始化硬件;
  • 卸载驱动时,调用 my_platform_remove 释放资源。
  • 核心流程
注册总线→注册设备 / 驱动→总线匹配→执行 probe 初始化硬件。
dmesg|grep my_dev
关键调试技巧
  1. 查看 Platform 设备列表:ls /sys/bus/platform/devices/
  2. 查看 Platform 驱动列表:ls /sys/bus/platform/drivers/
  3. 查看设备绑定的驱动:
  4. ls /sys/bus/platform/drivers/my_platform_driver/
  5. 查看内核匹配日志:dmesg | grep -i platform
接下来讲一下platform设备,这个因为设备树出现导致使用少了。

platform 设备

platform 驱动已经准备好了,我们还需要 platform 设备,否则的话单单一个驱动也做不了什么。 platform_device 这个结构体表示 platform 设备,这里我们要注意,如果内核支持设备树的话就不要再使用 platform_device 来描述设备了,因为改用设备树去描述了。
设备注册
设备树(DTB)解析后,内核将硬件设备封装成 device 结构体,挂到对应总线上;
当然了,你如果一定要用 platform_device 来描述设备信息的话也是可以的。 
platform_device 结构体定义在文件include/linux/platform_device.h 中
struct platform_device {constchar	*name;int		id;bool		id_auto;struct device	dev;u32		num_resources;struct resource	*resource;conststruct platform_device_id	*id_entry;/* MFD cell pointer */struct mfd_cell *mfd_cell;/* arch specific additions */struct pdev_archdata	archdata;};
定义
含义
name
要和所使用的 platform 驱动的 name 字段相同,否则的话设备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“xxx-led”,那么此 name字段也要设置为“xxx-led”
num_resources
资源数量
resource
- 指向 struct resource 数组,描述设备的硬件资源:
- 寄存器地址(IORESOURCE_MEM);
- 中断号(IORESOURCE_IRQ);
- DMA 通道(IORESOURCE_DMA);
- 驱动匹配后,可通过 platform_get_resource() 获取具体资源。
dev
struct device
通用设备结构体(基类)
- 所有 Linux 设备的通用父结构体,包含设备的核心属性:
- parent:父设备指针(用于设备树层级);
- platform_data:设备私有数据(旧版方式,现代推荐用 of_device_id/ 设备树);
- driver:匹配成功的驱动指针;
- kobj:设备的内核对象(sysfs 节点);
- 是 platform 设备融入 Linux 设备模型的核心。
其中resource 结构体在
structresource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;struct resource *parent, *sibling, *child; };

start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址, name 表示资源名字, flags 表示资源类型。

在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中。

intplatform_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);

函数参数和返回值含义如下:pdev:要注册的 platform 设备。返回值: 负数,失败; 0:成功。

如果要注销掉platform设备,对应调用函数不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉。

举一个例子

#include<linux/module.h>#include<linux/platform_device.h>#include<linux/init.h>#include<linux/kernel.h>// 定义设备私有数据(可选,用于存储设备相关的自定义信息)struct demo_dev_data {    int id;    char name[32];};// 设备私有数据初始化static struct demo_dev_data demo_data = {    .id = 1001,    .name = "platform_demo_device",};// 设备资源定义(可选,比如外设的寄存器地址、中断号等)static struct resource demo_resource[] = {    // 示例:定义一个内存资源(寄存器基地址)    [0] = {        .start = 0x12340000,  // 模拟的寄存器起始地址        .end   = 0x123400FF,  // 模拟的寄存器结束地址        .flags = IORESOURCE_MEM, // 资源类型:内存    },    // 示例:定义一个中断资源    [1] = {        .start = 50,          // 模拟的中断号        .end   = 50,        .flags = IORESOURCE_IRQ, // 资源类型:中断    },};// platform 设备结构体定义static struct platform_device demo_platform_device = {    .name = "demo_platform_dev",  // 设备名(需与驱动的 name 匹配才能成功匹配)    .id = PLATFORM_DEVID_AUTO,    // 自动分配设备 ID    .dev = {        .platform_data = &demo_data, // 挂载私有数据    },    .num_resources = ARRAY_SIZE(demo_resource), // 资源数量    .resource = demo_resource,                 // 资源数组};// 模块初始化函数:注册 platform 设备staticint __init demo_platform_device_init(void){    int ret;    // 注册 platform 设备    ret = platform_device_register(&demo_platform_device);    if (ret) {        pr_err("Failed to register platform device: %d\n", ret);        return ret;    }    pr_info("Platform device registered successfully!\n");    return 0;}// 模块退出函数:注销 platform 设备staticvoid __exit demo_platform_device_exit(void){    // 注销 platform 设备    platform_device_unregister(&demo_platform_device);    pr_info("Platform device unregistered successfully!\n");}// 模块入口和出口宏module_init(demo_platform_device_init);module_exit(demo_platform_device_exit);// 模块信息(必须)MODULE_LICENSE("GPL");MODULE_AUTHOR("Your Name");MODULE_DESCRIPTION("Demo for Platform Device Registration");MODULE_VERSION("1.0");
设备私有数据(demo_dev_data)
用于存储设备的自定义信息(如设备 ID、名称等),通过 platform_data 挂载到 platform_device 的 dev 成员中,驱动匹配后可读取这些数据。platform_data 挂载自定义数据,是驱动获取硬件信息的重要方式
  • .name
    设备名称,必须与 platform 驱动的名称一致 才能完成总线匹配。
  • .id
    设备 ID,PLATFORM_DEVID_AUTO 表示自动分配。
  • .dev.platform_data
    挂载私有数据的指针。
  • .num_resources
     和 .resource:指定设备资源的数量和数组。
编译测试
# 查看已注册的 platform 设备cat  /sys/bus/platform/devices/demo_platform_dev/name# 查看内核日志dmesg |grep "Platform device"
在现在设备树方式开发中(主流),只要编写底层驱动就可以了!
回顾一下
总线注册 → 设备注册(入列→遍历驱动匹配→未绑定) → 驱动注册(入列→遍历设备匹配)→ 匹配成功→probe初始化→已绑定 → 卸载→remove清理→解绑
步骤
核心 API
作用
总线注册
platform_bus_init()
创建 platform 总线
设备注册
platform_device_register()
注册设备到总线
驱动注册
platform_driver_register()
注册驱动到总线
匹配判定
platform_match()
/platform_match_id()
设备 - 驱动匹配
初始化
驱动的 .probe 函数
硬件初始化
资源清理
驱动的 .remove 函数
释放资源
卸载驱动
platform_driver_unregister()
移除驱动并解绑设备
移除设备
platform_device_unregister()
移除设备并清理资源

注意

  • 设备树定义的 Platform 设备,其 /sys/bus/platform/devices/ 下的目录名是节点名 + @reg 起始地址(如 my_platform_dev@12340000),而非 compatible 字符串;
  • driver
     软链接仅在驱动通过 of_match_table 匹配设备树 compatible 成功后生成,指向 /sys/bus/platform/drivers/驱动名;
    举一个例子
// 加入设备树的根节点(如 /soc 下)my_platform_dev@12340000 {    compatible = "my_driver,platform_dev";    reg = <0x12340000 0x1000>;    interrupts = <IRQ_TYPE_EDGE_RISING 5>;    status = "okay"; // 启用节点};//驱动代码// 设备树匹配表static const struct of_device_id my_dev_of_match[] = {    { .compatible = "my_driver,platform_dev" },    { },};MODULE_DEVICE_TABLE(of, my_dev_of_match);//........static struct platform_driver my_platform_drv = {    .probe = my_dev_probe,    .remove = my_dev_remove,    .driver = {        .name = "my_platform_driver",        .of_match_table = of_match_ptr(my_dev_of_match),    },};

编译设备树并烧录,启动系统

# 1. 查看设备目录(节点名+@地址)ls /sys/bus/platform/devices/my_platform_dev@12340000/# 2. 加载驱动前:无 driver 软链接ls /sys/bus/platform/devices/my_platform_dev@12340000/driver  # 报错:无此文件# 3. 加载驱动insmod my_platform_driver.ko# 4. 加载后:查看 driver 软链接ls -l /sys/bus/platform/devices/my_platform_dev@12340000/driver# 输出示例:# lrwxrwxrwx 1 root root 0 Jan  1 00:00 driver -> ../../../../bus/platform/drivers/my_platform_driver# 5. 查看驱动目录ls /sys/bus/platform/drivers/my_platform_driver/

谢谢点赞阅读收藏!

最新文章

随机文章