设备树的引入与作用

  • 以 LED 驱动为例,如果你要更换 LED 所用的 GPIO 引脚,需要修改驱动程序源码、重新编译驱动、重新加载驱动
  • 在内核中,使用同一个芯片的板子,它们所用的外设资源不一样,比如 A 板用 GPIO A,B 板用 GPIO B。而 GPIO 的驱动程序既支持 GPIO A 也支持 GPIO B,你需要指定使用哪一个引脚,怎么指定?在 c 代码中指定。
  • 随着 ARM 芯片的流行,内核中针对这些 ARM 板保存有大量的、没有技术含量的文件
  • 于是,Linux 内核开始引入设备树
  • 设备树只是用来给内核里的驱动程序,指定硬件的信息。比如 LED 驱动,在内核的驱动程序里去操作寄存器,但是操作哪一个引脚?这由设备树指定
  • 系统启动时U-boot 会把内核和设备树文件都读入内存,然后启动内核。在启动内核时会把设备树在内存中的地址告诉内核

设备树的语法

  • 为什么叫树?
    • image.png
  • 需要编写设备树文件(dts: device tree source),它需要编译为 dtb(device tree blob)文件,内核使用的是 dtb 文件
  • dts文件是根本,一个设备树示例如下:
    • image.png
  • 对应的 dts 文件为:
    • image.png
  • /表示根节点root,在树中有各种的子节点(CPU/MEM/IIC等)
  • 节点中有什么内容?
    • 可以加上各种属性
    • name = val;
      • val的取值可以有3钟:

arrays of cells(1 个或多个 32 位数据, 64 位数据使用 2 个 32 位数据表示),
string(字符串),
bytestring(1 个或多个字节)
image.png

  • 设备树太过于灵活,我们必须直到它有那些默认的节点、默认的属性
  • 设备树的基本单元称为node,一个节点的例子: ```c /dts-v1/; // 表示版本 / {
    uart0: uart@fe001000 {
    1. compatible="ns16550";
    2. reg=<0xfe001000 0x100>;
    }; };

一个node的格式为: [label:] node-name[@unit-address] { [properties definitions] [child nodes] };

  1. - 怎么修改node
  2. ```c
  3. // 在根节点之外使用 label 引用 node:
  4. &uart0 {
  5. status = “disabled”;
  6. };
  7. 或在根节点之外使用全路径:
  8. &{/uart@fe001000} {
  9. status = “disabled”;
  10. };
  • 一颗设备树中,有哪些默认的、常用的节点?
    • dts 文件中必须有一个根节点
    • CPU 节点
    • memory 节点
    • chosen 节点:chosen节点并不对应设备,它是一个虚拟的节点,可以在该节点中设置bootargs参数
  • 有那些常用的属性呢?
    • address-cells、#size-cells

address-cells:address 要用多少个 32 位数来表示;
size-cells:size 要用多少个 32 位数来表示。

  1. / {
  2. #address-cells = <1>;
  3. #size-cells = <1>;
  4. memory {
  5. reg = <0x80000000 0x20000000>;
  6. };
  7. };
  8. /*
  9. address-cells 为 1,所以 reg 中用 1 个数来表示地址,即用 0x80000000 来表示地址;
  10. size-cells 为 1,所以 reg 中用 1 个数来表示大小,即用 0x20000000 表示大小
  11. address-cells和size-cells会影响到它的子节点reg属性
  12. */
  • reg:reg 的本意是 register,用来表示寄存器地址
    • 在设备树里,它可以用来描述一段空间。反正对于 ARM 系统,寄存器和内存是统一编址的,即访问寄存器时用某块地址,访问内存时用某块地址,在访问方法上没有区别
    • reg 属性的值,是一系列的“address size”,用多少个 32 位的数来表示 address 和 size,由其父节点的#address-cells、#size-cells 决定
    • 还可以用来标记多核cpu中的哪个
      • image.png
      • 设备不同,reg属性的含义就不同
  • compatible:
    • “compatible”表示“兼容”,对于某个 LED,内核中可能有 A、B、C 三个驱动都支持它

led {
compatible = “A”, “B”, “C”;
};

  1. - 它的值是字符串列表
  • model:
    • model 属性与 compatible 属性有些类似,但是有差别
    • model 用来准确地定义这个硬件是什么。

/ {
compatible = “samsung,smdk2440”, “samsung,mini2440”;
model = “jz2440_v3”;
};

  1. - 表示这个单板,可以兼容内核中的“smdk2440”,也兼容“mini2440”;但实际是JZ2440_v3,可以用内核中mini2440/smdk2440兼容的源码来初始化
  • status
    • dtsi 文件中定义了很多设备,但是在你的板子上某些设备是没有的;你可以给这个设备节点添加一个 status 属性,设置为“disabled”
    • image.png
    • 经常使用的值为:okay,disabled
    • status属性用处很大
    • image.png
      • 设备树文件不需要我们从零写出来,内核支持了某款芯片比如 imx6ull,在内核的 arch/arm/boot/dts目录下就有了能用的设备树模板,一般命名为 xxxx.dtsi。“i”表示“include”,被别的文件引用的
      • 我们使用某款芯片制作出了自己的单板,所用资源跟 xxxx.dtsi 是大部分相同,小部分不同,所以需要引脚 xxxx.dtsi 并修改。
      • dtsi 文件跟 dts 文件的语法是完全一样的
      • dts 中可以包含.h 头文件,也可以包含 dtsi 文件,在.h 头文件中可以定义一些宏。
  • 对于64bit的CPU,dts文件位于:源码目录/arch/arm64/boot/dtsimage.png
  • 对于32位的CPU,dts文件位于:源码目录/arch/arm/boot/dts
  • 怎么编译设备树文件dts?
    • image.png
    • 在内核源码的根目录:make dtbs
    • 也可以进行手工编译
    • 首先使用gcc预编译(预处理 -E)为一个临时文件,然后再使用dtc设备树的编译器将临时文件编译成dtb文件
  • 怎么使用dtb文件?
    • image.png
    • 设备树文件是给驱动程序用的(也就是说是给内核用到),谁来把这些设备树文件传给内核?
      • 由bootloader来传给内核
  • 在内核的虚拟文件系统中,有一个目录会将所有的设备树都会列出来
    • image.png
    • ls /sys/firmware/
    • image.png
    • /sys/firmware下有devicetree和fdt文件,其中fdt就是dtb文件
    • 怎么将dtb反编译为dts文件?
      • image.png
      • image.png

内核对设备树的处理

  • image.png
  • 我们对这些处理过程并不关心
  • dtb 中每一个节点都被转换为 device_node 结构体
    • image.png
    • name和type来源于dts中过时的name和device_type属性,即使dts中没有定义也会有
  • 哪些设备树节点会被转换为 platform_device
    • A. 根节点下含有 compatile 属性的子节点
    • B. 含有特定 compatile 属性的节点的子节点

如 果 一 个 节 点 的compatile属 性 , 它 的 值 是 这4者 之 一 : “simple-bus”,”simple-mfd”,”isa”,”arm,amba-bus”,
那么它的子结点(需含 compatile 属性)也可以转换为 platform_device。

  • C. 总线 I2C、SPI 节点下的子节点:不转换为 platform_device

某个总线下到子节点,应该交给对应的总线驱动程序来处理, 它们不应该被转换为 platform_device。

  • 得到了一系列的platform_device,这些platform_device怎么找到对应的platform_driver?
    • 从设备树转换得来的 platform_device 会被注册进内核里,以后当我们每注册一个 platform_driver时,它们就会两两确定能否配对,如果能配对成功就调用 platform_driver 的 probe 函数。
    • image.png
  • 一个写得好的驱动程序, 它会尽量确定所用资源。
  • 只把不能确定的资源留给设备树, 让设备树来指定
  • 根据原理图确定”驱动程序无法确定的硬件资源”, 再在设备树文件中填写对应内容