【ARM-Linux开发】内核3.x版本之后设备树机制
内核3.x版本之后设备树机制
Based on Linux 3.10.24 source code
参考/documentation/devicetree/Booting-without-of.txt文档
目录
内核3.x版本之后设备树机制 1
一、设备树(Device Tree)基本概念及作用 3
二、设备树的组成和使用 4
①DTS和DTSI 4
②DTC 4
③DTB 5
④Bootloader 5
三、设备树中dts、dtsi文件的基本语法 5
㈠chosen node 8
㈡aliases node 8
㈢memory node 9
㈣ 其他节点 10
四、DTB相关结构 13
㈠Header 14
㈢字符串块 16
㈣ memory reserve map 16
五、解析DTB的函数及相关数据结构 17
㈠machine_desc结构 17
㈡设备节点结构体 18
㈢属性结构体 19
㈣ uboot下的相关结构体 19
六、DTB加载及解析过程 21
七、OF的API接口 32
一、设备树(Device Tree)基本概念及作用
Device Tree可以描述的信息包括CPU的数量和类别、内存基地址和大小、总线和桥、外设连接、中断控制器和中断使用情况、GPIO控制器和GPIO使用情况、Clock控制器和Clock使用情况。
另外,设备树对于可热插拔的热备不进行具体描述,它只描述用于控制该热插拔设备的控制器。
设备树的主要优势:对于同一SOC的不同主板,只需更换设备树文件.dtb即可实现不同主板的无差异支持,而无需更换内核文件。
注:要使得3.x之后的内核支持使用设备树,除了内核编译时需要打开相对应的选项外,bootloader也需要支持将设备树的数据结构传给内核。
二、设备树的组成和使用
设备树包含DTC(device tree compiler),DTS(device tree source和DTB(device tree blob)。其对应关系如图1-1所示:
①DTS和DTSI
.dts文件是一种ASCII文本对Device Tree的描述,放置在内核的/arch/arm/boot/dts目录。一般而言,一个.dts文件对应一个ARM的machine。
②DTC
dtb-$(CONFIG_ARCH_TEGRA) += tegra20-harmony.dtb \
③DTB
DTC编译.dts生成的二进制文件(.dtb),bootloader在引导内核时,会预先读取.dtb到内存,进而由内核解析。
④Bootloader
三、设备树中dts、dtsi文件的基本语法
在节点的{}里面是描述该节点的属性(property),即设备的特性。它的值是多样化的:
1.它可以是字符串string,如①;也可能是字符串数组string-list,如②
2.它也可以是32 bit unsigned integers,如cell⑧,用<>表示
在/arch/arm/boot/dts/目录中有一个文件skeleton.dtsi,该文件为各ARM vendor共用的一些硬件定义信息。以下为skeleton.dtsi的全部内容。
memory { device_type = "memory"; reg = <0 0>; };
如上,属性# address-cells的值为1,它代表以“/”根节点为parent的子节点中,reg属性中存在一个address值;#size-cells的值为1,它代表以“\” 根节点为parent的子节点中,reg属性中存在一个size值。即父节点的# address-cells和#size-cells决定了子节点的address和size的长度;Reg的组织形式为reg =
㈠chosen node
bootargs = "tegraid=40.0.0.00.00 vmalloc=256M video=tegrafb console=ttyS0,115200n8 earlyprintk";
㈡aliases node
㈢memory node
reg = <0x00000000 0x20000000>; /* 512 MB */
一般而言,在.dts中不对memory进行描述,而是通过bootargs中类似521M@0x00000000的方式传递给内核。
㈣ 其他节点
由于其他设备节点依据属性进行描述,具有类似的形式。接下来的部分主要分析各种属性的含义及作用,并结合相关的例子进行阐述。
设备节点的名称格式node-name@unit-address,节点名称用node-name唯一标识,为一个ASCII字符串。其中@unit-address为可选项,可以不作描述。unit-address的具体格式和设备挂载在哪个bus上相关。如:cpu的unit-address从0开始编址,以此加1;本例中,aips为0x70000000。
在①中,compatible属性为string list,用来将设备匹配对应的driver驱动,优先级为从左向右。本例中spba的驱动优先考虑“fsl,aips-bus”驱动;若没有“fsl,aips-bus”驱动,则用字符串“simple-bus”来继续寻找合适的驱动。即compatible实现了原先内核版本3.x之前,platform_device中.name的功能,至于具体的实现方法,本文后面会做讲解。
注:对于“/”root节点,它也存在compatible属性,用来匹配machine type。具体说明将在后面给出。
设备节点通过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>形式,具体每个数值的含义由驱动实现决定。
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会根据定义的先后顺序进行合并,其相同属性,取后定义的那个。
四、DTB相关结构
DTB由三部分组成:头(Header)、结构块(device-tree structure)、字符串块(string block)。下面将详细介绍这三部分的内容。
㈠Header
在\kernel\include\linux\of_fdt.h文件中有相关定义
设备树结构块是一个线性化的结构体,是设备树的主体,以节点的形式保存了主板上的设备信息。
在结构块中,以宏OF_DT_BEGIN_NODE标志一个节点的开始,以宏OF_DT_END_NODE标识一个节点的结束,整个结构块以宏OF_DT_END (0x00000009)结束。在\kernel\include\linux\of_fdt.h中有相关定义,我们把这些宏称之为token。
(1)FDT_BEGIN_NODE (0x00000001)。该token描述了一个node的开始位置,紧挨着该token的就是node name(包括unit address)
(2)FDT_END_NODE (0x00000002)。该token描述了一个node的结束位置。
(3)FDT_PROP (0x00000003)。该token描述了一个property的开始位置,该token之后是两个u32的数据,分别是length和name offset。length表示该property value data的size。name offset表示该属性字符串在device tree strings block的偏移值。length和name offset之后就是长度为length具体的属性值数据。
(4)FDT_NOP (0x00000004)。
(5)FDT_END (0x00000009)。该token标识了一个DTB的结束位置。
一个节点的结构如下:
(1)节点开始标志:一般为OF_DT_BEGIN_NODE(0x00000001)。
(2)节点路径或者节点的单元名(version<3以节点路径表示,version>=0x10以节点单元名表示)
(3)填充字段(对齐到四字节)
(4)节点属性。每个属性以宏OF_DT_PROP(0x00000003)开始,后面依次为属性值的字节长度(4字节)、属性名称在字符串块中的偏移量(4字节)、属性值和填充(对齐到四字节)。
(5)如果存在子节点,则定义子节点。
(6)节点结束标志OF_DT_END_NODE(0x00000002)。
㈢字符串块
通过节点的定义知道节点都有若干属性,而不同的节点的属性又有大量相同的属性名称,因此将这些属性名称提取出一张表,当节点需要应用某个属性名称时,直接在属性名字段保存该属性名称在字符串块中的偏移量。
㈣ memory reserve map
这个区域包括了若干的reserve memory描述符。每个reserve memory描述符是由address和size组成。其中address和size都是用U64来描述。
五、解析DTB的函数及相关数据结构
㈠machine_desc结构
在代码中,内核通过在start_kernel->setup_arch中调用setup_machine_fdt来实现上述功能,该函数的具体实现可参见/arch/arm/kernel/devtree.c。
㈡设备节点结构体
记录节点信息的结构体。.dtb经过解析之后将以device_node列表的形式存储节点信息。
㈢属性结构体
device_node结构体中的成员结构体,用于描述节点属性信息。
㈣ uboot下的相关结构体
首先我们看下uboot用于记录os、initrd、fdt信息的数据结构bootm_headers,其定义在/include/image.h中,这边截取了其中与dtb相关的一小部分。
六、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链表信息来初始化内核其他子系统、设备等。
七、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总线上还没有驱动,所以此时不进行匹配
转载于:https://www.cnblogs.com/huty/p/8517543.html
【ARM-Linux开发】内核3.x版本之后设备树机制相关推荐
- 移植中文TTS(ekho)到ARM linux开发板
移植ekho到arm linux,实现将中文转换为.wav格式的语音文件. 移植开源库libsndfile到arm linux开发板 1. 下载压缩源码包libsndfile-1.0.28.tar.g ...
- arm linux 内核崩溃,用sysrq-trigger实现ARM Linux一键内核崩溃、一键关机、一键dump信息等...
原创 宋宝华 Linux阅码场 2018-01-12 sysrq有多种功能,对于ARM Linux这种嵌入式没键盘的系统来说,也可以通过/proc/sysrq-trigger来进行操作. 实验平台:Q ...
- linux下s3c2440开发板,SAMSUNG S3C2440 ARM LINUX 开发板 上手初体验 --开发环境搭建
1,linux开发环境搭建 2,程序测试 easyOpentag驱动安装,打开连接,选择ARM-linux 1,环境搭建 我的系统版本 root@ubuntu:~# lsb_release -a No ...
- linux最新内核5.80版本移植详细过程
上一节 uboot移植 uboot 1.首先进入官网下载下载源码 https://mirrors.edge.kernel.org/pub/linux/kernel/ 下载最新5.8的版本 2.安装编译 ...
- arm linux死机不是崩溃,用sysrq-trigger实现ARM Linux一键内核崩溃、一键关机、一键dump信息等...
sysrq有多种功能,对于ARM Linux这种嵌入式没键盘的系统来说,也可以通过/proc/sysrq-trigger来进行操作. 实验平台:QEMU模拟的ARM Linux 内核版本: # una ...
- Linux内核编程12期:设备树overlay与ConfigFS文件系统
在嵌入式Linux下,设备树(device tree)用来描述硬件平台的各种资源,Linux内核在启动过程中,会解析设备树,获取各种硬件资源来初始化硬件. 设备树的overlay功能是指可以在系统运行 ...
- Exynos4412 内核移植(六)—— 设备树解析
一.描述 ARM Device Tree起源于OpenFirmware (OF),在过去的Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相 ...
- linux 驱动基础知识(2)---设备树
转载自 http://huaqianlee.me/2015/08/19/Android/高通平台Android源码分析之Linux内核设备树-DT-Device-Tree-dts文件/ 刚开始接触An ...
- 基于瑞芯微RV1109 Linux触摸屏GT911驱动调试心得(二)-设备树刷厂商给的触摸屏固件...
之前调试触摸屏的文章如下: 基于瑞芯微RV1109 Linux触摸屏GT911驱动调试心得_Bruce.yang的嵌入式之旅-CSDN博客 由于之前调触摸屏一直都对不上点,然后厂商又给了我新的驱动代码 ...
最新文章
- 超级全面的 SpringBoot 注解介绍,每一个用途都应该清晰
- 小强的HTML5移动开发之路(6)——Canvas图形绘制基础
- JQuery操作CheckBox和Radio
- C# ckeditor+ckfinder的图片上传配置
- CreateThread
- VTK:PolyData 的轮廓用法实战
- 11.19 rpm:RPM包管理器
- 【转】为什么要进行傅立叶变换?傅立叶变换究竟有何意义?如何用Matlab实现快速傅立叶变换?...
- Bootstrap初步
- Python程序中创建子进程时对环境变量的要求
- Linux学习总结(16)——CentOS 下 Nginx + Tomcat 配置负载均衡
- 仿头条新闻app,实现下拉刷新,上拉加载分页
- python中data.find_all爬取网站为空列表_利用Golang快速爬取盗版网站的整套音频
- linux哪国的,Linux是什么它是哪个国家开发的
- Python3制作网易云音乐下载器
- 如何使用CodeSmith批量生成代码(原创系列教程)
- indesign排版标点挤压_孔雀计划序——中文排版思路的重建
- [割点问题]HOJ 12307 Disconnected Pair
- Windows平台安装SQLite3数据库
- SLAM基础——李群李代数
热门文章
- java 下对字符串的格式化
- 金融的财务的一些概念
- Question for recommendation system, which is a baseline
- just have a view of the open source project i contributed!!!
- Array.Resize(ref arry, size);
- 2018.8.5 复习笔记
- 2013 多校联合4 1011 Fliping game (hdu 4642)
- 李开复:非常看好移动互联网的未来 --记CSDNCTO俱乐部李开复交流会
- 贝聊运维自动化体系演化
- topcoder srm 490 div1