时间轴
时间轴
2025-11-15
init
本文介绍了Linux内核中的平台总线(Platform Bus)机制,阐述了其作为平台设备与平台驱动之间桥梁的作用,主要用于管理直接集成在SoC或主板上的设备。文章说明了平台总线模型将设备驱动与平台设备分离,提高了驱动代码的重用性和可移植性,并指出设备与驱动是独立对象。同时,文章详细讲解了platform_device结构体的关键字段(如名称、ID、device结构体、资源数量及指针)以及struct resource中起始地址、结束地址、名称和标志位等参数的含义,并列举了常见资源类型与属性标志位。
Linux 驱动笔记
| 目录 | 链接 |
|---|---|
| 1. Linux 驱动框架 | |
| 2. Linux 驱动加载逻辑 | |
| 3. 字符设备基础 | |
| 4. 并发与竞争 | |
| 5. 高级字符设备进阶 | |
| 6. 中断 | |
| 7. 平台总线 | |
| 8. 设备树 | |
| 9. 设备模型 | |
| 10. 热插拔 | |
| 11. pinctrl 子系统 | |
| 12. gpio 子系统 | |
| 13. 输入子系统 | |
| 14. 单总线 | |
| 15. I2C | |
| 16. SPI | |
| 17. UART | |
| 18. PWM | |
| 19. RTC | |
| 20. Watchdog | |
| 21. CAN | |
| 22. 网络设备 | |
| 23. ADC | |
| 24. IIO | |
| 25. USB | |
| 26. LCD |
平台总线
在 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设备
struct platform_device
123456789101112131415161718192021222324 | // include/linux/platform_device.hstruct platform_device { const char *name;// 设备的名称,用于唯一标识设备 int id;// 设备的 ID,可以用于区分同一种设备的不同实例 bool id_auto;// 表示设备的 ID 是否自动生成 struct device dev;// 表示平台设备对应的 struct device 结构体,用于设备的基本管理和操作 u64 platform_dma_mask; struct device_dma_parameters dma_parms; u32 num_resources;// 设备资源的数量 struct resource *resource;// 指向设备资源的指针 const struct platform_device_id *id_entry;// 指向设备的 ID 表项的指针,用于匹配设备和驱动 /* * Driver name to force a match. Do not set directly, because core * frees it. Use driver_set_override() to set or clear it. */ const char *driver_override;// 强制设备与指定驱动匹配的驱动名称 /* MFD cell pointer */ struct mfd_cell *mfd_cell;// 指向多功能设备(MFD)单元的指针,用于多功能设备的描述 /* arch specific additions */ struct pdev_archdata archdata;// 用于存储特定于架构的设备数据}; |
const char *name:设备的名称,用于唯一标识设备。必须提供一个唯一的名称,以便内核能够正确识别和管理该设备。int id:设备的 ID,可以用于区分同一种设备的不同实例。这个参数是可选的,如果不需要使用 ID 进行区分,可以将其设置为-1struct device dev:表示平台设备对应的 struct device 结构体,用于设备的基本管理和操作。必须为该参数提供一个有效的 struct device 对象,该结构体的 release 方法必须要实现,否则在编译的时候会报错。u32 num_resources:设备资源的数量。如果设备具有资源(如内存区域、中断等),则需要提供资源的数量。struct resource *resource:指向设备资源的指针。如果设备具有资源,需要提供一个指向资源数组的指针,会在下个小节对该结构体进行详细的讲解。
struct resource
12345678910 | //include/linux/ioport.hstruct resource { resource_size_t start;/* 资源的起始地址 */ resource_size_t end;/* 资源的结束地址 */ const char *name;/* 资源的名称 */ unsigned long flags;/* 资源的标志位 */ unsigned long desc;/* 资源的描述信息 */ struct resource *parent, *sibling, *child;/* 指向子资源的指针 */}; |
中最重要的是前四个参数,每个参数的具体介绍如下所示:
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_BUS | 总线 | |
| 资源属性与特征相关标志位 | 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 | 驱动程序标记资源为繁忙 |
嵌入在struct platform_device中的struct resource可以通过platform_get_resource()函数进行检索。以下是platform_get_resource的原型:
1 | struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num); |
- 第一个参数是平台设备自身的实例。
- 第二个参数说明需要什么样的资源。对于内存,它应该是
IORESOURCE_MEM。 - num参数是一个索引,表示需要哪个资源类型。零表示第一个,依此类推。
如果是中断资源,则必须使用:
123 | int platform_get_irq(struct platform_device *dev, unsigned int num);int platform_get_irq_optional(struct platform_device *dev, unsigned int num) |
platform_device_register()
| 项目 | 说明 |
|---|---|
| 函数定义 | int platform_device_register(struct platform_device *pdev); |
| 头文件 | #include <linux/platform_device.h> |
| 参数 pdev | 指向platform_device结构体的指针,描述要注册的平台设备,包括设备名称、资源、设备 ID 等信息。 |
| 功能 | 将平台设备注册到内核,使其能够参与设备资源分配和驱动匹配。 |
| 返回值 | 成功:返回 0; 失败:返回负数错误码 |
1234567891011121314151617181920212223 | // include/linux/platform_device.hextern int platform_device_register(struct platform_device *);extern void platform_device_unregister(struct platform_device *);// drivers/base/platform.c/** * 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); setup_pdev_dma_masks(pdev); return platform_device_add(pdev);}EXPORT_SYMBOL_GPL(platform_device_register); |
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结构体指针 |
| 功能 | 取消注册已注册的平台设备,从内核中移除设备,并进行资源清理。 |
| 返回值 | 无返回值 |
12345678910111213141516 | // drivers/base/platform.c/** * platform_device_unregister - unregister a platform-level device * @pdev: platform device we're unregistering * * Unregistration is done in 2 steps. First we release all resources * and remove it from the subsystem, then we drop reference count by * calling platform_device_put(). */void platform_device_unregister(struct platform_device *pdev){ platform_device_del(pdev); platform_device_put(pdev);}EXPORT_SYMBOL_GPL(platform_device_unregister); |
platform_device_del函数,用于将设备从 platform 总线的设备列表中移除。它会将设备从设备层级结构中移除,停止设备的资源分配和驱动的匹配。platform_device_put函数,用于减少对设备的引用计数。这个函数会检查设备的引用计数,如果引用计数减为零,则会释放设备结构体和相关资源。通过减少引用计数,可以确保设备在不再被使用时能够被释放。
platform_device_unregister函数的作用是取消注册已经注册的平台设备,从内核中移除设备 。它先调用platform_device_del函 数 将设备从设层级结构中移除 , 然后调用platform_device_put函数减少设备的引用计数,确保设备在不再被使用时能够被释放。
示例
在较低内核版本 platform_device 的 release 回调函数必须实现,否则可能编译不过
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859 | static struct resource my_resources[] = { { .start = MEM_START_ADDR, .end = MEM_END_ADDR, .name = "test_resource1", .flags = IORESOURCE_MEM, // 标记为内存资源 }, { .start = IRQ_NUMBER, .end = IRQ_NUMBER, .flags = IORESOURCE_IRQ, // 标记为中断资源 } };void my_dev_release(struct device *dev) // pdev->dev资源释放的回调函数{ pr_info("my_dev_release is called\n");}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_dev_release,};static int __init platform_device_test_init(void){ int ret; ret = platform_device_register(&my_platform_device); if (ret < 0){ pr_err("platform_device_register fail\n"); return ret; } pr_info("platform_device register success\n"); return 0;}static void __exit platform_device_test_exit(void){ platform_device_unregister(&my_platform_device);}module_init(platform_device_test_init);module_exit(platform_device_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for platform_device"); |
加载后在/sys/bus/platform/devices目录下,可以看到我们创建的my_platform_device设备
1234567891011121314 | ~ # insmod platform_device_test.ko[ 10.099551] platform_device_test: loading out-of-tree module taints kernel.[ 10.115921] platform_device register success~ # ls /sys/devices/platform/Fixed MDIO bus.0/ fixedregulator_5v0/ scb/arm-pmu/ kgdboc/ sd_io_1v8_reg/cam1_regulator/ leds/ sd_vcc_reg/cam_dummy_reg/ my_platform_device/ soc/cpufreq-dt/ phy/ timer/emmc2bus/ power/ ueventfixedregulator_3v3/ reg-dummy/ v3dbus/~ # ls /sys/devices/platform/my_platform_device/driver_override power ueventmodalias subsystem |
注册platform驱动
struct platform_driver
1234567891011 | // include/linux/platform_device.hstruct 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;/* 平台设备与驱动程序的关联关系表 */ bool prevent_deferred_probe;/* 是否阻止延迟探测 */}; |
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,则延迟探测将被禁用。
注意:probe函数不是用来替代init函数的。每当给定设备与驱动程序匹配时,都会调用probe函数,而init函数只在模块加载时运行一次。
可以使用
MODULE_DEVICE_TABLE(type, name);把设备 ID 表platform_driver.driver.of_match_table导出到模块的 ELF section 中,让 userspace(udev/modprobe)能够自动加载驱动。
struct device_driver
struct device_driver是 设备模型(Device Model)层) 的抽象,它描述的是 驱动程序本身的身份和行为,例如:
- 驱动名称、所属总线、模块信息
- 设备匹配表(设备树、ACPI)
- 驱动操作回调:
probe/remove/suspend/resume - sysfs 属性和电源管理
核心思想:它关注的是驱动和设备之间的匹配与生命周期管理。
1234567891011121314151617181920212223242526272829 | // include/linux/device/driver.hstruct device_driver { const char *name;// 驱动的名字,用于匹配设备。 struct bus_type *bus;// 驱动所属的总线类型,例如 PCI、USB、I2C 等。 struct module *owner;// 指向驱动所属模块 (struct module),用于模块引用计数。 const char *mod_name; /* used for built-in modules */ //模块名字,如果是内置驱动而非模块的话,会用这个名字。 bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ //如果为 true,就禁用了通过 sysfs 手动绑定/解绑设备。 enum probe_type probe_type;// 驱动探测方式,决定驱动如何被自动绑定到设备(例如正常探测或延迟探测)。 const struct of_device_id *of_match_table;// 用于 设备树匹配(Device Tree) const struct acpi_device_id *acpi_match_table;// 用于 ACPI 设备匹配。 int (*probe) (struct device *dev);// 核心函数,当驱动匹配到设备时调用,初始化设备。 void (*sync_state)(struct device *dev);// 同步设备状态,主要是内核内部使用 int (*remove) (struct device *dev);// 设备被卸载或驱动被卸载时调用。 void (*shutdown) (struct device *dev);// 系统关机时调用。 int (*suspend) (struct device *dev, pm_message_t state);// 用于电源管理,挂起。 int (*resume) (struct device *dev);// 用于电源管理,恢复。 // 用于 暴露驱动和设备的 sysfs 属性,可以通过 /sys/bus/... 或 /sys/class/... 查看和操作。 const struct attribute_group **groups; const struct attribute_group**dev_groups; const struct dev_pm_ops *pm;// 指向电源管理操作的结构体,包含 suspend/resume、runtime PM 等回调函数。 void (*coredump) (struct device *dev);// 当设备出现严重错误时,可以触发驱动的 coredump 回调,用于调试。 struct driver_private *p;// 内核内部使用,存放与驱动相关的私有数据,例如绑定的设备列表。}; |
platform_driver_register()
| 项目 | 说明 |
|---|---|
| 函数定义 | int platform_driver_register(struct platform_driver *driver); |
| 头文件 | #include <linux/platform_device.h> |
| 参数 driver | 指向platform_driver结构体的指针,描述要注册的平台驱动程序,包括属性和回调函数。 |
| 功能 | 将平台驱动程序注册到内核,使内核能够与特定平台设备匹配并调用相应回调函数。 |
| 返回值 | 成功:返回 0; 失败:返回负数错误码 |
1234 | extern int __platform_driver_register(struct platform_driver *, struct module *); |
这个宏用于简化平台驱动程序的注册过程。它将实际的注册函数__platform_driver_register与当前模块(驱动程序)关联起来。宏的参数 drv 是一个指向struct platform_driver结构体的指针,描述了要注册的平台驱动程序的属性和回调函数。THIS_MODULE是一个宏,用于获取当前模块的指针。
123456789101112131415161718 | // drivers/base/platform.c/** * __platform_driver_register - register a driver for platform-level devices * @drv: platform driver structure * @owner: owning module/driver */int __platform_driver_register(struct platform_driver *drv, struct module *owner){ drv->driver.owner = owner;// 将平台驱动程序的所有权设置为当前模块 drv->driver.bus = &platform_bus_type;// 将平台驱动程序的总线类型设置为平台总线 drv->driver.probe = platform_drv_probe;// 设置平台驱动程序的探测函数 drv->driver.remove = platform_drv_remove;// 设置平台驱动程序的移除函数 drv->driver.shutdown = platform_drv_shutdown;// 设置平台驱动程序的关机函数 return driver_register(&drv->driver);// 将平台驱动程序注册到内核}EXPORT_SYMBOL_GPL(__platform_driver_register); |
通过这些操作,__platform_driver_register函数将平台驱动程序与内核关联起来,并确保内核能够正确识别和调用驱动程序的各种回调函数,以实现与平台设备的交互和管理。函数的返回值表示注册过程的执行状态,以便在需要时进行错误处理。
platform_driver_register():注册驱动程序并将其放入由内核维护的驱动程序列表中,以便每当发现新的匹配时就可以按需调用其probe()函数。platform_driver_probe():调用该函数后,内核立即运行匹配循环,检查是否有平台设备名称匹配,如果匹配则调用驱动程序的probe(),这意味着设备存在;否则,驱动程序将被忽略。此方法可防止延迟探测,因为它不会在系统上注册驱动程序。在这里,probe函数被放置在__init部分,当内核启动完成时这个部分被释放,从而防止了延迟探测并减少驱动程序的内存占用。如果100%确定设备存在于系统中,请使用此方法
platform_driver_unregister()
| 项目 | 说明 |
|---|---|
| 函数定义 | void platform_driver_unregister(struct platform_driver *driver); |
| 头文件 | #include <linux/platform_device.h> |
| 参数 driver | 指向platform_driver结构体的指针,描述要注销的平台驱动程序。 |
| 功能 | 从内核注销平台驱动程序,使其不再参与驱动匹配。 |
| 返回值 | 无返回值 |
12345678910111213141516171819202122232425262728293031323334 | // include/linux/platform_device.hextern void platform_driver_unregister(struct platform_driver *);// drivers/base/platform.c/** * platform_driver_unregister - unregister a driver for platform-level devices * @drv: platform driver structure */void platform_driver_unregister(struct platform_driver *drv){ driver_unregister(&drv->driver);}EXPORT_SYMBOL_GPL(platform_driver_unregister);// drivers/base/driver.c/** * driver_unregister - remove driver from system. * @drv: driver. * * Again, we pass off most of the work to the bus-level call. */void driver_unregister(struct device_driver *drv){ // 检查传入的设备驱动程序指针和 p 成员是否有效 if (!drv || !drv->p) { WARN(1, "Unexpected driver unregister!\n"); return; } driver_remove_groups(drv, drv->groups);// 移除与设备驱动程序关联的属性组 bus_remove_driver(drv);// 从总线中移除设备驱动程序}EXPORT_SYMBOL_GPL(driver_unregister); |
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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 | /* module_platform_driver() - Helper macro for drivers that don't do * anything special in module init/exit. This eliminates a lot of * boilerplate. Each module may only use this macro once, and * calling it replaces module_init() and module_exit() *//* builtin_platform_driver() - Helper macro for builtin drivers that * don't do anything special in driver init. This eliminates some * boilerplate. Each driver may only use this macro once, and * calling it replaces device_initcall(). Note this is meant to be * a parallel of module_platform_driver() above, but w/o _exit stuff. *//* module_platform_driver_probe() - Helper macro for drivers that don't do * anything special in module init/exit. This eliminates a lot of * boilerplate. Each module may only use this macro once, and * calling it replaces module_init() and module_exit() *//* builtin_platform_driver_probe() - Helper macro for drivers that don't do * anything special in device init. This eliminates some boilerplate. Each * driver may only use this macro once, and using it replaces device_initcall. * This is meant to be a parallel of module_platform_driver_probe above, but * without the __exit parts. */ |
module_platform_driver(driver)
📌 用途
用于可加载内核模块(.ko),且驱动结构体 已完整定义.probe,.remove等成员。
🔧 展开效果
1234567891011 | static int __init driver_init(void){ return platform_driver_register(&driver);}module_init(driver_init);static void __exit driver_exit(void){ platform_driver_unregister(&driver);}module_exit(driver_exit); |
✅ 使用前提
driver是一个完整的struct platform_driver变量;- 已实现
.probe,.remove等回调函数。
123456 | static struct platform_driver my_driver = { .probe = my_probe, .remove = my_remove, .driver = { .name = "my-dev", ... },};module_platform_driver(my_driver); |
builtin_platform_driver(driver)
📌 用途
用于编译进内核(非模块) 的驱动,且驱动结构体 已完整定义。
🔧 展开效果
12345 | static int __init driver_init(void){ return platform_driver_register(&driver);}device_initcall(driver_init); // 在内核启动时调用 |
❗ 没有
exit部分(因为内置驱动通常不卸载)。
✅ 适用场景
- 驱动静态链接到内核(
CONFIG_MY_DRIVER=y); - 不需要支持运行时卸载。
module_platform_driver_probe(driver, probe_fn)
📌 用途
用于可加载模块,但你只想提供一个.probe函数,不想手动定义完整的platform_driver结构体(尤其是当不需要.remove等其他回调时)。
🔧 展开效果
1234567891011 | static int __init driver_init(void){ return platform_driver_probe(&driver, probe_fn);}module_init(driver_init);static void __exit driver_exit(void){ platform_driver_unregister(&driver);}module_exit(driver_exit); |
⚠️ 关键点
platform_driver_probe()是一种轻量级注册方式:- 它只支持
.probe,不支持.remove、.shutdown等; - 驱动不能被卸载后重新加载(因为内部会设置
driver->prevent_deferred_probe = true); - 适用于简单、一次性探测的设备(如某些 SoC 内建控制器)。
- 它只支持
📝 示例
1234567891011 | static int my_probe(struct platform_device *pdev){ // 初始化设备 return 0;}static struct platform_driver my_driver = { .driver = { .name = "my-simple-dev", },};module_platform_driver_probe(my_driver, my_probe); |
💡 此时
my_driver中不需要写.probe = my_probe,宏会自动关联。
builtin_platform_driver_probe(driver, probe_fn)
📌 用途
module_platform_driver_probe的内置版本(编译进内核,不可卸载)。
🔧 展开效果
12345 | static int __init driver_init(void){ return platform_driver_probe(&driver, probe_fn);}device_initcall(driver_init); |
同样无 exit 路径,适用于简单内置驱动。
示例
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 | static int platform_driver_test_probe(struct platform_device *pdev){ struct resource *res_mem, *res_irq; // Method1: 直接访问 if (pdev->num_resources >= 2) { struct resource *res_mem = &pdev->resource[0]; struct resource *res_irq = &pdev->resource[1]; pr_info("using pdev->resource[i] to get resource\n"); pr_info("Memory Resource: start=0x%llx, end=0x%llx\n", res_mem->start, res_mem->end); pr_info("IRQ Resource: number=%lld\n", res_irq->start); } // Method 2:使用platform_get_resource() res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res_mem == NULL) { dev_err(&pdev->dev, "Fail to get MEMORY resource\n"); return -ENODEV; } res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (res_irq == NULL) { dev_err(&pdev->dev, "Fail to get IRQ resource\n"); return -ENODEV; } pr_info("using platform_get_resource() to get resource\n"); pr_info("Memory Resource: start=0x%llx, end=0x%llx\n", res_mem->start, res_mem->end); pr_info("IRQ Resource: number=%lld\n", res_irq->start); return 0;}static int platform_driver_test_remove(struct platform_device *pdev){ pr_info("platform_driver_test_remove is called\n"); return 0;}static struct platform_driver my_platform_driver={ .driver = { .name = "my_platform_device", .owner = THIS_MODULE, }, .probe = platform_driver_test_probe, .remove = platform_driver_test_remove,};static int __init platform_driver_test_init(void){ int ret; ret = platform_driver_register(&my_platform_driver); if (ret < 0) { pr_err("platform_driver_register failed\n"); return ret; } pr_info("platform_driver_register success\n"); return 0;}static void __exit platform_driver_test_exit(void){ platform_driver_unregister(&my_platform_driver);}module_init(platform_driver_test_init);module_exit(platform_driver_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for platform_driver"); |
测试:
1234567891011 | ~ # insmod platform_device_test.ko[ 13.668535] platform_device_test: loading out-of-tree module taints kernel.[ 13.679351] platform_device register success~ # insmod platform_driver_test.ko[ 20.597568] using pdev->resource[i] to get resource[ 20.597797] Memory Resource: start=0xfdd60000, end=0xfdd60004[ 20.597884] IRQ Resource: number=101[ 20.597958] using platform_get_resource() to get resource[ 20.598036] Memory Resource: start=0xfdd60000, end=0xfdd60004[ 20.598309] IRQ Resource: number=101[ 20.599166] platform_driver_register success |
LED灯平台总线示例
platform_device
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 | /* 复用寄存器 *//* 数据寄存器与方向寄存器 */// 方向寄存器// 数据寄存器// 外部输入寄存器,readonlystruct resource my_resources[] = { { .start = GPIO0B_IOMUX, .end = GPIO0B_IOMUX + REG_SIZE - 1, .name = "GPIO0B_IOMUX", .flags = IORESOURCE_MEM, }, { .start = GPIO0_SWPORT_DDR_L, .end = GPIO0_SWPORT_DDR_L + REG_SIZE - 1, .name = "GPIO0_SWPORT_DDR_L", .flags = IORESOURCE_MEM, }, { .start = GPIO0_SWPORT_DR_L, .end = GPIO0_SWPORT_DR_L + REG_SIZE - 1, .name = "GPIO0_SWPORT_DR_L", .flags = IORESOURCE_MEM, }, { .start = GPIO0_EXT_PORT, .end = GPIO0_EXT_PORT + REG_SIZE - 1, .name = "GPIO0_EXT_PORT", .flags = IORESOURCE_MEM, } };static struct platform_device my_platform_device={ .name = "light_up_led", .num_resources = ARRAY_SIZE(my_resources), .resource = my_resources,};static int __init platform_device_led_init(void){ int ret; ret = platform_device_register(&my_platform_device); if(ret < 0){ pr_info("platform_device_register fail\n"); return ret; } return 0;}static void __exit platform_device_led_exit(void){ platform_device_unregister(&my_platform_device);}module_init(platform_device_led_init);module_exit(platform_device_led_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is light_up_led example using platform"); |
platform_driver
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 | struct led_drv_data { dev_t dev_num; struct cdev cdev; struct class *class; struct device *dev; void __iomem *gpio0b_iomux; void __iomem *gpio0_swport_ddr_l; void __iomem *gpio0_swport_dr_l; void __iomem *gpio0_ext_port;};int led_open(struct inode *inode, struct file *file){ struct cdev *led_cdev = inode->i_cdev; file->private_data = container_of(led_cdev, struct led_drv_data, cdev); pr_info("led_open is called\n"); return 0;}ssize_t led_read(struct file *file, char __user *buf, size_t size, loff_t *offset){ struct led_drv_data *drv_data = file->private_data; u32 val; if (size != sizeof(u32)) { pr_info("read need 4 bytes\n"); return -ENOMEM; } val = readl(drv_data->gpio0_ext_port); val &= 1 << 15; // gpio0_b7 val = val >> 15; if (copy_to_user(buf, &val, sizeof(u32)) != 0) { return -EFAULT; } pr_info("led_read is called\n"); return sizeof(u32);}ssize_t led_write(struct file *file, const char __user *buf, size_t size, loff_t *offset){ struct led_drv_data *drv_data = file->private_data; u32 val; if (size != sizeof(u32)) { pr_err("write need 4 bytes\n"); return -ENOMEM; } if (copy_from_user(&val, buf, sizeof(u32)) != 0) { return -EFAULT; } if (val > 0) { // 配置为GPIO输出 val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_ddr_l); // 打开 val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_dr_l); } else if (val == 0) { // 配置为GPIO输出 val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_ddr_l); // 关闭 val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80000000; val &= 0xffff7fff; writel(val, drv_data->gpio0_swport_dr_l); } pr_info("led_write is called\n"); return sizeof(u32);}long led_unlocked_ioctl(struct file *file, unsigned int op, unsigned long arg){ struct led_drv_data *drv_data = file->private_data; int val; switch (op) { case LED_OPEN: // 配置为GPIO输出 val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_ddr_l); // 打开 val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_dr_l); break; case LED_CLOSE: // 配置为GPIO输出 val = readl(drv_data->gpio0_swport_ddr_l); val |= 0x80008000; writel(val, drv_data->gpio0_swport_ddr_l); // 关闭 val = readl(drv_data->gpio0_swport_dr_l); val |= 0x80000000; val &= 0xffff7fff; writel(val, drv_data->gpio0_swport_dr_l); break; case LED_STATUS: val = readl(drv_data->gpio0_ext_port); val &= 1 << 15; // gpio0_b7 val = val >> 15; if (copy_to_user((void __user *)arg, &val, sizeof(val))) return -EFAULT; break; default: return -EFAULT; } pr_info("led_ioctl is called\n"); return 0;}int led_release(struct inode *inode, struct file *file){ pr_info("led_release is called\n"); return 0;}struct file_operations fops = { .owner = THIS_MODULE, .open = led_open, .read = led_read, .write = led_write, .unlocked_ioctl = led_unlocked_ioctl, .release = led_release,};static int my_platform_driver_probe(struct platform_device *pdev){ int ret; u32 val; struct led_drv_data *led_drv_dat; struct resource *res_iomux, *res_ddr_l, *res_dr_l, *res_ext_port; led_drv_dat = devm_kzalloc(&pdev->dev, sizeof(struct led_drv_data), GFP_KERNEL); if (led_drv_dat == NULL) { ret = -ENOMEM; goto devm_kzalloc_fail; } ret = alloc_chrdev_region(&led_drv_dat->dev_num, 0, 1, "lightup_led_chrdev_region"); if (ret < 0) { ret = -ENOMEM; goto alloc_chrdev_region_fail; } platform_set_drvdata(pdev, led_drv_dat); cdev_init(&led_drv_dat->cdev, &fops); led_drv_dat->cdev.owner = THIS_MODULE; ret = cdev_add(&led_drv_dat->cdev, led_drv_dat->dev_num, 1); if (ret < 0) goto cdev_add_fail; led_drv_dat->class = class_create(THIS_MODULE, "test_led"); if (IS_ERR(led_drv_dat->class)) { ret = PTR_ERR(led_drv_dat->class); goto class_create_fail; } led_drv_dat->dev = device_create(led_drv_dat->class, NULL, led_drv_dat->dev_num, NULL, "led0"); if (IS_ERR(led_drv_dat->dev)) { ret = PTR_ERR(led_drv_dat->dev); goto device_create_fail; } res_iomux = platform_get_resource(pdev, IORESOURCE_MEM, 0); res_ddr_l = platform_get_resource(pdev, IORESOURCE_MEM, 1); res_dr_l = platform_get_resource(pdev, IORESOURCE_MEM, 2); res_ext_port = platform_get_resource(pdev, IORESOURCE_MEM, 3); if (!res_iomux || !res_ddr_l || !res_dr_l || !res_ext_port) { ret = -ENODEV; goto address_fail; } led_drv_dat->gpio0b_iomux = devm_ioremap(&pdev->dev, res_iomux->start, resource_size(res_iomux)); led_drv_dat->gpio0_swport_ddr_l = devm_ioremap(&pdev->dev, res_ddr_l->start, resource_size(res_ddr_l)); led_drv_dat->gpio0_swport_dr_l = devm_ioremap(&pdev->dev, res_dr_l->start, resource_size(res_dr_l)); led_drv_dat->gpio0_ext_port = devm_ioremap(&pdev->dev, res_ext_port->start, resource_size(res_ext_port)); if (!led_drv_dat->gpio0b_iomux || !led_drv_dat->gpio0_swport_ddr_l || !led_drv_dat->gpio0_swport_dr_l || !led_drv_dat->gpio0_ext_port) { ret = -ENOMEM; goto address_fail; } // led_drv_dat->gpio0b_iomux = // devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 0)); // if (IS_ERR(led_drv_dat->gpio0b_iomux)) { // ret = PTR_ERR(led_drv_dat->gpio0b_iomux); // goto ioremap_fail; // } // led_drv_dat->gpio0_swport_ddr_l = // devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 1)); // if (IS_ERR(led_drv_dat->gpio0_swport_ddr_l)) { // ret = PTR_ERR(led_drv_dat->gpio0_swport_ddr_l); // goto ioremap_fail; // } // led_drv_dat->gpio0_swport_dr_l = // devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 2)); // if (IS_ERR(led_drv_dat->gpio0_swport_dr_l)) { // ret = PTR_ERR(led_drv_dat->gpio0_swport_dr_l); // goto ioremap_fail; // } // led_drv_dat->gpio0_ext_port = // devm_ioremap_resource(&pdev->dev, platform_get_resource(pdev, IORESOURCE_MEM, 3)); // if (!led_drv_dat->gpio0_ext_port) { // ret = -ENODEV; // goto ioremap_fail; // } // 设置引脚复用为GPIO val = readl(led_drv_dat->gpio0b_iomux); val |= 0x70000000; // write access val &= 0xFFFF8FFF; // gpio0_b7 writel(val, led_drv_dat->gpio0b_iomux); pr_info("gpio0b_7 is set as GPIO\n"); return 0;address_fail: device_destroy(led_drv_dat->class, led_drv_dat->dev_num);device_create_fail: class_destroy(led_drv_dat->class);class_create_fail: cdev_del(&led_drv_dat->cdev);cdev_add_fail: unregister_chrdev_region(led_drv_dat->dev_num, 1);alloc_chrdev_region_fail:devm_kzalloc_fail: return ret;}static int my_platform_driver_remove(struct platform_device *pdev){ struct led_drv_data *led_drv_dat = platform_get_drvdata(pdev); device_destroy(led_drv_dat->class, led_drv_dat->dev_num); class_destroy(led_drv_dat->class); cdev_del(&led_drv_dat->cdev); unregister_chrdev_region(led_drv_dat->dev_num, 1); return 0;}static struct platform_driver my_platform_driver = { .driver ={ .name = "light_up_led", .owner = THIS_MODULE, }, .probe = my_platform_driver_probe, .remove = my_platform_driver_remove, };module_platform_driver(my_platform_driver);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is light up led example for platform bus"); |
一个 platform_driver 可能被多个 platform_device 使用。如果写在module_exit函数中当设备被热拔出(或通过 sysfs 移除)时,资源不会被释放 。因此资源清理逻辑要放在remove函数中

