设备树Device Tree详解
原文:https://www.cnblogs.com/aaronLinux/p/5496559.html
目录
1. 设备树(Device Tree)基本概念及作用
2. 设备树的组成和使用
2.1. DTS和DTSI
2.2. DTC
2.3. DTB
2.4. Bootloader
3. 设备树中dts、dtsi文件的基本语法
3.1. chosen node
3.2. aliases node
3.3. memory node
3.4. 其他节点
3.4.1. Reg属性
3.4.2. Compatible属性
3.4.3. Interrupts属性
3.4.4. Ranges属性
4. DTB相关结构
4.1. Header
4.2. 字符串块
4.3. memory reserve map
5. 解析DTB的函数及相关数据结构
5.1. machine_desc结构
5.2. 设备节点结构体
5.3. 属性结构体
5.4. uboot下的相关结构体
6. DTB加载及解析过程
7. OF的API接口
1. 设备树(Device Tree)基本概念及作用
Device Tree可以描述的信息包括CPU的数量和类别、内存基地址和大小、总线和桥、外设连接、中断控制器和中断使用情况、GPIO控制器和GPIO使用情况、Clock控制器和Clock使用情况。
另外,设备树对于可热插拔的热备不进行具体描述,它只描述用于控制该热插拔设备的控制器。
设备树的主要优势:对于同一SOC的不同主板,只需更换设备树文件.dtb即可实现不同主板的无差异支持,而无需更换内核文件。
注:要使得3.x之后的内核支持使用设备树,除了内核编译时需要打开相对应的选项外,bootloader也需要支持将设备树的数据结构传给内核。
2. 设备树的组成和使用
设备树包含DTC(device tree compiler),DTS(device tree source和DTB(device tree blob)。其对应关系如图1-1所示:
2.1. DTS和DTSI
*.dts文件是一种ASCII文本对Device Tree的描述,放置在内核的/arch/arm/boot/dts目录。一般而言,一个*.dts文件对应一个ARM的machine。
2.2. DTC
dtb-$(CONFIG_ARCH_TEGRA) += tegra20-harmony.dtb \
2.3. DTB
DTC编译*.dts生成的二进制文件(*.dtb),bootloader在引导内核时,会预先读取*.dtb到内存,进而由内核解析。
2.4. Bootloader
3. 设备树中dts、dtsi文件的基本语法
在节点的{}里面是描述该节点的属性(property),即设备的特性。它的值是多样化的:
1.它可以是字符串string,如①;也可能是字符串数组string-list,如②
2.它也可以是32 bit unsigned integers,如cell⑧,整形用<>表示
3.它也可以是binary data,如③,十六进制用[]表示
在/arch/arm/boot/dts/目录中有一个文件skeleton.dtsi,该文件为各ARM vendor共用的一些硬件定义信息。以下为skeleton.dtsi的全部内容。
memory { device_type = "memory"; reg = <0 0>; };
3.1. chosen node
bootargs = "tegraid=40.0.0.00.00 vmalloc=256M video=tegrafb console=ttyS0,115200n8 earlyprintk";
3.3. aliases node
&i2c6 {--------------这里&i2c6到底是label还是alias???
};------------------在*.dtsi中大多默认为设备为disable,然后在*.dts中将其enable,进行重写使能。
3.3. memory node
reg = <0x00000000 0x20000000>; /* 512 MB */
一般而言,在.dts中不对memory进行描述,而是通过bootargs中类似521M@0x00000000的方式传递给内核。
3.4. 其他节点
由于其他设备节点依据属性进行描述,具有类似的形式。接下来的部分主要分析各种属性的含义及作用,并结合相关的例子进行阐述。
3.4.1. Reg属性
在device node 中,reg是描述memory-mapped IO register的offset和length。子节点的reg属性address和length长度取决于父节点对应的#address-cells和#size-cells的值。例:
在上述的aips节点中,存在子节点spda。spda中的中reg为<0x70000000 0x40000 >,其0x700000000为address,0x40000为size。这一点在图3-1下有作介绍。
这里补充的一点是:设备节点的名称格式node-name@unit-address,节点名称用node-name唯一标识,为一个ASCII字符串。其中@unit-address为可选项,可以不作描述。unit-address的具体格式和设备挂载在哪个bus上相关。如:cpu的unit-address从0开始编址,以此加1;本例中,aips为0x70000000。
3.4.2. compatible属性
在①中,compatible属性为string list,用来将设备匹配对应的driver驱动,优先级为从左向右。本例中spba的驱动优先考虑“fsl,aips-bus”驱动;若没有“fsl,aips-bus”驱动,则用字符串“simple-bus”来继续寻找合适的驱动。即compatible实现了原先内核版本3.x之前,platform_device中.name的功能,至于具体的实现方法,本文后面会做讲解。
注:对于“/”root节点,它也存在compatible属性,用来匹配machine type。具体说明将在后面给出。
3.4.3. interrupts属性
设备节点通过interrupt-parent来指定它所依附的中断控制器,当节点没有指定interrupt-parent时,则从parent节点中继承。上面例子中,root节点的interrupt-parent = <&mic>。这里使用了引用,即mic引用了②中的inrerrupt-controller @40008000;root节点的子节点并没有指定interrupt-controller,如ahb、fab,它们均使用从根节点继承过来的mic,即位于0x40008000的中断控制器。
若子节点使用到中断(中断号、触发方法等等),则需用interrupt属性来指定,该属性的数值长度受中断控制器中#inrerrupt-controller值③控制,即interrupt属性<>中数值的个数为#inrerrupt-controller的值;本例中#inrerrupt-controller=<2>,因而④中interrupts的值为<0x3d 0>形式,具体每个数值的含义由驱动实现决定。
3.4.4. ranges属性
ranges属性为地址转换表,这在pcie中使用较为常见,它表明了该设备在到parent节点中所对用的地址映射关系。ranges格式长度受当前节点#address-cell、parent节点#address-cells、当前节点#size-cell所控制。顺序为ranges=<前节点#address-cell, parent节点#address-cells , 当前节点#size-cell。在本例中,当前节点#address-cell=<1>,对应于⑤中的第一个0x20000000;parent节点#address-cells=<1>,对应于⑤中的第二个0x20000000;当前节点#size-cell=<1>,对应于⑤中的0x30000000。即ahb0节点所占空间从0x20000000地址开始,对应于父节点的0x20000000地址开始的0x30000000地址空间大小。
注:对于相同名称的节点,dtc会根据定义的先后顺序进行合并,其相同属性,取后定义的那个。
4. DTB相关结构
DTB由三部分组成:头(Header)、结构块(device-tree structure)、字符串块(string block)。下面将详细介绍这三部分的内容。
4.1. Header
在\kernel\include\linux\of_fdt.h文件中有相关定义
4.2.device-tree structure
设备树结构块是一个线性化的结构体,是设备树的主体,以节点的形式保存了主板上的设备信息。
(1)FDT_BEGIN_NODE (0x00000001)。该token描述了一个node的开始位置,紧挨着该token的就是node name(包括unit address)
(2)FDT_END_NODE (0x00000002)。该token描述了一个node的结束位置。
(5)FDT_END (0x00000009)。该token标识了一个DTB的结束位置。
(1)节点开始标志:一般为OF_DT_BEGIN_NODE(0x00000001)。
(2)节点路径或者节点的单元名(version<3以节点路径表示,version>=0x10以节点单元名表示)
(4)节点属性。每个属性以宏OF_DT_PROP(0x00000003)开始,后面依次为属性值的字节长度(4字节)、属性名称在字符串块中的偏移量(4字节)、属性值和填充(对齐到四字节)。
(6)节点结束标志OF_DT_END_NODE(0x00000002)。
4.3. 字符串块
通过节点的定义知道节点都有若干属性,而不同的节点的属性又有大量相同的属性名称,因此将这些属性名称提取出一张表,当节点需要应用某个属性名称时,直接在属性名字段保存该属性名称在字符串块中的偏移量。
4.4. memory reserve map
这个区域包括了若干的reserve memory描述符。每个reserve memory描述符是由address和size组成。其中address和size都是用U64来描述。
5. 解析DTB的函数及相关数据结构
5.1. machine_desc结构
内核将机器信息记录为machine_desc结构体(该定义在/arch/arm/include/asm/mach/arch.h),并保存在_arch_info_begin到_arch_info_end之间(_arch_info_begin,_arch_info_end为虚拟地址,是编译内核时指定的,此时mmu还未进行初始化。它其实通过汇编完成地址偏移操作)
machine_desc结构体用宏MACHINE_START进行定义,一般在/arch/arm/子目录,与板级相关的文件中进行成员函数及变量的赋值。由linker将machine_desc聚集在.arch.info.init节区形成列表。
bootloader引导内核时,ARM寄存器r2会将.dtb的首地址传给内核,内核根据该地址,解析.dtb中根节点的compatible属性,将该属性与内核中预先定义machine_desc结构体的dt_compat成员做匹配,得到最匹配的一个machine_desc。
在代码中,内核通过在start_kernel->setup_arch中调用setup_machine_fdt来实现上述功能,该函数的具体实现可参见/arch/arm/kernel/devtree.c。
5.2. 设备节点结构体
记录节点信息的结构体。.dtb经过解析之后将以device_node列表的形式存储节点信息。
5.3. 属性结构体
device_node结构体中的成员结构体,用于描述节点属性信息。
5.4. uboot下的相关结构体
首先我们看下uboot用于记录os、initrd、fdt信息的数据结构bootm_headers,其定义在/include/image.h中,这边截取了其中与dtb相关的一小部分。
6. DTB加载及解析过程
在do_bootm中,主要调用函数为do_bootm_states,第四个参数为bootm所要处理的阶段和状态。
在do_bootm_states中,bootm_start会对lmb进行初始化操作,lmb所管理的物理内存块有三种方式获取。起始地址,优先级从上往下:
2. 宏CONFIG_SYS_SDRAM_BASE(在tegra124中为0x80000000)
经过初始化之后,这块内存就归lmb所管辖。接着,调用bootm_find_os进行kernel镜像的相关操作,这里不具体阐述。
还记得之前讲过bootm的三个参数么,第一个参数内核地址已经被bootm_find_os处理,而接下来的两个参数会在bootm_find_other中执行操作。
首先,bootm_find_other根据第二个参数找到ramdisk的地址,得到ramdisk的镜像;然后根据第三个参数得到DTB镜像,同检查kernel和ramdisk镜像一样,检查DTB镜像也会进行一系列的校验工作,如果校验错误,将无法正常启动内核。另外,uboot在确认DTB镜像无误之后,会将该地址保存在环境变量“fdtaddr”中。
接着,uboot会把DTB镜像reload一次,使得DTB镜像所在的物理内存归lmb所管理:①boot_fdt_add_mem_rsv_regions会将原先的内存DTB镜像所在的内存置为reserve,保证该段内存不会被其他非法使用,保证接下来的reload数据是正确的;②boot_relocate_fdt会在bootmap区域中申请一块未被使用的内存,接着将DTB镜像内容复制到这块区域(即归lmb所管理的区域)
注:若环境变量中,指定“fdt_high”参数,则会根据该值,调用lmb_alloc_base函数来分配DTB镜像reload的地址空间。若分配失败,则会停止bootm操作。因而,不建议设置fdt_high参数。
接下来,do_bootm会根据内核的类型调用对应的启动函数。与linux对应的是do_bootm_linux。
① boot_prep_linux
为启动后的kernel准备参数
② boot_jump_linux
以上是boot_jump_linux的片段代码,可以看出:若使用DTB,则原先用来存储ATAG的寄存器R2,将会用来存储.dtb镜像地址。
boot_jump_linux最后将调用kernel_entry,将.dtb镜像地址传给内核。
下面我们来看下内核的处理部分:
在arch/arm/kernel/head.S中,有这样一段:
_vet_atags定义在/arch/arm/kernel/head-common.S中,它主要对DTB镜像做了一个简单的校验。
真正解析处理dbt的开始部分,是setup_arch->setup_machine_fdt。这部分的处理在第五部分的machine_mdesc中有提及。
如图,是setup_machine_fdt中的解析过程。
解析chosen节点将对boot_command_line进行初始化。
解析根节点的{size,address}将对dt_root_size_cells,dt_root_addr_cells进行初始化。为之后解析memory等其他节点提供依据。
解析memory节点,将会把节点中描述的内存,加入memory的bank。为之后的内存初始化提供条件。
解析设备树在函数unflatten_device_tree中完成,它将.dtb解析成device_node结构(第五部分有其定义),并构成单项链表,以供OF的API接口使用。
下面主要结合代码分析:/drivers/of/fdt.c
总的归纳为:
① kernel入口处获取到uboot传过来的.dtb镜像的基地址
② 通过early_init_dt_scan()函数来获取kernel初始化时需要的bootargs和cmd_line等系统引导参数。
③ 调用unflatten_device_tree函数来解析dtb文件,构建一个由device_node结构连接而成的单向链表,并使用全局变量of_allnodes保存这个链表的头指针。
④ 内核调用OF的API接口,获取of_allnodes链表信息来初始化内核其他子系统、设备等。
7. OF的API接口
OF的接口函数在/drivers/of/目录下,有of_i2c.c、of_mdio.c、of_mtd.c、Adress.c等等
unsigned long __init of_get_flat_dt_root(void)
2. 根据deice_node结构的full_name参数,在全局链表of_allnodes中,查找合适的device_node
struct device_node *of_find_node_by_path(const char *path)
cpus=of_find_node_by_path("/cpus");
3. 若from=NULL,则在全局链表of_allnodes中根据name查找合适的device_node
struct device_node *of_find_node_by_name(struct device_node *from,const char *name)
np = of_find_node_by_name(NULL,"firewire");
struct device_node *of_find_node_by_type(struct device_node *from,const char *type)
tsi_pci= of_find_node_by_type(NULL,"pci");
5. 根据compatible字符串查找device_node
struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name)
struct device_node *of_find_node_by_phandle(phandle handle)
int of_alias_get_id(struct device_node *np, const char *stem)
struct device_node *of_node_get(struct device_node *node)
void of_node_put(struct device_node *node)
10. 根据property结构的name参数,在指定的device node中查找合适的property
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
11. 根据property结构的name参数,返回该属性的属性值
const void *of_get_property(const struct device_node *np, const char *name,int *lenp)
12. 根据compat参数与device node的compatible匹配,返回匹配度
int of_device_is_compatible(const struct device_node *device,const char *compat)
struct device_node *of_get_parent(const struct device_node *node)
14. 将matches数组中of_device_id结构的name和type与device node的compatible和type匹配,返回匹配度最高的of_device_id结构
15. 根据属性名propname,读出属性值中的第index个u32数值给out_value
16. 根据属性名propname,读出该属性的数组中sz个属性值给out_values
17. 根据属性名propname,读出该属性的u64属性值
int of_property_read_u64(const struct device_node *np, const char *propname,u64 *out_value)
18. 根据属性名propname,读出该属性的字符串属性值
int of_property_read_string(struct device_node *np, const char *propname,const char **out_string)
19. 根据属性名propname,读出该字符串属性值数组中的第index个字符串
int of_property_count_strings(struct device_node *np, const char *propname)
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
22. 读取该设备的第index个irq号,并填充一个irq资源结构体
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
int of_irq_count(struct device_node *dev)
int of_address_to_resource(struct device_node *dev, int index,struct resource *r)
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,unsigned int *flags)
void __iomem *of_iomap(struct device_node *np, int index)
24. 根据device_node查找返回该设备对应的platform_device结构
struct platform_device *of_find_device_by_node(struct device_node *np)
25. 根据device node,bus id以及父节点创建该设备的platform_device结构
void *platform_data,struct device *parent)
26. 遍历of_allnodes中的节点挂接到of_platform_bus_type总线上,由于此时of_platform_bus_type总线上还没有驱动,所以此时不进行匹配
设备树Device Tree详解相关推荐
- Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统介绍
文章目录 一.Linux设备模型介绍 (1)设备驱动模型总体介绍 (2)设备驱动模型文件表现 (3)设备驱动模型工作原理 [1]总线 [2]设备 [3]驱动 [4]注册流程 二.平台设备驱动介绍 (1 ...
- dts 编译过程_linux设备树dts移植详解
[转] 摘 要:设备树的引入减少了内核为支持新硬件而需要的改变,提高代码重用,加速了Linux 支持包的开发,使得单个内核镜像能支持多个系统.作为U-Boot 和Linux 内核之间的动态 接口,本文 ...
- Linux 设备树device tree 使用手册
摘要:设备树使用手册Thispagewalksthroughhowtowriteadevicetreeforanewmachine.Itisintendedtoprovideanoverviewofd ...
- linux设备树dts文件详解
1.什么是设备树? (1)设备树(dt:device tree)是linux内核采用的参数表示和传递技术,在系统引导启动阶段进行设备初始化的时候,将设备树中描述的硬件信息传递给操作系统: (2)dts ...
- 嵌入式linux mtd,嵌入式Linux驱动设备之MTD技术详解
原标题:嵌入式Linux驱动设备之MTD技术详解 MTD(memory technology device内存技术设备)是用于访问memory设备(ROM.flash)的Linux的子系统. MTD的 ...
- BTree和B+Tree详解结构
如果是复合索引: 关键字的排序先排左侧字段,在左侧字段相同的情况下,再排序右侧字段. BTree和B+Tree详解 B+树索引是B+树在数据库中的一种实现,是最常见也是数据库中使用最为频繁的一种索引. ...
- B-Tree 和 B+Tree详解
B-Tree 和 B+Tree详解 一.什么是B-Tree 1.B-树插入 2.B-树删除 3.总结 二.什么是B+Tree 1.B+树插入 2.B+树删除 3.总结 一.什么是B-Tree B-Tr ...
- 数据结构--树链剖分详解
数据结构--树链剖分详解 关于模板题---->传送门 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将 ...
- STM32固件库文件树及构成详解
(想要pfd格式文件的可以留下邮箱) STM32固件库文件树及构成详解(库版本:STM32F10x_StdPeriph_Lib_V3.5.0) 库减压后所有文件夹构成如下: /
最新文章
- RecyclerView smoothScrollToPosition 和 scrollToPosition 的区别
- CF 459A 459B 459C 459D 459E
- 配置tomcat6.0的HTTPS(单向)
- 管家机器人先生txt_《管家机器人先生》(主角墨青如玉)大结局全文阅读
- linux获取字符格式化,Linux 格式化字符串漏洞利用
- P4055 [JSOI2009]游戏
- The Classic IQ Test
- 配置防盗链 访问控制Directory 访问控制FilesMatch
- 遗传算法原理及其python实现
- 话筒在multisim怎么找_基于Multisim软件的调频无线话筒仿真
- Semi-prime H-numbers(POJ 3292)
- [人工智能-深度学习-65]:环境搭建 - Nvidia最新显卡性能排名
- 运维堡垒机—如何解决企业运维操作审计问题?
- 含有隐函数的离散常微分方程求解
- 女人假正经的十三种表现
- 【声源定位】语音信号
- 常见的浏览器有哪些?其核心分别是什么 ?
- 《脚本》Python在线百度文库爬虫(免下载券)
- 设置GPU及显存大小
- 如何制作并美化我们的微信公众号的二维码?
热门文章
- Centos7 网络装机
- 有品css进度12.6
- Java的基础:判断语句if、switch
- 绿了杰克船长?深扒马斯克情感关系史…
- Certified Adversarial Robustness via Randomized Smoothing
- 第14个天猫双11,技术创新带来消费新体验
- 【转贴】“被开通”工行“e支付”,巨额存款被盗,怎么破?
- SSM框架英语在线学习考试网站四级六级八级等级考试职称考试自测托福TOEFL测试雅思IELTS-GRE(idea开发javaweb-javaee-j2ee-springboot)
- js replace 替换全部
- CSS中position属性(sticky)