4. 设备树(DTS)中UART节点配置

设备树(Device Tree Source, DTS)是嵌入式 Linux 和 Android 系统中描述硬件拓扑的核心机制。对于 UART 驱动而言,DTS 节点是驱动获取硬件资源(寄存器地址、中断号、时钟频率、引脚复用)的唯一入口。一个完整且正确的 UART 节点,是串口驱动正常工作的前提。

本章将逐一拆解 UART 节点中最关键的五个属性:compatible、reg、interrupts、clocks 以及 pinctrl 配置,并结合实际案例说明其配置要点与常见错误。

核心原则: DTS 中的 UART 节点必须与 SoC 参考手册中的 UART 外设寄存器基地址、中断号、时钟源完全一致。任何偏差都会导致驱动 probe 失败或数据传输异常。

4.1 compatible 属性:驱动匹配的钥匙

compatible 属性是设备节点与驱动代码之间的“握手信号”。内核通过该属性的字符串值,在驱动注册表中查找对应的 of_device_id 匹配表。

  • 格式: "厂商,型号" 或 "厂商,型号,版本",通常包含一个通用兼容字符串和一个精确兼容字符串。
  • 匹配优先级: 内核优先使用精确匹配,若失败则尝试通用匹配。
  • 常见错误: 字符串拼写错误、大小写不匹配、缺少通用兼容项导致驱动无法加载。
/* 典型 UART 节点 compatible 配置 */
uart0: serial@10000000 {
    compatible = "samsung,exynos-uart", "samsung,s3c6400-uart";
    /* 第一个为精确匹配,第二个为通用 fallback */
    ...
};
兼容字符串 匹配驱动 说明
"samsung,exynos-uart" exynos_uart.c Exynos 系列专用驱动
"samsung,s3c6400-uart" s3c6400_uart.c 通用 fallback 驱动
"ns16550a" 8250_early.c 标准 16550A 兼容 UART
调试技巧: 如果驱动 probe 失败,首先检查 /sys/firmware/devicetree/base/.../uart@.../compatible 文件内容是否与驱动中的 of_match_table 完全一致。

4.2 reg 属性:硬件资源地址映射

reg 属性定义了 UART 外设的寄存器基地址和地址范围。驱动通过 platform_get_resource() 或 of_iomap() 获取该信息,并完成 MMIO 映射。

  • 格式: <基地址 地址长度>,支持多个地址段(如 FIFO 控制寄存器分离的情况)。
  • 地址长度: 通常为 4KB(0x1000)或 256B(0x100),取决于 SoC 设计。
  • 多段 reg: 某些 UART 将数据寄存器与控制寄存器分开编址,需配置多个 reg 条目。
/* 单段 reg 配置 */
uart1: serial@10001000 {
    reg = <0x10001000 0x100>;   /* 基地址 0x10001000,长度 256 字节 */
    ...
};

/* 多段 reg 配置(数据通道与控制通道分离) */
uart2: serial@10002000 {
    reg = <0x10002000 0x40>,    /* 数据寄存器区域 */
          <0x10002400 0x40>;    /* 控制寄存器区域 */
    reg-names = "data", "ctrl";
    ...
};
reg 属性地址映射流程 DTS 节点 reg = <0x1000 0x100> 内核 OF 解析 platform_get_resource() ioremap() 虚拟地址访问 关键点:reg 中的基地址必须是物理地址,驱动通过 ioremap 转换为内核虚拟地址 地址长度必须覆盖所有必要的控制/状态/数据寄存器

4.3 interrupts 属性:中断信号绑定

interrupts 属性将 UART 外设的中断输出线连接到中断控制器(GIC)。驱动通过 request_irq() 或 devm_request_irq() 注册中断处理函数。

  • 格式: <中断控制器phandle 中断号 触发类型>。
  • 中断号: 是 SoC 内部中断号,不是 GPIO 号。需查阅 SoC 数据手册。
  • 触发类型: 0x0(默认)、0x1(上升沿)、0x2(下降沿)、0x4(高电平)、0x8(低电平)。
  • 多中断: 某些 UART 支持 TX、RX、错误三个独立中断线。
/* 单中断配置(最常见) */
uart3: serial@10003000 {
    interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-parent = <&gic>;
    ...
};

/* 多中断配置(TX/RX/错误分离) */
uart4: serial@10004000 {
    interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>,  /* RX 中断 */
                <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>,  /* TX 中断 */
                <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>;  /* 错误中断 */
    interrupt-names = "rx", "tx", "err";
    interrupt-parent = <&gic>;
    ...
};
中断类型 触发条件 典型处理
RX 中断 接收 FIFO 达到触发阈值 读取数据寄存器,存入环形缓冲区
TX 中断 发送 FIFO 空或低于阈值 从环形缓冲区取数据写入发送寄存器
错误中断 帧错误、奇偶校验错误、溢出 记录错误统计,清除错误标志
常见陷阱: 中断号配置错误会导致 request_irq() 返回 -EINVAL 或 -EBUSY。务必确认 SoC 手册中的中断号是 SPI(共享外设中断)还是 PPI(私有外设中断),GIC 的 SPI 中断号通常从 32 开始编号。

4.4 clocks 属性:时钟源与频率

UART 模块需要时钟信号驱动波特率发生器。clocks 属性指定了 UART 使用的时钟源,驱动通过 clk_get() 或 devm_clk_get() 获取时钟句柄,并调用 clk_set_rate() 或 clk_get_rate() 计算波特率分频系数。

  • 格式: <时钟控制器phandle 时钟ID>。
  • 时钟名称: 使用 clock-names 属性区分不同时钟(如 uart_clk、baud_clk)。
  • 频率匹配: 驱动需要根据时钟频率计算分频值,频率不匹配会导致波特率误差过大。
/* 单时钟源配置 */
uart5: serial@10005000 {
    clocks = <&cmu CLK_UART0>;
    clock-names = "uart";
    ...
};

/* 双时钟源配置(IP 时钟 + 波特率时钟) */
uart6: serial@10006000 {
    clocks = <&cmu CLK_UART1_IP>,
             <&cmu CLK_UART1_BAUD>;
    clock-names = "ip", "baud";
    ...
};
时钟属性与波特率生成关系 CMU 时钟控制器 clocks = <&cmu ...> UART 驱动 clk_get_rate() 波特率发生器 分频系数 = 时钟 / (16 × 波特率) 时钟频率不匹配会导致实际波特率与目标波特率偏差超过 2%,产生乱码

4.5 pinctrl 配置:引脚功能与电气特性

UART 的 TX、RX、RTS、CTS 等引脚必须通过 pinctrl 子系统配置为 UART 功能模式,并设置上下拉、驱动强度等电气参数。pinctrl 配置通常在 SoC 的 pinctrl 节点中定义,然后在 UART 节点中通过 pinctrl-0、pinctrl-1 等属性引用。

  • pinctrl-names: 定义状态名称,如 default、sleep、active。
  • pinctrl-0: 指向默认状态的引脚配置 phandle。
  • 引脚复用: 每个引脚必须设置为 UART 功能(如 func = "uart0")。
  • 电气特性: 上拉/下拉、驱动强度、施密特触发等。
/* pinctrl 节点中定义 UART 引脚组 */
&pinctrl {
    uart0_pins_default: uart0-default {
        samsung,pins = "gpa0-0", "gpa0-1";  /* TX, RX */
        samsung,pin-function = <2>;          /* 设置为 UART 功能 */
        samsung,pin-pud = <0>;              /* 禁用上下拉 */
        samsung,pin-drv = <0>;              /* 默认驱动强度 */
    };

    uart0_pins_sleep: uart0-sleep {
        samsung,pins = "gpa0-0", "gpa0-1";
        samsung,pin-function = <0>;          /* 设置为输入 */
        samsung,pin-pud = <1>;              /* 内部上拉 */
    };
};

/* UART 节点引用 pinctrl */
uart0: serial@10000000 {
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&uart0_pins_default>;
    pinctrl-1 = <&uart0_pins_sleep>;
    ...
};
pinctrl 状态 典型场景 引脚配置
default 系统正常运行 UART 功能、禁用上下拉、标准驱动
sleep 系统休眠 输入模式、内部上拉、降低功耗
active 唤醒后恢复 同 default 配置
idle 空闲低功耗 输入模式、下拉、弱驱动
调试要点: 如果 UART 无法收发数据,首先检查 pinctrl 配置是否正确。通过 /sys/kernel/debug/pinctrl/... 查看当前引脚功能状态,确认 TX 引脚是否被正确设置为输出、RX 引脚为输入。

4.6 完整 UART 节点示例

以下是一个完整的 UART 设备树节点,整合了上述所有属性:

/* 完整 UART 节点配置 */
uart0: serial@10000000 {
    compatible = "samsung,exynos-uart", "samsung,s3c6400-uart";
    reg = <0x10000000 0x100>;
    interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
    interrupt-parent = <&gic>;
    clocks = <&cmu CLK_UART0>;
    clock-names = "uart";
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&uart0_pins_default>;
    pinctrl-1 = <&uart0_pins_sleep>;
    samsung,uart-fifosize = <256>;          /* FIFO 大小 */
    status = "okay";                         /* 启用节点 */
};
UART 设备树节点完整结构 serial@10000000 compatible reg interrupts clocks pinctrl-0 pinctrl-1 status samsung,uart-fifosize 每个属性都对应驱动中的一个资源获取函数,缺一不可

4.7 常见配置错误与排查方法

错误案例 1: compatible 字符串与驱动不匹配,导致 of_match_table 查找失败,驱动 probe 函数不被调用。
排查: 检查 dmesg | grep uart 是否出现 No match found 或 Unknown device。
错误案例 2: reg 地址长度不足,驱动访问到未映射区域导致 Unhandled fault。
排查: 确认 SoC 手册中 UART 寄存器偏移最大值,确保地址长度覆盖所有寄存器。
错误案例 3: pinctrl 配置错误,TX 引脚未正确输出,RX 引脚未正确输入。
排查: 使用 cat /sys/kernel/debug/gpio 查看引脚方向,或用示波器测量 TX 引脚是否有电平变化。
错误现象 可能原因 排查命令
驱动 probe 失败 compatible 不匹配 / reg 冲突 dmesg | grep -i uart
无法收发数据 pinctrl 配置错误 / 时钟未使能 cat /sys/kernel/debug/pinctrl/*/pinmux-pins
波特率乱码 时钟频率不匹配 / 分频计算错误 cat /sys/kernel/debug/clk/uart*/clk_rate
中断不响应 interrupts 号错误 / 中断控制器配置错误 cat /proc/interrupts | grep uart
最佳实践: 在开发阶段,建议在 UART 节点中添加 status = "okay" 显式启用,并确保 bootargs 中未禁用该串口。使用 dtc -I dtb -O dts 反编译 DTB 验证最终生效的配置。