dts展开为platform_device结构过程分析

dts展开为platform_device结构过程分析dts节点展开为platform_device结构过程分析1.概述本文主要是记录学习Linux解析dts的代码分析,以便进行后续回顾。平台:ARMVexpress内核版本:linux-4.92.dts节点展开为platform_device结构过程分析自从ARM引入的dts之后,bsp驱动代码产生了非常之大的变化,像在linux-2.6.32这些版本的platform驱动中,会存在大…

大家好,又见面了,我是你们的朋友全栈君。

dts节点展开为platform_device结构过程分析

1.概述

本文主要是记录学习Linux解析dts的代码分析,以便进行后续回顾。

平台:ARM Vexpress

内核版本:linux-4.9

2.dts节点展开为platform_device结构过程分析

自从ARM引入的dts之后,bsp驱动代码产生了非常之大的变化,像在linux-2.6.32这些版本的platform驱动中,会存在大量类似一下的代码:

	static struct resource xxx_resources[] = { 
   
	        [0] = { 
   
	                .start  =,
	                .end    =,
	                .flags  = IORESOURCE_MEM,
	        },
	        [1] = { 
   
	                .start  =,
	                .end    =,
	                .flags  = IORESOURCE_IRQ,
	         },
	 };

	 static struct platform_device xxx_device = { 
   
	         .name           = "xxx",
	         .id             = -1,
	         .dev            = { 
   
	                                 .platform_data          = &xxx_data,
	         },
	         .resource       = xxx_resources,
	         .num_resources  = ARRAY_SIZE(xxx_resources),
	};

但是通过dts,像上述的代码不再需要我们程序员进行手动配置,只需在dts相应的节点通过reg、interrupt等属性的配置,就可以通过内核提供的解析dts的接口把dts中的节点信息展开为platform_device,然后把reg、interrupt等属性的信息填充到struct resource资源结构体中,有效的减少了我们驱动代码的冗余,做到配置简单,易读。以下就是通过分析代码,了解linux是如何把dts节点信息展开为struct platform_device结构体的过程。

将dts节点展开为struct platform_device结构体的过程主要是交给of_platform_populate()函数完成,通过对该函数使用dump_stack()回溯其调用过程可以得到以下log:

	futex hash table entries: 1024 (order: 4, 65536 bytes)
	NET: Registered protocol family 16
	DMA: preallocated 256 KiB pool for atomic coherent allocations
	CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.9.56+ #7
	Hardware name: ARM-Versatile Express
	[<80111158>] (unwind_backtrace) from [<8010ca80>] (show_stack+0x20/0x24)
	[<8010ca80>] (show_stack) from [<803dd1b4>] (dump_stack+0xac/0xd8)
	[<803dd1b4>] (dump_stack) from [<80586318>] (of_platform_populate+0x30/0xbc)
	[<80586318>] (of_platform_populate) from [<80928410>] (vexpress_config_init+0xb8/0xec)
	[<80928410>] (vexpress_config_init) from [<80101c9c>] (do_one_initcall+0x54/0x184)
	[<80101c9c>] (do_one_initcall) from [<80900f48>] (kernel_init_freeable+0x214/0x2a8)
	[<80900f48>] (kernel_init_freeable) from [<806a4a94>] (kernel_init+0x18/0x124)
	[<806a4a94>] (kernel_init) from [<80108190>] (ret_from_fork+0x14/0x24)
	CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.9.56+ #7
	Hardware name: ARM-Versatile Express
	[<80111158>] (unwind_backtrace) from [<8010ca80>] (show_stack+0x20/0x24)
	[<8010ca80>] (show_stack) from [<803dd1b4>] (dump_stack+0xac/0xd8)
	[<803dd1b4>] (dump_stack) from [<80586318>] (of_platform_populate+0x30/0xbc)
	[<80586318>] (of_platform_populate) from [<80928410>] (vexpress_config_init+0xb8/0xec)
	[<80928410>] (vexpress_config_init) from [<80101c9c>] (do_one_initcall+0x54/0x184)
	[<80101c9c>] (do_one_initcall) from [<80900f48>] (kernel_init_freeable+0x214/0x2a8)
	[<80900f48>] (kernel_init_freeable) from [<806a4a94>] (kernel_init+0x18/0x124)
	[<806a4a94>] (kernel_init) from [<80108190>] (ret_from_fork+0x14/0x24)
	cpuidle: using governor ladder

通过栈回溯信息,我们可以知道of_platform_populate()函数的最早调用入口是vexpress_config_init()函数,除此之外,在of_platform_default_populate()函数中也会调用该函数,分别如下所示

vexpress_config_init()函数是定义在drivers/bus/vexpress-config.c文件中,该函数实现如下:

	static int __init vexpress_config_init(void)
	{ 
   
		int err = 0;
		struct device_node *node;

		/* Need the config devices early, before the "normal" devices... */
		for_each_compatible_node(node, NULL, "arm,vexpress,config-bus") { 
   
			err = vexpress_config_populate(node);
			if (err) { 
   
				of_node_put(node);
				break;
			}
		}

		return err;
	}
	postcore_initcall(vexpress_config_init);

  //另外一个函数入口
  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);
  }
  EXPORT_SYMBOL_GPL(of_platform_default_populate);

  #ifndef CONFIG_PPC
  static int __init of_platform_default_populate_init(void)
  { 
   
  	struct device_node *node;

  	if (!of_have_populated_dt())
  		return -ENODEV;

  	/* * Handle ramoops explicitly, since it is inside /reserved-memory, * which lacks a "compatible" property. */
  	node = of_find_node_by_path("/reserved-memory");
  	if (node) { 
   
  		node = of_find_compatible_node(node, NULL, "ramoops");
  		if (node)
  			of_platform_device_create(node, NULL, NULL);
  	}

  	/* Populate everything else. */
  	of_platform_default_populate(NULL, NULL, NULL);

  	return 0;
  }
  arch_initcall_sync(of_platform_default_populate_init);
  #endif

首先,通过postcore_initcall和arch_initcall_sync这些宏声明该platform driver的调用等级,当内核执行到以下调用时,会调用到该函数:

	start_kernel(void)
		->rest_init()
			->kernel_init()
				->kernel_init_freeable()
					->do_basic_setup()
						->do_initcalls()
							->do_initcall_level()
								->do_one_initcall()

在do_initcalls()函数中,会从0遍历到ARRAY_SIZE(initcall_levels) – 1,代码如下:

	static void __init do_initcalls(void)
	{ 
   
		int level;

		for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
			do_initcall_level(level);
	}

其中initcall_levels是一个指针数组,里面存放着各个level下的函数指针的首地址,该数组定义在init/main.c中,如下所示:

	static initcall_t *initcall_levels[] __initdata = { 
   
		__initcall0_start,
		__initcall1_start,
		__initcall2_start,
		__initcall3_start,
		__initcall4_start,
		__initcall5_start,
		__initcall6_start,
		__initcall7_start,
		__initcall_end,
	};

因此在do_initcalls()函数中,会从level=0遍历到level=8,每个driver的调用顺序可以通过以下宏来声明:

	#define pure_initcall(fn) __define_initcall(fn, 0)
	#define core_initcall(fn) __define_initcall(fn, 1)
	#define core_initcall_sync(fn) __define_initcall(fn, 1s)
	#define postcore_initcall(fn) __define_initcall(fn, 2)
	#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)
	#define arch_initcall(fn) __define_initcall(fn, 3)
	#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
	#define subsys_initcall(fn) __define_initcall(fn, 4)
	#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)
	#define fs_initcall(fn) __define_initcall(fn, 5)
	#define fs_initcall_sync(fn) __define_initcall(fn, 5s)
	#define rootfs_initcall(fn) __define_initcall(fn, rootfs)
	#define device_initcall(fn) __define_initcall(fn, 6)
	#define device_initcall_sync(fn) __define_initcall(fn, 6s)
	#define late_initcall(fn) __define_initcall(fn, 7)
	#define late_initcall_sync(fn) __define_initcall(fn, 7s)

	#define __initcall(fn) device_initcall(fn)

以上这些宏都定义在include/linux/init.h文件中。从这里可以看出postcore_initcall宏声明的driver初始化函数的调用level为2。

了解到如何调用到of_platform_default_populate()函数的过程,我们再来看看of_platform_populate()函数如何把一个dts节点展开struct platform_device结构的,分析如下:

/** * of_platform_populate() - Populate platform_devices from device tree data * @root: parent of the first level to probe or NULL for the root of the tree * @matches: match table, NULL to use the default * @lookup: auxdata table for matching id and platform_data with device nodes * @parent: parent to hook devices from, NULL for toplevel * * Similar to of_platform_bus_probe(), this function walks the device tree * and creates devices from nodes. It differs in that it follows the modern * convention of requiring all device nodes to have a 'compatible' property, * and it is suitable for creating devices which are children of the root * node (of_platform_bus_probe will only create children of the root which * are selected by the @matches argument). * * New board support should be using this function instead of * of_platform_bus_probe(). * * Returns 0 on success, < 0 on failure. */
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: %s\n", root->full_name);

	for_each_child_of_node(root, child) { 
   
    /* 1.遍历root节点下的所有子节点,调用of_platform_bus_create() */
		rc = of_platform_bus_create(child, matches, lookup, parent, true);
		if (rc) { 
   
			of_node_put(child);
			break;
		}
	}
	of_node_set_flag(root, OF_POPULATED_BUS);

	of_node_put(root);
	return rc;
}
EXPORT_SYMBOL_GPL(of_platform_populate);

通常root节点都是一些特定的总线节点,如platform device的simple-bus,如下所示:

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 */
};

dts对应节点如下:

smb@04000000 { 
   
  compatible = "simple-bus";

  #address-cells = <2>;
  #size-cells = <1>;
  ranges = <0 0 0x40000000 0x04000000>,
     <1 0 0x44000000 0x04000000>,
     <2 0 0x48000000 0x04000000>,
     <3 0 0x4c000000 0x04000000>,
     <7 0 0x10000000 0x00020000>;

  ...
}

还有arm vexpress的config-bus,dts节点如下:

dcc { 
   
  compatible = "arm,vexpress,config-bus";
  arm,vexpress,config-bridge = <&v2m_sysreg>;

  oscclk0: extsaxiclk { 
   
    /* ACLK clock to the AXI master port on the test chip */
    compatible = "arm,vexpress-osc";
    arm,vexpress-sysreg,func = <1 0>;
    freq-range = <30000000 50000000>;
    #clock-cells = <0>;
    clock-output-names = "extsaxiclk";
  };
  ...
}

在of_platform_bus_create()函数中,会根据节点的compatible属性进行相应的设备创建,代码分析如下:

/** * of_platform_bus_create() - Create a device for a node and its children. * @bus: device node of the bus to instantiate * @matches: match table for bus nodes * @lookup: auxdata table for matching id and platform_data with device nodes * @parent: parent for new device, or NULL for top level. * @strict: require compatible property * * Creates a platform_device for the provided device_node, and optionally * recursively create devices for all the child nodes. */
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;

	/* 1.检查节点是否具备compatible属性,没有,则返回0 */
	if (strict && (!of_get_property(bus, "compatible", NULL))) { 
   
		pr_debug("%s() - skipping %s, no compatible prop\n",
			 __func__, bus->full_name);
		return 0;
	}

  /* 2.检查节点是否已经解析过,是,则返回0 */
	if (of_node_check_flag(bus, OF_POPULATED_BUS)) { 
   
		pr_debug("%s() - skipping %s, already populated\n",
			__func__, bus->full_name);
		return 0;
	}

	auxdata = of_dev_lookup(lookup, bus);
	if (auxdata) { 
   
		bus_id = auxdata->name;
		platform_data = auxdata->platform_data;
	}

  /* 3.判断是否是amba device,如果是,则创建该类型设备 */
	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;
	}

  /* 4.创建platform device */
	dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
	if (!dev || !of_match_node(matches, bus))
		return 0;

  /* 5.如果该节点为总线节点,则遍历该节点下的所有子节点,并创建相应的设备 */
	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_platform_device_create_pdata - Alloc, initialize and register an of_device * @np: pointer to node to create device for * @bus_id: name to assign device * @platform_data: pointer to populate platform_data pointer with * @parent: Linux device model parent device. * * Returns pointer to created platform device, or NULL if a device was not * registered. Unavailable devices will not get registered. */
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;

  /* 1.检查节点status属性值是否为ok或okay,不等,则返回false, * 2.status属性不存在,返回true */
	if (!of_device_is_available(np) ||
	    of_node_test_and_set_flag(np, OF_POPULATED))
		return NULL;

  /* 2.分配struct platform_device结构体 * 3.解析dts mem/irq等资源信息,填充到设备结构中 */
	dev = of_device_alloc(np, bus_id, parent);
	if (!dev)
		goto err_clear_flag;

  /* 4.初始化设备bus类型,用于驱动probe */
	dev->dev.bus = &platform_bus_type;
	dev->dev.platform_data = platform_data;
	of_dma_configure(&dev->dev, dev->dev.of_node);
	of_msi_configure(&dev->dev, dev->dev.of_node);

  /* 5.添加到设备链表,至此,设备创建完成 */
	if (of_device_add(dev) != 0) { 
   
		of_dma_deconfigure(&dev->dev);
		platform_device_put(dev);
		goto err_clear_flag;
	}

	return dev;

err_clear_flag:
	of_node_clear_flag(np, OF_POPULATED);
	return NULL;
}

至此,设备创建完成,可以等待驱动的probe

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/163821.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 详解Linux双网卡绑定之bond0「建议收藏」

    1、什么是bond?  网卡bond是通过多张网卡绑定为一个逻辑网卡,实现本地网卡的冗余,带宽扩容和负载均衡,在生产场景中是一种常用的技术。Kernels2.4.12及以后的版本均供bonding模块,以前的版本可以通过patch实现。2、实现原理:  网卡工作在混杂(promisc)模式,接收到达网卡的所有数据包,tcpdump工作用的也是混杂模式(promisc),将两块网卡的MAC地址…

  • java 数组转换_java数组转json

    java 数组转换_java数组转json 1.Arrays.asList坑点说明在开发中,我们有时候会需要将数组转换为集合List,这时候可能会想到Arrays.asList(),毕竟它是java提供的,肯定专业。。。吗?Integer[]a={1,2,3};List<Integer>list=Arrays.asList(a);System.out.println(list);但是实际上这里面有个大坑,如果不熟悉很容易GG。就是它转换成的其实是个“假List”,为什么这么说呢,因为它返回的其实

  • FFmpeg的H.264解码器源代码简单分析:概述「建议收藏」

    FFmpeg的H.264解码器源代码简单分析:概述「建议收藏」本文简单记录FFmpeg中libavcodec的H.264解码器(H.264Decoder)的源代码。这个H.264解码器十分重要,可以说FFmpeg项目今天可以几乎“垄断”视音频编解码技术,很大一部分贡献就来自于这个H.264解码器。这个H.264解码器一方面功能强大,性能稳定;另一方面源代码也比较复杂,难以深入研究。本文打算梳理一下这个H.264解码器的源代码结构,以方便以后深入学习H.264使用。

  • Java集合面试题[通俗易懂]

    Java集合面试题Java集合框架的基础接口有哪些?Collection,为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java平台不提供这个接口任何直接的实现。Set,是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。List,是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List更像长度动态…

  • linux vim用不了(排位老是遇到坑怎么办)

    这里写自定义目录标题Vim是Linux常用的文本编辑器新的改变插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML图表FLowchart流程图导出与导入导出导入Vim是Linux常用的文本编辑器1、 输入命令vi文件名,进…

  • 关于cBridge2.0,你不能错过的关键信息(三)!

    关于cBridge2.0,你不能错过的关键信息(三)!上篇ELI5短文中我们讨论了cBridge2.0如何解决「自管」流动性模型中与状态守卫者网络(SGN)的协调问题。今天我们来继续讨论自管模型的最后一个话题:cBridge2.0的设计如何利用SGN为非托管桥接系统中的「作恶问题」提供有史以来第一个解决方案。那么什么是「作恶」呢?首先我们要知道,在cBridge2.0自管桥接模型中,对于桥接节点和用户来说,跨链交易的发生总是需要两个步骤,其顺序如下: 用户的第1步:向源链上的桥接节点进行「时间锁定」的转账,发起转账的用户…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号