设备树 platform 驱动仍由 platform_driver 实现。变化在于设备实例通常由内核根据固件描述创建,驱动从已经绑定的设备取得资源和属性,而不必为每块板编写一份注册 platform_device 的 C 代码。
本文采用 Linux 6.8 的接口,通过 GPIO 描述符完成一个带 sysfs 状态的消费者示例。它演示匹配、每实例状态、延迟探测和解绑顺序;普通 GPIO LED 另给出更直接的 gpio-leds 配置。
1. 节点、设备和 probe 的四个阶段
跳转到“1. 节点、设备和 probe 的四个阶段”设备树节点可用 → 总线创建 Linux 设备 → 找到匹配驱动 → probe 初始化成功节点存在不保证设备被创建,也不保证驱动已加载或探测成功。创建规则由总线决定:simple-bus 等常见总线会递归填充,I²C/SPI 子设备由各自总线处理,AMBA 设备也有自己的路径。没有 reg 或 interrupts 的 GPIO 消费者仍可成为 platform 设备。

图中“地址加长度减一得到资源 end”的关系可保留,但有三处不能照搬:compatible 应与 OF 匹配表项比较,不是必须等于 driver.name;GIC 的 <0 168 4> 不能直接变成 F1C 的 IRQ_EINT168;普通中断消费者也不因使用一个 IRQ 就要填写 #interrupt-cells。
reg 的 cell 分组、ranges 转换和 GPIO 控制器资源归属见 设备树基础。不要通过 of_property_read_u32_index(..., 0/1/2/3, ...) 把地址、长度交替出现的四个 cell 当成四个寄存器地址。
2. platform 的实际匹配顺序
跳转到“2. platform 的实际匹配顺序”Linux 6.8 的 platform_match 按下面规则工作:
| 次序 | 规则 |
|---|---|
| 1 | 若设置了 driver_override,只按 override 与驱动名比较,直接决定结果 |
| 2 | 尝试 OF 兼容匹配,成功则匹配 |
| 3 | 尝试 ACPI 匹配,成功则匹配 |
| 4 | 若驱动提供 id_table,按 platform ID 表决定结果 |
| 5 | 没有该 ID 表时,才回退到 pdev->name 与 driver.name 比较 |
特别是:提供了 platform id_table 却没有匹配项时,不会继续通过同名兜底。驱动可以同时支持多个固件来源,但不能凭表中先后顺序推断每个节点一定会绑定哪一份外部模块。实际匹配代码
OF 表放在 .driver.of_match_table,platform ID 表则放在外层 .id_table。表需要空的结束项;MODULE_DEVICE_TABLE(of, ...) 生成模块别名信息,供系统按设备事件查找模块。别名不等于驱动已经被加载,也不替代 probe。
3. GPIO 消费者的板级描述
跳转到“3. GPIO 消费者的板级描述”以下片段假定板级设备树已包含 Linux 6.8 的 F1C100S SoC 描述并存在 pio 标签。PE3 和 active-low 沿用原笔记的接线意图;应用到实际板卡之前仍须根据原理图确认,不能由处理器型号推断 LED 一定接在该脚。
#include <dt-bindings/gpio/gpio.h>
/* Merge with an F1C100S board tree only after checking its schematic. *// { output-demo { compatible = "example,vlog-gpio"; output-gpios = <&pio 4 3 GPIO_ACTIVE_LOW>; /* PE3, logical 1 -> low. */ status = "okay"; };};output-gpios 与驱动中 devm_gpiod_get(dev, "output", ...) 对应。Allwinner GPIO 参数 <4 3 ...> 表示 E bank 的 3 号脚;末项表达逻辑极性。GPIOD_OUT_LOW 的初值是逻辑 0,在 active-low 接线上会输出物理高电平,使负逻辑负载处于未激活状态。GPIO 描述符接口
这个消费者没有直接寄存器资源,因此没有 reg,也不需要带单元地址的节点名。GPIO 控制器继续拥有 PIO 的整块 MMIO;消费者只申请它所需的引脚。example,vlog-gpio 是教学私有 binding,正式产品应为自己的硬件接口维护明确的 schema。
4. 完整驱动:实例、错误与清理顺序
跳转到“4. 完整驱动:实例、错误与清理顺序”保存为 vlog_gpio.c,并在该模块的 Kconfig 中按实际使用要求依赖 GPIO 子系统。示例直接要求有一根输出 GPIO,缺少引脚或提供者未就绪时保留原始错误,不把所有情况都改成“设备不存在”。
// SPDX-License-Identifier: GPL-2.0-only#include <linux/gpio/consumer.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/of.h>#include <linux/platform_device.h>#include <linux/slab.h>#include <linux/string.h>#include <linux/sysfs.h>
struct vlog_gpio_state { struct gpio_desc *output; struct mutex lock; bool enabled; /* Last commanded logical value, not electrical feedback. */};
static ssize_t enabled_show(struct device *dev, struct device_attribute *attr, char *buf){ struct vlog_gpio_state *s = dev_get_drvdata(dev); bool value;
mutex_lock(&s->lock); value = s->enabled; mutex_unlock(&s->lock); return sysfs_emit(buf, "%u\n", value ? 1U : 0U);}
static ssize_t enabled_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct vlog_gpio_state *s = dev_get_drvdata(dev); bool value; if (memchr(buf, '\0', count)) return -EINVAL; if (sysfs_streq(buf, "1") || sysfs_streq(buf, "on")) value = true; else if (sysfs_streq(buf, "0") || sysfs_streq(buf, "off")) value = false; else return -EINVAL; mutex_lock(&s->lock); gpiod_set_value_cansleep(s->output, value); s->enabled = value; mutex_unlock(&s->lock); return count;}
static DEVICE_ATTR_RW(enabled);static struct attribute *vlog_attrs[] = { &dev_attr_enabled.attr, NULL };ATTRIBUTE_GROUPS(vlog);
static void vlog_output_off(void *data){ struct vlog_gpio_state *s = data; gpiod_set_value_cansleep(s->output, 0);}
static int vlog_probe(struct platform_device *pdev){ struct device *dev = &pdev->dev; struct vlog_gpio_state *s; int ret;
s = devm_kzalloc(dev, sizeof(*s), GFP_KERNEL); if (!s) return -ENOMEM; mutex_init(&s->lock); s->output = devm_gpiod_get(dev, "output", GPIOD_OUT_LOW); if (IS_ERR(s->output)) return dev_err_probe(dev, PTR_ERR(s->output), "cannot acquire output\n"); platform_set_drvdata(pdev, s); ret = devm_add_action_or_reset(dev, vlog_output_off, s); if (ret) return ret; return devm_device_add_groups(dev, vlog_groups);}
/* An educational private binding; real LED hardware normally uses gpio-leds. */static const struct of_device_id vlog_of_match[] = { { .compatible = "example,vlog-gpio" }, { }};MODULE_DEVICE_TABLE(of, vlog_of_match);
static struct platform_driver vlog_driver = { .probe = vlog_probe, .driver = { .name = "vlog_gpio", .of_match_table = vlog_of_match, },};module_platform_driver(vlog_driver);MODULE_LICENSE("GPL");MODULE_DESCRIPTION("GPIO consumer with per-device command state");代码把每个实例的 GPIO、锁和最后一次命令值保存在自己的结构体中,避免多个设备共用一组全局寄存器指针。属性 enabled 精确接受 0/1/on/off,可带一个换行;非法输入或嵌入 NUL 返回错误,保持旧命令值。
gpiod_set_value_cansleep 可在进程上下文访问可能睡眠的 GPIO 控制器,active-low 由 GPIO 核心解释。这个设置接口返回 void,因此 enabled 表示已提交的逻辑命令,不是电平反馈或负载确实动作的证明;需要故障诊断时应增加实际反馈和相应错误接口。
资源申请顺序为:实例内存 → GPIO → 关闭输出的清理动作 → sysfs 属性组。失败或解绑时逆序执行:先撤销属性并结束其活动调用,再把输出置逻辑 0,释放 GPIO,最后释放实例。这样属性回调不会在 GPIO 已经释放后继续操作它。
该释放顺序针对失败/解绑,不是所有系统关机和掉电行为的保证。需要特定关机电平、休眠状态或上电毛刺约束时,还要设计对应回调、pinctrl 状态和外部电路。
5. 普通 LED 优先使用 gpio-leds
跳转到“5. 普通 LED 优先使用 gpio-leds”如果目标只是控制 GPIO LED,内核已有 gpio-leds,通常无需上面的自定义消费者。下面是替代 output-demo 的板级片段;两者不能同时申请 PE3:
#include <dt-bindings/gpio/gpio.h>
/* Alternative to output-demo: do not let two drivers claim PE3. *// { leds { compatible = "gpio-leds"; led-0 { label = "vlog:green:status"; gpios = <&pio 4 3 GPIO_ACTIVE_LOW>; default-state = "off"; }; };};CONFIG_LEDS_GPIO 及其所需子系统需在对应内核启用。绑定后通过 LED class 暴露的亮度和触发器等属性使用设备,具体路径由实际 LED 命名决定。label 在这里保留直观的历史命名方式;正式 binding 也可用标准颜色、功能和枚举属性组织名称。GPIO LED binding
6. 构建、观测与原示例迁移
跳转到“6. 构建、观测与原示例迁移”模块 Makefile 填写 obj-m += vlog_gpio.o,按 字符设备文章的外部模块构建方法 使用目标内核。设备树则应在所用内核/BSP 中预处理并构建;F1C100S 是 32 位 ARM,不能使用原笔记中混入的 ARCH=arm64 配置。
运行时分别检查设备树节点、/sys/bus/platform/devices 中的设备、其 driver 链接以及探测日志。GPIO 被另一驱动占用、提供者未就绪、pinctrl/供电错误,都可能使一个存在的节点不能成功绑定。
原代码公开字符设备后才映射资源,也对映射错误、设备创建错误和用户复制错误处理不完整。迁移时必须先确定真正需要的用户接口:小型状态可用设备属性;数据流可用字符设备或 misc。后两者还需要处理已打开文件跨越设备解绑的寿命,不能只把原来四个全局 MMIO 指针换成 devm_ 就完成修正。
原始 PIO 地址和 PE3/PE12 位域保留在 GPIOE 核对表。确实拥有独立寄存器块的其他外设可用 devm_platform_ioremap_resource,但访问宽度、时钟、复位和并发仍由相应驱动约束;GPIO 消费者没有必要重新映射控制器。
7. 验证范围
跳转到“7. 验证范围”完整 C 模块已针对 Ubuntu 6.8.0-146-generic 的 x86-64 构建资料编译并完成 MODPOST;属性回调使用 GPIO 模型检查合法/非法命令与实例隔离。板级片段在 Linux 6.8 F1C100S SoC 描述背景下完成预处理和 DTB 编码检查。未加载驱动、未驱动实际引脚;DTC 通过也不等同于私有 binding 的完整 schema 或板上电气验证。