Linux 设备树详解

╰半橙微兮° 2022-09-04 13:44 254阅读 0赞

原文地址::https://blog.csdn.net/ethercat_i7/article/details/83786670

相关文章

1、Linux设备树语法详解-—-https://www.cnblogs.com/xiaojiang1025/p/6131381.html

2、linux设备树详解-—-http://blog.chinaunix.net/uid-31087949-id-5747835.html

本文基于天嵌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的编写规则可参考文档<>, 以下简称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

  1. 注册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()会采用多种方法进行匹配:

of_driver_match_device将根据驱动程序of_match_table中的compatible属性,与设备中的compatible属性进行比对。
其次调用acpi_driver_match_device()进行匹配。
如果前2种方法都没有匹配的,最后比对设备和驱动的name字符串是否一致。
以GPIO-key为例,设备和驱动匹配示意图如下:

————————————————
版权声明:本文为CSDN博主「ethercat_i7」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ethercat\_i7/article/details/83786670

发表评论

表情:
评论列表 (有 0 条评论,254人围观)

还没有评论,来说两句吧...

相关阅读

    相关 设备dts详解

    原文网址:[设备树dts详解\_IT利刃出鞘的博客-CSDN博客][dts_IT_-CSDN] DTS文件结构 1.dts文件布局 ![设备树布局][watermar

    相关 Linux设备如何配置

    Linux 设备树是一种树形数据结构,用于存储系统中的设备信息。它描述了系统中每个设备的类型、功能和位置。 在 Linux 系统中,设备树由一个名为 "devicetree"

    相关 Linux设备语法详解

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

    相关 linux raw设备详解

    裸设备概述 裸设备:也叫裸分区(原始分区),是一种没有经过格式化,不被Unix/Linux通过文件系统来读取的特殊字符设备。裸设备可以绑定一个分区,也可以绑定一个磁盘。

    相关 Linux设备详解

    ARM Linux社区为什么要引入设备树 Linux之父Linus Torvalds闲来无事,在翻看ARM Linux代码的时候,有一天终于忍不住了。他在2011年3月17日

    相关 Linux 设备介绍

    > 设备树的历史 > > 1、kernel最早加入设备树的历史得追溯到v2.6.23,从这个版本开始,在driver目录下多了一个of目录。当然,此时只是引入一些新想法而已。