转自:http://www.pianshen.com/article/428276673/;jsessionid=D90FC6B215155680E0B89A6D060892D4

本文基于天嵌E9V3开发板,详解设备树的规则和用法。

一、基本概念

DTS即Device Tree Source,是一个文本形式的文件,用于描述硬件信息,包括CPU的数量和类别、内存基地址和大小、中断控制器、总线和桥、外设、时钟和GPIO控制器等。
DTB即Device Tree Blob,是一个二进制形式的文件,由linux内核识别,为其中的设备匹配合适的驱动程序。
DTC即Device Tree Compiler,将适合人类阅读和编辑的DTS文件编译成适合机器处理的DTB文件。
编译内核的时候会同时使用DTC 将DTS编译成DTB,天嵌E9V3使用的DTS文件e9v3-sabresd.dts位于/arch/arm/boot/dts目录下。

如上图所示,bootloader读取dtb文件放入RAM中,并将存放地址告诉linux内核,内核启动以后从该地址读取相应的设备信息,匹配平台和设备驱动。

二、E9V3设备树总览

linux中的一个dts文件对应一个machine, 不同的machine可能使用相同的SOC,只是对外设的使用不同,这些不同的dts文件势必包含很多相同的内容,为了简化,可以把公用的部分提炼为dtsi文件。
e9v3-sabresd.dts包含dtsi的结构如下:

列出各个文件中的节点,如下图所示,是不是有点像有很多分支的树?

三、设备树编写规则

Device Tree的编写规则可参考文档<<devicetree-specification-v0.2.pdf>>, 以下简称spec,下载链接为:
https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.2

设备树由一个一个的节点组成,每个设备树有且仅有一个根节点,节点可以包含子节点。

1、节点名称
基本的节点名格式如下:
node-name@unit-address
其中node-name由字母、数字和一些特殊字符构成的字符串,长度不超过31个字符,可自定义,但为了可读性,spec中规定了一些约定成熟的名称,比如cpus, memory, bus,clock等。
unit-address为节点的地址,通常为寄存器的首地址,比如imx6q datasheet中uart1的寄存器地址范围为0202_0000~0202_3FFF,在定义uart1节点时,对应的unit-address为0202_0000:
uart1: serial@02020000 {

}
有些节点没有对应的寄存器,则unit-address可省略,节点名只由node-name组成,比如cpus:
cpus {

}
根节点的名称比较特殊,由一个斜杠组成:
/{

}

2、label标签

三、设备与驱动的匹配

linux内核启动以后,先解析并注册dts中的设备,然后再注册驱动,比较驱动中的compatible 属性和设备中的compatible 属性,或者比较两者的name属性,如果一致则匹配成功。
1、解析dtb
在start_kernel() --> setup_arch(0 --> unflatten_device_tree() --> __unflatten_device_tree()函数中扫描dtb,并转换成节点是device_node的树状结构。
注:代码基于linux4.1.15内核(下同)

static void __unflatten_device_tree()
{
...
/* First pass, scan for size */
start = 0;
size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
size = ALIGN(size, 4);
...
/* Second pass, do actual unflattening */
start = 0;
unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);
...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2. 注册dts设备

imx6q_init_machine() --> of_platform_populate()。
在of_platform_populate()中循环扫描根节点下的各节点:

int of_platform_populate()
{
...
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
}
...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
static int of_platform_bus_create()
{
...
/* Make sure it has a compatible property */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %s, no compatible prop\n",
__func__, bus->full_name);
return 0;
} auxdata = of_dev_lookup(lookup, bus);
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}
...
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
如果节点有子节点,则递归调用of_platform_bus_create()扫描节点的子节点:
for_each_child_of_node(bus, child) {
pr_debug(" create child: %s\n", child->full_name);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

最终调用of_platform_device_create_pdata() —> of_device_add() 注册设备并添加到对应的链表中。

3、注册驱动
Linux注册驱动的函数为driver_register(),或者其包装函数如platform_driver_register(),而driver_register()或者其包装函数一般在驱动的初始化函数xxx_init()中调用。
驱动初始化函数xxx_init()被调用的路劲为:
start_kernel() --> rest_init() --> Kernel_init() --> kernel_init_freeable() --> do_basic_setup() --> do_initcalls:

简而言之,在start_kernel()中调用driver_register()注册驱动程序。

4、匹配设备
追踪driver_register()函数,driver_register() --> bus_add_driver() --> driver_attach() --> __driver_attach:

static int __driver_attach(struct device *dev, void *data)
{
struct device_driver *drv = data;
if (!driver_match_device(drv, dev))
return 0; if (dev->parent) /* Needed for USB */
device_lock(dev->parent);
device_lock(dev);
if (!dev->driver)
driver_probe_device(drv, dev);
device_unlock(dev);
if (dev->parent)
device_unlock(dev->parent);
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

driver_match_device()中寻找匹配的设备,如果匹配成功则执行驱动的probe函数。
driver_match_device()最终会调用平台的匹配函数platform_match():

static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv); /* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name); /* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1; /* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1; /* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL; /* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

从代码中可以看出, platform_match()会采用多种方法进行匹配:

  1. of_driver_match_device将根据驱动程序of_match_table中的compatible属性,与设备中的compatible属性进行比对。
  2. 其次调用acpi_driver_match_device()进行匹配。
  3. 如果前2种方法都没有匹配的,最后比对设备和驱动的name字符串是否一致。

以GPIO-key为例,设备和驱动匹配示意图如下:

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。

Linux 设备树详解【转】的更多相关文章

  1. Linux DTS(Device Tree Source)设备树详解之二(dts匹配及发挥作用的流程篇)【转】

    转自:https://blog.csdn.net/radianceblau/article/details/74722395 版权声明:本文为博主原创文章,未经博主允许不得转载.如本文对您有帮助,欢迎 ...

  2. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

  3. Linux dts 设备树详解(一) 基础知识

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 1 前言 2 概念 2.1 什么是设备树 dts(device tree)? 2. ...

  4. Linux设备驱动详解 宋宝华 硬件基础

    处理器 存储器 接口与总线 I2C时序 SPI总线时序 以太网

  5. Linux设备树语法详解

    概念 Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离.在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写.引入了设备树之后,驱动代 ...

  6. Linux设备树语法详解【转】

    转自:http://www.cnblogs.com/xiaojiang1025/p/6131381.html 概念 Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离.在设备 ...

  7. 【转】Linux 网络工具详解之 ip tuntap 和 tunctl 创建 tap/tun 设备

    原文:https://www.cnblogs.com/bakari/p/10449664.html -------------------------------------------------- ...

  8. Linux常用命令详解—基于CentOS7

    ## Linux 目录- /:根目录,一般只存放目录,不存放文件- /bin -> /usr/bin:可执行二进制文件的目录,也是常用命令目录,如常用的命令 ls.cat.mv 等- /boot ...

  9. Linux常用命令详解下

    Linux常用命令详解 目录 一.Linux常用命令 1.1.查看及切换目录(pwd.cd.ls.du) 1.2.创建目录和文件(mkdir.touch.ln) 1.3.复制.删除.移动目录和文件(c ...

随机推荐

  1. python 部署lvs

    import paramiko ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ...

  2. 读书笔记_python网络编程3_(3)

    3.TCP:传输控制协议 第一个版本在1974年定义,建立在网际层协议(IP)提供的数据包传输技术之上.TCP使程序可以使用连续的数据流进行相互通信. 除非网络原因导致连接中断/冻结,TCP都能保证将 ...

  3. 服务器 vim模式下报错E37: No write since last change (add ! to override)

    故障现象: 使用vim修改文件报错,系统提示如下: E37: No write since last change (add ! to override) 故障原因: 文件为只读文件,无法修改. 解决 ...

  4. android 圆角ImageView类,可设置弧度

    public class RoundImageView extends ImageView { private Paint paint; private int roundWidth = 50; pr ...

  5. 修改robotframework的元素定位方式,使之支持带括号的xpath定位方式

    今天困扰我的一个问题终于解决了 robot框架默认的xpth定位方式是不支持带括号运算的xpth表达式的,例如: (//*[@content-desc="iv_message_icon_21 ...

  6. Octave计算数据

    设A=[1 2;3 4;5 6] B=[11 12;13 14;15 16] A.*B =     :对A以及B中的对应的元素进行相乘 11 24 39 56 75 96 A.^2 :对A中的每一个元 ...

  7. Java入门程序HelloWorld

    程序开发步骤说明 开发环境已经搭建完毕,可以开发我们第一个Java程序了.Java程序开发三步骤:编写.编译.运行.如下图所示 详解: 编写源程序:通俗来说就是我们通过Java的语法自己写的代码 编译 ...

  8. WPF 精修篇 数据绑定到对象

    原文:WPF 精修篇 数据绑定到对象 数据绑定到对象 首先 我们需要一个对象 public class Preson { private string name; public string Name ...

  9. 基础知识 Asp.Net MVC EF各版本区别

    原文:https://www.cnblogs.com/liangxiaofeng/p/5840754.html 2009年發行ASP.NET MVC 1.0版 2010年發行ASP.NET MVC 2 ...

  10. Python:程序练习题(二)

    Python:程序练习题(二) 2.1温度转换程序. 代码如下: t=input("请输入带符号的温度值(如:32C):") if t[-1] in ["C", ...