拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Zephyr BSP: 07-SoC驱动与设备树绑定

摘要:承接第 06 篇的 Devicetree 实战,本篇进入 SoC BSP 的驱动层,核心主线是「Binding 定义规则、DTS 描述实例、Driver 执行操作」。通过一个完整 UART 实例,串联 Binding YAML → Devicetree 节点 → Driver 宏的编译期链路,并剖析 Config/Data 模式与多实例生成机制,为下一篇 UART Driver 最小实战铺路。SoC Driver 与 Devicetree Binding这一篇正好进入Zephyr SoC BSP 真正的"驱动层"。你前面已经建立了这条主线:01 Zephyr Hardware Model02 Architecture → SoC → Board03 Zephyr SoC Porting04 SoC Porting 实战05 Zephyr SoC Devicetree06 SoC Devicetree 实战07 SoC Driver + Devicetree Binding ← 现在对于你的最终目标——把公司自己的 SoC 加入 Zephyr 开发生态——这一篇尤其重要,因为从这里开始,你要理解:本文核心结论速览在深入正文之前,先把本篇最关键的几条结论放在这里,方便你带着主线阅读:三者分工:Binding 定义规则(属性 schema),DTS 描述实例(硬件长什么样),Driver 执行操作(如何控制硬件)。三者通过compatible这条"钥匙"串成完整链路。compatible 匹配机制:Devicetree 节点里的compatible = "mycompany,myuart"与 Binding YAML 中的compatible完全一致时,Binding 才会被应用;Driver 再通过#define DT_DRV_COMPAT mycompany_myuart(逗号转下划线)绑定到同一批节点。编译期生成原理:DT_INST_*宏(如DT_INST_REG_ADDR、DT_INST_IRQ)不是运行时查询,而是 Devicetree Compiler 在编译期把节点属性展开成字面量常量,直接写进config结构体。多实例机制:DT_INST_FOREACH_STATUS_OKAY(MYUART_DEFINE)在编译期遍历所有status = "okay"的同 compatible 节点,逐个展开宏,让一个 Driver 自动服务多个硬件实例(uart0 → inst=0,uart1 → inst=1)。Config/Data 设计模式:config保存"这个硬件是什么"(编译期确定),data保存"当前处于什么状态"(运行时可变),这是 Zephyr Driver 的核心思维。SoC BSP 职责边界:硬件描述问题看 Devicetree/Binding,硬件操作问题看 Driver,SoC 初始化问题看 SoC Porting,板级连接问题看 Board DTS——四类问题各有归属,不要混为一谈。SoC 硬件资源如何通过 Devicetree 描述,再由 Driver 读取这些描述并控制硬件。一、先建立整体认识很多初学者会把这几个概念混在一起:Devicetree Binding Driver HAL SoC Board实际上它们各自解决不同的问题。可以先记住:Zephyr Application │ ▼ Zephyr API │ ▼ Driver │ ┌──────────┴──────────┐ │ │ Devicetree HAL / Register │ │ ▼ ▼ 硬件描述 硬件操作 │ │ └──────────┬──────────┘ ▼ SoC例如:gpio_pin_set_dt(led, 1);Application 并不知道:GPIO controller 在哪里 哪个寄存器 哪个 bit clock 怎么开 pinmux 怎么配置为了更直观地看清六者的完整调用链和数据流,这里给出一张总览图:┌─────────────────────────────────────────────────────────────────────┐ │ Zephyr Application │ │ (调用 Zephyr API,不感知底层硬件细节) │ └───────────────────────────────┬─────────────────────────────────────┘ │ ① 调用标准 API(如 uart_poll_out) ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Zephyr API │ │ (uart / gpio / spi 等子系统对外统一接口) │ └───────────────────────────────┬─────────────────────────────────────┘ │ ② 分发到具体 Driver 的 API 回调 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Driver │ │ ┌───────────────────────┐ ┌───────────────────────────┐ │ │ │ config(编译期常量) │ │ data(运行时状态) │ │ │ │ 来自 DT_INST_* 宏 │ │ 波特率、缓冲、标志位等 │ │ │ └───────────┬───────────┘ └───────────────────────────┘ │ │ │ ③ 读取编译期生成的硬件配置 │ └───────────────┼─────────────────────────────────────────────────────┘ │ ④ 通过 DT_INST_* 宏取地址/中断/时钟 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Devicetree(DTS)│ │ uart0: serial@40000000{compatible="mycompany,myuart";...}│ │ (描述硬件实例:地址、中断、时钟、引脚) │ └───────────────┬─────────────────────────────────────────────────────┘ │ ⑤ 节点属性由 Binding 校验合法性 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Binding YAML │ │ compatible:"mycompany,myuart"│ │ properties: reg / interrupts / clocks... │ │ (定义属性 schema,编译期被 Devicetree Compiler 读取) │ └───────────────┬─────────────────────────────────────────────────────┘ │ ⑥ 校验通过后,节点属性被编译成宏 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ HAL │ │ (寄存器读写封装:sys_read32 / sys_write32,屏蔽位操作细节) │ └───────────────┬─────────────────────────────────────────────────────┘ │ ⑦ 最终读写 SoC 物理寄存器 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ SoC │ │ (UART / GPIO / SPI 等外设的物理寄存器,由 SoC Porting 提供时钟、 │ │ 中断控制器、低层启动等基础能力) │ └───────────────┬─────────────────────────────────────────────────────┘ │ ⑧ 引脚/板级连接由 Board DTS 决定 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Board │ │ (板级 DTS / overlay:UART0 接到哪个 pin、LED 接哪个 GPIO 等) │ └─────────────────────────────────────────────────────────────────────┘这张图把六者的职责和相邻层之间的接口串成一条完整链路,可以这样理解:Application → Zephyr API:应用只调用标准 API,不感知底层硬件细节,接口是uart_poll_out()、gpio_pin_set_dt()这类统一函数。Zephyr API → Driver:API 层把调用分发到具体 Driver 的 API 回调(如uart_driver_api中的poll_out),接口是struct uart_driver_api。Driver → Devicetree → Binding:Driver 通过DT_INST_*宏在编译期读取 Devicetree 节点属性,而节点属性是否合法由 Binding 校验;接口是DT_INST_REG_ADDR()、DT_INST_IRQ()等宏。Driver → HAL → SoC:Driver 通过 HAL 的寄存器读写封装(sys_read32/sys_write32)最终操作 SoC 物理寄存器;接口是寄存器读写函数。SoC → Board:SoC 提供外设控制器,但具体引脚连接、板级外设由 Board DTS 决定;接口是板级 DTS 中的pinctrl和节点引用。这些事情由下面几层共同完成:Application ↓ GPIO API ↓ GPIO Driver ↓ Devicetree ↓ SoC GPIO registers二、为什么 SoC BSP 一定会遇到 Driver?假设你的公司 SoC 有:UART0 UART1 GPIO0 GPIO1 I2C0 SPI0 TIMER0 PWM0 ADC0那么 Zephyr 必须知道:UART0 在哪里? UART0 有哪些寄存器? UART0 的 clock 怎么打开? UART0 的 interrupt 是什么? UART0 使用哪个 pin?这些信息不能全部硬编码在 Driver 中。否则你最后可能写出:# define UART0_BASE 0x40000000# define UART0_IRQ 32# define UART0_CLK ...然后 Driver 里面到处都是:# define ...这会导致 Driver 与具体 SoC 强耦合。Zephyr 更希望:Devicetree ↓ 描述硬件实例 ↓ Driver ↓ 读取硬件配置所以:Devicetree Binding + Devicetree + Driver 是一组完整体系。三、什么是 Devicetree Binding?Binding 可以理解成:告诉 Zephyr:一个 Devicetree 节点允许有哪些属性,以及这些属性分别是什么类型。例如我们有:uart0: serial@40000000{compatible="company,foo-uart";reg=lt;0x40000000 0x1000gt;;interrupts=lt;32gt;;clocks=lt;clk UART0_CLKgt;;status="okay";};这里:compatible reg interrupts clocks status都是 Devicetree properties。Binding 则描述:compatible: company,foo-uart properties: reg: type: array interrupts: type: array clocks: type: phandle-array所以:DTS │ │"我有这些属性"▼ Binding │ │"这些属性是否合法、是什么类型"▼ Devicetree validation四、Binding 最重要的东西:compatible在 Zephyr Driver 中,最重要的连接点之一就是:compatible="company,foo-uart";Driver 通过它识别硬件。例如:compatible="mycompany,myuart";对应:compatible:"mycompany,myuart"然后 Driver:# define DT_DRV_COMPAT mycompany_myuart注意这里有一个非常重要的转换:"mycompany,myuart"↓ mycompany_myuart也就是:vendor,device ↓ vendor_device然后:DT_INST_FOREACH_STATUS_OKAY(...)就可以找到 Devicetree 中:compatible="mycompany,myuart";的实例。五、Binding 文件放在哪里?Zephyr 中通常是:dts/ └── bindings/ ├── serial/ ├── gpio/ ├── i2c/ ├── spi/ ├── pwm/ └──...例如:dts/bindings/serial/mycompany,myuart.yaml可能写成:description: MyCompany UART controller compatible:"mycompany,myuart"include:\- name: base.yaml properties: reg: required:trueinterrupts: required:trueclocks: required:true这里最重要的是:compatible:"mycompany,myuart"它把:Binding和:Devicetreenode联系起来。六、一个完整例子假设公司 SoC 有一个 UART:UART0 Base Address=0x40000000 Size=0x1000 IRQ=32那么我们可以设计:1. Bindingdts/bindings/serial/mycompany,myuart.yaml例如:description: MyCompany UART compatible:"mycompany,myuart"properties: reg: required:trueinterrupts: required:trueclocks: required:true2. Devicetreeuart0: serial@40000000{compatible="mycompany,myuart";reg=lt;0x40000000 0x1000gt;;interrupts=lt;32gt;;clocks=lt;clk UART0_CLKgt;;status="okay";};3. Driver# define DT_DRV_COMPAT mycompany_myuart然后:static const struct device\*dev;Driver 就可以通过 Devicetree 宏取得:reg interrupts clocks例如:# define UART_BASE(inst) \\DT_INST_REG_ADDR(inst)最终:UART_BASE(0)可能得到:0x40000000这就是 Zephyr 非常核心的一种工作方式:DTS │ ▼ Devicetree Compiler │ ▼ generated devicetree information │ ▼ DT_INST_REG_ADDR()DT_INST_IRQ()DT_INST_PROP()DT_INST_CLOCKS_... │ ▼ Driver七、Driver 到底在做什么?以 UART 为例。应用:printk("Hello\\n");最终需要 UART Driver。Driver 的职责通常包括:初始化 ↓ clock ↓ pinmux ↓ UART registers ↓ interrupt ↓ buffer ↓ Zephyr UART API例如:staticintmyuart_init(conststructdevice\*dev){conststructmyuart_config\*config=dev-config;/* enable clock *//* configure registers *//* configure interrupt */return0;}这里:config往往就是从 Devicetree 生成的。八、Config 和 Data 是 Driver 设计中的核心Zephyr Driver 中非常重要的一个模式:structmyuart_config{uintptr_tbase;intirq;};structmyuart_data{...};通常:config ↓ 硬件配置 ↓ 编译期确定而:data ↓ 运行时状态例如:struct myuart_config{uintptr_t base;};struct myuart_data
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门