Platform子系统

Linux 的 Platform 子系统(平台总线) 是 Linux 设备驱动模型(Device Model)的绝对基石。将那些直接挂在 CPU 内存空间、不支持热插拔、不能被自动探测的设备,强行纳入 Linux 统一的“总线-设备-驱动”模型中。

1. 核心数据结构

1.1 struct platform_device (平台设备,一般不用自己定义,设备树自动生成,驱动里只用 pdev。)

代表具体的硬件资源(如寄存器物理地址、中断号)。在现代引入了设备树(Device Tree)后,这个结构体几乎不需要你手动编写,内核会在解析 .dts 文件时自动为你创建。

struct platform_device {
	const char	*name;        /* 设备名字:旧时代用于匹配驱动,必须与驱动名一致 */
	int		id;           /* 设备 ID:同名设备的编号,通常填 -1 表示唯一 */
	struct device	dev;          /* 继承的通用设备模型基类 */
	u32		num_resources;/* 硬件资源的数量 */
	struct resource	*resource;    /* 硬件资源数组指针(存放物理地址、中断号等) */
	const struct platform_device_id	*id_entry;
	/* ... 省略其他内部成员 ... */
};

1.2 struct platform_driver (平台驱动)

这是你写驱动时必须实现的核心结构体。

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;              /* 通用驱动结构:包含设备树匹配表 of_match_table */
	const struct platform_device_id *id_table;/* 旧时代的 ID 匹配表 */
};

1.3. 设备树匹配表

struct of_device_id {
    char compatible[128];  // 和设备树 compatible 完全一样
    const void *data;
};

2. 常用函数与宏 (API 速查手册)

2.1 module_platform_driver(my_platform_driver)

这是现代 Platform 驱动最常用的宏,它帮你省去了手写 module_init 和 module_exit 的麻烦。

2.2 硬件资源获取 API

当你的 probe 函数被调用时,第一件事通常是从 platform_device 中提取设备树里定义的寄存器地址和中断号。

2.2.1 struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)

作用:从内核的 platform_device 结构体中提取指定的硬件资源(如物理内存地址范围)。这通常对应设备树(DTS)中 reg 属性定义的内容。
参数:

  • dev:指向当前平台设备的指针 (struct platform_device *)。
  • dtype:资源类型宏。最常用的是 IORESOURCE_MEM(内存映射的 I/O 寄存器地址)。
  • dnum:资源的索引号。如果设备树里只有一个 reg 属性段,填 0;如果有多个,依次填 1, 2...。

返回值:

  • d成功:返回指向 struct resource 的指针,里面包含了物理首地址和末地址。
  • d失败:返回 NULL。

2.2.2 int platform_get_irq(struct platform_device *dev, unsigned int num)

作用:获取该平台设备对应的虚拟中断号。内核会在解析设备树的 interrupts 属性时,自动将硬件中断号映射为 Linux 内部的虚拟中断号。
参数:

  • dev:指向当前平台设备的指针。
  • num:中断索引号。通常设备只有一个中断线,填 0。

返回值:

  • 成功:返回一个大于 0 的整数(有效的 Linux 虚拟中断号)。
  • 失败:返回负数错误码(如 -ENXIO)。

2.2.3 static inline int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)

作用:直接从设备树节点中读取一个 32 位无符号整数属性值。在开发像 SPI 或 CAN 这样的异构总线网关时,常用来读取自定义的硬件配置(如 clock-frequency 或自定义的引脚复用编号)。
参数:

  • node:指向设备树节点的指针(通常通过 pdev->dev.of_node 获取)。
  • propname:属性的名称字符串(如 "spi-max-frequency")。
  • out_value:输出参数,用于保存读到的 32 位整数值的指针 (u32 *)。

返回值:

  • 成功:返回 0。
  • 失败:返回负数错误码(如 -EINVAL 表示属性不存在或格式不对)。

2.3 私有数据存储 API

2.3.1 static inline void platform_set_drvdata(struct platform_device *pdev, void *data)

作用:将驱动自定义的私有数据结构体指针“挂载”到平台设备上。通常在 probe 函数分配完内存并初始化后调用。
参数:

  • pdev:平台设备指针。
  • data:指向你自定义私有数据结构体的指针(void *)。

2.3.2 static inline void *platform_get_drvdata(const struct platform_device *pdev)

作用:把刚才存进去的私有数据指针取出来。通常在 remove 函数、休眠/唤醒回调、或者中断处理函数中使用。
参数:pdev:平台设备指针。

2.4 #define platform_driver_register(drv) __platform_driver_register(drv, THIS_MODULE)

作用:将你编写的 platform_driver 注册到内核的 Platform 总线上。注册后,总线会自动遍历现有的设备树节点,寻找 compatible 字符串匹配的设备;如果找到,就会触发你写的 probe 函数。
参数:drv:指向填充好的 struct platform_driver 结构体的指针。

2.5 void platform_driver_unregister(struct platform_driver *drv)

作用:从内核的 Platform 总线中注销该驱动程序。这会引发一系列连锁反应:内核会断开所有与该驱动绑定的设备,并依次调用驱动的 remove 函数来释放硬件资源。
参数:drv:指向要注销的 struct platform_driver 结构体的指针。

2.6 void __iomem *devm_ioremap_resource(struct device *dev, const struct resource *res)

作用:将 platform_get_resource 获取的物理内存资源进行检查、申请并映射为虚拟地址。
返回值:成功返回可供 readl/writel 使用的虚拟地址指针;失败返回错误指针(用 IS_ERR() 判断)。

2.7 void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp)

作用:分配一块指定大小的内存,并将内存清零。这块内存会在设备驱动注销(卸载)时自动被内核释放,你不需要在 remove 函数中写对应的 kfree。
参数:

  • dev:申请内存的设备指针(struct device *)。
  • size:要申请的内存大小(字节数)。
  • gfp:分配标志。通常填 GFP_KERNEL(允许进程休眠);如果在中断等原子上下文中,填 GFP_ATOMIC。

2.8 int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)

作用:向内核申请并注册一个硬件中断的服务函数(ISR)。设备卸载时,内核会自动注销该中断。
参数:

  • dev:申请中断的设备指针。
  • irq:之前通过 platform_get_irq 获取到的虚拟中断号。
  • handler:中断处理函数指针(即当硬件产生中断时,内核去调用的那个函数)。
  • irqflags:触发标志(如 IRQF_TRIGGER_RISING 上升沿触发,IRQF_SHARED 共享中断)。如果设备树中已经配置了触发类型,这里可以直接填 0。
  • devname:中断名称字符串。注册成功后,你可以在终端输入 cat /proc/interrupts 看到这个名字。
  • dev_id:私有数据指针。发生中断时,内核会把这个指针原封不动地传给你的 handler 函数。通常传入你自己定义的驱动结构体指针。

返回值:

  • 成功:返回 0。
  • 失败:返回负数错误码(如 -EBUSY 表示中断号已被占用)。

2.9 struct clk *devm_clk_get(struct device *dev, const char *id)

作用:解析设备树,查找并获取指定的硬件时钟(Clock)的句柄。设备卸载时自动调用 clk_put 释放句柄。
参数:

  • dev:设备指针。
  • id:时钟的名称字符串。它对应设备树节点中的 clock-names 属性。如果设备树里该节点只有一个时钟,且没有写 clock-names,这里可以直接填 NULL。

返回值:

  • 成功:返回一个指向 struct clk 的时钟指针。
  • 失败:返回错误指针。

2.10 int clk_prepare_enable(struct clk *clk)

作用:这是一个复合函数,内部依次执行了 clk_prepare(准备时钟,可能会休眠,比如等待 PLL 锁相环稳定)和 clk_enable(使能时钟,属于原子操作,直接打开门控开关)。
参数:clk:刚才通过 devm_clk_get 获取到的时钟指针。
返回值:

  • 成功:返回 0。
  • 失败:返回负数错误码。
posted @ 2026-04-15 19:48  r5ett  阅读(10)  评论(0)    收藏  举报