3、UART设备树绑定:设备树中UART节点的标准属性、解析与匹配流程
在嵌入式Linux系统中,设备树(Device Tree)是描述硬件拓扑结构的核心机制。UART控制器作为典型的串行通信外设,其设备树节点的正确绑定是驱动能够正常工作的前提。本章将深入解析UART节点中必须包含的标准属性,并详细阐述内核从设备树解析到驱动匹配的完整流程。
核心概念:设备树为UART驱动提供了“即插即用”的硬件描述能力。驱动开发者无需修改内核代码,只需调整设备树源文件(.dts),即可适配不同SoC上的UART外设。
3.1 UART设备树节点标准属性详解
一个标准的UART设备树节点通常位于SoC的APB(高级外设总线)或AHB(高级高性能总线)节点下。以下是一个典型的UART节点示例:
uart0: serial@10000000 {
compatible = "vendor,soc-uart", "ns16550a";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
clocks = <&clk_uart0>;
dmas = <&dma0 0>, <&dma0 1>;
dma-names = "tx", "rx";
clock-frequency = <1843200>;
current-speed = <115200>;
fifo-size = <16>;
status = "okay";
};
下表详细列出了UART节点中每个标准属性的含义与作用:
| 属性名 | 类型 | 必需性 | 描述与典型值 |
|---|---|---|---|
compatible |
字符串列表 | 必需 | 驱动匹配的关键标识。格式为"厂商,型号"。如"ns16550a"是通用16550A兼容UART,"vendor,soc-uart"是SoC定制UART。内核通过该属性查找对应的驱动。 |
reg |
地址/长度对 | 必需 | 描述UART控制器的物理基地址和地址范围。如<0x10000000 0x1000>表示基地址0x10000000,映射4KB空间。 |
interrupts |
中断描述符 | 必需 | 指定UART使用的中断号、触发类型和优先级。格式依赖中断控制器(如GIC)。<0 33 4>表示SPI类型、中断号33、高电平触发。 |
clocks |
phandle列表 | 强烈建议 | 指向UART模块的时钟源。用于驱动使能/关闭UART时钟,计算波特率。如<&clk_uart0>。 |
dmas / dma-names |
phandle列表 / 字符串列表 | 可选 | 描述DMA传输通道。dmas列出DMA控制器和通道号,dma-names为每个通道命名(如"tx"、"rx")。用于实现DMA模式的UART传输。 |
clock-frequency |
整数 | 可选 | 直接指定UART输入时钟频率(Hz)。当clocks属性不可用时,驱动可读取此值计算波特率。 |
current-speed |
整数 | 可选 | 默认的波特率设置,如115200。bootloader或内核早期初始化阶段使用。 |
fifo-size |
整数 | 可选 | 硬件FIFO深度,如16、32、64。驱动据此配置FIFO触发阈值。 |
status |
字符串 | 可选 | 设备状态。"okay"表示启用,"disabled"表示禁用。用于在板级文件中覆盖SoC默认配置。 |
实战要点:在编写UART设备树节点时,
compatible属性应优先使用SoC厂商提供的专用字符串,其次才是通用兼容字符串(如"ns16550a")。这能确保驱动获得最佳的硬件特性支持。
3.2 设备树解析与驱动匹配流程
从设备树二进制文件(DTB)加载到UART驱动成功绑定的过程,涉及内核多个子系统的协作。下图展示了完整的解析与匹配流程:
上述流程中,第4步“平台总线匹配”是UART驱动绑定的核心环节。内核通过of_match_table中的compatible字符串列表与设备树节点的compatible属性进行逐项比对。匹配成功后,内核调用驱动的probe()函数,驱动在probe()中通过OF API获取reg、interrupts、clocks等资源,完成UART控制器的初始化。
3.3 驱动中的OF API解析示例
在UART驱动的probe()函数中,开发者使用以下OF API从设备树节点中提取配置信息:
static int my_uart_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct resource *res;
int irq;
u32 clock_freq;
// 1. 获取reg属性中的内存资源(基地址+大小)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
// 2. 获取中断号
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 3. 读取clock-frequency属性(可选)
if (of_property_read_u32(np, "clock-frequency", &clock_freq) == 0) {
dev_info(dev, "Clock frequency from DT: %u Hz\n", clock_freq);
}
// 4. 获取时钟(clocks属性)
struct clk *clk = devm_clk_get(dev, NULL);
if (IS_ERR(clk))
return PTR_ERR(clk);
// 5. 读取fifo-size属性(可选)
u32 fifo_size;
if (of_property_read_u32(np, "fifo-size", &fifo_size) == 0) {
dev_info(dev, "FIFO size from DT: %u bytes\n", fifo_size);
}
// ... 后续初始化代码
return 0;
}
// 驱动匹配表
static const struct of_device_id my_uart_of_match[] = {
{ .compatible = "vendor,soc-uart", },
{ .compatible = "ns16550a", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_uart_of_match);
static struct platform_driver my_uart_driver = {
.probe = my_uart_probe,
.remove = my_uart_remove,
.driver = {
.name = "my_uart",
.of_match_table = my_uart_of_match,
},
};
module_platform_driver(my_uart_driver);
常见陷阱:当设备树中同时存在
clocks和clock-frequency属性时,驱动应优先使用clocks属性获取时钟框架提供的频率,因为时钟框架支持动态调频和电源管理。clock-frequency仅作为后备方案。
3.4 多实例UART的设备树组织
在实际SoC中,通常存在多个UART实例。设备树通过不同的reg地址和interrupts号来区分它们:
uart1: serial@10001000 {
compatible = "vendor,soc-uart";
reg = <0x10001000 0x1000>;
interrupts = <0 34 4>;
clocks = <&clk_uart1>;
dmas = <&dma0 2>, <&dma0 3>;
dma-names = "tx", "rx";
fifo-size = <64>;
status = "okay";
};
uart2: serial@10002000 {
compatible = "vendor,soc-uart";
reg = <0x10002000 0x1000>;
interrupts = <0 35 4>;
clocks = <&clk_uart2>;
fifo-size = <16>;
status = "disabled"; // 板级文件中可覆盖为"okay"
};
每个UART实例拥有独立的地址空间、中断线和时钟源。驱动通过platform_get_resource()和platform_get_irq()函数,根据pdev参数自动获取对应实例的资源,实现多实例复用同一驱动代码。
3.5 设备树属性与驱动数据结构的映射关系
下表总结了设备树属性与内核驱动中常用数据结构的对应关系:
| 设备树属性 | 内核数据结构/API | 获取方式 |
|---|---|---|
compatible |
struct of_device_id |
驱动匹配时自动比对 |
reg |
struct resource |
platform_get_resource(pdev, IORESOURCE_MEM, 0) |
interrupts |
unsigned int irq |
platform_get_irq(pdev, 0) |
clocks |
struct clk * |
devm_clk_get(dev, NULL) 或 of_clk_get(np, 0) |
dmas |
struct dma_chan * |
dma_request_slave_channel(dev, "tx") |
clock-frequency |
u32 |
of_property_read_u32(np, "clock-frequency", &val) |
fifo-size |
u32 |
of_property_read_u32(np, "fifo-size", &val) |
status |
设备状态标志 | 内核在创建platform_device时自动过滤"disabled"节点 |
最佳实践:在编写UART驱动时,建议将所有可配置的硬件参数(如FIFO大小、波特率、流控模式)都通过设备树属性暴露出来。这样,同一个驱动二进制文件可以适配不同硬件版本的UART控制器,无需重新编译内核。