[ARM Linux 驱动开发] linux 设备树

在没有引入设备树之前,每一个具体的硬件平台都会在 linux 源码包的 arch/arm/mach-xxx/ 目录下存在一个硬件信息描述的源码包,其中定义了 GPIO、I2C 等外设和系统信息。这些信息也定义为 .h.c 文件,内容多为描述设备信息的结构体,同驱动源码一起编译。

由此可见,在没有设备树的情况下,随着新硬件平台的不断产生(特别是手机行业,更新特别迭代快),linux 驱动代码中会增加越来越多的板级描述 .h.c 文件。而 linux 源码中应该更多关注和体现的是处理驱动的逻辑,而不是这些“没有的”板级描述文件:也就是 Linus 所说的 "this whole ARM thing is a f*cking pain in the ass"。

在 x86 体系下没有特别强调设备树的概念。

应该是 PC 兼容机有组织标准规定,或者是 PCI 总线也有类似的“设备树”机制。

导致以上问题的主要原因就是驱动代码和设备信息没有分离,而设备树就是为了解决这个问题的。由于目前对设备树没有很好的总体观念,所以接下来的行文组织会不是很有条例,基本算是知识的“收集和记录”。

大致感受设备树

设备树一般位于 linux 源码的 arch/arm/boot/dts 目录,对应 .dts(device tree source) 后缀的文件。dts 文件可以通过

  • > make dtbs

命令编译成 .dtb(device tree blob) 后缀的二进制文件,以供内核驱动使用。图 1 为一个 dts 文件的一部分,可以看到它也是类似 xml 格式的层级结构。

图1 imx6ull-14x14-evk.dts

linux 内核在启动的时候会解析设备树中的各个节点信息,并在 /proc/device-tree/ 目录下根据节点名字创建不同的文件夹或文件。如图 2 所示,可以看到创建的目录层级结构和 dts 中指定的层级结构是完全对应的。

图2 设备树在系统中的体现

我们这篇文章先不深究设备树加载等详细逻辑流程,但是可以大抵推测设备的相关操作:编译后的 dtb 文件会被内核存放在某处以供查询,而查询的操作会和 xml 文件类似,提供一系列层级相关的查询函数。

在大致感受了一下什么是设备树之后,文章后续会简单介绍一下设备树文本的内容语法和语义;然后会编写一个简单驱动,了解一下相关的查询函数。

设备树标准

设备树的标准文档可参考:

Devicetree specification

图 3 就是我们上节所说的设备树的层级结构,根节点为 /

图3 Devicetree Structure and Conventions in 2.2

术语节点(node)的结构如下,我们依次进行说明,同时涉及术语属性(property)标签(label)

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

node-name

node-name 组件指定节点的名称。长度应为 1 至 31 个字符,并且需要描述设备的一般类型。

unit-address

unit-address 特别针对于节点所在的总线类型。unit-address 必须与此节点的 reg 属性中指定的第一个地址匹配。如果该节点没有 reg 属性,则必须省略 @unit-address。仅 node-name 就可以将该节点与书中同一级别的其他节点区分开。特定总线的绑定可以为 regunit-address 指定额外的、更加具体的要求。

properties

设备树中的每个节点都有描述节点特征的属性。属性由名称和值组成。

属性的值是零个或多个字节的数组,包含与属性相关联的信息。如果传递的是 true-false 信息,那么属性可以有空值。在这种情况下,属性的存在与否就已经足够说明问题了。

label

设备树的源格式中允许将标签附加到任何节点和属性值上。phandle 和路径引用可以通过引用标签自动生成,而不是显式指定 phandle 的值或节点的完整路径。标签仅在设备树的源格式中使用,不会编码进 DTB 二进制文件中。

标签是通过在标签名称后面附加冒号(':')来创建的。引用是通过在标签前面加取值符号('&')来创建的。

节点名称和属性名称都分标准规定和私有定义。标准定义的内容含义可在 Devicetree specification 中查阅;私有特定的内容含义可在源码目录下的 Documentation/devicetree/bindings/ 目录下查阅。这些内容就不赘述了,准备之后用到什么再查阅什么。

设备树操作函数

最后我们编写一个简单的驱动,借此了解一下设备树相关的操作函数。设备树相关的操作函数在 linux/of.h 头文件中定义。相关操作函数都是以 of 打头的。

我们以根节点下的 backlight 节点为例,依次读取其 compatiblebrightness-levelsdefault-brightness-levelstatus 的属性值。

  • / {
  •     …………
  •     backlight {
  •         compatible = "pwm-backlight";
  •         pwms = <&pwm1 0 5000000>;
  •         brightness-levels = <0 4 8 16 32 64 128 255>;
  •         default-brightness-level = <6>;
  •         status = "okay";
  •     };

of_find_node_by_path

of_find_node_by_path 函数可以根据节点的路径获取节点结构体。此例中节点路径为 /backlight。这个函数的使用见代码 1 中的第 13 至 19 行,其中保存了返回的节点结构体指针。

of_find_property

of_find_property 函数可以获取指定节点下的某一属性结构体。见代码 1 的第 21 至 29 行,此例中我们想获取 compatible 属性结构体。见第 30 行,属性结构体中的 value 成员保存了属性的值,这边我们将其打印出来。

有特别针对的函数用于获取不同类型的属性值,我们接着看。

of_property_read_string

of_property_read_string 函数用于获取类型为字符串的属性值。例子中 status 属性值为字符串类型,代码 1 中的第 31 至 35 行对其进行了读取。需要注意的是,返回的是一个字符串指针,其指向已分配内容的地址,而不是一份拷贝。因此,我们并不需要额外为字符串分配空间。

of_property_read_u32

of_property_read_u32 函数用于获取类型为 4 字节整形的属性值。代码 1 中的第 54 至 58 行,对 default-brightness-level 属性进行了读取。

of_property_count_elems_of_size

of_property_read_u32 函数针对于类型为数组的属性值,它可以获取数组的长度。我们可以根据获取到的长度,动态分配需要的空间。代码 1 中的第 54 至 58 行,获取了 brightness-levels 属性值的数组长度。然后第 66 行使用 kmalloc 分配了指定大小的空间,用于存储后续读取的值。

of_property_read_u32_array

of_property_read_u32_array 函数用于获取类型为数组的属性值。此例中存放读取数组的内容是 kmalloc 分配的,最后不要忘记使用 kfree 释放(代码 1 第 90 行)。

代码1 设备树操作函数
  1. #include <linux/module.h>
  2. #include <linux/of.h>
  3. #include <linux/slab.h>
  4.  
  5. static int __init dtsof_init(void)
  6. {
  7.     struct device_node* backlight_node = NULL;
  8.     struct property* compatible_prop = NULL;
  9.     const char* str;
  10.     u32 u32_val;
  11.     u32* u32_arr;
  12.     int res, cnt, i;
  13.     /* 'backlight' node */
  14.     backlight_node = of_find_node_by_path("/backlight");
  15.     if (backlight_node == NULL)
  16.     {
  17.         printk("of_find_node_by_path /backlight failed\n");
  18.         return -EINVAL;
  19.     }
  20.     /* 'compatible' property */
  21.     compatible_prop = of_find_property(
  22.         backlight_node,
  23.         "compatible",
  24.         NULL);
  25.     if (compatible_prop == NULL)
  26.     {
  27.         printk("of_find_property compatible failed\n");
  28.         return -EINVAL;
  29.     }
  30.     printk("compatible = %s\n", (char*)compatible_prop->value);
  31.     /* 'status' property - string value */
  32.     res = of_property_read_string(
  33.         backlight_node,
  34.         "status",
  35.         &str);
  36.     if (res < 0)
  37.     {
  38.         printk("of_property_read_string status failed\n");
  39.         return res;
  40.     }
  41.     printk("status = %s\n", str);
  42.     /* 'default-brightness-level' property - u32 value */
  43.     res = of_property_read_u32(
  44.         backlight_node,
  45.         "default-brightness-level",
  46.         &u32_val);
  47.     if (res < 0)
  48.     {
  49.         printk("of_property_read_u32 default-brightness-level failed\n");
  50.         return res;
  51.     }
  52.     printk("default-brightness-level = %u\n", u32_val);
  53.  
  54.     /* 'brightness-levels' property - u32 array value */
  55.     cnt = of_property_count_elems_of_size(
  56.         backlight_node,
  57.         "brightness-levels",
  58.         sizeof(u32));
  59.     if (cnt < 0)
  60.     {
  61.         printk("of_property_count_elems_of_size brightness-levels failed\n");
  62.         return cnt;      
  63.     }
  64.     printk("brightness-levels has %u elements\n", cnt);
  65.  
  66.     u32_arr = kmalloc(sizeof(u32) * cnt, GFP_KERNEL);
  67.     if (u32_arr == NULL)
  68.     {
  69.         printk("kmalloc failed\n");
  70.         return -EINVAL;
  71.     }
  72.  
  73.     res = of_property_read_u32_array(
  74.         backlight_node,
  75.         "brightness-levels",
  76.         u32_arr,
  77.         cnt);
  78.     if (res < 0)
  79.     {
  80.         printk("of_property_read_u32_array brightness-levels failed\n");
  81.         goto fail_free;
  82.     }
  83.  
  84.     for (i = 0; i < cnt; i++)
  85.     {
  86.         printk("brightness-levels[%d] = %u\n", i, u32_arr[i]);
  87.     }
  88.  
  89. fail_free:
  90.     kfree(u32_arr);
  91.     return res;
  92. }
  93.  
  94. static void __exit dtsof_exit(void)
  95. {
  96.  
  97. }
  98.  
  99. module_init(dtsof_init);
  100. module_exit(dtsof_exit);
  101.  
  102. MODULE_LICENSE("GPL");
  103. MODULE_AUTHOR("hanhanblog");

我们开发板上验证所写的程序,如图 4 所示,参照 dts 文件进行核对,打印的内容正确无误。

图4 打印节点属性内容

总结

这是第一次接触设备树,每次接触一块新知识,进展就会变慢。“本着用到时再深入的原则”,本篇文章也算是对设备树的一个初步认识和总结,希望后续和设备树相关的内容能越学越快。