第四节:设备树与平台设备

设备树,说白了就是嵌入式Linux世界里的一张「硬件地图」。我刚开始接触时也觉得这东西有点玄乎,不就是描述硬件嘛,搞这么复杂干嘛?直到我在一个项目里,因为一个GPIO管脚号写错了,整块板子死活起不来网络——嗯,从那以后我再也不敢小看设备树了。

4.1 设备树语法基础

设备树(Device Tree)是一种描述硬件资源的数据结构。它用树形结构来描述CPU、内存、外设、中断、GPIO等信息。内核在启动时解析这个树,然后根据描述来初始化驱动。

先看一个最简单的设备树节点长什么样:

/dts-v1/;

/ {
    model = "MyBoard";
    compatible = "vendor,myboard";
    
    cpus {
        cpu@0 {
            compatible = "arm,cortex-a7";
            reg = <0>;
        };
    };
    
    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;
    };
};

这里有几个关键概念,我一个个说:

  • 节点(Node):每个硬件设备就是一个节点,用大括号包裹。比如上面的 cpusmemory 都是节点。
  • 属性(Property):节点的特征描述,比如 compatiblereg 这些都是属性。
  • 标签(Label):给节点起个名字,方便其他地方引用。比如 &uart1 这种写法。

我个人习惯:写设备树时,先把 compatible 属性写好。因为内核驱动就是靠这个字符串来匹配设备的。名字起错了,驱动根本认不出你。

设备树的语法其实不复杂,核心就几条规则:

  1. 每个节点必须有 compatible 属性,这是驱动匹配的关键。
  2. reg 属性描述地址和大小,格式是 <地址 长度>
  3. interrupts 属性描述中断号,需要配合 interrupt-parent 使用。
  4. 节点可以嵌套,子节点继承父节点的某些属性。

你想想看,如果没有设备树,我们得在板级文件里写一堆 platform_device 结构体,改一个管脚就得重新编译内核。有了设备树,改硬件配置只需要改 .dts 文件,方便太多了。

4.2 网卡节点绑定

网卡设备树节点,是咱们课程的重点。我以最常见的 DesignWare MAC(就是 STM32MP1 系列用的那个)为例,给你看看网卡节点怎么写:

&eth1 {
    status = "okay";
    pinctrl-0 = <&eth1_rgmii_pins_a>;
    pinctrl-1 = <&eth1_sleep_pins_a>;
    pinctrl-names = "default", "sleep";
    phy-mode = "rgmii-id";
    phy-handle = <&phy0>;
    max-speed = <1000>;
    
    mdio0 {
        #address-cells = <1>;
        #size-cells = <0>;
        compatible = "snps,dwmac-mdio";
        
        phy0: ethernet-phy@1 {
            reg = <1>;
            compatible = "ethernet-phy-ieee802.3-c22";
            reset-gpios = <&gpiog 0 GPIO_ACTIVE_LOW>;
            reset-assert-us = <10000>;
            reset-deassert-us = <80000>;
        };
    };
};

这段代码里,我挑几个重点属性讲:

属性 含义 我在项目中踩过的坑
phy-mode PHY接口模式,如rgmii、rmii、mii 有一次我写成了"rgmii",少了个i,内核直接报错
phy-handle 指向PHY节点的引用 这个引用必须和mdio子节点里的标签一致
max-speed 最大速率,单位Mbps 不写的话默认1000,但有些PHY只支持100M
pinctrl-* 管脚复用配置 这个最容易忘,忘了网卡根本不通

我曾经在一个项目里,网卡驱动加载成功了,但死活ping不通。查了两天,最后发现是 phy-handle 写错了,指向了一个不存在的PHY节点。内核不会报错,它只是默默地把PHY当作不存在来处理。嗯,这种坑最要命。

还有一个容易被忽略的点:PHY节点的 compatible 属性。很多新手会写具体的PHY型号,比如 compatible = "realtek,rtl8211f"。其实更通用的写法是 compatible = "ethernet-phy-ieee802.3-c22",这样内核会自动用通用PHY驱动来匹配。除非你有特殊需求,否则我建议用通用写法。

4.3 platform_driver 与 platform_device 匹配

设备树写好了,内核怎么知道用哪个驱动来伺候它?这就涉及到 platform_driverplatform_device 的匹配机制了。

说白了,设备树里的每个节点,内核启动时会把它变成一个 platform_device。而驱动里注册的 platform_driver,会通过 compatible 属性来寻找自己对应的设备。

看一个网卡驱动的典型写法:

static const struct of_device_id stmmac_dt_ids[] = {
    { .compatible = "snps,dwmac-3.70a", .data = &dwmac370_ops },
    { .compatible = "snps,dwmac-4.00",  .data = &dwmac400_ops },
    { .compatible = "snps,dwmac-4.10a", .data = &dwmac410_ops },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, stmmac_dt_ids);

static struct platform_driver stmmac_platform_driver = {
    .probe  = stmmac_platform_probe,
    .remove = stmmac_platform_remove,
    .driver = {
        .name           = "stmmac",
        .of_match_table = stmmac_dt_ids,
        .pm             = &stmmac_platform_pm_ops,
    },
};
module_platform_driver(stmmac_platform_driver);

匹配流程其实就三步:

  1. 内核解析设备树,生成 platform_device,其中 dev.of_node 指向设备树节点。
  2. 驱动注册时,内核遍历 of_match_table,和设备树节点的 compatible 属性做比较。
  3. 匹配成功,调用驱动的 probe 函数,把 platform_device 传进去。

你可能会问:如果设备树里没有 compatible 属性怎么办?嗯,那就只能用传统的 platform_device.name 来匹配了。但说实话,现在新板子基本都用设备树,很少有人再写板级文件了。

这里有个小技巧:调试驱动匹配问题时,可以在内核启动参数里加 dyndbg="file drivers/base/platform.c +p",这样内核会打印出平台设备的匹配过程。我曾经靠这个定位过一个驱动死活不probe的问题,最后发现是设备树节点被 status = "disabled" 了。

4.4 知识体系总览

为了让你更直观地理解这三者的关系,我画了一张图:

设备树与平台设备匹配流程 设备树 (.dts) eth1: ethernet@5800a000 compatible = "snps,dwmac" reg = <0x5800a000 0x2000> 内核解析 platform_device name = "stmmac" of_node = &eth1_node resource = [mem, irq] 匹配 platform_driver of_match_table probe() remove() 匹配条件 驱动中的 of_match_table 与设备树节点的 compatible 属性字符串完全一致 常见匹配失败原因 1. compatible 字符串拼写错误(大小写、标点符号) 2. 设备树节点 status = "disabled" 3. 驱动未编译进内核或未加载

从这张图你可以看到,整个流程其实就是一个「描述-解析-匹配」的过程。设备树负责描述硬件,内核负责解析生成平台设备,驱动负责声明自己能处理哪些设备。三者配合好了,网卡就能正常工作。

我建议:刚开始学设备树时,不要试图一次性把所有属性都记住。先记住 compatiblereginterruptsstatus 这四个核心属性,其他的用到再查。我在实际项目中,80%的设备树问题都出在这四个属性上。

好了,这一节的内容就到这里。设备树和平台设备的匹配,说白了就是「对暗号」——驱动说一句暗号(compatible),设备树回一句暗号,对上了就握手成功。下次你遇到网卡驱动不工作,先检查这个暗号对不对得上。


公众号:蓝海资料掘金营,微信deep3321