之前学习了platform设备与总线是如何匹配的,但是在读某一驱动程序中,该设备由dts文件描述,设备的匹配与platform设有所不同,因此记录下来。
1. 什么是设备树
在内核源码中存在大量对板级细节信息描述的代码,但是对于内核而言,这些代码对于内核毫无意义。 ARM内核版本3.x引入了Flattened Device Tree(FDT),这是一种描述硬件资源的数据结构,通过BootLoader将硬件资源传给内核,使得内核和硬件资源描述相对独立。采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余码。
本质上,Device Tree改变了原来用hardcode方式将HW 配置信息嵌入到内核代码的方法,改用bootloader传递一个DB的形式。
设备树是一种描述硬件的数据结构,采用设备树后,硬件细节可以直接通过设备树传递给Linux,不需要在内核中进行大量的冗余编码。
Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):
- CPU的数量和类别
- 内存基地址和大小
- 总线和桥
- 外设连接
- 中断控制器和中断使用情况
- GPIO控制器和GPIO使用情况
- Clock控制器和Clock使用情况
它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。
DTS(device tree source)
.dts文件是一种对Device Tree的ASCII文本描述,一个dts文件对应一个ARM架构的machine。但是一个SOC板可能对应多个产品。这些产品的dts文件会存在大量冗余。为了简化,Device Tree将这些冗余提炼为.dtsi文件,dtsi文件相当于C语言的头文件,dts文件需要include引入dtsi文件。
DTC(编译工具)
DTC为编译工具,它可以将.dts文件编译成.dtb文件。DTC的源码位于内核的scripts/dtc目录下,内核选中CONFIG_OF,编译内核的时候,主机可执行程序DTC就会被编译出来。
DTB(二进制文件)
DTC编译.dts生成的二进制文件(.dtb),bootloader在引到内核时,会预先读取.dtb到内存,进而由内核解析。
BootLoader(bootloader支持)
Bootloader需要将设备树在内存中的地址传给内核。在ARM中通过bootm或bootz命令来进行传递。
Device Tree组成和结构
device tree的基本单元是node。这些node被组织成树状结构,除了root node,每个node都只有一个parent。一个device tree文件中只能有一个root node。每个node中包含了若干的property/value来描述该node的一些特性。每个node用节点名字(node name)标识,节点名字的格式是node-name@unit-address。如果该node没有reg属性),那么该节点名字中必须不能包括@和unit-address。root node的node name是确定的,必须是“/”。
2. 设备树信息加载
设备树的目的是用于抽象硬件资源,减少代码的重复定义。内核会识别出相应设备树中的节点,并展开出platform_device、i2c_client等各种设备,对应到platform上,设备树上的node会展开为platform_device,设备用到的资源也会由内核绑定给相应设备。
2.1 dtb展开为device_node
Linux最底层的初始化是用汇编实现的,而在C中的start_kernel()中,具体是在setup_arch()中完成初始化的。
setup_arch()中有三个重要的函数,分别是setup_machine_fdt()、arm_memblock_init()以及unflatten_device_tree()。
setup_machine_fdt()函数根据传入的设备树dtb的首地址完成一些初始化操作;arm_memblock_init()函数主要是内存相关,为设备树保留相应的内存空间,保证设备树dtb本身存在于内存中而不被覆盖。用户可以在设备树中设置保留内存,这一部分同时作了保留指定内存的工作;unflatten_device_tree()从命名可以看出,这个函数就是对设备树具体的解析,事实上在这个函数中所做的工作就是将设备树各节点转换成相应的struct device_node结构体。
各函数详细作用可看附录,以后在详细了解后应该会进行补充。
2.2 device_node展开为platform_device
将device_node展开为platform_device是of_platform_default_populate_init()函数实现的。
arch_initcall_sync(of_platform_default_populate_init);
其具体调用为
static int __init of_platform_default_populate_init(void)
int of_platform_default_populate(struct device_node *root, const struct of_dev_auxdata *lookup,struct device *parent)
int of_platform_populate(struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent)
static int of_platform_bus_create(struct device_node *bus, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent, bool strict)
static struct platform_device *of_platform_device_create_pdata(struct device_node *np, const char *bus_id, void *platform_data, struct device *parent)
of_platform_default_populate_init()在内核中没有直接对它的调用,而是使用放在".initcall3s.init"代码段中,在内核启动时进行 do_initcall_level()时调用这个函数。
static int __init of_platform_default_populate_init(void)
{
struct device_node *node;
device_links_supplier_sync_state_pause();
if (!of_have_populated_dt())
return -ENODEV;
/*
* Handle certain compatibles explicitly, since we don't want to create
* platform_devices for every node in /reserved-memory with a
* "compatible",
*/
for_each_matching_node(node, reserved_mem_matches) //处理一些保留的节点
of_platform_device_create(node, NULL, NULL);
node = of_find_node_by_path("/firmware"); //处理firmware目录下的节点
if (node) {
of_platform_populate(node, NULL, NULL, NULL);
of_node_put(node); //减少node引用计数
}
/* Populate everything else. */
of_platform_default_populate(NULL, NULL, NULL); //处理其余类型的节点
return 0;
}
其作用是遍历device_tree,生成platform_device,最主要的处理函数就是of_platform_default_populate(),对应在该函数中,root、lookup与parent均为NULL。
int of_platform_default_populate(struct device_node *root,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
return of_platform_populate(root, of_default_bus_match_table, lookup,
parent);
}
const struct of_device_id of_default_bus_match_table[] = {
{ .compatible = "simple-bus", },
{ .compatible = "simple-mfd", },
{ .compatible = "isa", },
#ifdef CONFIG_ARM_AMBA
{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
{} /* Empty terminated list */
};
of_platform_populate()函数会根据node的compatible属性调用of_platform_bus_create()创建device,其中root的值为of_find_node_by_path("/"),并对其子节点分别创建device。matches为上面的of_default_bus_match_table。
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %pOF\n", root);
device_links_supplier_sync_state_pause();
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc) {
of_node_put(child);
break;
}
}
device_links_supplier_sync_state_resume();
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}
该函数的作用是根据device_node创建platform_device,并递归的对子节点创建platform_device。首先判断节点是否有compatible属性,没有的话不做处理;接着判断of_match_node()函数,根据of_skipped_node_table表查看节点属性,判断节点是否需要创建设备;如果节点中的compatible属性的值为operating-points-v2,则跳过;接着根据bus总线的值判断该总线节点是否已被展开,如果是,跳过;lookup根据of_platform_default_populate()函数发现值为空,直接返回NULL;接着,amba设备做单独处理;然后调用of_platform_device_create_pdata()函数将节点转化为设备;最后递归地将子节点转化为设备。在总线上的节点转化完毕后,设置标志OF_POPULATED_BUS。
在对子节点进行操作时,根据matchs传入的数组进行判断,如果包括"simple-bus"、“simple-mfd”、“isa”、"arm,amba-bus"等属性时,会创建对子节点创建platform_device。
static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool strict)
{
const struct of_dev_auxdata *auxdata;
struct device_node *child;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;
/* Make sure it has a compatible property */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %pOF, no compatible prop\n",
__func__, bus);
return 0;
}
/* Skip nodes for which we don't want to create devices */
if (unlikely(of_match_node(of_skipped_node_table, bus))) {
pr_debug("%s() - skipping %pOF node\n", __func__, bus);
return 0;
}
if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
pr_debug("%s() - skipping %pOF, already populated\n",
__func__, bus);
return 0;
}
auxdata = of_dev_lookup(lookup, bus);
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}
if (of_device_is_compatible(bus, "arm,primecell")) {
/*
* Don't return an error here to keep compatibility with older
* device tree files.
*/
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
for_each_child_of_node(bus, child) {
pr_debug(" create child: %pOF\n", child);
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()为主要函数,其代码如下。作用是分配
初始化并注册一个设备。
static struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev;
if (!of_device_is_available(np) ||
of_node_test_and_set_flag(np, OF_POPULATED))
return NULL;
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag;
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
if (!dev->dev.dma_mask)
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_msi_configure(&dev->dev, dev->dev.of_node);
if (of_device_add(dev) != 0) {
platform_device_put(dev);
goto err_clear_flag;
}
return dev;
err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}
由of_device_alloc()分配一个platform_device型结构体,在对bus、platform_data(此时为NULL)赋值后,由of_device_add()将创建的设备添加到系统中。
对于of_device_alloc(),首先调用platform_device_alloc()创建一个platform_device,接着函数统计设备树中reg属性和中断irq属性的个数,然后分别为它们申请内存空间,链入到platform_device中的struct resources成员中。除了设备树中"reg"和"interrupt"属性之外,还有可选的"reg-names"和"interrupt-names"这些io中断资源相关的设备树节点属性也在这里被转换。
struct platform_device *of_device_alloc(struct device_node *np,
const char *bus_id,
struct device *parent)
{
struct platform_device *dev;
int rc, i, num_reg = 0, num_irq;
struct resource *res, temp_res;
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
if (!dev)
return NULL;
//统计中断irq属性的数量
/* count the io and irq resources */
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
num_irq = of_irq_count(np); //统计中断irq属性的数量
//根据num_irq和num_reg的数量申请相应的struct resource内存空间。
/* Populate the resource table */
if (num_irq || num_reg) {
res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}
dev->num_resources = num_reg + num_irq;
dev->resource = res;
//将device_node中的reg属性转换成platform_device中的struct resource成员
for (i = 0; i < num_reg; i++, res++) {
rc = of_address_to_resource(np, i, res);
WARN_ON(rc);
}
//将device_node中的irq属性转换成platform_device中的struct resource成员
if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
np);
}
//将platform_device的dev.of_node成员指针指向device_node。
dev->dev.of_node = of_node_get(np);
dev->dev.fwnode = &np->fwnode;
dev->dev.parent = parent ? : &platform_bus;
if (bus_id)
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);
return dev;
}
最后调用of_device_add()将相应的设备树节点生成的device_node节点链入到platform_device的dev.of_node中。
设备树的目的是将硬件资源抽象出来,由系统统一解析,避免驱动中对硬件资源大量的重复定义。在老版本的内核中,platform_device部分是静态定义的,其实最主要的部分就是resources部分,这一部分描述了当前驱动需要的硬件资源,一般是IO,中断等资源。在设备树中,这一类资源通常通过reg属性来描述,中断则通过interrupts来描述,所以,设备树中的reg和interrupts资源将会被转换成platform_device内的struct resources资源。在platform_device中有一个成员struct device dev,这个dev中又有一个指针成员struct device_node* of_node,linux的做法就是将这个of_node指针直接指向由设备树转换而来的device_node结构。例如,有这么一个struct platform_device* of_test.我们可以直接通过of_test->dev.of_node来访问设备树中的信息.
3. 设备的匹配
在之前分析的platform_match()中,调用了of_driver_match_device()函数,这个函数就是将驱动与设备树中的设备进行匹配,其具体调用为:
static inline int of_driver_match_device(struct device *dev,const struct device_driver *drv)
const struct of_device_id *of_match_device(const struct of_device_id *matches,const struct device *dev)
static const struct of_device_id *__of_match_node(const struct of_device_id *matches, const struct device_node *node)
static int __of_device_is_compatible(const struct device_node *device, const char *compat, const char *type, const char *name)
首先调用of_driver_match_device()函数进行设备与驱动的匹配,而该函数调用了of_match_device()函数,主要是通过驱动的of_match_table结构体。与设备进行匹配。
static inline int of_driver_match_device(struct device *dev,
const struct device_driver *drv)
{
return of_match_device(drv->of_match_table, dev) != NULL;
}
of_match_table的具体类型如下所示,包含了驱动支持的多个设备类型
struct of_device_id
{
char name[32]; //设备名
char type[32]; //设备类型
char compatible[128]; //与设备信息compatible相匹配
const void *data; //驱动私有数据
};
设备树中的设备信息在加载时被组织为device_node结构体,转变为platform_device时,其of_node指针指向的就是该device_node结构。
const struct of_device_id *of_match_device(const struct of_device_id *matches,
const struct device *dev)
{
if ((!matches) || (!dev->of_node))
return NULL;
return of_match_node(matches, dev->of_node);
}
其匹配的方式主要是通过compatible、type以及name等方式进行匹配,其优先级在__of_device_is_compatible()函数注释中进行了说明。
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
const struct device_node *node)
{
const struct of_device_id *best_match = NULL;
int score, best_score = 0;
if (!matches)
return NULL;
for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
score = __of_device_is_compatible(node, matches->compatible,
matches->type, matches->name);
if (score > best_score) {
best_match = matches;
best_score = score;
}
}
return best_match;
}
参数 device 指向一个 device_node; 参数指向 compatible 字符串。函数调用 __of_find_property() 函数获得该设备的属性“compatible”。如果属性compatible找到,那么返回属性compatible的值;否则返回 NULL。调用 of_compat_cmp() 函数对比 compatible 属性的值是否和参数一致。
static int __of_device_is_compatible(const struct device_node *device,
const char *compat, const char *type, const char *name)
{
struct property *prop;
const char *cp;
int index = 0, score = 0;
/* Compatible match has highest priority */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}
/* Matching type is better than matching name */
if (type && type[0]) {
if (!__of_node_is_type(device, type))
return 0;
score += 2;
}
/* Matching name is a bit better than not */
if (name && name[0]) {
if (!of_node_name_eq(device, name))
return 0;
score++;
}
return score;
}
由于每个 device_node 包含一个名为 properties 的成员,properties 是一个单链表,包含了该节点的所有属性,函数通过使用 for 循环遍历这个链表,以此遍历节点所包含的所有属性。每遍历一个属性就会调用 of_prop_cmp() 函数, of_prop_cmp() 函数用于对比两个字符串是否相等,如果相等返回 0。因此,当遍历到的属性的名字与参数 name 一致,那么定义为找到了指定的属性。
static struct property *__of_find_property(const struct device_node *np,
const char *name, int *lenp)
{
struct property *pp;
if (!np)
return NULL;
for (pp = np->properties; pp; pp = pp->next) {
if (of_prop_cmp(pp->name, name) == 0) {
if (lenp)
*lenp = pp->length;
break;
}
}
return pp;
}
附
在设备匹配方式改变的同时,其driver的初始化也发生了改变,这是基于设备树的设备匹配方式的驱动:
static const struct of_device_id arc_pmu_match[] = {
{ .compatible = "snps,arc700-pct" },
{ .compatible = "snps,archs-pct" },
{},
};
static struct platform_driver arc_pmu_driver = {
.driver = {
.name = "arc-pct",
.of_match_table = of_match_ptr(arc_pmu_match),
},
.probe = arc_pmu_device_probe,
};
这是一般platform_device设备匹配的驱动的初始化:
static struct platform_driver db1300_wm97xx_driver = {
.driver.name = "wm97xx-touch",
.driver.owner = THIS_MODULE,
.probe = db1300_wm97xx_probe,
};
针对设备树节点展开为platform_device的过程,主要通过这篇文章进行学习的: