Linux设备树学习 简记
2022/7/26 5:24:03
本文主要是介绍Linux设备树学习 简记,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目录- 一、结构示例
- 二、节点
- 2.1 根节点
- 2.2 特殊节点
- /aliases 子节点
- /memory 子节点
- /chosen 子节点 (uboot的设备树中使用)
- /cpus 和 /cpus/cpu* 子节点
- 2.3 节点属性
- compatible
- model
- phandle
- status
#address-cells
和#size-cells
- reg 属性
- ranges 属性(略)
- name 属性(略)
- device_type 属性
- 2.3 引用其他节点
- phandle
- label
- 三、DTB格式
参考:https://blog.csdn.net/qq_35031421/article/details/105002645
一、结构示例
jz2440的设备树: s3c2440-jz2440.dts (include)----> s3c2440.dtsi (include)----> s3c24xx.dtsi (include)----> skeleton.dtsi (include)----> s3c2440-pinctrl.dtsi make dtbs后的设备树: s3c2440-jz2440.dtb
设备树是一种描述硬件的数据结构。
(1) DTS: .dts文件是设备树的源文件。
(2) DTSI:一个系列的多款开发板可能包含很多共同的部分,共同的部分一般被提炼为一个或多个.dtsi 文件。
(3) DTC: DTC是将.dts编译为.dtb的工具,相当于gcc。
(4) DTB: .dtb文件是 .dts 被 DTC 编译后的二进制格式的设备树文件,它可以被linux内核解析。
二、节点
设备树中节点命名格式
node-name@unit-address
node-name: 设备节点的名称。为ASCII字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是UART1外设;
unit-address: 一般表示设备寄存器首地址或者index。如果某个节点没有地址或者寄存器的话 “unit-address” 可以不要;
节点示例:
在上图中:多个cpu 和 ethernet依靠不同的unit-address 来分辨;可见,node-name相同的情况下,可以通过不同的unit-address定义不同的设备节点。
2.1 根节点
没有node-name 或者 unit-address,它被定义为 /。
2.2 特殊节点
/aliases 子节点
aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。例如:定义 flexcan1 和 flexcan2 的别名是 can0 和 can1。
aliases { can0 = &flexcan1; can1 = &flexcan2; };
/memory 子节点
所有设备树都需要一个memory设备节点,它描述了系统的物理内存布局。如果系统有多个内存块,可以创建多个memory节点,或者可以在单个memory节点的reg属性中指定这些地址范围和内存空间大小。
例如:一个64位的系统有两块内存空间:RAM1: 起始地址是0x0,地址空间是 0x80000000;RAM2: 起始地址是0x10000000,地址空间也是0x80000000;同时根节点/
下的 #address-cells = <2>和#size-cells = <2>,这个memory节点描述为:
memory@0 { device_type = "memory"; reg = <0x00000000 0x00000000 0x00000000 0x80000000 0x00000000 0x10000000 0x00000000 0x80000000>; };
或者:
memory@0 { device_type = "memory"; reg = <0x00000000 0x00000000 0x00000000 0x80000000>; }; memory@10000000 { device_type = "memory"; reg = <0x00000000 0x10000000 0x00000000 0x80000000>; };
/chosen 子节点 (uboot的设备树中使用)
chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。例如:
chosen { bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200"; };
/cpus 和 /cpus/cpu* 子节点
/cpus节点下有1个或多个cpu子节点,
/cpus节点的 #size-cells
必须设置为0,
/cpus/cpu* 子节点中用reg属性用来标明自己是哪一个cpu。
// RK3288: cpus { #address-cells = <1>; #size-cells = <0>; cpu0: cpu@500 { device_type = "cpu"; compatible = "arm,cortex-a12"; reg = <0x500>; resets = <&cru SRST_CORE0>; operating-points = < /* KHz uV */ 1608000 1350000 1512000 1300000 >; #cooling-cells = <2>; /* min followed by max */ clock-latency = <40000>; clocks = <&cru ARMCLK>; }; cpu1: cpu@501 { device_type = "cpu"; compatible = "arm,cortex-a12"; reg = <0x501>; resets = <&cru SRST_CORE1>; }; cpu2: cpu@502 { device_type = "cpu"; compatible = "arm,cortex-a12"; reg = <0x502>; resets = <&cru SRST_CORE2>; }; cpu3: cpu@503 { device_type = "cpu"; compatible = "arm,cortex-a12"; reg = <0x503>; resets = <&cru SRST_CORE3>; }; };
2.3 节点属性
compatible
manufacturer,model
manufacturer : 表示厂商; model : 一般是模块对应的驱动名字。设备树中的设备 与 内核中的驱动(machine_desc) 利用该值进行绑定。
例如:
compatible = "fsl,mpc8641", "ns16550";
上面的compatible有两个属性,分别是 “fsl,mpc8641” 和 “ns16550”;设备首先会使用第一个属性值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件;如果没找到,就使用第二个属性值查找。
注:一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。
model
用于标识开发板(名字),推荐的格式是“manufacturer,model-name”
model = "Samsung S3C2416 SoC";
phandle
phandle属性为devicetree中唯一的节点指定一个数字标识符,节点中的phandle属性,它的取值必须是唯一的(不要跟其他的phandle值一样),例如:
pic@10000000 { phandle = <1>; interrupt-controller; }; another-device-node { interrupt-parent = <1>; // 使用phandle值为1来引用上述节点 };
注:DTS中的大多数设备树将不包含显式的phandle属性,当DTS被编译成二进制DTB格式时,DTC工具会自动插入phandle属性。
status
字符串,描述设备的状态信息,可选的状态如下表所示:
status值 描述
“okay” 表明设备是可操作的。
“disabled” 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。至于 disabled 的具体含义还要看设备的绑定文档。
“fail” 表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。
“fail-sss” 含义和“fail”相同,后面的 sss 部分是检测到的错误内容
#address-cells
和 #size-cells
#address-cells
和 #size-cells
表明了子节点应该如何编写 reg 属性值:起始地址(address)和地址长度(size)。
#address-cells
决定了在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address),
#size-cells
决定了在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)、(len)。
reg = <address1 size1 address2 size2 address3 size3 ……>
例如一个64位的处理器:
soc { #address-cells = <2>; /*子节点的reg中,addr占64位*/ #size-cells = <1>; /*子节点的reg中,size占32位*/ serial { compatible = "xxx"; reg = <0x4600 0x5000 0x100>; /*地址信息是64位:0x00004600 00005000,长度信息是32位:0x00000100*/ }; };
reg 属性
reg 属性的值一般是 (address, length) 对,reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。
例如:一个设备有两个寄存器块,一个的地址是0x3000,占据32字节;另一个的地址是0xFE00,占据256字节,表示如下:
reg = <0x3000 0x20 0xFE00 0x100>;
注:上述对应#address-cells = <1>;(32位) #size-cells = <1>;(32位)
。
ranges 属性(略)
ranges属性值可以为空或者按照 (child-bus-address,parent-bus-address,length) 格式编写的数字矩阵, ranges 是一个地址映射/转换表, ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:
child-bus-address: 子总线地址空间的物理地址,由父节点的 #address-cells 确定此物理地址所占用的字长。
parent-bus-address: 父总线地址空间的物理地址,同样由父节点的 #address-cells 确定此物理地址所占用的字长。
length: 子地址空间的长度,由父节点的 #size-cells 确定此地址长度所占用的字长。
soc { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <1>; ranges = <0x0 0xe0000000 0x00100000>; serial { device_type = "serial"; compatible = "ns16550"; reg = <0x4600 0x100>; clock-frequency = <0>; interrupts = <0xA 0x8>; interrupt-parent = <&ipic>; }; };
soc的ranges值 <0x0 0xe0000000 0x00100000>,指定了一个 1024KB(0x00100000) 的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000。
serial是串口设备节点, 它的reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,寄存器长度为 0x100。经过ranges属性的地址转换, serial 设备可以从 0xe0004600 开始进行读写操作,0xe0004600=0x4600+0xe0000000。
name 属性(略)
name 属性值为字符串, name 属性用于记录节点名字, name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性。
device_type 属性
此属性只能用于 cpu 节点或者 memory 节点。
#include "rk3288.dtsi" / { memory@0 { device_type = "memory"; /*高32 低32 高32 低32*/ reg = <0x0 0x0 0x0 0x80000000>; };
2.3 引用其他节点
phandle
pic@10000000 { phandle = <1>; interrupt-controller; }; another-device-node { interrupt-parent = <1>; // 使用phandle值为1来引用上述节点 };
label
PIC: pic@10000000 { interrupt-controller; }; another-device-node { interrupt-parent = <&PIC>; // 使用label来引用上述节点, // 使用lable时实际上也是使用phandle来引用, // 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性 };
三、DTB格式
DTB文件主要包含四部分内容:
① struct ftd_header: 用来表明各个分部的偏移地址,整个文件的大小,版本号等;
② memory reservation block: 在设备树中使用/memreserve/ 定义的保留内存信息;
③ structure block: 保存节点的信息,节点的结构;
④ strings block: 保存属性的名字,单独作为字符串保存;
总结:
(1) DTB文件可以分为四个部分:struct ftd_header、memory reservation block、structure block、strings block;
(2) 最开始的为struct ftd_header,包含其它三个部分的偏移地址;
(3) memory reservation block记录保留内存信息;
(4) structure block保存节点的信息,节点的结构;
(5) strings block保存属性的名字,将属性名字单独作为字符串保存;
struct ftd_header结构体的定义如下:
struct fdt_header { uint32_t magic; /*它的值为0xd00dfeed,以大端模式保存*/ uint32_t totalsize; /*整个DTB文件的大小*/ uint32_t off_dt_struct; /*structure block的偏移地址*/ uint32_t off_dt_strings; /*strings block的偏移地址*/ uint32_t off_mem_rsvmap; /*memory reservation block的偏移地址*/ uint32_t version; /*设备树版本信息*/ uint32_t last_comp_version; /*向后兼容的最低设备树版本信息*/ uint32_t boot_cpuid_phys; /*CPU ID*/ uint32_t size_dt_strings; /*strings block的大小*/ uint32_t size_dt_struct; /*structure block的大小*/ };
fdt_reserve_entry结构体如下:
该结构体用于表示memreserve的起始地址和内存空间的大小,它紧跟在struct ftd_header结构体后面。例如:/memreserve/ 0x33000000 0x10000,fdt_reserve_entry 结构体的成员 address = 0x33000000,size = 0x10000。
struct fdt_reserve_entry { uint64_t address; /*64bit 的地址*/ uint64_t size; /*保留的内存空间的大小*/ };
structure block是用于描述设备树节点的结构,保存着节点的信息、节点的结构,它有5种标记类型:
① FDT_BEGIN_NODE (0x00000001):表示节点的开始,它的后面紧跟的是节点的名字;
② FDT_END_NODE (0x00000002):表示节点的结束;
③ FDT_PROP (0x00000003) :表示开始描述节点里面的一个属性,在FDT_PROP后面紧跟一个结构体如下所示:
struct { uint32_t len; /*表示属性值的长度*/ uint32_t nameoff; /*属性的名字在string block的偏移*/ } 注:这个结构体后紧跟着是属性值,属性的名字保存在字符串块(Strings block)中。
④ FDT_END (0x00000009):表示structure block的结束。
这篇关于Linux设备树学习 简记的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-12如何创建可引导的 ESXi USB 安装介质 (macOS, Linux, Windows)
- 2024-11-08linux的 vi编辑器中搜索关键字有哪些常用的命令和技巧?-icode9专业技术文章分享
- 2024-11-08在 Linux 的 vi 或 vim 编辑器中什么命令可以直接跳到文件的结尾?-icode9专业技术文章分享
- 2024-10-22原生鸿蒙操作系统HarmonyOS NEXT(HarmonyOS 5)正式发布
- 2024-10-18操作系统入门教程:新手必看的基本操作指南
- 2024-10-18初学者必看:操作系统入门全攻略
- 2024-10-17操作系统入门教程:轻松掌握操作系统基础知识
- 2024-09-11Linux部署Scrapy学习:入门级指南
- 2024-09-11Linux部署Scrapy:入门级指南
- 2024-08-21【Linux】分区向左扩容的方法