1、设备树dts文件的语法规范node
1. DTS文件布局(layout)linux
/dts-v1/; [memory reservations] // 格式为: /memreserve/ <address> <length>; / { [property definitions] [child nodes] };
(1) 特殊的、默认的属性编程
a. 根节点的:框架
#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address) #size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size) compatible // 定义一系列的字符串, 用来指定内核中哪一个 machine_desc 能够支持本设备,即这个板子兼容哪些平台 model // 这个板子是什么,好比有2款板子配置基本一致, 它们的compatible是同样的,那么就经过model来分辨这2款板子
(2) /memory 节点ide
device_type = "memory"; reg // 用来指定内存的地址、大小
(3) /chosen 节点函数
bootargs // 内核 command lin e参数,跟u-boot中设置的bootargs做用同样
(4) /cpus 节点工具
/cpus节点下有1个或多个cpu子节点, cpu子节点中用reg属性用来标明本身是哪个cpu,因此 /cpus 中有如下2个属性:布局
#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address) #size-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size),必须设置为0
2. Devicetree node格式:ui
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
(1) Property的2种格式this
[label:] property-name = value; //有值 [label:] property-name; //有值
(2) Property的3种取值
arrays of cells:1个或多个32位数据,64位数据使用2个32位数据表示。 string:字符串, bytestring:1个或多个字节 示例: a. Arrays of cells,一个cell就是一个32位的数据 interrupts = <17 0xc>; b. 64bit数据使用2个cell来表示 clock-frequency = <0x00000001 0x00000000>; c. null-terminated string compatible = "simple-bus"; d. bytestring(字节序列) local-mac-address = [00 00 12 34 56 78]; // 每一个byte使用2个16进制数来表示 local-mac-address = [000012345678]; // 每一个byte使用2个16进制数来表示(中间也能够没有空格) e. 能够是各类值的组合, 用逗号隔开: compatible = "ns16550", "ns8250"; //是能够附多个值的,对每一个字符串的获取可参考__of_device_is_compatible() example = <0xf00f0000 19>, "a strange property format";
3. 引用其余节点
(1) 经过phandle来引用 // 节点中的phandle属性, 它的取值必须是惟一的(不要跟其余的phandle值同样),例子:
pic@10000000 { phandle = <1>; interrupt-controller; }; another-device-node { interrupt-parent = <1>; // 使用phandle值为1来引用上述节点 };
(2) 经过label来引用
PIC: pic@10000000 { interrupt-controller; }; another-device-node { /* * 使用label来引用上述节点,使用lable时实际上也是使用phandle来引用, * 在编译dts文件为dtb文件时,编译器dtc会在dtb中插入phandle属性。 */ interrupt-parent = <&PIC>; };
4. dts文件示例
/dts-v1/; /memreserve/ 0x33f00000 0x100000 //预留1M内存,不给内核使用 / { model = "SMDK24440"; /* * 这里指定了两个值,从左到右依次匹配,只要有一个值匹配上了便可,匹配函数可见上 * 面的__of_device_is_compatible(). * 全部的字符串,通常是从具体到通常。 * 也能够是前面是咱们本身开发的平台的,后面是EVB的。利用EVB的进行匹配,本身的起 * 说明做用。 */ compatible = "samsung,smdk2440", "samsung,smdk24xx"; /* * 一个cells表示一个32bit的unsigned int。 * 这里表示在其子节点里面,起始地址使用一个32bit的int表示, * 大小使用一个32bit的int表示。 */ #address-cells = <1>; #size-cells = <1>; /*解析成平台设备的设备名字为"30000000.memory",设备树中的路径名是"/memory@30000000"*/ memory@30000000 { /*内存的device_type是约定好的,必须写为"memory"*/ device_type = "memory"; /* * 表示一段内存,起始地址是0x30000000,大小是0x4000000字节。 * 如果reg=<0x30000000 0x4000000 0 4096> 则表示两段内存,另外一段的 * 起始地址是0,大小是4096字节。解析成这样的结果的缘由是上面指定了 * address-cells和size-cells都为1. */ reg = <0x30000000 0x4000000>; /*解析成平台设备的设备名字为"38000000.trunk",设备树中的路径名是"/memory@30000000/trunk@38000000"*/ trunk@38000000 { device_type = "memory_1"; reg = <0x38000000 0x4000000>; }; }; /*指定命令行启动参数*/ chosen { bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200"; }; led { compatible = "jz2440_led"; pin = <S3C2410_GPF(5)>; }; pic@10000000 { /*这个phandle必须是惟一的*/ phandle = <1>; interrupt-controller; }; another-device-node { interrupt-parent = <1>; // 使用phandle值为1来引用上面的节点 }; /*上面的引用比较麻烦,可使用下面的方法来引用lable*/ PIC: pic@10000000 { interrupt-controller; }; another-device-node { /* * 使用label来引用上述节点,使用lable时实际上也是 * 使用phandle来引用,在编译dts文件为dtb文件时, 编译 * 器dtc会在dtb中插入phandle属性 */ interrupt-parent = <&PIC>; }; };
5. dts文件对dtsi文件中节点的引用与改写
设备树中把一些公共的部分写在 .dtsi 文件中,.dts 文件能够去包含 .dtsi 文件,二者的语法格式是相同的。如果把上面内容定义在 smdk2440.dtsi 文件中,使用基于smdk2440的平台的dts文件包含它,而且想覆盖led节点的方法是在dts文件中:
(1) 如果led节点在dtsi中没有指定label,须要经过全路径引用
/dts-v1/; #include "jz2440.dtsi" / { &led { pin = <S3C2410_GPF(6)>; }; };
(2) 如果在dtsi中指定了label,如在dtsi中的表示为
Led1: led { compatible = "jz2440_led"; pin = <S3C2410_GPF(5)>; };
这样的话在dts文件中只须要下面操做便可:
/dts-v1/; #include "jz2440.dtsi" &Led1 { pin = <S3C2410_GPF(6)>; }
也就是后面写的属性会覆盖前面写的属性。
使用lable后,不须要也不能写在根节点里面了,直接写。
设备树中任何节点的路径名不能相同,不然就被认为是同一个设备树节点。
能够经过反编译dtb文件来验证修改的正确性,将dtb转换为dts的方法: ./scripts/dtc/dtc -I dtb -O dts -o tmp.dts arch/arm/boot/dts/jz2440.dtb
6. 相关资料
DT官方文档: https://www.devicetree.org/specifications/
官方文档(DTB格式): https://www.devicetree.org/specifications/
内核文档: Documentation/devicetree/booting-without-of.txt
内核文档: Documentation/devicetree/usage-model.txt
2、设备树dtb的内存布局
1. DTB文件布局:
------------------------------ base -> | struct boot_param_header | ------------------------------ | (alignment gap) (*) | ------------------------------ | memory reserve map | ------------------------------ | (alignment gap) | ------------------------------ | | | device-tree structure | | | ------------------------------ | (alignment gap) | ------------------------------ | | | device-tree strings | | | -----> ------------------------------ | | --- (base + totalsize)
“device-tree strings” 区域中存放dts中全部属性的名字,使用‘\0’隔开各个字符。如“compatible”、“#address-cells”、“#size-cells”、“device_type”、“reg”、“bootargs”等左值字符串。可是右值字符串不是存放在这里的。
“memory reserve map” 中存放预留内存信息,例如:“/memreserve/ 0x33f00000 0x100000”,使用struct fdt_reserve_entry结构存储。
“device-tree structure” 中存储全部的设备节点
2. 注意,在dtb文件中数据的存放格式是大字节序的,大小字节序只对数值的存储有差异,对于字符串的存储是没有差异的。
3. 相关结构体描述定义在:linux4.14.39/scripts/dtc/libfdt/fdt.h
/*设备树的头部信息描述,用UE打开一个dtb文件,最开始的就是fdt_header*/ struct fdt_header /*描述reserved的内存*/ struct fdt_reserve_entry
4. 参考文档:Documentation\devicetree\booting-without-of.txt
3、设备树dtc,dtdiff工具
1. dtc工具安装
# apt-get install device-tree-compiler # dtc # dtc --help
2. 由dts生成dtb:
# dtc -I dts -O dtb -o devicetree.dtb jz2440.dts
3. 由dtb生成dts
# dtc -I dtb -O dts -o tmp.dts devicetree.dtb
4. dtc --help
Usage: dtc [options] <input file> Options: -[qI:O:o:V:d:R:S:p:fb:i:H:sW:E:hv] -q, --quiet Quiet: -q suppress warnings, -qq errors, -qqq all -I, --in-format <arg> Input formats are: dts - device tree source text dtb - device tree blob fs - /proc/device-tree style directory -o, --out <arg> Output file -O, --out-format <arg> Output formats are: dts - device tree source text dtb - device tree blob asm - assembler source -V, --out-version <arg> Blob version to produce, defaults to %d (for dtb and asm output) -d, --out-dependency <arg> Output dependency file -R, --reserve <arg> tMake space for <number> reserve map entries (for dtb and asm output) -S, --space <arg> Make the blob at least <bytes> long (extra space) -p, --pad <arg> Add padding to the blob of <bytes> long (extra space) -b, --boot-cpu <arg> Set the physical boot cpu -f, --force Try to produce output even if the input tree has errors -i, --include <arg> Add a path to search for include files -s, --sort Sort nodes and properties before outputting (useful for comparing trees) -H, --phandle <arg> Valid phandle formats are: legacy - "linux,phandle" properties only epapr - "phandle" properties only both - Both "linux,phandle" and "phandle" properties -W, --warning <arg> Enable/disable warnings (prefix with "no-") -E, --error <arg> Enable/disable errors (prefix with "no-") -h, --help Print this help and exit -v, --version Print version and exit
5. dtdiff 工具用于对比设备树的差异:
# dtdiff devicetree.dtb devicetree_1.dtb --- /dev/fd/63 2019-06-08 11:43:56.086042406 +0800 +++ /dev/fd/62 2019-06-08 11:43:56.086042406 +0800 @@ -17,6 +17,6 @@ memory@30000000 { device_type = "memory"; - reg = <0x30000000 0x4000000>; + reg = <0x30000000 0x4000002>; }; };
5. 一个使用场景
经过反编译dtb获取的dts文件比较纯净,由于实际项目上可能有多个dtsi被包含进来,搞的人眼花缭乱。经过反编译获得的dts文件只须要看这一个文件便可。
4、设备树节点变为 platform_device 的过程和与驱动的匹配过程
1. 在dts文件中构造节点,每个节点中都含有资源,充当平台设备的设备端。编译后生成 .dtb 文件传给内核,内核解析设备树后为每个节点生成一个 device_node 结构,而后根据这个结构生成平台设备的设备端。根据设备树节点的 compatible 属性来匹配平台设备的驱动端。
.dts ---> .dtb ---> struct device_node ---> struct platform_device 注: dts - device tree source // 设备树源文件 dtb - device tree blob // 设备树二进制文件, 由dts编译得来 blob - binary large object
2. 来自dts的platform_device结构体 与 咱们写的platform_driver 的匹配过程
来自 dts 的 platform_device 结构体 里面有成员 ".dev.of_node",它里面含有各类属性, 好比 compatible, reg, pin等。咱们写的 platform_driver 里面有成员 ".driver.of_match_table",它表示能支持哪些来自于 dts 的 platform_device。
若是设备端的 of_node 中的 compatible 跟 驱动端的 of_match_table 中的 compatible 一致,就能够匹配成功,则调用platform_driver 中的 probe 函数。在probe函数中,能够继续从 of_node 中得到各类属性来肯定硬件资源。
platform_match(struct device *dev, struct device_driver *drv) // drivers/base/platform.c of_driver_match_device(dev, drv) // include/linux/of_device.h of_match_device(drv->of_match_table, dev) // drivers/of/device.c of_match_node(matches, dev->of_node); // drivers/of/device.c __of_match_node(matches, node); // drivers/of/device.c //对于驱动of_device_id中给出的每一项都与设备树节点的compatible属性中的每个值进行匹配, //返回匹配度最高的计数值best_match __of_device_is_compatible(node, matches->compatible, matches->type, matches->name);
static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev = to_platform_device(dev); struct platform_driver *pdrv = to_platform_driver(drv); /* When driver_override is set, only bind to the matching driver */ if (pdev->driver_override) return !strcmp(pdev->driver_override, drv->name); /* Attempt an OF style match first */ if (of_driver_match_device(dev, drv)) return 1; /* Then try ACPI style match */ if (acpi_driver_match_device(dev, drv)) return 1; /* Then try to match against the id table */ if (pdrv->id_table) return platform_match_id(pdrv->id_table, pdev) != NULL; /* fall-back to driver name match */ return (strcmp(pdev->name, drv->name) == 0); }
platform_match分析:
a. 若是 pdev->driver_override 被赋值,就直接使用它进行设备和驱动的名字进行匹配。
b. 尝试使用设备树进行匹配
c. 若是平台驱动端提供了 pdrv->id_table,则使用平台设备的名字与平台驱动 id_table 列表中的名字进行匹配。
d. 不然直接匹配平台设备的名字和平台驱动的名字。
3. 有可能compatible相同也不必定选择的就是这个匹配,由于有可能有匹配度更高的,好比除了compatible匹配上了之外,name和type也都匹配上了,那么匹配度就是最高的。
of_match_table是struct device_driver的成员 const struct of_device_id *of_match_table,定义以下:
struct of_device_id { char name[32]; char type[32]; char compatible[128]; const void *data; };
经过设备树的compatible属性的匹配的规则就是:若是compatible属性不存在,就匹配type和name,可是权重极低。如果compatible属性存在,可是匹配补上就当即返回,不在进行后续的匹配。
4. compatible 属性的书写规范为:"厂家,产品",例如: "jz2440,led"
5. 设备树是平台总线设备模型的改进
引入设备树以前,平台设备模型的资源定义在平台设备的设备端,引入设备树后定义在设备树中,能够说设备树是对平台设备模型的一种改进,本质上仍是平台设备模型。
6. 设备树dts文件的语法
a. 可使用一些C语言语法
b. 使用/* */ 或 // 来注释
c. 每一句源代码都使用 ";" 隔开
好比:
#define S3C2410_GPA(_nr) ((1<<16)+1) // dts文件中使用C语言的语法定义宏
补充: 是 platform_match 进行匹配的缘由
start_kernel // init/main.c rest_init(); pid = kernel_thread(kernel_init, NULL, CLONE_FS); /*建立kernel_init内核线程*/ kernel_init kernel_init_freeable(); do_basic_setup(void) /*负责设备节点,固件,平台设备初始化,sysfs文件框架搭建*/ driver_init(void) platform_bus_init(void) bus_register(&platform_bus_type); .match = platform_match,
五. 设备树的/sys目录下的文件和驱动获取
1. 设备树的sysfs属性都存在于of_node下,of_node(open firmare node) 这个目录下就是设备树中节点的成员了。能够直接打印里面的 reg 成员寄存器中的值
# hexdump reg # hexdump -C reg 以字节和ASCII码的方式显示出来,能够本身加"-h"选项查看出来。
例如:
led { compatible = "jz2440_led"; reg = <(5<<16)+5, 1> }; # hexdump -C reg 的结果就是: 00 05 00 05 00 00 00 01 // 设备树中是使用大字节序描述的。
2. 驱动中对设备树节点属性的获取
参考 linux4.14.39/include/linux/of.h,这里面的函数都是以 struct device_node 结构为参数的。
设备树节点构形成的 struct device_node 结构存在于:
struct platform_device; //include/linux/platform_device.h struct device dev; //include/linux/device.h struct device_node *of_node; //include/linux/of.h 对应的设备树节点
3. 内核帮助文档
(1) 驱动程序中使用的设备树节点的内容的编写方法在:documentation/devicetree/bindings
(2) 整个设备树如何写参考EVB板的: arch/arm64/boot/dts/qcom
4. 使用设备树编程
一个写的好的驱动程序,会尽可能肯定所用的资源,只把不能肯定的资源留给设备树,让设备树来指定。
在设备树节点中填写哪些内容能够经过下面方法肯定:a. 看文档 documentation/devicetree/bindingsb. 参考同类型的单板的设备树文件 arch/arm/boot/dtsc. 网上搜索d. 没有其它办法了,就去研究驱动源代码。