转自:https://www.cnblogs.com/downey-blog/p/10486568.html

以下讨论基于linux4.14,arm平台

platform device

设备树的产生就是为了替代driver中过多的platform_device部分的静态定义,将硬件资源抽象出来,由系统统一解析,这样就可以避免各驱动中对硬件资源大量的重复定义,这样一来,几乎可以肯定的是,设备树中的节点最终目标是转换成platform device结构,在驱动开发时就只需要添加相应的platform driver部分进行匹配即可。

在上一节中讲到设备树dtb文件中的各个节点转换成device_node的过程,每个设备树子节点都将转换成一个对应的device_node节点,那么:

是不是每个由设备树节点转换而来的device_node结构体都将转换成对应的?

首先,对于所有的device_node,如果要转换成platform_device,必须满足以下条件:

  • 一般情况下,只对设备树中根的子节点进行转换,也就是子节点的子节点并不处理。但是存在一种特殊情况,就是当某个根子节点的compatible属性为"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"时,当前节点中的子节点将会被转换成platform_device节点。

  • 节点中必须有compatible属性。

如果是device_node转换成platform device,这个转换过程又是怎么样的呢?

在老版本的内核中,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来访问设备树中的信息.

设备树节点到device_node的转换参考一篇博客:设备树dtb到device_node的转换.

大体流程讲完了,接下来从源代码中进行求证。

platform_device转换的开始

事实上,如果从C语言的开始函数start_kernel进行追溯,是找不到platform device这一部分转换的源头的,事实上,这个转换过程的函数是of_platform_default_populate_init(),它被调用的方式是这样一个声明:

  1. arch_initcall_sync(of_platform_default_populate_init);

在linux中,同系列的调用声明还有:

  1. #define pure_initcall(fn) __define_initcall(fn, 0)
  2. #define core_initcall(fn) __define_initcall(fn, 1)
  3. #define core_initcall_sync(fn) __define_initcall(fn, 1s)
  4. #define postcore_initcall(fn) __define_initcall(fn, 2)
  5. #define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
  6. #define arch_initcall(fn) __define_initcall(fn, 3)
  7. #define arch_initcall_sync(fn) __define_initcall(fn, 3s)
  8. #define subsys_initcall(fn) __define_initcall(fn, 4)
  9. #define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
  10. #define fs_initcall(fn) __define_initcall(fn, 5)
  11. #define fs_initcall_sync(fn) __define_initcall(fn, 5s)
  12. #define rootfs_initcall(fn) __define_initcall(fn, rootfs)
  13. #define device_initcall(fn) __define_initcall(fn, 6)
  14. #define device_initcall_sync(fn) __define_initcall(fn, 6s)
  15. #define late_initcall(fn) __define_initcall(fn, 7)
  16. #define late_initcall_sync(fn) __define_initcall(fn, 7s)

这些宏最终都是调用__define_initcall(fn, n),这个数字代表系统启动时被调用的优先级,数字越小,优先级越低,用这一系列宏声明一个新的函数就是将这个函数指针放入内存中一个指定的段内。

  1. #define __define_initcall(fn, id) \
  2. static initcall_t __initcall_##fn##id __used \
  3. __attribute__((__section__(".initcall" #id ".init"))) = fn;

即放入到".initcalln.init"中,n代表优先级,当系统启动时,会依次调用这些段中的函数。
(详细了解linux的initcall机制可以参考我的另一篇博客:linux的initcall机制)

下面我们就进入到of_platform_default_populate_init()中,查看它的执行过程:

  1. static int __init of_platform_default_populate_init(void)
  2. {
  3. ...
  4. of_platform_default_populate(NULL, NULL, NULL);
  5. ...
  6. }

在函数of_platform_default_populate_init()中,调用了of_platform_default_populate(NULL, NULL, NULL);,传入三个空指针:

  1. const struct of_device_id of_default_bus_match_table[] = {
  2. { .compatible = "simple-bus", },
  3. { .compatible = "simple-mfd", },
  4. { .compatible = "isa", },
  5. #ifdef CONFIG_ARM_AMBA
  6. { .compatible = "arm,amba-bus", },
  7. #endif /* CONFIG_ARM_AMBA */
  8. {} /* Empty terminated list */
  9. };
  10. int of_platform_default_populate(struct device_node *root,const struct of_dev_auxdata *lookup,struct device *parent)
  11. {
  12. return of_platform_populate(root, of_default_bus_match_table, lookup,
  13. parent);
  14. }

of_platform_default_populate()调用了of_platform_populate()。

需要注意的是,在调用of_platform_populate()时传入了参数of_default_bus_match_table[],这个table是一个静态数组,这个静态数组中定义了一系列的compatible属性:"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"。

按照我们上文中的描述,当某个根节点下的一级子节点的compatible属性为这些属性其中之一时,它的一级子节点也将由device_node转换成platform_device.

到底是不是这样呢?接着往下看:

  1. int of_platform_populate(struct device_node *root,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent){
  2. root = root ? of_node_get(root) : of_find_node_by_path("/");
  3. for_each_child_of_node(root, child) {
  4. rc = of_platform_bus_create(child, matches, lookup, parent, true);
  5. if (rc) {
  6. of_node_put(child);
  7. break;
  8. }
  9. }
  10. ...
  11. }

首先,从设备树中获取根节点的device_node结构体,然后对每个根目录下的一级子节点调用of_platform_bus_create(),从命名上来看,这部分解析的目的是建立各个bus的platform_device结构,需要注意的是对于of_platform_bus_create(child, matches, lookup, parent, true),matchs参数是上文中提到的compatible静态数组,而lookup和parent依旧为NULL。

接着跟踪代码:

  1. 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)
  2. {
  3. dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
  4. if (!dev || !of_match_node(matches, bus))
  5. return 0;
  6. for_each_child_of_node(bus, child) {
  7. pr_debug(" create child: %pOF\n", child);
  8. rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
  9. if (rc) {
  10. of_node_put(child);
  11. break;
  12. }
  13. }
  14. ...
  15. }

对于节点的转换,是由of_platform_device_create_pdata(bus, bus_id, platform_data, parent)函数来实现的。

紧接着,在第二行的函数调用中,判断of_match_node(matches,bus)函数的返回值,这个matchs就是compatible的静态数组,这个函数的作用就是判断当前节点的compatible属性是否包含上文中compatible静态数组中的元素,如果不包含,函数返回。

如果当前compatible属性中包含静态数组中的元素,即当前节点的compatible属性有"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"其中一项,递归地对当前节点调用of_platform_bus_create(),即将符合条件的子节点转换为platform_device结构。

关于节点转换的细节部分我们接着跟踪of_platform_device_create_pdata(bus, bus_id, platform_data, parent)函数,此时的参数platform_data为NULL。

  1. static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id,void *platform_data,struct device *parent)
  2. {
  3. struct platform_device *dev;
  4. dev = of_device_alloc(np, bus_id, parent);
  5. dev->dev.bus = &platform_bus_type;
  6. dev->dev.platform_data = platform_data;
  7. if (of_device_add(dev) != 0) {
  8. platform_device_put(dev);
  9. goto err_clear_flag;
  10. }
  11. }

struct platform_device终于现出了真身,在这个函数调用中,显示申请并初始化一个platform_device结构体,将传入的device_node链接到成员:dev.fo_node中
赋值bus成员和platform_data成员,platform_data成员为NULL。

再使用of_device_add()将当前生成的platform_device添加到系统中。

对于of_platform_device_create_pdata()函数中的实现,我们需要逐一讲解其中的函数实现:

of_device_alloc()

  1. struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)
  2. {
  3. //统计reg属性的数量
  4. while (of_address_to_resource(np, num_reg, &temp_res) == 0)
  5. num_reg++;
  6. //统计中断irq属性的数量
  7. num_irq = of_irq_count(np);
  8. //根据num_irq和num_reg的数量申请相应的struct resource内存空间。
  9. if (num_irq || num_reg) {
  10. res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
  11. if (!res) {
  12. platform_device_put(dev);
  13. return NULL;
  14. }
  15. //设置platform_device中的num_resources成员
  16. dev->num_resources = num_reg + num_irq;
  17. //设置platform_device中的resource成员
  18. dev->resource = res;
  19. //将device_node中的reg属性转换成platform_device中的struct resource成员。
  20. for (i = 0; i < num_reg; i++, res++) {
  21. rc = of_address_to_resource(np, i, res);
  22. WARN_ON(rc);
  23. }
  24. //将device_node中的irq属性转换成platform_device中的struct resource成员。
  25. if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
  26. pr_debug("not all legacy IRQ resources mapped for %s\n",
  27. np->name);
  28. }
  29. //将platform_device的dev.of_node成员指针指向device_node。
  30. dev->dev.of_node = of_node_get(np);
  31. //将platform_device的dev.fwnode成员指针指向device_node的fwnode成员。
  32. dev->dev.fwnode = &np->fwnode;
  33. //设备parent为platform_bus
  34. dev->dev.parent = parent ? : &platform_bus;
  35. }

首先,函数先统计设备树中reg属性和中断irq属性的个数,然后分别为它们申请内存空间,链入到platform_device中的struct resources成员中。除了设备树中"reg"和"interrupt"属性之外,还有可选的"reg-names"和"interrupt-names"这些io中断资源相关的设备树节点属性也在这里被转换。

将相应的设备树节点生成的device_node节点链入到platform_device的dev.of_node中。

of_device_add

  1. int of_device_add(struct platform_device *ofdev){
  2. ...
  3. return device_add(&ofdev->dev);
  4. }

将当前platform_device中的struct device成员注册到系统device中,并为其在用户空间创建相应的访问节点。

总结

总的来说,将device_node转换为platform_device的过程还是比较简单的。

整个转换过程的函数调用关系是这样的:

  1. of_platform_default_populate_init()
  2. |
  3. of_platform_default_populate();
  4. |
  5. of_platform_populate();
  6. |
  7. of_platform_bus_create()
  8. _____________________|_________________
  9. | |
  10. of_platform_device_create_pdata() of_platform_bus_create()
  11. _________________|____________________
  12. | |
  13. of_device_alloc() of_device_add()

好了,关于linux设备树中device_node到platform_device的转换过程的讨论就到此为止啦,如果朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言

原创博客,转载请注明出处!

祝各位早日实现项目丛中过,bug不沾身.

设备树处理之——device_node转换成platform_device【转】的更多相关文章

  1. linux设备驱动程序-设备树(2)-device_node转换成platform_device

    设备树处理之--device_node转换成platform_device 以下讨论基于linux4.14,arm平台 platform device 设备树的产生就是为了替代driver中过多的pl ...

  2. linux设备驱动程序-设备树(3)-设备树多级子节点的转换

    linux设备驱动程序--设备树多级子节点的转换 在上一章:设备树处理之--device_node转换成platform_device中,有提到在设备树的device_node到platform_de ...

  3. linux设备驱动程序-设备树(0)-dtb格式

    linux设备树dtb格式 设备树的一般操作方式是:开发人员根据开发需求编写dts文件,然后使用dtc将dts编译成dtb文件. dts文件是文本格式的文件,而dtb是二进制文件,在linux启动时被 ...

  4. linux设备驱动程序-设备树(1)-dtb转换成device_node

    linux设备驱动程序-设备树(1)-dtb转换成device_node 本设备树解析基于arm平台 从start_kernel开始 linux最底层的初始化部分在HEAD.s中,这是汇编代码,我们暂 ...

  5. 设备树..ing

    .dts==>.dtb ==>device_node ==>  platform_device ==> led_dev.c  ==>匹配 led_drv.c    (设备 ...

  6. 宋牧春: Linux设备树文件结构与解析深度分析(2) 【转】

    转自:https://mp.weixin.qq.com/s/WPZSElF3OQPMGqdoldm07A 作者简介 宋牧春,linux内核爱好者,喜欢阅读各种开源代码(uboot.linux.ucos ...

  7. 【总结】设备树对platform平台设备驱动带来的变化(史上最强分析)【转】

    本文转载自:http://blog.csdn.net/fengyuwuzu0519/article/details/74375086 版权声明:本文为博主原创文章,转载请注明http://blog.c ...

  8. 基于设备树的TQ2440的中断(1)

    作者 姓名:彭东林 E-mail:pengdonglin137@163.com QQ:405728433 平台 板子:TQ2440 内核:Linux-4.9 u-boot: 2015.04 工具链: ...

  9. 【linux】驱动-8-一文解决设备树

    目录 前言 8. Linux设备树 8.1 设备树简介 8.2 设备树框架 8.2.1 设备树格式 8.2.1.1 DTS 文件布局 8.2.1.2 node 格式 8.2.1.3 propertie ...

随机推荐

  1. Gradle之FTP文件下载

    Gradle之FTP文件下载 1.背景 项目上需要使用本地web,所以我们直接将web直接放入assets资源文件夹下.但是随着开发进行web包越来越大:所以我们想着从版本库里面去掉web将其忽略掉, ...

  2. selinux disable

    临时关闭: [root@localhost ~]# getenforceEnforcing [root@localhost ~]# setenforce 0[root@localhost ~]# ge ...

  3. 039.[转] 基于 Kubernetes 和 Spring Cloud 的微服务化实践

    http://dockone.io/article/2967 基于 Kubernetes 和 Spring Cloud 的微服务化实践 写在前面 网易云容器平台期望能给实施了微服务架构的团队提供完整的 ...

  4. 【分享】nginx负载均衡全套视频教程

    1.课件 百度网盘链接:https://pan.baidu.com/s/1On2oONVZmPwI9yIDekgRiA        提取码:c4fw 2.教程列表 3.教程下载 3.1.直接在线学习 ...

  5. mysql5.7 Multiple-Column Indexes 多列索引(二)

    场景一: 复合索引的替代方法,对多列字段拼接做hash,引入一个hashed 字段,对此字段添加索引,可以做到复合索引查询速度快,例: SELECT * FROM tbl_name WHERE has ...

  6. python虚拟环境virtualenv下安装MySQL-python(1.2.3)

    该文章很有用建议收藏 我们在Windows下开发python应用时,可能需要安装各种第三方模块,但如果又不想污染公共的python环境,怎么办呢?最好是在各自的 python工程中创建一个virtua ...

  7. Linux系统学习 四、网络基础—互联网概述,互联网接入方式

    互联网概述 WWW:万维网 FTP:文件传输协议 E-MAIL:电子邮件 WWW 典型的C/S架构 URL:统一资源定位 协议+域名或IP:端口+网页路径+网页名 http://www.xxx.com ...

  8. UML工具-1-StarUML下载及破解

    UML工具-StarUML   下载地址   http://staruml.io/  

  9. OMM机制(占位)

    由于没有swap分区,导致系统启动omm机制,把mysql干掉

  10. Java Web 学习(4) —— Spring MVC 概览

    Spring MVC 概览 一. Spring MVC Spring MVC 是一个包含了 Dispatcher Servlet 的 MVC 框架. Dispatcher Servlet 实现了 : ...