转自:http://www.pianshen.com/article/428276673/;jsessionid=D90FC6B215155680E0B89A6D060892D4node
本文基于天嵌E9V3开发板,详解设备树的规则和用法。linux
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内核,内核启动之后从该地址读取相应的设备信息,匹配平台和设备驱动。git
linux中的一个dts文件对应一个machine, 不一样的machine可能使用相同的SOC,只是对外设的使用不一样,这些不一样的dts文件势必包含不少相同的内容,为了简化,能够把公用的部分提炼为dtsi文件。
e9v3-sabresd.dts包含dtsi的结构以下:github
列出各个文件中的节点,以下图所示,是否是有点像有不少分支的树?markdown
Device Tree的编写规则可参考文档<<devicetree-specification-v0.2.pdf>>, 如下简称spec,下载连接为:
https://github.com/devicetree-org/devicetree-specification/releases/tag/v0.2session
设备树由一个一个的节点组成,每一个设备树有且仅有一个根节点,节点能够包含子节点。ide
一、节点名称
基本的节点名格式以下:
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 {
…
}
根节点的名称比较特殊,由一个斜杠组成:
/{
…
}函数
二、label标签atom
linux内核启动之后,先解析并注册dts中的设备,而后再注册驱动,比较驱动中的compatible 属性和设备中的compatible 属性,或者比较二者的name属性,若是一致则匹配成功。
一、解析dtb
在start_kernel() --> setup_arch(0 --> unflatten_device_tree() --> __unflatten_device_tree()函数中扫描dtb,并转换成节点是device_node的树状结构。
注:代码基于linux4.1.15内核(下同)spa
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); ... }
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); } ... }
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; }
最终调用of_platform_device_create_pdata() —> of_device_add() 注册设备并添加到对应的链表中。
三、注册驱动
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()注册驱动程序。
四、匹配设备
追踪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; }
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); }
从代码中能够看出, platform_match()会采用多种方法进行匹配:
以GPIO-key为例,设备和驱动匹配示意图以下: