DeviceTree(2) - platform_device

1 dtb文件内存管理

start_kernel // init/main.c
	setup_arch // arch/arm/kernel/setup.c
		arm_memblock_init(mdesc); // arch/arm/mm/init.c
			early_init_fdt_reserve_self();
				/* Reserve the dtb region */
				early_init_dt_reserve_memory_arch(__pa(initial_boot_params),fdt_totalsize(initial_boot_params),0);
					memblock_reserve(base, size);

  在引入DTB后,内核会保留DTB占用内存,即使不在dts文件中利用/memreserve/指明区域,内核还是会进行保留

2 设备树的构造

  dts文件中每一个{ }代表一个节点,如树型结构一致,存在父子节点、兄弟节点

start_kernel // init/main.c
	setup_arch(&command_line);  // arch/arm/kernel/setup.c
		unflatten_device_tree(); // drivers/of/fdt.c
			__unflatten_device_tree(initial_boot_params, NULL, &of_root,
									early_init_dt_alloc_memory_arch, false);
			{
				/* First pass, scan for size */
				unflatten_dt_nodes(blob, NULL, dad, NULL); // 扫描节点数量
				/* Allocate memory for the expanded device tree */
				mem = dt_alloc(size + 4, __alignof__(struct device_node));
				/* Second pass, do actual unflattening */
				unflatten_dt_nodes(blob, mem, dad, mynodes); // 真正分配内存
					fpsizes[depth+1] = populate_node(blob, offset, &mem, nps[depth], fpsizes[depth], &nps[depth+1], dryrun);
					{
						np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node));\
						np->full_name = fn = ((char *)np) + sizeof(*np);
						populate_properties(blob, offset, mem, np, pathp, dryrun); // 属性
						{
							pp = unflatten_dt_alloc(mem, sizeof(struct property), __alignof__(struct property));
							pp->name   = (char *)pname;
							pp->length = sz;
							pp->value  = (__be32 *)val;
						}
					}	
			}

  可见整个流程中涉及两个结构:struct device_node与struct property

2.1 device_node

struct device_node { \\ include/linux/of.h
	const char *name; // 此为节点属性name,没有该属性即为NULL
	const char *type; // 此为节点属性device_type,没有该属性即为NULL
	phandle phandle;
	const char *full_name; // 指向节点名字,在整个结构体之后
	struct fwnode_handle fwnode;

	struct	property *properties; // 节点属性
	struct	property *deadprops;	/* removed properties */
	struct	device_node *parent; // 节点父节点
	struct	device_node *child; // 节点子节点
	struct	device_node *sibling; // 节点兄弟节点
	struct	kobject kobj;
	unsigned long _flags;
	void	*data;
#if defined(CONFIG_SPARC)
	const char *path_component_name;
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};

  device_node节点中绑定着自身的property属性

2.2 property

struct property { \\ include/linux/of.h
	char	*name; // 属性名字, 指向dtb文件中的字符串
	int	length; // 属性值的长度
	void	*value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储
	struct property *next;
	unsigned long _flags;
	unsigned int unique_id;
	struct bin_attribute attr;
};

3 device_node转换platform_device

  在总线设备驱动模型未使用设备树情况下,对于一个字符设备而言,会创建platform_driver与platform_device,platform_driver为实际的驱动位置,platform_device来设置相应硬件资源,所以对于一个设备树而言,对于一个节点是否需要创建platform_device结构,在3.3节再进行展开。

start_kernel
	/* Do the rest non-__init'ed, we're now alive */
	rest_init(); \\ init/main.c
		pid = kernel_thread(kernel_init, NULL, CLONE_FS);
			// kernel_init
			kernel_init_freeable();
				do_basic_setup();
					do_initcalls();
						do_initcall_level(level);
						{
							for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
								do_one_initcall(*fn);
						}

这部分代码看似与platform_device转换没有任何关联,一步步分析:
向do_one_initcall函数传入的参数为fn,fn = initcall_levels[level];initcall_levels定义为以下

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

  这一部分涉及linux的initcall机制(针对编译进内核的驱动)

3.1 initcall机制

参考:https://www.cnblogs.com/downey-blog/p/10486653.html
  在接触驱动程序时,通常以module_init()函数接口来启动驱动程序,编译进内核,内核70%以上都是驱动程序,驱动程序如此多,内核启动init程序去访问每一个驱动程序的xxx_init()函数,这并不是一个高效的做法,linux中采取了自定义段方式存放初始化函数地址,通过函数指针取指方式执行。

3.1.1 initcall定义

 在include/linux/init.h文件中定义了以下宏

#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 __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)

#define ___define_initcall(fn, id, __sec) \
	static initcall_t __initcall_##fn##id __used \
		__attribute__((__section__(#__sec ".init"))) = fn;

  __attribute__机制作为GNU C的一大特色,主要用于设置函数、变量、类型的属性,这里表示将目标符号防止在括号指定的段中
#在宏定义里表示将目标字符串化,##表将多个符号连接成一个符号
__used表编译器不能优化该符号,需保留该符号并放置在内核镜像.initcall0.init段处

3.1.2 函数调用

  现在可以继续跟踪do_one_initcall(*fn)函数了,这个函数就是执行所有使用xxx_initcall()声明的函数,fn作为参数传入

typedef int (*initcall_t)(void);

extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];

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

  可见initcall_levels为指针数组,levels递增,可见在存在优先级的驱动程序里是可以让优先级高的先加载的,该函数一共执行8次
根据3.1中的符号规则,分解

  • initcall_levels[level] -> *__initcall##level##_start
  • 取__initcall##level##_start地址的函数指针 = “.initcall##level##.init"段 = xxx_initcall()添加函数
    Q:initcall_levels[level]是怎么链接到”.initcall##level##.init"段的?
    A:在链接脚本中指定了
#define INIT_CALLS_LEVEL(level)						\
		VMLINUX_SYMBOL(__initcall##level##_start) = .;		\
		KEEP(*(.initcall##level##.init))			\
		KEEP(*(.initcall##level##s.init))			\

#define INIT_CALLS							\
		VMLINUX_SYMBOL(__initcall_start) = .;			\
		KEEP(*(.initcallearly.init))				\
		INIT_CALLS_LEVEL(0)					\
		INIT_CALLS_LEVEL(1)					\
		INIT_CALLS_LEVEL(2)					\
		INIT_CALLS_LEVEL(3)					\
		INIT_CALLS_LEVEL(4)					\
		INIT_CALLS_LEVEL(5)					\
		INIT_CALLS_LEVEL(rootfs)				\
		INIT_CALLS_LEVEL(6)					\
		INIT_CALLS_LEVEL(7)					\
		VMLINUX_SYMBOL(__initcall_end) = .;

扩展(想到啥说啥,单纯防止忘了,与本文无关):
加载地址与运行地址:加载地址表示程序加载到哪个地址,运行(链接)地址表示程序在哪里运行
在加载地址≠链接地址时,会碰到程序读取乱码现象,甚至奔溃,主要还是由于代码未重定位,数据信息在加载地址开始的内存,并不在链接地址的内存块中,程序读取时数据会从链接地址开始读,但此时的值并未从加载地址拷贝过来,导致读取错误,重定位表示将数据从加载地址向运行地址拷贝的过程。拷贝的过程由最前面的代码来执行,这也代表着拷贝之前的代码必须是位置无关码

3.2 函数调用

  内核中设备树的初始化在of_platform_default_populate_init函数,以arch_initcall_sync(of_platform_default_populate_init);加载

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);

  在of_platform_device_create中递归处理每个节点

of_platform_default_populate_init
{
	of_platform_default_populate
	{
		of_platform_populate(root, of_default_bus_match_table, lookup, parent);
		{
			root = root ? of_node_get(root) : of_find_node_by_path("/"); // 获取根节点
			for_each_child_of_node(root, child) 
			{ // 遍历所有的子节点
				rc = of_platform_bus_create(child, matches, lookup, parent, true); // 默认为总线节点进行处理
				{
					if (strict && (!of_get_property(bus, "compatible", NULL))) // 确定节点包含compatible属性
					of_platform_device_create_pdata(bus, bus_id, platform_data, parent); // 填充platform_device结构
					{
						of_device_alloc(np, bus_id, parent);
						{
							// 分配platform_device
							dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
							/* count the io and irq resources */
							of_address_to_resource(np, num_reg, &temp_res) // IO与IRQ资源处理
								of_find_device_by_node(node)
							of_irq_count(np)
							
							kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL)
							
							// 设置具体资源描述
							dev->num_resources = num_reg + num_irq;
							dev->resource = res;
							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);
							
						}
						dev->dev.bus = &platform_bus_type;
						dev->dev.platform_data = platform_data;
						of_device_add(dev)
							device_add(&ofdev->dev)
					}
					for_each_child_of_node(bus, child) // 为子节点匹配of_default_bus_match_table,进行递归处理
					{
						rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
						if (rc) of_node_put(child);
					}
				}
			}
			of_node_set_flag(root, OF_POPULATED_BUS); // 更新标志
			of_node_put(root);
		}
	}
}

  从of_platform_default_populate看匹配过程,发现传入了of_default_bus_match_table,原型为

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

  可见处理完根节点下包含compatible属性的子节点后,会根据of_default_bus_match_table进一步判断是否需要进一步处理该子节点下的下一级node

3.3 设备节点转换

  对于总线设备而言,节点下包含具体硬件节点,这些挂载的硬件节点如何处理由platform_driver中的.probe函数决定

i2c { // 当前节点包含platform_device <--> platform_driver(.probe处理子节点at24c02)
		compatile = "samsung,i2c";
		at24c02 { // 总线子节点不会被转换当前节点包含platform_device,对于I2C转换为I2C_Client,SPI会转换为spi_device
            compatile = "at24c02";                      
        };
};

  根据之前函数调用流程可知,of_platform_default_populate_init函数会遍历device_node树来生成platform_device,但并非所有节点都会转化为oplatform_device,大致有以下规则:

  • 该节点必须含有compatible属性
  • 根节点的子节点(节点必须含有compatible属性)
  • 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性):这些特殊的compatilbe属性为:“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”
      总结起来就是:kernel为DTB中所有包含compatible的第一级node创建platform_device,并向平台设备总线注册。若第一级node的compatible属性为"simple-bus",“simple-mfd”,“isa”,“arm,amba-bus”,则为当前node的包含compatible属性的第二级node创建platform_device,所有子节点都会注册到该总线上。

例:
(1)I2C总线设备
  platform_device匹配到platform_driver之后会调用probe结构体,结合https://www.linuxidc.com/Linux/2017-03/142201.htm 可以确定在创建I2C设备时是通过i2c_add_numbered_adapter添加适配器的

s3c24xx_i2c_probe
	i2c_add_numbered_adapter(&i2c->adap) // drivers/i2c/i2c-core-base.c
		__i2c_add_numbered_adapter(adap)
			i2c_register_adapter(adap)
				of_i2c_register_devices(adap) // drivers/i2c/i2c-core-of.c
				{
					bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");
					if (!bus)
						bus = of_node_get(adap->dev.of_node);

					for_each_available_child_of_node(bus, node) {
						if (of_node_test_and_set_flag(node, OF_POPULATED))
							continue;

						client = of_i2c_register_device(adap, node);
						if (IS_ERR(client)) {
							dev_warn(&adap->dev,
								 "Failed to create I2C device for %pOF\n",
								 node);
							of_node_clear_flag(node, OF_POPULATED);
						}
					}

					of_node_put(bus);
				}

  进一步展开of_i2c_register_device

of_i2c_register_device // drivers/i2c/i2c-core-of.c
	of_modalias_node(node, info.type, sizeof(info.type)) // drivers/of/base.c
		compatible = of_get_property(node, "compatible", &cplen);
	addr_be = of_get_property(node, "reg", &len);
	addr = be32_to_cpup(addr_be);
	of_property_read_bool(node, "host-notify")
	result = i2c_new_device(adap, &info);

(2)SPI总线设备
  该部分包括I2C部分,后续树莓派系列文章上结合内核及以前的学习再深入分析

stm32_spi_probe
	devm_spi_register_master(&pdev->dev, master) = spi_register_controller(_ctlr) // drivers/spi/spi.c
		of_register_spi_devices(ctlr)
			for_each_available_child_of_node(ctlr->dev.of_node, nc) 
			{
				if (of_node_test_and_set_flag(nc, OF_POPULATED))
					continue;
				spi = of_register_spi_device(ctlr, nc);
				if (IS_ERR(spi)) {
					dev_warn(&ctlr->dev,
						 "Failed to create SPI device for %pOF\n", nc);
					of_node_clear_flag(nc, OF_POPULATED);
				}
			}
		

4 驱动匹配过程

  总线设备驱动模型采取了分离机制,将硬件相对稳定的东西分离出来,形成dev-bus-drv模型。将dev、drv分别放入bus管理的链表中,以bus提供的match函数进行匹配。涉及设备树的匹配结合代码来看:

struct bus_type platform_bus_type = { // drivers/base/platform.c
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match, // 匹配函数
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

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_device.driver_override 与 platform_driver.driver.name

strcmp(pdev->driver_override, drv->name)

② platform_dev.dev.of_node的compatible属性 与 platform_driver.drv->of_match_table

of_driver_match_device(dev, drv)
	of_match_device(drv->of_match_table, dev)
		of_match_node(matches, dev->of_node)
			__of_match_node(matches, node)
				__of_device_is_compatible(node, matches->compatible, matches->type, matches->name);
				{
					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;
						}
					}
				}

③ platform_dev.name 与 platform_driver.id_table

platform_match_id(pdrv->id_table, pdev)
	strcmp(pdev->name, id->name

④ platform_dev.name 与 platform_driver.drv->name

strcmp(pdev->name, drv->name

附:几个结构体

struct platform_device {
	const char	*name; // 驱动名称
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources; // 资源数组长度
	struct resource	*resource; // 资源数组,IO与中断资源

	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */

	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;
	bool prevent_deferred_probe;
};

struct device_driver {
	const char		*name;
	struct bus_type		*bus; // 定义连接总线

	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */

	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
	enum probe_type probe_type;

	const struct of_device_id	*of_match_table; // 匹配表
	const struct acpi_device_id	*acpi_match_table;

	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;

	const struct dev_pm_ops *pm;

	struct driver_private *p;
};

附:设备中查看dtb
a. /sys/firmware/fdt
  原始dtb文件
b. /sys/firmware/devicetree
  目录结构dtb文件
c. /sys/devices/platform
  设备树或传统注册的platform_device,设备树看是否存在of_node
d. /proc/device-tree
  链接文件

参考:https://blog.csdn.net/thisway_diy/article/details/84336817

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值