转自: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. LiveData使用

    ### Andorid LiveData 使用 [[_TOC_]] #### Lifycycle 使用1.继承FragmentActivity 实现LifecycleOwner接口2.声明一个Life ...

  2. CSS绘制三角形和箭头,不用再用图片了

    前言 还在用图片制作箭头,三角形,那就太lou了.css可以轻松搞定这一切,而且颜色大小想怎么变就怎么变,还不用担心失真等问题. 先来看看这段代码: /**css*/.d1{ width: 0; he ...

  3. FAQ – Automatic Undo Management (AUM) / System Managed Undo (SMU) (Doc ID 461480.1)

    FAQ – Automatic Undo Management (AUM) / System Managed Undo (SMU) (Doc ID 461480.1) APPLIES TO: Orac ...

  4. Shell—常见报错问题

    bash:$'\r': command not found 造成这个问题的原因是Windows环境下换行的“\r”到了Linux环境下不能够识别了,因为Linux环境下默认的换行符为“\n”,我们只需 ...

  5. Redis—简介与安装

    Redis 简介 Redis 安装 Redis 配置文件 # Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程.daemonize yes # 当Redis以守护进 ...

  6. 轻量级监控平台之cpu监控

    轻量级监控平台之cpu监控脚本 #!/bin/bash #进程监控脚本 #功能需求: 上报机器的硬件层面-cpu负载数据 . /etc/profile . ~/.bash_profile pushur ...

  7. spring mongodb用法

    A field annotated with @Id (org.springframework.data.annotation.Id) will be mapped to the '_id' fiel ...

  8. React 以两种形式去创建组件 类或者函数(二)

    08==>创建组件以 1类的形式 或者以 2函数的形式 09==>使用组件 在src下创建components文件夹 是放组件的 CompType.js 组件 组件开头大写(重要) Com ...

  9. AcWing 901. 滑雪

    地址 https://www.acwing.com/problem/content/description/903/ 题目描述给定一个R行C列的矩阵,表示一个矩形网格滑雪场. 矩阵中第 i 行第 j ...

  10. LG5202 「USACO2019JAN」Redistricting 动态规划+堆/单调队列优化

    问题描述 LG5202 题解 \[opt[i]=xx+(cnt[i]-cnt[yy]<=0)\] 发现\(cnt[i]-cnt[yy] <= 0\)只能有两种取值 于是直接堆优化即可 \( ...