设备树学习

本文深入探讨设备树DTS在嵌入式领域的应用,解析DTS、DTSI及DTB的作用与生成流程,通过实例展示如何配置硬件信息,如设备寄存器地址、中断号等,以及驱动代码如何与DTS匹配,实现高效资源管理。

我的理解本质上还是复用。尤其是嵌入式领域,设备多种多样,但是很多设备接口都是标准的,或者大同小异。以前驱动开发可能每个设备商都去抄别家的现成代码,这样造成了大量的垃圾代码。后面linux内核就把这些做成公共库抽象出来,后面设备只需要传入“我是什么设备”,那么linux就可以从内核中公共库找到需要的驱动,避免了很多质量不一的冗余代码,而且也方便了管理。

在设备树中,涉及到的文件主要是DTS,DTSI,还有DTB。

基本流程是编译前编写DTS(有一个DTSi,可以视为厂商的DTS头文件,可以提取一些共性数据),之后会通过DTC编译成DTB,通过Bootloader传给kernel。

DTS基本上描述一个特定的硬件,DTSi则是包含可共享的硬件信息。DTB是编译之后的二进制文件。

下面是一个DTS文件的例子:

/dts-v1/;

/ {
    compatible = "my_board";
    model = "My Custom Board";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            device_type = "cpu";
            compatible = "arm,cortex-a7";
            reg = <0>;
        };
    };

    memory {
        device_type = "memory";
        reg = <0x80000000 0x4000000>; // Start address and size of the memory
    };

    uart@0 {
        compatible = "ns16550a";
        reg = <0x01c28000 0x20>; // Base address and size of the UART registers
        interrupt-parent = <&intc>;
        interrupts = <1>;
    };
};

这里就描述这个设备的硬件信息,其中compatible 就是这个设备的硬件名称,用来在驱动中做匹配的。reg描述设备的寄存器地址和长度。第一个值是基地址,第二个值是寄存器空间大小。interrupts定义设备使用的中断号,interrupt-parent指定中断控制器。#address-cells 和 #size-cells 定义子节点地址和大小的单元数目。常用于总线节点,例如CPU节点。device_type 指定设备的类型,常用于CPU和内存节点。

总之,设备描述符的内容其实就是驱动中相关配置的提取。所以具体这些参数怎么定义?怎么填写,怎么用,都要看驱动里面是怎么搞的。

之后就是编译,生成二进制的dtb文件:

dtc -I dts -O dtb -o example.dtb example.dts

驱动里面怎么做的呢?

static const struct of_device_id my_uart_of_match[] = {
    { .compatible = "ns16550a" },
    { }
};
MODULE_DEVICE_TABLE(of, my_uart_of_match);

static int my_uart_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *base;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENODEV;

    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base))
        return PTR_ERR(base);

    // Initialize UART hardware and register the driver

    return 0;
}

static struct platform_driver my_uart_driver = {
    .probe = my_uart_probe,
    .driver = {
        .name = "my_uart",
        .of_match_table = my_uart_of_match,
    },
};

module_platform_driver(my_uart_driver);

可以看到,大致应该就是遍历匹配,DTS里面有compatible,驱动代码里面也有compatible。两个match上了就OK。之后继续处理其它的参数。

到这里,可以看出,DTS其实就是驱动的配置文件。。。

再说一下设备树的语法:

设备树分别属性和Node节点两种类型。

这个就是节点:

uart0: serial@40010000 {
    compatible = "example,uart";  // 兼容性字符串
    reg = <0x40010000 0x1000>;    // 地址和大小
    interrupts = <5>;             // 中断号
    status = "okay";              // 设备状态
};

节点的格式如下:

[lable:] node-name[@unit-address] {
    [properties definitions];
    [child nodes] ;
};

label是标号,用来引用的。必须全局唯一。而node-name是可以重复的。

比如这个&uart0 {status = "disable";};,就是修改了上面serial@40010000中属性的值。也可以用node-name[@unit-address] 来引用。

这个就是属性:

compatible = "example,uart"; 

属性中不同的类型,用的符号不同,比如“”是字符串,<>是数值。[]是字节序列。三者可以用逗号分隔。

在设备树中,还有一种机制就是overlay。这个鬼词在嵌入式真的用烂了,之前openwrt的文件系统也是overlay。

在这里是指的追加DTB。命令如下:

dtc -I dts -O dtb -o base.dtb base.dts
dtc -I dts -O dtb -o overlay.dtb overlay.dts
fdtoverlay -i base.dtb -o final.dtb overlay.dtb

最后解答一下以前写的一些疑问。

1 DTS写的驱动只是一个配置,那么真实的驱动在哪里?

代码中,device或者vendor中。内核中也有可能。

2 从上面的描述看Bootloader和Kernel是共用一个DTS,是怎么实现的?

Bootloader读取编译后的DTB,载入内存中。

之后通过命令传入这个地址给Kernel。bootz ${loadaddr} - ${fdtaddr}

3 DTS编写的规则。

这个好像没有统一的规则,就像每个程序的配置文件都不同,只有看每个驱动要什么。。。

4 增加一个常用设备,是什么流程?

基本上只用改DTS文件就可以。

5 增加一个全新设备,又是什么流程?

一般来说就是增加一套驱动,当然,可以再增加一套DTS,也可以不增加。这个取决于项目需求。

这篇真的写的比较全:

linux内核机制之设备树

Linux DTS (Device Tree Source)设备树源码_设备树编译源文件-优快云博客

Linux DTS(Device Tree Source)设备树详解之一(背景基础知识篇)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值