2 Linux 设备树

2.1 什么是设备树?

设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息。

设备树的机制其实也是总线型的 BUS/Dev/Drv 模型,只是编写 Dev 的方式变了。即编写 设备树文件 .dts。dst 文件会被编译成 dtb 文件。dtb文件会传给内核, 内核会解析dtb文件, 构造出一系列的 device_node 结构体,
device_node 结构体会转换为 platform_device 结构体。

不了解 总线型驱动可看另外一篇:
https://blog.csdn.net/weixin_46640184/article/details/124229608
对应的实例可以看这篇
https://blog.csdn.net/weixin_46640184/article/details/124291470

所以: 我们可以在 dts 文件中指定资源, 不再需要在 .c 文件中设置 platform_device 结构体

“来自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中获得各种属性来确定硬件资源。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9zKh4329-1650347058800)(assets/image-20220419103127867.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDajUKjD-1650347058801)(assets/image-20220419103411940.png)]

2.2 设备树的规范

2.2.1 DTS 格式

//语法:
//Devicetree node格式:
[label:] node-name[@unit-address] {[properties definitions][child nodes]
};//Property格式1:
[label:] property-name = value;//Property格式2(没有值):
[label:] property-name;/* 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. A null-terminated string (有结束符的字符串):
compatible = "simple-bus";//d. A bytestring(字节序列) :
local-mac-address = [00 00 12 34 56 78];  // 每个byte使用2个16进制数来表示, 必须两位
local-mac-address = [000012345678];       // 每个byte使用2个16进制数来表示// 可以是各种值的组合, 用逗号隔开:
compatible = "ns16550", "ns8250";
example = <0xf00f0000 19>, "a strange property format";

2.2.2 DTS 文件布局

/dts-v1/;        //版本
[memory reservations]    // 格式为: /memreserve/ <address> <length>;
/ {             // '/'表示根节点[property definitions][child nodes]
};

[memory reservations] 作用:

留出该空间给自己使用。

2.2.3 特殊的、默认的属性

  1. 根节点

    • #address-cells

      在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)

    • #size-cells

      在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)

    • compatible

      定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备

      即这个板子兼容哪些平台
      uImage : smdk2410 smdk2440 mini2440 ==> machine_desc

    • model

      咱这个板子是什么
      比如有2款板子配置基本一致, 它们的 compatible 是一样的
      那么就通过model来分辨这2款板子

  2. /memory

    device_type = "memory";
    reg = < start_addr memory_size >;           // 用来指定内存的地址、大小
    //e.g.:
    memory{device_type = "memory";reg = <0x80000000 0x20000000>;
    }
    
  3. /chosen

    bootargs        // 内核command line参数, 跟u-boot中设置的bootargs作用一样
    //e.g.:
    chosen {bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";};
    
  4. /cpus

    /cpus 节点下有 1 个或多个 cpu 子节点, cpu 子节点中用 reg 属性用来标明自己是哪一个 cpu
    所以 /cpus 中有以下2个属性:

    • #address-cells

      在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)

    • #size-cells

      在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)
      必须设置为0

    cpus {cpu {compatible = "arm,arm926ej-s";};};
    

2.2.4 引用其他节点:

phandle

节点中的phandle属性, 它的取值必须是唯一的(不要跟其他的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属性
};

2.2.5 DTB 文件布局

2.3 内核对设备树的处理

2.3.1 前提

Linux uses DT data for three major purposes:

  1. platform identification,
  2. runtime configuration, and
  3. device population.

bootloader启动内核时,会设置r0,r1,r2三个寄存器,

  • r0一般设置为0;
  • r1一般设置为machine id (在使用设备树时该参数没有被使用);
  • r2一般设置ATAGS或DTB的开始地址

2.3.2 设备树中平台信息的处理

  1. 设备树根节点的compatible属性列出了一系列的字符串,

    表示它兼容的单板名,

    从"最兼容"到次之

  1. 内核中有多个machine_desc, 其中有dt_compat成员, 它指向一个字符串数组, 里面表示该 machine_desc支持哪些单板,

  2. 使用 compatile 属性的值, 跟每一个 machine_desc.dt_compat 比较,成绩为"吻合的 compatile 属性值的位置", 成绩越低越匹配, 对应的 machine_desc 即被选中,选中后就采用该 machine_desc 的初始化函数初始化。

2.3.3 函数调用过程

start_kernel // init/main.csetup_arch(&command_line);  // arch/arm/kernel/setup.cmdesc = setup_machine_fdt(__atags_pointer);  // arch/arm/kernel/devtree.cearly_init_dt_verify(phys_to_virt(dt_phys)  // 判断是否有效的dtb, drivers/of/ftd.cinitial_boot_params = params;mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);  // 找到最匹配的machine_desc, drivers/of/ftd.cwhile ((data = get_next_compat(&compat))) {score = of_flat_dt_match(dt_root, compat);if (score > 0 && score < best_score) {best_data = data;best_score = score;}}machine_desc = mdesc;

2.4 对设备树中运行时配置信息的处理

2.4.1 函数调用过程

start_kernel // init/main.csetup_arch(&command_line);  // arch/arm/kernel/setup.cmdesc = setup_machine_fdt(__atags_pointer);  // arch/arm/kernel/devtree.cearly_init_dt_scan_nodes();      // drivers/of/ftd.c/* Retrieve various information from the /chosen node */of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);/* Initialize {size,address}-cells info */of_scan_flat_dt(early_init_dt_scan_root, NULL);/* Setup memory, calling early_init_dt_add_memory_arch */of_scan_flat_dt(early_init_dt_scan_memory, NULL);
  1. /chosen节点中bootargs属性的值, 存入全局变量: boot_command_line
  2. 确定根节点的这2个属性的值: #address-cells, #size-cells
    存入全局变量: dt_root_addr_cells, dt_root_size_cells
  3. 解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);

2. 5 dtb 转换为device_node(unflatten)

2.5.1 函数调用过程

start_kernel // init/main.csetup_arch(&command_line);  // arch/arm/kernel/setup.carm_memblock_init(mdesc);   // arch/arm/kernel/setup.cearly_init_fdt_reserve_self();/* Reserve the dtb region */// 把DTB所占区域保留下来, 即调用: memblock_reserveearly_init_dt_reserve_memory_arch(__pa(initial_boot_params),fdt_totalsize(initial_boot_params),0);           early_init_fdt_scan_reserved_mem();  // 根据dtb中的memreserve信息, 调用memblock_reserveunflatten_device_tree();    // arch/arm/kernel/setup.c__unflatten_device_tree(initial_boot_params, NULL, &of_root,early_init_dt_alloc_memory_arch, false);            // drivers/of/fdt.c/* First pass, scan for size */size = unflatten_dt_nodes(blob, NULL, dad, NULL);/* Allocate memory for the expanded device tree */mem = dt_alloc(size + 4, __alignof__(struct device_node));/* Second pass, do actual unflattening */unflatten_dt_nodes(blob, mem, dad, mynodes);populate_nodenp = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,__alignof__(struct device_node));np->full_name = fn = ((char *)np) + sizeof(*np);populate_propertiespp = unflatten_dt_alloc(mem, sizeof(struct property),__alignof__(struct property));pp->name   = (char *)pname;pp->length = sz;pp->value  = (__be32 *)val;

2.5.2 规则

  1. 在DTB文件中,
    每一个节点都以TAG(FDT_BEGIN_NODE, 0x00000001)开始, 节点内部可以嵌套其他节点,
    每一个属性都以TAG(FDT_PROP, 0x00000003)开始

  2. 每一个节点都转换为一个device_node结构体:

    利用该结构体构造树。

    struct device_node {const char *name;  // 来自节点中的name属性, 如果没有该属性, 则设为"NULL"const char *type;  // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL"phandle phandle;const char *full_name;  // 节点的名字, node-name[@unit-address]struct fwnode_handle fwnode;struct  property *properties;  // 节点的属性struct  property *deadprops;    /* removed properties */struct  device_node *parent;   // 节点的父亲struct  device_node *child;    // 节点的孩子(子节点)struct  device_node *sibling;  // 节点的兄弟(同级节点)#if defined(CONFIG_OF_KOBJ)struct  kobject kobj;#endifunsigned long _flags;void    *data;#if defined(CONFIG_SPARC)const char *path_component_name;unsigned int unique_id;struct of_irq_controller *irq_trans;#endif};
    
  3. device_node结构体中有properties, 用来表示该节点的属性

    //每一个属性对应一个property结构体:
    struct property {char    *name;    // 属性名字, 指向dtb文件中的字符串int length;       // 属性值的长度void    *value;   // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储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};
    
  4. 这些device_node构成一棵树, 根节点为: of_root

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQvHDi0U-1650347058808)(assets/image-20220419130539279.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jsDnxQWl-1650347058809)(assets/image-20220419130112484.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HJFJEhde-1650347058809)(assets/image-20220419130333904.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q21U6JYf-1650347058809)(assets/image-20220419130424248.png)]

2.6 device_node转换为platform_device

dts -> dtb -> device_node -> platform_device

两个问题:

  1. 哪些device_node可以转换为platform_device?

    • 根节点下含有compatile属性的子节点
    • 如果一个结点的compatile属性含有这些特殊的值(“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”)之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device
    • i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device
  2. 怎么转换?

    • platform_device中含有resource数组, 它来自device_node的reg, interrupts属性;
    • platform_device.dev.of_node指向device_node, 可以通过它获得其他属性

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K6mBk2JB-1650347058810)(assets/image-20220419131303274.png)]

2.7 Dev 和 Drv 比配过程

  1. 平台总线上有个

    其中的 platform_match 用来判断 platform_deviceplatform_drive 是否匹配

  2. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzKXZzp4-1650347058811)(assets/image-20220419132505934.png)]

e.g.:

  1. 先比较 platform_device.driver_overrideplatform_driver.driver.name
  2. 再逐个比较 platform_device.dev.comatibleplatform_driver.driver.of_match_table
  3. 然后比较 platform_device.nameplatform_driver.idtable
  4. 最后比较 paltform_device.nameplatform_driver.driver.name

按这些顺序比较,有一个成功就采用。

2.8 总结

  1. 内核函数of_platform_default_populate_init, 遍历device_node树, 生成platform_device

  2. 并非所有的device_node都会转换为platform_device,只有以下的device_node会转换:

    • 该节点必须含有compatible属性

    • 根节点的子节点(节点必须含有compatible属性)

    • 含有特殊compatible属性的节点的 子节点(子节点必须含有compatible属性):

      这些特殊的compatilbe属性为: “simple-bus”, “simple-mfd”, “isa”, “arm,amba-bus

    //e.g.:
    /*
    /mytest会被转换为platform_device,
    因为它兼容"simple-bus", 它的子节点/mytest/mytest@0 也会被转换为platform_device/i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
    /i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定,
    一般是被创建为一个i2c_client。
    */
    / {mytest {compatile = "mytest", "simple-bus";mytest@0 {compatile = "mytest_0";};};i2c {compatile = "samsung,i2c";at24c02 {compatile = "at24c02";                      };};};
    

2.9 内核中设备树的操作函数

include/linux/ 目录下有很多 of 开头的头文件:

处理 DTB

of_fdt.h

dtb文件的相关操作函数, 我们一般用不到, 因为dtb文件在内核中已经被转换为device_node树(它更易于使用)

处理device_node

of.h               // 提供设备树的一般处理函数, //比如 of_property_read_u32(读取某个属性的u32值), of_get_child_count(获取某个device_node的子节点数)
of_address.h       // 地址相关的函数, 比如 of_get_address(获得reg属性中的addr, size值)
of_match_device(从matches数组中取出与当前设备最匹配的一项)
of_dma.h           // 设备树中DMA相关属性的函数
of_gpio.h          // GPIO相关的函数
of_graph.h         // GPU相关驱动中用到的函数, 从设备树中获得GPU信息
of_iommu.h         // 很少用到
of_irq.h           // 中断相关的函数
of_mdio.h          // MDIO (Ethernet PHY) API
of_net.h           // OF helpers for network devices.
of_pci.h           // PCI相关函数
of_pdt.h           // 很少用到
of_reserved_mem.h  // reserved_mem的相关函数

处理 platform_device

of_platform.h      // 把device_node转换为platform_device时用到的函数, // 比如of_device_alloc(根据device_node分配设置platform_device), //     of_find_device_by_node (根据device_node查找到platform_device),//     of_platform_bus_probe (处理device_node及它的子节点)
of_device.h        // 设备相关的函数, 比如 of_match_device

2.10 在根文件系统中查看设备树

  1. /sys/firmware/fdt

    原始dtb文件

    hexdump -C /sys/firmware/fdt

  2. /sys/firmware/devicetree

    以目录结构程现的dtb文件, 根节点对应base目录, 每一个节点对应一个目录, 每一个属性对应一个文件

  3. /sys/devices/platform

    系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的

    对于来自设备树的platform_device, 可以进入 /sys/devices/platform/<设备名>/of_node 查看它的设备树属性

  4. /proc/device-tree

    是链接文件, 指向 /sys/firmware/devicetree/base

linux 设备树详解相关推荐

  1. Linux设备树详解

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

  2. 正点原子----Linux设备树详解

    linux设备树 1.什么是设备树 2.DTS.DTB.DTC的关系 3.如何编译设备树 4.DTS基本结构 4.1.语法 4.2.设备树在系统中的体现 4.3.尝试自己添加节点 4.4.尝试对根节点 ...

  3. 【Linux驱动开发】Linux设备树详解

    目录 一.设备树基础 1.概念 2.文件格式 3.编译工具 二.DTS语法 1..dtsi 头文件 2. 设备节点 3.标准属性 4.compatible 属性详解 5.修改设备树文件,增加或修改节点 ...

  4. 奇小葩讲设备树(5/5)-- Linux设备树详解(五)设备树的使用

    对于任何的知识来说,了解了理论的知识,知道了设备树怎么解析用以代替传统的范式之后,我们需要知道怎么使用设备树.对于使用我们分两部分,一部分是它有哪些接口,能做些什么,至于怎么编写dts文件本章不讨论. ...

  5. 奇小葩讲设备树(4/5)-- Linux设备树详解(四)kernel的解析

    uboot将一些参数,设备树文件传给内核,那么内核如何处理这些设备树文件呢?本章就kernel解析设备树的过程和原理,本章的主要内容以Device Tree相关的数据流分析为索引,对ARM linux ...

  6. 奇小葩讲设备树(2/5)-- Linux设备树详解(二)文件构成

    设备树就是描述单板资源以及设备的一种文本文件.至于出现的原因,基本的语法和使用方法,上一章节做了基本的介绍.本篇文章主要是更深层次的探讨设备文件的构成. 1. devie tree的编译 Device ...

  7. 奇小葩讲设备树(1/5)-- Linux设备树详解(一) 基础知识

    关于设备树,之前就经过详细的系统培训,但是本着会用就行的原则,对各个知识点都没有进行系统的总结.都是用到哪里学哪里,时间长了,基本也忘记了.所以对于后期知识各个知识点进行总结,本章主要讨论一下内容,能 ...

  8. linux 视频教程 韦山东,韦东山 linux 设备树详解

    简 介 设备树视频录制完毕,29节,现在只要69元.学员对此课程的评价:这是最翔实最实惠最精益求精的设备树教程,感兴趣的了解一下, 以下是课程详情~ [设备树是什么?] 设备树是一种机制,用文本的方式 ...

  9. 奇小葩讲设备树(3/5)-- Linux设备树详解(三)u-boot设备树的传递

    前面两节介绍了设备的基本概念.编译.结构的组成,本章讨论的主要内容为 dtb如何通过Bootloader引导程序加载到内核 bootloader如何解析dbt bootloader支持哪些dtb的操作 ...

最新文章

  1. 以太坊开启区块链2.0时代
  2. 内网穿透从搭建到溯源
  3. CTFshow 文件上传 web155
  4. 【Hibernate】Hibernate查询语言HQL详解
  5. virtual多态 你不知道的事情
  6. 如何模拟Spring bean(版本2)
  7. 重新查看Play Framework发布的值
  8. Java(五)异常处理,异常参数,自定义异常,嵌套异常
  9. node yarn_使用Yarn Plug'n'Play摆脱node_modules
  10. 常用API2 正则表达式
  11. 博文目录(最新更新:2018.6.6)
  12. 单片机ADC采样算法----限幅平均滤波法
  13. python中的dict是什么数据类型_Python基本数据类型之dict
  14. AD9371开发总结(一)
  15. java程序员必须安装的软件
  16. html调查问卷页面,html+js 问卷调查页面的展示以及form提交
  17. 用canvas画太极图(一步步详解附带源代码)
  18. 【dgl框架】dgl.metapath_reachable_graph函数解析
  19. 一个微信公众号sdk(封装的比较全面)
  20. Python报错处理libpng warning: iCCP: cHRM chunk does not match sRGB

热门文章

  1. Vue h5 调用微信扫码接口
  2. 树莓派无法识别摄像头+树莓派中opencv调用视频流人脸检测
  3. Activity销毁不调用Ondestroy情况以及处理
  4. 频谱仪的更改ip_【正点原子FPGA连载】第五十一章 基于FFT IP核的音频频谱仪-摘自【正点原子】开拓者 FPGA 开发指南 (amobbs.com 阿莫电子论坛)...
  5. 为了让5G更省电,这家设备商秀出黑科技
  6. linux:shell命令之软硬链接
  7. 从USB数据采集板看技术造诣
  8. 如何将PDF删除水印?PDF怎么删除水印
  9. 如何用电脑画平面坐标图_如何在WORD或者EXERL上画坐标图?
  10. 微信小程序之图片压缩