Linux驱动开发24之设备树的解析

1.dts文件结构

通过查看fdtdump输出信息以及dtb二进制文件信息,得到struct fdt_header和文件结构之间的关系信息如所示。

通过以上分析,可以得到Device Tree文件结构如上所示。dtb的头部首先存放的是fdt_header的结构体信息,接着是填充区域,填充大小为off_dt_struct – sizeof(struct fdt_header),填充的值为0。接着就是struct fdt_property结构体的相关信息。最后是dt_string部分。

Device Tree源文件的结构分为header、fill_area、dt_struct及dt_string四个区域。fill_area区域填充数值0。节点(node)信息使用struct fdt_node_header结构体描述。属性信息使用struct fdt_property结构体描述。

30: struct fdt_node_header {
31: uint32_t tag;
32: char name[0];
33: };
34:

struct fdt_node_header描述节点信息,tag是标识node的起始结束等信息的标志位,name指向node名称的首地址。tag的取值如下:

47: #define FDT_BEGIN_NODE 0x1 /* Start node: full name */
48: #define FDT_END_NODE 0x2 /* End node */
49: #define FDT_PROP 0x3 /* Property: name off,
50: size, content */
51: #define FDT_NOP 0x4 /* nop */
52: #define FDT_END 0x9
FDT_BEGIN_NODEFDT_END_NODE标识node节点的起始和结束;

FDT_PROP标识node节点下面的属性起始符,FDT_END标识Device Tree的结束标识符。因此,对于每个node节点的tag标识符一般为FDT_BEGIN_NODE,对于每个node节点下面的属性的tag标识符一般是FDT_PROP

描述属性采用struct fdt_property描述,tag标识是属性,取值为FDT_PROPlen为属性值的长度(包括‘\0’,单位:字节);nameoff为属性名称存储位置相对于off_dt_strings的偏移地址。

35: struct fdt_property {
36: uint32_t tag;
37: uint32_t len;
38: uint32_t nameoff;
39: char data[0];
40: };

例如:compatible = samsung,goni, samsung,s5pv210;compatible是属性名称,”samsung,goni, samsung,s5pv210”是属性值。compatible属性名称字符串存放的区域是dt_string。”samsung,goni, samsung,s5pv210”存放的位置是fdt_property.data后面。因此fdt_property.data指向该属性值。fdt_property.tag的值为属性标识,len为属性值的长度(包括‘\0’,单位:字节),此处len = 29nameoffcompatible字符串的位置相对于off_dt_strings的偏移地址,即&compatible = nameoff + off_dt_strings

dt_struct在Device Tree中的结构如图所示。节点的嵌套也带来tag标识符的嵌套:

2.kernel解析设备树

Device Tree文件结构描述就以上struct fdt_headerstruct fdt_node_headerstruct fdt_property三个结构体描述kernel会根据Device Tree的结构解析出kernel能够使用的struct property结构体。kernel根据Device Tree中所有的属性解析出数据填充struct property结构体。

34: struct property {
35: char *name; /* property full name */
36: int length; /* property value length */
37: void *value; /* property value */
38: struct property *next; /* next property under the same node */
39: unsigned long _flags;
40: unsigned int unique_id;
41: struct bin_attribute attr; /* 属性文件,与sysfs文件系统挂接 */
42: };

总的来说,kernel根据Device Tree的文件结构信息转换成struct property结构体,并将同一个node节点下面的所有属性通过property.next指针进行链接,形成一个单链表。

kernel中究竟是如何解析Device Tree的呢?下面分析函数解析过程。函数调用过程如图所示。kernel的C语言阶段的入口函数是init/main.c/start_kernel()函数,early_init_dt_scan_nodes()中会做以下三件事:

1扫描/chosen或者/chose@0节点下面的bootargs属性值到boot_command_line,此外,还处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中;

2扫描根节点下面,获取{size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中;

3扫描具有device_type =memory”属性的/memory或者/memory@0节点下面的reg属性值,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息。

Device Tree中的每一个node节点经过kernel处理都会生成一个struct device_node的结构体,struct device_node最终一般会被挂接到具体的struct device结构体。struct device_node结构体描述如下:

48: struct device_node {
49: const char *name; /* node的名称,取最后一次“/”和“@”之间子串 */
50: const char *type; /* device_type的属性名称,没有为<NULL> */
51: phandle phandle; /* phandle属性值 */
52: const char *full_name; /* 指向该结构体结束的位置,存放node的路径全名,例如:/chosen */
53:
54: struct property *properties; /* 指向该节点下的第一个属性,其他属性与该属性链表相接 */
55: struct property *deadprops; /* removed properties */
56: struct device_node *parent; /* 父节点 */
57: struct device_node *child; /* 子节点 */
58: struct device_node *sibling; /* 姊妹节点,与自己同等级的node */
59: struct device_node *next; /* next device of same type */
60: struct device_node *allnext; /* next in list of all nodes */
61: struct kobject kobj; /* sysfs文件系统目录体现 */
62: unsigned long _flags; /* 当前node状态标志位,见/include/linux/of.h line124-127 */
63: void *data;
69: } « end device_node » ;

203: /* flag descriptions */
204: #define OF_DYNAMIC 1 /* node and properties were allocated via kmalloc */
205: #define OF_DETACHED 2 /* node has been detached from the device tree */
206: #define OF_POPULATED 3 /* device already created for the node */
207: #define OF_POPULATED_BUS 4 /* of_platform_populate recursed to children of this node */

struct device_node结构体中的每个成员作用已经备注了注释信息,下面分析以上信息是如何得来的。Device Tree的解析首先从unflatten_device_tree()开始。

在unflatten_device_tree()中,调用函数__unflatten_device_tree(),参数initial_boot_params指向Device Tree在内存中的首地址,of_root在经过该函数处理之后,会指向根节点,early_init_dt_alloc_memory_arch是一个函数指针,为struct device_node和struct property结构体分配内存的回调函数(callback)。在__unflatten_device_tree()函数中,两次调用unflatten_dt_node()函数,第一次是为了得到Device Tree转换成struct device_node和struct property结构体需要分配的内存大小,第二次调用才是具体填充每一个struct device_node和struct property结构体。

通过以上函数处理就得到了所有的struct device_node结构体,为每一个node都会自动添加一个名称为“name”的property,property.length的值为当前node的名称取最后一个“/”和“@”之间的子串(包括‘\0’)。例如:/serial@e2900800,则length = 7,property.value = device_node.name = “serial”。

3. platform_devicedevice_node绑定

经过以上解析,Device Tree的数据已经全部解析出具体的struct device_node和struct property结构体,下面需要和具体的device进行绑定。

首先讲解platform_device和device_node的绑定过程。在arch/arm/kernel/setup.c文件中,customize_machine()函数负责填充struct platform_device结构体。函数调用过程如图所示。

/* 获取根节点 */

/* 为根节点下面的每一个节点创建platform_device结构体 */

/* 只有包含"compatible"属性的node节点才会生成相应的platform_device结构体 */

/*

     * 针对节点下面得到status = "ok" 或者status = "okay"或者不存在status属性的节点分配内存并填充platform_device结构体

*/

/* 递归调用节点解析函数,为子节点继续生成platform_device结构体,前提是父节点的“compatible” = “simple-bus”,也就是匹配of_default_bus_match_table结构体中的数据

*/

总的来说,当of_platform_populate()函数执行完毕,kernel就为DTB中所有包含compatible属性名的第一级node创建platform_device结构体,并向平台设备总线注册设备信息。如果第一级node的compatible属性值等于“simple-bus”、“simple-mfd”或者”arm,amba-bus”的话,kernel会继续为当前node的第二级包含compatible属性的node创建platform_device结构体,并注册设备。Linux系统下的设备大多都是挂载在平台总线下的,因此在平台总线被注册后,会根据of_root节点的树结构,去寻找该总线的子节点,所有的子节点将被作为设备注册到该总线上。

7. i2c_clientdevice_node绑定

经过customize_machine()函数的初始化,DTB已经转换成platform_device结构体,这其中就包含i2c adapter设备,不同的SoC需要通过平台设备总线的方式自己实现i2c adapter设备的驱动。例如:i2c_adapter驱动的probe函数中会调用i2c_add_numbered_adapter()注册adapter驱动,函数流执行如图所示。

在of_i2c_register_devices()函数内部遍历i2c节点下面的每一个子节点,并为子节点(status = “disable”的除外)创建i2c_client结构体,并与子节点的device_node挂接。其中i2c_client的填充是在i2c_new_device()中进行的,最后device_register()。在构建i2c_client的时候,会对node下面的compatible属性名称的厂商名字去除作为i2c_client的name。例如:compatible = “maxim,ds1338”,则i2c_client->name = “ds1338”。

8. Device_Treesysfs

kernel启动流程为start_kernel()→rest_init()→kernel_thread():kernel_init()→do_basic_setup()→driver_init()→of_core_init(),在of_core_init()函数中在sys/firmware/devicetree/base目录下面为设备树展开成sysfs的目录和二进制属性文件,所有的node节点就是一个目录,所有的property属性就是一个二进制属性文件。

9.设备树对platform平台设备驱动带来的变化

(1)platform_driver的入口函数,仍采用platform_driver_register注册(不变)

(2)平台驱动:稍微的变化,多了of_match_table成员

1310: static struct platform_driver s3c24xx_i2c_driver = {
1311: .probe = s3c24xx_i2c_probe,
1312: .remove = s3c24xx_i2c_remove,
1313: .id_table = s3c24xx_driver_ids,
1314: .driver = {
1315: .owner = THIS_MODULE,
1316: .name = "s3c-i2c",
1317: .pm = S3C24XX_DEV_PM_OPS,
1318: .of_match_table = of_match_ptr(s3c24xx_i2c_match),
1319: },
1320: };
1321:
1322: static int __init i2c_adap_s3c_init(void)
1323: {
1324: return platform_driver_register(&s3c24xx_i2c_driver);
1325: }
1326: subsys_initcall(i2c_adap_s3c_init);

3)匹配方式的变化

没有引入设备树之前,我们采用设备名字匹配的方式,当platform_driver_register的时候,会去匹配一个名字为" s3c-i2c "的设备,如果找到同名设备则调用probe函数。

由于设备树的引入,被硬编码在arch/arm/plat-xx和arch/arm/mach-xxx,比如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data将不存在。

那么这些设备信息在哪里,什么时候被add进内核,platform_driver如何匹配platform_device呢?答案是设备信息存在设备树中,设备树加载的时候被转换成设备结构体。platform不在像以前那样匹配设备名字,而是匹配驱动中的.compatible与设备树中相应节点的compatible属性是否一致,且不区分大小写。一致则调用probe函数。下面我们就来详细分析为什么是这样。

144: #ifdef CONFIG_OF
145: static const struct of_device_id s3c24xx_i2c_match[] = {
146: { .compatible = "samsung,s3c2410-i2c", .data = (void *)0 },
147: { .compatible = "samsung,s3c2440-i2c", .data = (void *)QUIRK_S3C2440 },
148: { .compatible = "samsung,s3c2440-hdmiphy-i2c",
149: .data = (void *)(QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO) },
150: { .compatible = "samsung,exynos5440-i2c",
151: .data = (void *)(QUIRK_S3C2440 | QUIRK_NO_GPIO) },
152: { .compatible = "samsung,exynos5-sata-phy-i2c",
153: .data = (void *)(QUIRK_S3C2440 | QUIRK_POLL | QUIRK_NO_GPIO) },
154: {},
155: };

10. 详细分析platform_match的过程

10.1函数调用流程

去内核里查看,便可发现一层一层是这么调用的。

platform_match-->of_driver_match_device-->of_match_device-->of_match_node-->of_device_is_compatible-->of_get_property/of_compat_cmp-->strcasecmp((s1), (s2))

我们发现最后是在比较字符串内容一否一致,所以我们只需要分析这几个方法的成员列表,看到底比较的是哪两个字符串即可。

10.2方法分析

platform_driver_register,首先调用到如下匹配函数。

platform_match(device,device_driver)

device:猜测是设备树构建的

device_driver:被platform_driver封装,就是我们的s3c24xx_i2c_driver

831: static int platform_match(struct device *dev, struct device_driver *drv)
832: {
833: struct platform_device *pdev = to_platform_device(dev);
834: struct platform_driver *pdrv = to_platform_driver(drv);
835:
836:
/* When driver_override is set, only bind to the matching driver */
837: if (pdev->driver_override)
838: return !strcmp(pdev->driver_override, drv->name);
839:
840:
/* Attempt an OF style match first */
841: if (of_driver_match_device(dev, drv))
842: return 1;
843:
844:
/* Then try ACPI style match */
845: if (acpi_driver_match_device(dev, drv))
846: return 1;
847:
848:
/* Then try to match against the id table */
849: if (pdrv->id_table)
850: return platform_match_id(pdrv->id_table, pdev) != NULL;
851:
852:
/* fall-back to driver name match */
853: return (strcmp(pdev->name, drv->name) == 0);
854: } « end platform_match »

of_driver_match_devicedevicedevice_driver

21: const struct of_device_id *of_match_device(const struct of_device_id *matches,const struct device *dev)
23: {
24: if ((!matches) || (!dev->of_node))
25: return NULL;
26: return of_match_node(matches, dev->of_node);
27: }

of_device_id:device_driver>of_match_table= of_match_ptr(s3c24xx_i2c_match):这个不就是我们在驱动里面定义的of_match_table成员

device:猜测是设备树构建的

904: const struct of_device_id *__of_match_node(const struct of_device_id *matches,
905: const struct device_node *node)
906: {
907: const struct of_device_id *best_match = NULL;
908: int score, best_score = 0;
909:
910: if (!matches)
911: return NULL;
912:
913: for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
914: score = __of_device_is_compatible(node, matches->compatible,
915: matches->type, matches->name);
916: if (score > best_score) {
917: best_match = matches;
918: best_score = score;
919: }
920: }
921:
922: return best_match;
923: } « end __of_match_node »

Of_match_node(of_device_id,device_node)

of_device_id:of_match_ptr(s3c24xx_i2c_match)

device_node:device->of_node(设备树完成了of_node的初始化)继续:

of_device_is_compatible(device_node,char *compat)=of_device_is_compatibledevice_nodesamsung,s3c2410-i2c

430: static int __of_device_is_compatible(const struct device_node *device,
431: const char *compat, const char *type, const char *name)
432: {
433: struct property *prop;
434: const char *cp;
435: int index = 0, score = 0;
436:
437:
/* Compatible match has highest priority */
438: if (compat && compat[0]) {
439: prop = __of_find_property(device, "compatible", NULL);
440: for (cp = of_prop_next_string(prop, NULL); cp;
441: cp = of_prop_next_string(prop, cp), index++) {
442: if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
443: score = INT_MAX/2 - (index << 2);
444: break;
445: }
446: }
447: if (!score)
448: return 0;
449: }
450:
451:
/* Matching type is better than matching name */
452: if (type && type[0]) {
453: if (!device->type || of_node_cmp(type, device->type))
454: return 0;
455: score += 2;
456: }
457:
458:
/* Matching name is a bit better than not */
459: if (name && name[0]) {
460: if (!device->name || of_node_cmp(name, device->name))
461: return 0;
462: score++;
463: }
464:
465:
return score;
466: } « end __of_device_is_compatib

device_node:device->of_node(设备树完成了of_node的初始化)

char *compat:of_device_id->compatible= samsung,s3c2410-i2c

到此我们已经可以发现 ,现在是在和驱动里面定义的of_device_id结构体的compatible成员做对比,那么是谁和它对比呢?我们继续看下一个函数:

of_compat_cmp:忽略大小写比较字符串。#define of_compat_cmp(s1, s2, l)strcasecmp((s1), (s2))

device_node:device->of_node(设备树完成了of_node的初始化)

设备树加载的时候构建了device设备,被初始化了of_node成员,现在我们根据of_node去获取节点对应的compatible属性。cp就等于设备树里我们定义的节点的compatible属性值。如上函数of_device_is_compatible,则对比了设备树中节点的compatible与我们定义的是否存在名字一致的设备。存在则返回1;

11总结

到此我们知道了。是在比较驱动中我们定义的of_device_id类型的结构体里面的compatible名字与设备树节点的compatible来决定是否执行probe函数。我们并没有初始化platform_device,这些是内核加载设备树的时候帮我们完成的,并且根据设备树节点初始化了of_node成员,我们可以根据of_node找到节点对应的成员属性。即设备树加载之后,内核会自动把设备树节点转换成 platform_device这种格式,同时把名字放到of_node这个地方。

还有一点我们上面用到的结构体是device,和device_driver,为什么不是我们定义的platform_device和platform_driver呢?其实platform是对device的一层封装,查看源码我们就可以发现函数调用流程:

platform_device--》device            

platform_device_register  --》device_add
 platform_driver--》device_driver      

 platform_driver_register--》device_register
所以platform是对struct device和struct device_driver的封装。

 

 

 

 

 

 

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值