1. 设备树文件

  • 内核版本: linux-5.13.5
  • 设备树文件所在路径:linux-5.13.5\arch\arm\boot\dts
  • 每个xxx.dts对应一个板子

dts目录下有两种文件:xxx.dts xxx.dtsi, 因为每个板子都有一个xxx.dts文件,在有多个板子共用同一个芯片时,那么dts文件中对于芯片的信息就会重复,这时就需要将这些重复的信息放入一个文件中,也就是dtsi文件,供dts文件调用,调用方式:

/*在dts文件的头部引入即可*/
#include "omap36xx.dtsi"

2. 设备树文件编译

Linux在目录:linux/scripts/dtc下准备了编译设备树文件的工具DTC,在make dtbs的时候,会根据Makefile找到该目录下的DTC工具进行编译
如果把.dts文件当作.c文件,那么DTC工具就相当于gcc编译器,它会将.dts文件编译成.dtb文件,.dtb文件就相当于.bin文件

在Ubuntu中编译设备树文件方法:

export ARCH=arm
make deconfig
make dtbs


由打印信息可以看出,在arch/arm/boot/dts目录下生成了很多的dtb文件,make ARCH=arm设置的就是编译arch/arm下的dts文件,当然arch下还有很多架构,可以按需修改,进入如下目录可以看到生成的dtb文件。

如果我们自己写了一个dts文件,比如叫做zyuandy-zhang-can.dts,我们需要将该文件添加到arch/arm/boot/dts/目录下,再打开arch/arm/boot/dts/Makefile中添加zyuandy-zhang-can.dtb即可,因为dtc的编译规则是根据名字找到xxx.dts文件编译成xxx.dtb,具体添加到哪个系列下,要看开发板的型号,我添加到了CONFIG_ARCH_EXYNOS4系列下,make dtbs之后就会生成对应的dtb

3. DTS语法

3.1 设备节点

设备树以根目录" / "开始,每个根目录可以理解为一个开发板,那么每个节点就是该开发板上的设备

// SPDX-License-Identifier: GPL-2.0
/** Support for CompuLab CM-T3730*/
/dts-v1/;#include "omap36xx.dtsi"
#include "omap3-cm-t3x30.dtsi"/ {model = "CompuLab CM-T3730";compatible = "compulab,omap3-cm-t3730", "ti,omap3630", "ti,omap36xx", "ti,omap3";...
};&omap3_pmx_wkup {dss_dpi_pins_cm_t3730: pinmux_dss_dpi_pins_cm_t3730 {pinctrl-single,pins = <OMAP3_WKUP_IOPAD(0x2a08, PIN_OUTPUT | MUX_MODE3)   /* sys_boot0.dss_data18 */...>;};
};

&omap3_pmx_wkup节点的意思是向omap36xx.dtsi中的omap3_pmx_wkup节点中追加信息,在omap36xx.dtsi文件中没有搜索到omap3_pmx_wkup,但是该文件引入了omap3.dtsi文件,在omap3.dtsi中找到了节点:

omap3_pmx_wkup: pinmux@a00 {compatible = "ti,omap3-padconf","pinctrl-single";reg = <0xa00 0x5c>;#address-cells = <1>;#size-cells = <0>;#pinctrl-cells = <1>;#interrupt-cells = <1>;interrupt-controller;pinctrl-single,register-width = <16>;pinctrl-single,function-mask = <0xff1f>;
};

那么追加之后,节点就变成了:

omap3_pmx_wkup: pinmux@a00 {compatible = "ti,omap3-padconf","pinctrl-single";reg = <0xa00 0x5c>;#address-cells = <1>;#size-cells = <0>;#pinctrl-cells = <1>;#interrupt-cells = <1>;interrupt-controller;pinctrl-single,register-width = <16>;pinctrl-single,function-mask = <0xff1f>;/* 以下为追加内容 */dss_dpi_pins_cm_t3730: pinmux_dss_dpi_pins_cm_t3730 {pinctrl-single,pins = <OMAP3_WKUP_IOPAD(0x2a08, PIN_OUTPUT | MUX_MODE3)   /* sys_boot0.dss_data18 */...>;};
};

节点定义格式: 节点名称@节点单元地址

chipid@10000000 { //chipid就是节点名称 1000000为该节点的起始地址...
};

还有一种格式: 节点标签名: 节点名称@节点单元地址

serial_0: serial@13800000 { //serial_0是标签名 serial是节点名称 13800000是节点的起始地址...
};

由于有的节点名字过长,使用的时候不方便,所以就有了标签名,例如&serial_0就表示引用serial节点

serial节点就是用来描述数据手册中UART0的信息,在数据手册的Memory Map部分,如下图:

3.2 通用属性

sdhci_0: sdhci@12510000 {compatible = "samsung,exynos4210-sdhci";reg = <0x12510000 0x100>;interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;clocks = <&clock CLK_SDMMC0>, <&clock CLK_SCLK_MMC0>;clock-names = "hsmmc", "mmc_busclk.2";status = "disabled";
};

compatible属性:该属性用来绑定设备和驱动,格式:compatible = “厂商,驱动名称",一个节点代表一个设备,sdhci设备要想运行就需要驱动程序,在驱动程序中会有一个of_match_table属性,用来表示该驱动程序支持哪些设备,如下图所示,sdhci_s3c_dt_mach[]中的compatible属性支持 samsung公司的s3c6410-sdhci设备与samsung公司的exynos4210-sdhci设备

status属性:该属性表示设备的状态信息,格式:status = “value”; 常用的value有两种

value describe
okey 设备可操作
disable 设备不可操作

reg属性:表示该设备的地址信息,格式:reg = <addr1 len1 addr2 len2 …> addr表示设备的起始地址,len代表地址的长度,合起来就是该设备的地址空间,具体如何配置需要参照数据手册

#address-cells与#size-cells属性

apb: bus@fe000000 {  //某些地址为了讲解方便进行了修改compatible = "simple-bus";reg = <0x0 0xfe000000 0x0 0x1000000>;#address-cells = <2>;#size-cells = <2>;ranges = <0x1 0x2 0x3 0xfe000000 0x5 0x1000000>;reset: reset-controller@0 {compatible = "amlogic,meson-a1-reset";reg = <0x1 0x3 0x2 0x8c>;#reset-cells = <1>;
};

这两个属性用来控制子结点中reg属性中addr和len的数量,格式:#address-cells = <n>; #size-cells = <n>; 上述代码中,由于父节点apb(节点标签名)中的address-cells = 2,所以reg中有两个addr,size-cells = 2,所以有两个len

rangs属性,该属性是一个地址转换表,格式:rangs = <child-bus-address,parent-bus-address, length …>;上述代码中,父节点的

4. 开发板、设备树、驱动程序之间的关系

根节点的compatible属性:前边讲到设备节点的compatible属性是用来匹配设备与驱动的,根节点的compatible属性比较特殊,因为根节点代表的不是设备而更像是开发板,难道Linux内核烧到开发板上就能运行,显然不是的,要看内核支不支持该开发板,在没有引入设备树之前,是通过机器ID来判断的,这里主要讲解设备树的匹配方式,就是根据根节点的compatible属性判断,一个板子对应一个设备树文件,在内核启动阶段会调用start_kernel()函数

start_kernel() -> setup_arch(&command_line) -> setup_machine_fdt() -> of_flat_dt_match_machine() 在该函数中:

dt_root = of_get_flat_dt_root(); //获取设备树的根节点
while ((data = get_next_compat(&compat))) { //获取machine_desc中的.dt_compat属性score = of_flat_dt_match(dt_root, compat); //将根节点的cpmpatible属性与每个machine_desc中的.dt_compat进行比较,相同就返回machine_descif (score > 0 && score < best_score) {best_data = data;best_score = score;}
}

如果返回的best_data不为空,就证明找到了对应开发板的驱动程序

get_next_compat会调用arch_get_next_mach函数,配合while循环到__arch_info_begin段去遍历machine_desc结构体,将取到的machine_desc结构体的.dt_compat属性存入compat中,作为of_flat_dt_match函数的参数

static const void * __init arch_get_next_mach(const char *const **match)
{static const struct machine_desc *mdesc = __arch_info_begin;const struct machine_desc *m = mdesc;if (m >= __arch_info_end)return NULL;mdesc++;*match = m->dt_compat;return m;
}

of_flat_dt_match会将取到的.dt_compat属性与根节点的compatible属性进行比较,如果相同说明开发板与设备树文件匹配成功

static int __init of_flat_dt_match(unsigned long node, const char *const *compat)
{unsigned int tmp, score = 0;if (!compat)return 0;while (*compat) {tmp = of_fdt_is_compatible(initial_boot_params, node, *compat);if (tmp && (score == 0 || (tmp < score)))score = tmp;compat++;}return score;
}

前边说到去__arch_info_begin去遍历machine_desc结构体,为什么呢?

在Linux/arch/arm目录下有很多的mach-xxxx文件,这里边的文件基本都是有厂家编写好的,每一个都代表一个或一系列的开发板,那么我们就以mach-imx目录下的mach-imx6q.c举例,该文件就是用来做开发板与设备树匹配的

在该文件中,我们可以看到以下代码

static const char * const imx6q_dt_compat[] __initconst = {"fsl,imx6dl","fsl,imx6q","fsl,imx6qp",NULL,
};DT_MACHINE_START(IMX6Q, "Freescale i.MX6 Quad/DualLite (Device Tree)").l2c_aux_val  = 0,.l2c_aux_mask  = ~0,.smp      = smp_ops(imx_smp_ops),.map_io     = imx6q_map_io,.init_irq   = imx6q_init_irq,.init_machine = imx6q_init_machine,.init_late      = imx6q_init_late,.dt_compat = imx6q_dt_compat,
MACHINE_END

DT_MACHINE_START在linux/arch/arm/include/asm/mach/arch.h中定义,内容如下

#define DT_MACHINE_START(_name, _namestr)        \
static const struct machine_desc __mach_desc_##_name    \__used                         \__section(".arch.info.init") = {            \.nr        = ~0,              \.name      = _namestr,#endif

宏定义展开之后其实是定义了一个machine_desc结构体,并将该结构体存到了.arch.info.init段中,这就是去__arch_info_begin去遍历machine_desc结构体的原因

现在machine_desc知道了从何而来,要到哪里去,可是要和设备树的根节点作比较,machine_desc从.arch.info.init段中获取,那设备树文件在哪取出来的?

of_flat_dt_match调用的of_fdt_is_compatible函数中有一个参数initial_boot_params,这个就是设备树文件的起始位置,是uboot提供给内核的,根节点就是从initial_boot_params指向的设备树中取出的,这样设备树文件与设备的匹配就清楚了

那驱动程序与开发板的设备是怎么匹配上的呢?

开发板就是我们说的设备树文件,以linux/arch/arm/boot/dts/s3c64xx.dtsi文件举例,假设我们的开发版已经和该文件匹配成功,所以该文件中每一个节点就代表开发板上的一个设备

sdhci0: sdhci@7c200000 {compatible = "samsung,s3c6410-sdhci";...
};

要想使用该sdhci0设备,就需要有对应的驱动程序,在前文通用属性,设备的compatible属性的讲解中,该设备的compatible属性值"samsung,s3c6410-sdhci"与drivers/mmc/host/sdhci-s3c.c文件中的sdhci_s3c_dt_mach[]中的compatible属性相同,所以该设备的驱动程序就是sdhci-s3c.c文件,这样驱动程序与开发板的设备关系就清楚了

5. 设备树解析

调用流程start_kernel() -> setup_arch(&command_line) -> unflatten_device_tree()

void __init unflatten_device_tree(void)
{__unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false);/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */of_alias_scan(early_init_dt_alloc_memory_arch);unittest_unflatten_overlay_base();
}

of_root(linuxdrivers/of/fdt.c)在__unflatten_device_tree执行之后会指向设备树的根节点,__unflatten_device_tree第一次调用unflatten_dt_nodes给设备节点们分配空间,第二次调用才是真正的解析出了节点的内容,并将内容封装成device_node结构体

void *__unflatten_device_tree(const void *blob,struct device_node *dad,struct device_node **mynodes,void *(*dt_alloc)(u64 size, u64 align),bool detached)
{int size;void *mem;int ret;/* First pass, scan for size */size = unflatten_dt_nodes(blob, NULL, dad, NULL);size = ALIGN(size, 4);/* Allocate memory for the expanded device tree */mem = dt_alloc(size + 4, __alignof__(struct device_node));memset(mem, 0, size);*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);/* Second pass, do actual unflattening */ret = unflatten_dt_nodes(blob, mem, dad, mynodes);...return mem;
}

这时,设备树文件已经被解析成多个device_node存储到了内存中,等到之后用到设备树的驱动程序调用

6. 绑定信息文档

在linux\Documentation\devicetree\bindings\下有很多的绑定信息文档,以.txt形式存在,可以帮助我们更好的编写设备树文件和添加设备节点

Required properties:
- compatible: Every devices present in OMAP SoC should be in theform: "ti,XXX"
- ti,hwmods: list of hwmod names (ascii strings), that comes from the OMAPHW documentation, attached to a device. Must contain at leastone hwmod.Optional properties:
- ti,no_idle_on_suspend: When present, it prevents the PM to idle the moduleduring suspend.
- ti,no-reset-on-init: When present, the module should not be reset at init
- ti,no-idle-on-init: When present, the module should not be idled at init
- ti,no-idle: When present, the module is never allowed to idle.Example:spinlock@1 {compatible = "ti,omap4-spinlock";ti,hwmods = "spinlock";
};

7. of函数集的使用

linux\include\linux\of.h中定义了很多以of开头的函数,这些函数可以用来获取设备的节点信息,要使用这些函数要引入一些头文件

#include <of.h>
#include <of_address.h>
#include <of_irq.h>

假设我们要获取下面代码中的uart4节点的compatible属性

/ {cpus {cpu: cpu@0 {/* Based on OMAP3630 variants OPP50 and OPP100 */operating-points-v2 = <&cpu0_opp_table>;clock-latency = <300000>; /* From legacy driver */};};ocp@68000000 {uart4: serial@4809e000 {compatible = "ti,omap3-uart";ti,hwmods = "uart4";status = "disabled";reg = <0x4809e000 0x400>;interrupts = <84>;dmas = <&sdma 55 &sdma 54>;dma-names = "tx", "rx";clock-frequency = <48000000>;};};
};

函数:

static inline struct device_node *of_find_node_by_path(const char *path);//根据节点路径查找指定结点

用法举例:

struct device_node *node;
node = of_find_node_by_path("/ocp/uart4"); //uart4也可以用serial

节点获取到了不是我们最终的目标,我们的最终目标是获取节点的属性值,可以通过以下函数进行获取

属性结构体(linux/include/linux/of.h):

struct property {char    *name;int   length;void *value;struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)struct bin_attribute attr;
#endif
};

函数:

extern struct property *of_find_property(const struct device_node *np, // 节点const char *name, // 属性名称int *lenp); // 属性值的字节数,一般为NULL

用法举例:

struct property *prop;
prop = of_find_property(node, "compatible", NULL); // node为之前获取到的节点
printf("compatible = %s\r\n", prop->value); //该输出值等于"ti,omap3-uart"

在of.h中获取节点与其属性的函数还有很多这里不再一一举例

8. device_node与platform_device的转换

随便找了一个驱动函数的probe函数,看一看它是怎么获取节点与节点的属性的

static int plat_mpc8xxx_spi_probe(struct platform_device *pdev)
{struct resource *mem;int irq;struct spi_master *master;mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!mem)return -EINVAL;irq = platform_get_irq(pdev, 0);if (irq <= 0)return -EINVAL;return PTR_ERR_OR_ZERO(master);
}

在上述代码中,我们并没有看到它用of函数获取结点,也没有看到device_node结构体,但是获取到的mem就是节点的reg属性,它是怎么获取到的呢?

of_platform_default_populate_init -> of_platform_populate() -> of_platform_bus_create() -> of_platform_device_create_pdata() -> of_device_alloc()

static int __init of_platform_default_populate_init(void)
{struct device_node *node;for_each_matching_node(node, reserved_mem_matches)of_platform_device_create(node, NULL, NULL);node = of_find_node_by_path("/firmware");if (node) {of_platform_populate(node, NULL, NULL, NULL);of_node_put(node);}/* Populate everything else. */of_platform_default_populate(NULL, NULL, NULL);return 0;
}
arch_initcall_sync(of_platform_default_populate_init);

由于用arch_initcall_sync,该函数会在启动内核的过程中被调用

经过上述的调用过程,最终会调用到of_device_alloc函数

struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)
{struct platform_device *dev;int rc, i, num_reg = 0, num_irq;struct resource *res, temp_res;dev = platform_device_alloc("", PLATFORM_DEVID_NONE); //申请platform_device结构体空间if (!dev)return NULL;/* count the io and irq resources */while (of_address_to_resource(np, num_reg, &temp_res) == 0) //从设备树中获取reg的数量num_reg++;num_irq = of_irq_count(np); //获取irq的数量/* Populate the resource table */if (num_irq || num_reg) {res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL); //申请resource结构体空间if (!res) {platform_device_put(dev);return NULL;}dev->num_resources = num_reg + num_irq;dev->resource = res; //将申请的res赋值给platform_device结构体的resource属性for (i = 0; i < num_reg; i++, res++) { //将device_node的reg属性赋值给resource的属性rc = of_address_to_resource(np, i, res);WARN_ON(rc);}if (of_irq_to_resource_table(np, res, num_irq) != num_irq)//将device_node的irq属性赋值给resource的属性pr_debug("not all legacy IRQ resources mapped for %pOFn\n",np);}dev->dev.of_node = of_node_get(np); //将device_node赋值给platform_device的dev.of_node属性dev->dev.fwnode = &np->fwnode;dev->dev.parent = parent ? : &platform_bus;if (bus_id)dev_set_name(&dev->dev, "%s", bus_id);elseof_device_make_bus_id(&dev->dev);return dev;
}
EXPORT_SYMBOL(of_device_alloc);

该函数会将device_node转化为platform_device,所以之前的通过platform_device来获取节点的reg属性就很合理了

关于linux设备树的简单理解(基于linux-5.13.5)相关推荐

  1. Linux设备树led,linux设备树下LED灯控制

    linux设备树下LED灯控制 linux设备树下LED灯控制 原理图: 所以在设备树下子节点下插入gpioled节点: gpioled { #address-cells = <1>; # ...

  2. linux设备树笔记__基于msm8x10的基本分析

    由文章,linux设备树笔记__dts基本概念及语法,我们知道了基本概念,知道了大概的设备树节点及其属性,而节点下的属性大多是自定义,除了保留的几个属性,大多从.dts是无法知道其用途的,这个就需要看 ...

  3. i.MX6ULL终结者Linux设备树DTS设备树语法结构

    文章目录 1 dtsi头文件 2 设备节点信息 3 设备节点及label的命名 4 标准属性 5 根节点compatible属性 6 在设备节点中添加或修改内容 一般情况下,我们不会从头编写一个完整的 ...

  4. 探索Linux设备树:硬件描述与驱动程序的桥梁

    目录标题 引言:Linux设备树简介 | Introduction: Linux Device Tree Overview a. 设备树的背景与发展 | Background and Developm ...

  5. 【正点原子MP157连载】第二十三章 Linux设备树-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  6. ARM Linux设备树

    1.ARM设备树起源 在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,很多代码只是在描述板级细节,而这些板级细节对于内核来讲 ...

  7. 【正点原子Linux连载】第四十三章 Linux设备树 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  8. Linux设备树详解

    Linux设备树详解 设备树小故事 设备树文件 使用设备树 修改设备树文件 编译设备树 异常处理 编写驱动文件 参考资料 设备树小故事 设备树(Device Tree),将这个词分开就是"设 ...

  9. linux文件系统只有几k,关于Linux文件系统的的简单理解和认识

    关于Linux文件系统的的简单理解和认识 关于文件系统的运作,这与操作系统带的档案数据有关.例如Linux操作系统的档案权限(rwx)与文件属性(拥有者,群组,时间参数等).文件系统通常会将这两部分的 ...

最新文章

  1. WRF,WPS,WRF-Chem安装及编译步骤及bug总结(1)
  2. 使网页变灰的代码(包括FLASH等所有网页元素).
  3. 省带宽、耗电小,腾讯游戏学院专家解析手游渲染架构
  4. java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListene解决办法
  5. 大前端的自动化工厂(5)—— 基于Karma+Mocha+Chai的单元测试和接口测试
  6. cjuiautocomplete ajax,Yii CJuiAutoComplete小部件:空响应消息事件
  7. Flask: wsgi接口
  8. 寻找黑匣子——程序行为记录与跟踪
  9. JavaScript事件串连执行多个处理过程的方法
  10. 面试题: ,| 与,||的区别?
  11. 写给Javaer看的Kotlin教程
  12. DSP的cmd文件详解
  13. HDFS—Web页面操作
  14. android跳转到锁屏及后台管理界面,Android 锁屏状态下后台弹出界面
  15. 微信小程序开发常见warnings警告解决方案
  16. FCPX插件:屏幕分屏特效插件Stupid Raisins Split Pop
  17. sdoi2017苹果树
  18. C语言函数体内无条件的大括号
  19. 韩剧TV APP案例分析
  20. java检查word文档内容缺失_恢复Word文档内容需要了解的知识

热门文章

  1. php循环volist,thinkphp volist三级循环
  2. wordpress主题_WordPress的5个美丽酒店主题
  3. 设置Button的字体颜色状态选择器
  4. 超实用的30 个简短的代码片段(一)
  5. 雪佛兰-科鲁泽-车机破解[安装第三方软件]
  6. 把你的unity 工程中的cs文件封装成 dll
  7. proguard.cfg(转载)
  8. python使用pip安装依赖库_使用Pip在离线环境安装Python依赖库
  9. Linux命令:tail -f 与tail -F的区别
  10. PAT1050_乙级_螺旋矩阵