轻松学习Zephyr BSP: 27-Binding定义硬件

摘要:本文是 Zephyr BSP 开发系列的第 27 篇,深入讲解 Devicetree Binding 的核心概念。Binding 是硬件在 Zephyr 中的"数据结构定义",它规定了 compatible 对应的硬件节点允许哪些属性、每个属性的类型与是否必填。文章从 Binding 与 DTS 的"类型与对象"关系讲起,逐步展开 compatible 的匹配机制、propertiesrequired 的语义、属性类型约束、include 继承复用,以及 Binding 如何通过 DT macros 最终影响 Driver 的 C 代码生成。理解 Binding,是建立"数据驱动" BSP 开发思想、完成从硬件规格到软件驱动契约的关键一步。
Binding:告诉 Zephyr “我的硬件是什么”

在上一篇 26 — Devicetree:把 Company SoC 描述给 Zephyr 之后,我们已经能写:

uart0: uart@40000000 {
    compatible = "company,uart";
    reg = <0x40000000 0x1000>;
    interrupts = <5 0>;
    status = "okay";
};

但这里有一个关键问题:

Zephyr 怎么知道 company,uart 允许哪些属性?
reg 是什么?
interrupts 是什么?
clock-frequency 能不能写?
current-speed 是不是合法?
compatible = “company,uart” 到底对应什么硬件?

答案就是:

Devicetree Binding

Binding 可以理解成:

Devicetree Binding = 你的硬件在 Zephyr 中的"数据结构定义"。

如果 Devicetree 是:

“这块板子上有什么硬件?”

那么 Binding 就是:

“这种硬件应该长什么样、有哪些属性、每个属性是什么类型?”

一、先把整个关系图建立起来

到了 BSP 开发阶段,你应该形成这样一张脑图:

                    Company SoC
                        │
                        ▼
              ┌──────────────────┐
              │   Hardware IP    │
              │                  │
              │ UART / SPI / GPIO│
              │ Timer / I2C ...  │
              └────────┬─────────┘
                       │
                       │ 硬件规格
                       ▼
              ┌──────────────────┐
              │ Devicetree       │
              │ Binding           │
              │                  │
              │ company,uart     │
              │ company,spi      │
              │ company,timer    │
              └────────┬─────────┘
                       │
                       │ compatible
                       ▼
              ┌──────────────────┐
              │ Board .dts       │
              │                  │
              │ uart0: uart@...  │
              │ {                │
              │   compatible =   │
              │     "company,uart";
              │   reg = ...      │
              │   interrupts=... │
              │ };               │
              └────────┬─────────┘
                       │
                       ▼
              Devicetree Compiler
                       │
                       ▼
              generated devicetree
                       │
                       ▼
              C / C++ macros
                       │
                       ▼
              DEVICE_DT_DEFINE()
                       │
                       ▼
                  struct device
                       │
                       ▼
                  UART Driver

所以:

Binding
↓
规定 DTS 节点可以描述什么

DTS
↓
描述具体板子上的实例

Driver
↓
真正操作硬件

这三个东西不要混在一起。

二、Binding 本质上是什么?

一个最简单的 Binding:

description: Company UART controller

compatible: "company,uart"

properties:
  reg:
    required: true

  interrupts:
    required: true

  clock-frequency:
    type: int
    required: false

  current-speed:
    type: int
    required: false

你可以把它理解成类似 C 的:

struct company_uart_dt {
    reg;
    interrupts;
    clock_frequency;
    current_speed;
};

当然,Binding 并不是 C struct。
它实际上是一个 YAML schema
它告诉 Zephyr:

compatible = "company,uart"

这种节点:

必须有 reg

必须有 interrupts

可以有 clock-frequency

可以有 current-speed



为了帮你快速建立概念边界,这里把 **Binding、DTS、Driver** 三者放在一起对比:

| 维度 | Binding | DTS | Driver |
| --- | --- | --- | --- |
| **作用** | 定义某类硬件"允许有哪些属性、每个属性是什么类型、是否必填" | 描述某块具体板子上"有哪些硬件实例、每个实例的具体配置值" | 真正操作硬件寄存器,实现读写、初始化、中断处理等行为 |
| **定义位置** | `dts/bindings/` 下的 YAML 文件,如 `company,uart.yaml` | `boards/``soc/` 下的 `.dts` / `.dtsi` 文件,如 `my_board.dts` | `drivers/` 下的 C 源文件,如 `uart_company.c` |
| **语法格式** | YAML schema(`compatible` + `properties` + `required` + `include`| Devicetree 源语法(节点 + `compatible` + `reg` + `interrupts` 等属性赋值) | C 语言(`struct device``DEVICE_DT_DEFINE()``static int xxx_init()`|
| **与硬件关系** | 对应"硬件类型"(如 Company UART 这一类 IP) | 对应"硬件实例"(如这块板子上的 `uart0`| 对应"硬件操作"(读写寄存器、配置波特率等) |
| **影响范围** | 全局:影响所有 `compatible` 匹配该类型的节点 | 局部:只影响当前板子/SoC 的实例配置 | 局部:只影响该驱动所服务的设备实例 |

一句话总结:

```bash
Binding 定义"硬件长什么样"
DTS 描述"这块板子上具体是什么配置"
Driver 负责"真正去操作硬件"

这些属性分别是什么类型


**三、Binding 和 DTS 是"类型"和"对象"的关系**

这个概念非常重要。

假设 Binding:

```bash
compatible: "company,uart"

properties:
  reg:
    required: true

  interrupts:
    required: true

  current-speed:
    type: int

那么 DTS:

uart0: uart@40000000 {
    compatible = "company,uart";

    reg = <0x40000000 0x1000>;

    interrupts = <5 0>;

    current-speed = <115200>;

    status = "okay";
};

可以类比成:

struct CompanyUart {
    uintptr_t reg;
    int irq;
    int current_speed;
};

struct CompanyUart uart0 = {
    .reg = 0x40000000,
    .irq = 5,
    .current_speed = 115200,
};

所以:

Binding
    ↓
定义“Company UART 是什么”

DTS
    ↓
定义“我的板子上这个 UART 是什么配置”

四、为什么 Zephyr 需要 Binding?

因为 DTS 本身只是描述硬件。

例如:

uart0: uart@40000000 {
    compatible = "company,uart";
    reg = <0x40000000 0x1000>;
    interrupts = <5 0>;
};

如果没有 Binding,工具链很难知道:

reg

到底应该是什么。
也不知道:

interrupts

是不是这个硬件允许的属性。
更不知道:

current-speed

是不是合法。
Binding 就提供了这个语义。

五、Binding 文件放在哪里?

对于你自己的 SoC BSP,典型结构会逐渐变成:

zephyr/
├── boards/
│   └── company/
│       └── my_board/
│           ├── my_board.dts
│           ├── my_board.yaml
│           └── ...
│
├── dts/
│   ├── bindings/
│   │   ├── serial/
│   │   │   └── company,uart.yaml
│   │   │
│   │   ├── spi/
│   │   │   └── company,spi.yaml
│   │   │
│   │   └── timer/
│   │       └── company,timer.yaml
│
└── soc/
    └── company/
        └── ...

例如:

dts/bindings/serial/company,uart.yaml

六、最重要的字段:compatible

Binding 的核心就是:

compatible: "company,uart"

而 DTS:

compatible = "company,uart";

两者通过字符串连接。

也就是说:

DTS
│
│ compatible
▼
"company,uart"
│
│ matching
▼
Binding
│
▼
company,uart.yaml

所以:
compatible 是 Devicetree 世界中硬件类型的"身份证"。

七、compatible 为什么不是随便写?

比如你写:

compatible = "abc,uart";

那么 Zephyr 会去找对应 Binding。

通常会寻找:

abc,uart.yaml

因此最好按照:

vendor,device

形式命名。
例如:

company,uart
company,gpio
company,spi
company,i2c
company,timer
company,watchdog

如果公司名字是:

acme

那么:

acme,uart
acme,gpio
acme,timer

八、Binding 不只是 compatible

一个完整 Binding 往往会描述很多内容。

例如:

description: Company UART controller

compatible: "company,uart"

include:
  - name: uart-controller.yaml
    property-allowlist:
      - current-speed
      - parity
      - stop-bits

properties:
  reg:
    required: true

  interrupts:
    required: true

  clocks:
    required: true

  clock-frequency:
    type: int
    required: false

这里出现了几个非常重要的概念。

九、properties

这是 Binding 最核心的部分。

例如:

properties:
  clock-frequency:
    type: int
    required: true

表示:

这个设备有一个属性:

clock-frequency

类型:
int

必须存在:
true

于是:

uart0 {
	clock-frequency = &lt;50000000&gt;;
};

合法。

十、required

例如:

properties:
  reg:
    required: true

意味着:

uart0 {
	compatible = "company,uart";
};

缺少:

reg

就不符合这个 Binding。
而:

properties:
  clock-frequency:
    required: false

则意味着:

uart0 {
    compatible = "company,uart";
    reg = <...>;
};

可以没有:

clock-frequency

十一、Binding 还能规定属性类型

例如:

properties:

  fifo-size:
    type: int

  dma-enabled:
    type: boolean

  label:
    type: string

  clocks:
    type: phandle-array

  reset:
    type: phandle

  pinctrl-0:
    type: phandles

所以 Binding 不只是:
“这个属性存在。”
而是:
“这个属性存在,而且它应该是什么数据类型。”

十二、reg 为什么经常不用自己定义?

这是初学者非常容易困惑的地方。

你可能看到:

properties:
  reg:
    required: true

但 Zephyr 的 Binding 经常会:

include:
  - base.yaml

或者:

include:
	- name: base.yaml

通过 include 复用标准 Binding 定义。

因为:

reg
status
interrupts
compatible
...

这些 Devicetree 基础概念不应该每个 Binding 都重新定义。

十三、Binding 可以继承/复用

例如 UART。

你的硬件:

Company UART

本质上还是一个:

UART controller

所以可以复用 Zephyr 已有的 UART controller Binding。

概念上:

uart-controller.yaml
│
│ include
▼
company,uart.yaml
│
▼
uart0.dts

这样你不需要自己重新定义所有 UART 通用属性。

十四、这就是 Zephyr Binding 非常强大的地方

假设 Zephyr 已经知道:

UART controller

具有:

current-speed
parity
stop-bits
data-bits

那么你的 SoC UART:

company,uart

可以在这个基础上增加自己的东西:

fifo-size
clock-frequency
dma

最终形成:

标准 UART 属性
        +
Company SoC 特有属性
        ↓
company,uart

这比从零定义一个 UART 更合理。

十五、举一个完整的 Company UART Binding

例如:

description: Company SoC UART controller

compatible: "company,uart"

include:
  - uart-controller.yaml

properties:
  reg:
    required: true

  interrupts:
    required: true

  clocks:
    required: true

  clock-frequency:
    type: int
    required: false

  fifo-size:
    type: int
    required: false

然后 DTS:

uart0: uart@40000000 {
    compatible = "company,uart";

    reg = <0x40000000 0x1000>;

    interrupts = <5 0>;

    clocks = <&clk UART0_CLK>;

    clock-frequency = <50000000>;

    fifo-size = <32>;

    current-speed = <115200>;

    status = "okay";
};

现在:

Binding
↓
告诉 Zephyr:
↓
company,uart 有哪些属性
↓
DTS
↓
填写这些属性的具体值

十六、Binding 和 Driver 到底是什么关系?

这是 BSP 开发最关键的一点。
很多人会误认为:

Binding = Driver

不是。
实际上:

Binding
│
│ 描述硬件数据
▼
Devicetree
│
│ 提供实例配置
▼
Driver
│
│ 操作寄存器
▼
Hardware

例如:

company,uart.yaml

描述:

reg
interrupts
clocks
fifo-size

而:

uart_company.c

里面才会有:

# define UART_STATUS 0x00
# define UART_CTRL 0x04
# define UART_TXDATA 0x08
# define UART_RXDATA 0x0c

然后:

static int uart_company_init(...)
{
    ...
}

Binding 不负责:

读 UART_STATUS
写 UART_TXDATA
配置波特率

这些属于 Driver。

十七、最终它们如何汇合?

这才是你前面 17~26 篇一直在追踪的主线。

现在完整链路可以画成:

                 company,uart.yaml
                       │
                       │ Binding
                       ▼
              ┌─────────────────┐
              │ Devicetree Node │
              │                 │
              │ uart0: uart@... │
              │                 │
              │ compatible      │
              │ reg             │
              │ interrupts      │
              │ clocks          │
              └────────┬────────┘
                       │
                       ▼
             Devicetree Generator
                       │
                       ▼
          devicetree_generated.h
                       │
                       ▼
             DT_NODELABEL(uart0)
                       │
                       ▼
             DEVICE_DT_DEFINE(...)
                       │
                       ▼
                struct device
                       │
                       ▼
                 UART Driver
                       │
                       ▼
                UART Hardware

这就是:
Binding → DTS → DT macros → Device → Driver

十八、Binding 还会影响 C 宏

这一点特别重要。

假设 DTS:

uart0: uart@40000000 {
    compatible = "company,uart";
    reg = <0x40000000 0x1000>;
    current-speed = <115200>;
};

Driver 可以写:

#define UART_INIT(node_id)                                      \
    static const struct uart_company_config                    \
        uart_config_##node_id = {                              \
            .base = DT_REG_ADDR(node_id),                      \
            .baudrate = DT_PROP(node_id, current_speed),       \
        };

然后:

UART_INIT(DT_NODELABEL(uart0));

最终相当于获得:

static const struct uart_company_config uart_config_uart0 = {
    .base = 0x40000000,
    .baudrate = 115200,
};

注意:

Driver 没有硬编码 0x40000000。

地址来自:

DTS
↓
DT_REG_ADDR()

而:

baudrate

来自:

DTS
↓
DT_PROP()

Binding 定义了:

current-speed

这个属性。

十九、这就是 BSP 的"数据驱动"思想

你以后做 Company SoC BSP,尽量形成这种结构:

不要:

# define UART0_BASE 0x40000000
# define UART1_BASE 0x40001000

然后:

if (board == XXX) {
    ...
}

而是:

Hardware
↓
DTS
↓
Binding
↓
DT macros
↓
Driver

例如不同芯片:

SoC A
UART0 = 0x40000000

SoC B
UART0 = 0x50000000

Driver 本身不需要修改。
只需要:

reg = <0x40000000 ...>;

或者:

reg = <0x50000000 ...>;

二十、Binding 的真正价值

因此不要把 Binding 理解成:
“一个 YAML 配置文件。”
它实际上承担了一个非常重要的角色:

                Hardware Specification
                         │
                         ▼
                Devicetree Binding
                         │
              ┌──────────┴──────────┐
              │                     │
          Property Schema       Hardware Type
              │                     │
              ▼                     ▼
        reg / irq / clock      company,uart
              │                     │
              └──────────┬──────────┘
                         ▼
                    Devicetree
                         │
                         ▼
                       Driver

它是:
硬件描述世界和软件 Driver 世界之间的契约。

二十一、回到你的 Company SoC BSP

到目前为止,我们已经逐步建立了:

20 理解 Zephyr BSP
↓
21 SoC Port Skeleton
↓
22 CPU / Architecture
↓
23 Startup
↓
24 Interrupt Controller
↓
25 Clock / Reset
↓
26 Devicetree
↓
27 Binding

现在你的 BSP 已经开始具备完整形态:

Company SoC
│
├── ARCH
│
├── SOC
│   ├── startup
│   ├── clock
│   ├── reset
│   └── interrupt controller
│
├── DTS
│   ├── SoC nodes
│   └── board nodes
│
├── Bindings
│   ├── company,uart
│   ├── company,gpio
│   ├── company,timer
│   └── company,spi
│
└── Drivers
    ├── uart
    ├── gpio
    ├── timer
    └── spi

这时你已经不是在"学习怎么使用 Zephyr"。

而是在:
为一个真实 SoC 建立 Zephyr Hardware Abstraction。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值