Cover image for Linux平台总线

Linux平台总线

字数 7.9k
阅读
访客

时间轴

时间轴

2025-11-15

init

本文介绍了Linux内核中的平台总线(Platform Bus)机制,阐述了其作为平台设备与平台驱动之间桥梁的作用,主要用于管理直接集成在SoC或主板上的设备。文章说明了平台总线模型将设备驱动与平台设备分离,提高了驱动代码的重用性和可移植性,并指出设备与驱动是独立对象。同时,文章详细讲解了platform_device结构体的关键字段(如名称、ID、device结构体、资源数量及指针)以及struct resource中起始地址、结束地址、名称和标志位等参数的含义,并列举了常见资源类型与属性标志位。

Linux 驱动笔记


平台总线

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 进行区分,可以将其设置为-1
  • struct 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.h#define platform_get_device_id(pdev)	((pdev)->id_entry)#define dev_is_platform(dev) ((dev)->bus == &platform_bus_type)#define to_platform_device(x) container_of((x), struct platform_device, dev)extern 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->devstruct 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
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/ioport.h> // struct resource#define MEM_START_ADDR 0xFDD60000#define MEM_END_ADDR 0xFDD60004#define IRQ_NUMBER 101static 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_table

  • prevent_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
#define platform_driver_register(drv) \	__platform_driver_register(drv, THIS_MODULE)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() */#define module_platform_driver(__platform_driver) \	module_driver(__platform_driver, platform_driver_register, \			platform_driver_unregister)/* 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. */#define builtin_platform_driver(__platform_driver) \	builtin_driver(__platform_driver, platform_driver_register)/* 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() */#define module_platform_driver_probe(__platform_driver, __platform_probe) \static int __init __platform_driver##_init(void) \{ \	return platform_driver_probe(&(__platform_driver), \				     __platform_probe);    \} \module_init(__platform_driver##_init); \static void __exit __platform_driver##_exit(void) \{ \	platform_driver_unregister(&(__platform_driver)); \} \module_exit(__platform_driver##_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. */#define builtin_platform_driver_probe(__platform_driver, __platform_probe) \static int __init __platform_driver##_init(void) \{ \	return platform_driver_probe(&(__platform_driver), \				     __platform_probe);    \} \device_initcall(__platform_driver##_init); \

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
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/ioport.h>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
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/ioport.h>/* 复用寄存器 */#define PMU_GRF_BASE 0xFDC20000#define GPIO0B_IOMUX_OFFSET 0xC#define GPIO0B_IOMUX (PMU_GRF_BASE + GPIO0B_IOMUX_OFFSET)/* 数据寄存器与方向寄存器 */#define GPIO0_BASE 0xFDD60000#define GPIO_SWPORT_DDR_L_OFFSET 0x8#define GPIO_SWPORT_DR_L_OFFSET 0x0#define GPIO_EXT_PORT_OFFSET 0x70// 方向寄存器#define GPIO0_SWPORT_DDR_L (GPIO0_BASE + GPIO_SWPORT_DDR_L_OFFSET)// 数据寄存器#define GPIO0_SWPORT_DR_L (GPIO0_BASE + GPIO_SWPORT_DR_L_OFFSET)// 外部输入寄存器,readonly#define GPIO0_EXT_PORT (GPIO0_BASE + GPIO_EXT_PORT_OFFSET)#define REG_SIZE 4struct 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
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/ioport.h>#include <linux/cdev.h>#include <linux/fs.h>#include <linux/slab.h>#include <linux/uaccess.h>#include <linux/io.h>#include <linux/ioctl.h>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);}#define LED_OPEN _IO('A', 0)#define LED_CLOSE _IO('A', 1)#define LED_STATUS _IOR('A', 2, 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函数中

评论加载中…