RT-Thread GPIO/PIN 设备模型(DM):全局虚拟引脚命名空间与设备树 `gpios` 解析实战

发布时间:2026/10/7 20:26:30
RT-Thread GPIO/PIN 设备模型(DM):全局虚拟引脚命名空间与设备树 `gpios` 解析实战 操作系统嵌入式物联网嵌入式OSRTOS【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址https://gitcode.com/gh_mirrors/rt/rt-thread点击查看免费下载RT-Thread 在启用设备模型Device ModelDM后PIN 框架不再依赖 BSP 里硬编码的GET_PIN(port, n)引脚表而是由每个 GPIO 控制器按设备树注册一段连续的虚拟引脚号应用统一通过rt_pin_mode/rt_pin_write/rt_pin_read操作驱动侧则用rt_pin_get_named_pin按属性名如reset、led取引脚。本文以仓库内 dm.md 为骨架结合components/drivers/pin/下的真实源码完整讲解 DM 引脚框架的 Kconfig 配置、全局命名空间架构、平台驱动移植清单、设备树解析与 GPIO 中断PIC实现以及常见踩坑点帮助你在新平台上一口气打通 GPIO 的注册、解析与中断链路。一、为什么需要 DM 引脚框架传统 BSP 的 PIN 驱动如drv_gpio.c维护一张静态pins[]表把“RT-Thread 引脚号”映射到具体的芯片端口/引脚应用必须靠GET_PIN(port, pin)宏或查表才能拿到引脚号。这套方案在单控制器、引脚固定的场景下够用但在多 GPIO 控制器SoC 常有多组 GPIO bank、需要与设备树DT联动的平台上就显得僵硬。RT_USING_PIN与RT_USING_DM同时开启后框架引入了一套全局 GPIO 命名空间每个平台 GPIO 控制器在 probe 时注册一段连续虚拟引脚号范围应用代码仍然使用rt_pin_mode/rt_pin_write/rt_pin_read与经典 PIN API 完全一致参见 pin.md驱动代码使用rt_pin_get_named_pin从设备树gpios/xxx-gpios属性中解析出虚拟引脚并可选使用GPIO IRQ domainPIC实现中断。这一设计把“引脚是谁、在哪一段、电平极性如何”全部交给设备树描述BSP 与上层驱动解耦代码可以跨平台复用。二、Kconfig开启 DM 引脚路径配置项作用RT_USING_PINGPIO 框架总开关默认ycomponents/drivers/pin/Kconfig中default yRT_USING_DM本篇文章描述的 DM 路径的前提RT_PIN_PL061树内 ARM PL061 GPIO 控制器驱动default n依赖RT_USING_DM与RT_USING_PINSOC_DM_PIN_DIRBSP 通过osource $(SOC_DM_PIN_DIR)/Kconfig引入各自 SoC 的 GPIO 驱动从 Kconfig 源码可以看到框架的扩展方式menuconfig RT_USING_PIN bool Using Generic GPIO device drivers default y config RT_PIN_PL061 bool ARM PL061 depends on RT_USING_DM depends on RT_USING_PIN default n if RT_USING_DM RT_USING_PIN osource $(SOC_DM_PIN_DIR)/Kconfig endif也就是说通用框架只内置 PL061 作为参考驱动其余 SoC GPIO 驱动由各 BSP 通过SOC_DM_PIN_DIR挂载保证框架主体干净、平台差异下沉。三、架构全局虚拟引脚命名空间DT: gpio-controller node (#gpio-cells, interrupt-controller optional) | | platform probe v struct rt_device_pin rt_pin_ops (per controller) | -- pin_api_init(gpio, pin_nr) → global pin_start..pin_startnr-1 -- pin_pic_init(gpio, irq) → optional per-line PIC domain -- rt_dm_dev_bind_fwdata → rt_ofw_data(np) gpio | v First controller also: rt_device_pin_register(gpio, pin_api_dm_ops) | v Consumers: rt_pin_get_named_pin(dev, foo, index, mode, val) rt_pin_mode(pin, mode); rt_pin_write(pin, val);核心概念对照概念含义Local pin传入rt_pin_ops回调的引脚索引范围为0 … pin_nr-1即控制器内部的相对编号Virtual pinpin_start local是rt_pin_*系列 API 与rt_pin_get_named_pin返回并使用的全局值pin_device_find由虚拟引脚反查所属的struct rt_device_pin完成“虚拟号 → 控制器 本地号”的映射在 dev_pin_dm.c 中可以看到这一机制的具体实现。全局状态由三部分组成static rt_size_t pin_total_nr 0; static RT_DEFINE_SPINLOCK(pin_lock); static rt_list_t pin_nodes RT_LIST_OBJECT_INIT(pin_nodes);pin_device_find遍历pin_nodes链表判断虚拟引脚是否落在某个控制器的[pin_start, pin_start pin_nr)区间内rt_list_for_each_entry(gpio_tmp, pin_nodes, list) { if (pin gpio_tmp-pin_start pin - gpio_tmp-pin_start gpio_tmp-pin_nr) { gpio gpio_tmp; break; } }所有控制器都挂在pin_nodes链表上pin_total_nr只增不减引脚号区间不回收因此虚拟引脚号在整个系统生命周期内保持稳定、不重复分配。正是这个“先注册先分配、区间单调增长”的策略保证了设备树解析出的引脚号长期有效。pin_api_init还负责为第一个注册的控制器注册全局gpio设备dev_pin_dm.cif (rt_list_isempty(pin_nodes)) { rt_spin_unlock(pin_lock); rt_device_pin_register(gpio, pin_api_dm_ops, RT_NULL); rt_spin_lock(pin_lock); } gpio-pin_start pin_total_nr; gpio-pin_nr pin_nr; pin_total_nr pin_nr; rt_list_init(gpio-list); rt_list_insert_before(pin_nodes, gpio-list);pin_api_dm_ops是 DM 层的shim适配层所有rt_pin_*调用先进到这一层由pin_device_find找到控制器后再把“虚拟引脚 → 本地引脚”转换最终落到具体控制器的rt_pin_ops回调上例如static void pin_api_write(struct rt_device *device, rt_base_t pin, rt_uint8_t value) { struct rt_device_pin *gpio pin_device_find(pin); if (gpio gpio-ops-pin_write) { gpio-ops-pin_write(gpio-parent, pin - gpio-pin_start, value); } }四、平台驱动移植清单Platform driver checklist要让一个新 GPIO 控制器接入 DM 引脚框架按以下步骤实现参考驱动为 pin-pl061.c 的pl061_probe定义struct rt_device_pin可以直接使用也可以把它嵌入私有结构体的第一个成员便于用rt_container_of从struct rt_device反查私有数据。PL061 的做法即是如此struct pl061 { struct rt_device_pin parent; /* 必须位于第一个成员 */ int irq; void *base; struct rt_clk *pclk; struct rt_spinlock spinlock; }; #define raw_to_pl061(raw) rt_container_of(raw, struct pl061, parent)实现struct rt_pin_ops至少实现pin_mode、pin_write、pin_read三个回调PL061 的pl061_pin_ops还额外提供了pin_irq_enable与pin_irq_mode。probe函数完成寄存器 iomap、时钟使能、rt_dm_dev_bind_fwdata(dev, RT_NULL, gpio-parent)绑定固件数据这样设备树节点rt_ofw_data(np)才能取到struct rt_device_pin *。PL061 的 probe 流程是rt_dm_dev_iomap→rt_dm_dev_get_irq→rt_clk_get_by_name/rt_clk_prepare_enable→rt_dm_dev_bind_fwdata。pin_api_init(gpio-parent, gpio_count)分配pin_start把控制器挂上全局链表见第三节。如果 GPIO 共享一根中断线如 PL061 的 8 个引脚共享一个 IRQirq rt_dm_dev_get_irq(dev, 0)取中断号pin_pic_init(gpio-parent, irq)建立逐引脚 PIC domain硬件 ISR 中循环读取 pending 位逐位调用pin_pic_handle_isr(gpio-parent, local_pin)分发用rt_hw_interrupt_install(irq, …)安装 ISR 并rt_hw_interrupt_umask(irq)使能。PL061 的中断处理是标准模板pin-pl061.cstatic void pl061_isr(int irqno, void *param) { ... pending pl061_read(pl061, PL061_MIS); if (pending) { for (int pin 0; pin PL061_GPIO_NR; pin) { if (pending RT_BIT(pin)) { mask | RT_BIT(pin); pin_pic_handle_isr(pl061-parent, pin); } } pl061_write(pl061, PL061_IC, mask); /* 清中断 */ } }而 probe 末尾的安装动作则是pin_api_init(pl061-parent, PL061_GPIO_NR); pin_pic_init(pl061-parent, pl061-irq); rt_hw_interrupt_install(pl061-irq, pl061_isr, pl061, gpio-pl061); rt_hw_interrupt_umask(pl061-irq);可选实现pin_parse当#gpio-cells不止 1 个例如带 flags 的 2-cell 描述时用于把args[]解码为本地引脚 flagsflags 语义来自dt-bindings/pin/pin.h。未实现时框架默认取args[0]为本地引脚。最后注册平台驱动PL061 通过INIT_SUBSYS_EXPORT在子系统初始化阶段注册compatible 为arm,pl061static const struct rt_ofw_node_id pl061_ofw_ids[] { { .compatible arm,pl061 }, { /* sentinel */ } }; static struct rt_platform_driver pl061_driver { .name pin-pl061, .ids pl061_ofw_ids, .probe pl061_probe, };五、struct rt_pin_opsDM 相关的回调集合struct rt_pin_ops定义了控制器需要向框架提供的全部能力定义见 dev_pin.h回调作用pin_mode设置输入/输出/上拉/开漏等模式pin_write/pin_read写/读电平pin_attach_irq直接绑定逐引脚 ISR可选pin_detach_irq移除处理函数pin_irq_enable使能/禁止线路中断pin_irq_mode使用 legacy 中断路径时配置边沿/电平触发pin_parse解码#gpio-cells→ 本地引脚 flagspin_get名字 → 虚拟引脚可选pin_debounce设置消抖时间这里有一个重要的双路径设计如果控制器没有实现pin_attach_irqdev_pin_dm.c会把中断处理函数存进legacy_isr[]数组并依赖pin_irq_modepin_pic_handle_isr来工作。看 dev_pin_dm.c 的pin_api_attach_irqif (!gpio-ops-pin_attach_irq) { rt_err_t err; struct rt_pin_irq_hdr *legacy_isr; if ((err gpio-ops-pin_irq_mode(gpio-parent, pin_index, mode))) return err; legacy_isr gpio-legacy_isr[pin_index]; legacy_isr-pin pin_index; legacy_isr-mode mode; legacy_isr-hdr hdr; legacy_isr-args args; return RT_EOK; }legacy_isr数组由pin_pic_init通过rt_calloc(gpio-pin_nr, sizeof(*gpio-legacy_isr))分配因此每个本地引脚都有一个struct rt_pin_irq_hdr槽位。pin_pic_handle_isr在分发时同时走 PIC 路径和 legacy 回调dev_pin_dm.cpirq rt_pic_find_irq(irqchip-parent, pin_index); if (pirq-irq 0) err rt_pic_handle_isr(pirq); legacy_isr gpio-legacy_isr[pin_index]; if (legacy_isr-hdr) legacy_isr-hdr(legacy_isr-args);六、设备树gpios属性解析设备树中的 GPIO 引用属性由 dev_pin_ofw.c 中的rt_ofw_get_named_pin(np, propname, index, out_mode, out_value)负责解析。属性名查找顺序若传入propname如reset依次尝试{propname}-gpios、{propname}-gpio若propname为NULL依次尝试gpios、gpio。这一点在源码中有明确体现static const char * const gpio_suffixes[] { gpios, gpio };完整解析流程rt_ofw_parse_phandle_cells(..., #gpio-cells, …)解析 phandle cells取出 GPIO provider 节点与args[]若 provider 尚未 probe调用rt_platform_ofw_request主动请求加载平台驱动这正是“Provider not probed”坑的官方解法调pin_parse或按默认规则取args[0]为本地引脚将flags转换成PIN_MODE_*与有效电平active level返回local pin_dev-pin_start即全局虚拟引脚。其中 flags 的转换逻辑dev_pin_ofw.c值得细看value PIN_LOW; mode PIN_MODE_OUTPUT; if (pin_dev-ops-pin_parse) pin pin_dev-ops-pin_parse(pin_dev-parent, pin_args, flags); else /* We always assume that the args[0] is the pin number if driver not implemented pin_parse. */ pin pin_args.args[0]; if (out_mode) { if (flags PIN_OPEN_DRAIN) mode PIN_MODE_OUTPUT_OD; switch (flags RT_GENMASK(6, 4)) { case PIN_PULL_UP: mode PIN_MODE_INPUT_PULLUP; break; case PIN_PULL_DOWN: mode PIN_MODE_INPUT_PULLDOWN; break; case PIN_PULL_DISABLE: mode PIN_MODE_INPUT; break; } } if (out_value) { if ((flags 1) PIN_ACTIVE_HIGH) value PIN_HIGH; else if ((flags 1) PIN_ACTIVE_LOW) value PIN_LOW; }可以看到flags的 bit0 表示有效电平极性PIN_ACTIVE_HIGH/PIN_ACTIVE_LOWbit4~6 表示上下拉PIN_PULL_UP/PIN_PULL_DOWN/PIN_PULL_DISABLE这些常量定义在dt-bindings/pin/pin.h。也就是说设备树里“低有效复位脚”这种描述会被自动换算成PIN_MODE_OUTPUTPIN_LOW应用层无需关心极性细节。设备device侧封装提供两个便捷 APIdev_pin_dm.crt_ssize_t rt_pin_get_named_pin(struct rt_device *dev, const char *propname, int index, rt_uint8_t *out_mode, rt_uint8_t *out_value); rt_ssize_t rt_pin_get_named_pin_count(struct rt_device *dev, const char *propname);rt_pin_get_named_pin内部以dev-ofw_node为节点调用rt_ofw_get_named_pin因此只要驱动持有struct rt_device *且其节点有 GPIO 属性即可解析返回值为负数时表示错误如-RT_ENOSYS/-RT_EINVAL。典型 probe 用法pin rt_pin_get_named_pin(dev, reset, 0, mode, active); if (pin 0) return pin; rt_pin_mode(pin, mode); rt_pin_write(pin, active); /* 按设备树 flags 写有效或无效电平 */仓库内可参考的现成实例phye-generic-usb.creset属性、gpio-restart.c、led-gpio.c子节点用法、backlight-gpio.c。七、GPIO 中断与 PICInterrupt Controllerstruct rt_pin_irqchip被内嵌在struct rt_device_pin中紧跟parent之后头文件注释明确要求“MUST keep the order member after parent”见 dev_pin.hstruct rt_device_pin { struct rt_device parent; #ifdef RT_USING_DM struct rt_pin_irqchip irqchip; /* PIC 域 */ rt_base_t pin_start; rt_size_t pin_nr; rt_list_t list; struct rt_pin_irq_hdr *legacy_isr; #endif const struct rt_pin_ops *ops; };相关 API 与职责API作用pin_pic_init(gpio, irq)调用rt_pic_linear_irq建立线性 PIC 域、分配legacy_isr数组、挂载pin_dm_opspin_pic_handle_isr(gpio, local_pin)分发PIC路径和/或legacy_isr回调见第五节代码对逐引脚per-line的设备树中断2 cellspin flags使用pin_dm_ops.irq_parse/irq_mapirq_parse把args[0]作为 hwirq本地引脚、args[1] RT_IRQ_MODE_MASK作为触发模式dev_pin_dm.cirq_map中rt_pic_cascade(pirq, gpio-irqchip.irq)把 GPIO 块的中断级联到控制器通过rt_dm_dev_get_irq取得的总中断线实现“多引脚共享一条物理 IRQ”的经典场景dev_pin_dm.c。pin_dm_ops的掩码/去掩码直接映射到控制器的pin_irq_enablestatic void pin_dm_irq_mask(struct rt_pic_irq *pirq) { struct rt_device_pin *gpio pirq-pic-priv_data; gpio-ops-pin_irq_enable(gpio-parent, pirq-hwirq, 0); } static void pin_dm_irq_unmask(struct rt_pic_irq *pirq) { struct rt_device_pin *gpio pirq-pic-priv_data; gpio-ops-pin_irq_enable(gpio-parent, pirq-hwirq, 1); }触发模式的转换在pin_dm_irq_set_triger_mode中完成RT_IRQ_MODE_EDGE_RISING→PIN_IRQ_MODE_RISINGRT_IRQ_MODE_EDGE_BOTH→PIN_IRQ_MODE_RISING_FALLINGRT_IRQ_MODE_LEVEL_HIGH/LOW→PIN_IRQ_MODE_HIGH_LEVEL/LOW_LEVEL。需要特别注意的是对于调用rt_pin_attach_irq(virtual_pin, …)但设备树该行没有 interrupt 描述的情况legacy 路径仍然生效——框架在 attach 时通过pin_irq_mode配置触发模式、把回调存入legacy_isr[]硬件使能则依靠pin_irq_enable。因此这类控制器如 PL061即便没有实现pin_attach_irq应用层依然能正常使用中断回调。八、应用与驱动侧的使用方式DM 系统中应用/驱动应优先使用命名解析而不是从旧的 BSP 引脚表抄GET_PIN(port, n)/* Not GET_PIN(port, n) from old BSP tables */ pin rt_pin_get_named_pin(pdev-parent, led, 0, mode, NULL); rt_pin_mode(pin, mode);此后对该pin虚拟引脚的rt_pin_write/rt_pin_read/rt_pin_attach_irq调用与经典 PIN API 完全一致用户态代码无需感知控制器数量与引脚布局。经典 API 的完整能力设置模式、读写电平、绑定/使能中断参见 pin.md。需要澄清适用范围GET_PIN/drv_gpio.c静态引脚表只适用于非 DM 的 BSP一旦启用RT_USING_DM引脚号来源就切换为设备树 pin_api_init的全局分配。九、常见坑与规避Pitfalls问题后果规避方法跳过pin_api_initrt_ofw_get_named_pin会把错误的pin_start加上去pin_start为 0/未初始化返回错误的虚拟引脚probe 中必须调用pin_api_init(gpio-parent, pin_nr)缺少pin_parse仅使用args[0]当#gpio-cells 1 且带 flags 时引脚/极性解析错误为多 cell 控制器实现pin_parse解码本地引脚与 flags有中断却不调用pin_pic_init共享中断线控制器无法逐引脚分发rt_pic_find_irq找不到 IRQ必须调用pin_pic_init并在 ISR 中逐位调用pin_pic_handle_isrpin_attach_irq为 NULL 时漏掉使能回调已注册但中断不触发attach 之后必须通过pin_irq_enable打开对应引脚的中断使能GPIO provider 尚未 probert_ofw_data(np)为 NULL解析返回-RT_ERROR确保注册了平台驱动框架也会自动调用rt_platform_ofw_request请求加载十、源码路径速查文件作用dev_pin_dm.c全局引脚表、pin_api_init、pin_pic_init、DMrt_pin_opsshim、rt_pin_get_named_pin设备侧封装dev_pin_ofw.crt_ofw_get_named_pin/rt_ofw_get_named_pin_countgpios/gpio属性解析与 flags 转换dev_pin_dm.hpin_api_init/pin_pic_init/pin_pic_handle_isr/pin_gpio_requestRT_USING_PINCTRL声明dev_pin.c无 DM 控制器时的 legacy 单gpio设备路径pin-pl061.c参考平台驱动arm,pl061完整展示了 probe、ISR、PIC 集成dev_pin.hstruct rt_device_pin、struct rt_pin_irqchip、PIN 模式/电平宏定义KconfigRT_USING_PIN、RT_PIN_PL061、SOC_DM_PIN_DIR配置入口综上DM 引脚框架的落地路径非常清晰Kconfig 开配置 → 控制器实现rt_pin_ops→ probe 里pin_api_initpin_pic_init→ 设备树gpios属性驱动上层命名解析 → PIC 级联承载中断。对照 pin-pl061.c 移植新控制器再配合rt_pin_get_named_pin编写消费驱动即可完全摆脱静态引脚表让 GPIO 能力随设备树“即插即用”。赞分享操作系统嵌入式物联网嵌入式OSRTOS【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址https://gitcode.com/gh_mirrors/rt/rt-thread点击查看免费下载相关推荐RT-Thread PIN 设备驱动详解从引脚编号获取到 GPIO 中断应用实战RT Thread PIN 设备驱动详解从引脚编号获取到 GPIO 中断应用实战 RT Thread 将芯片的 GPIO 引脚抽象为统一的 PIN 设备通过操作系统嵌入式物联网嵌入式OSRTOSRT-Thread SPI 设备模型DM实战指南从设备树扫描到驱动绑定RT Thread SPI 设备模型DM实战指南从设备树扫描到驱动绑定 本文以 RT Thread 开源仓库中的 SPI 设备模型Device Mode操作系统嵌入式物联网嵌入式OSRTOSRT-Thread Mailbox 设备模型DM详解控制器注册、设备树绑定与 PIC 邮箱实战RT Thread Mailbox 设备模型DM详解控制器注册、设备树绑定与 PIC 邮箱实战 本文以 RT Thread 驱动框架DMDevice操作系统嵌入式物联网嵌入式OSRTOS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询