Linux设备树10(完结篇)

Linux设备树10(基于Linux6.6)---设备树文件结构解析以及总结


一、 设备树头信息


Linux内核中使用struct fdt_header结构体描述。struct fdt_header结构体定义在scripts\dtc\libfdt\fdt.h文件中。

struct fdt_header {
	fdt32_t magic;			     /* magic word FDT_MAGIC */
	fdt32_t totalsize;		     /* total size of DT block */
	fdt32_t off_dt_struct;		 /* offset to structure */
	fdt32_t off_dt_strings;		 /* offset to strings */
	fdt32_t off_mem_rsvmap;		 /* offset to memory reserve map */
	fdt32_t version;		         /* format version */
	fdt32_t last_comp_version;	 /* last compatible version */
 
	/* version 2 fields below */
	fdt32_t boot_cpuid_phys;	 /* Which physical CPU id we're booting on */
	/* version 3 fields below */
	fdt32_t size_dt_strings;	 /* size of the strings block */
 
	/* version 17 fields below */
	fdt32_t size_dt_struct;		 /* size of the structure block */
};

hexdump  -C -n 1000 rk3568-evb1-ddr4-v10-linux.dtb

设备树中的节点信息举例如下图所示。

上述.dts文件并没有什么真实的用途,但它基本表征了一个设备树源文件的结构:

设备树源文件的结构

一个典型的Linux设备树源文件(DTS)结构大致如下:

dts

/dts-v1/;
/ {
    model = "Generic ARM System";
    compatible = "generic-arm-system";

    / {
        #address-cells = <1>;
        #size-cells = <1>;

        cpu@0 {
            compatible = "arm,cortex-a57";
            reg = <0x0>;
            device_type = "cpu";
        };

        memory@80000000 {
            compatible = "ram";
            reg = <0x80000000 0x20000000>; /* Start address and size */
        };

        interrupt-controller@f0010000 {
            compatible = "arm,pl390";
            interrupt-controller;
            #interrupt-cells = <3>;
            reg = <0xf0010000 0x1000>;
        };

        gpio {
            compatible = "gpio";
            gpio-controller;
            #gpio-cells = <2>;
            reg = <0x1000 0x100>;
        };

        uart@101f1000 {
            compatible = "arm,pl011";
            reg = <0x101f1000 0x1000>;
            interrupts = <0 29 4>;
            clock-frequency = <115200>;
        };
    };
};

关键组成部分

1. 文件头

dts

/dts-v1/;
  • 这是设备树源文件的版本声明。它指示设备树的版本信息,/dts-v1/ 是设备树源文件的标准版本。
2. 根节点(Root Node)

设备树的根节点通常用一个单独的 / 来表示,它是整个树的根基,包含所有硬件描述信息。

dts

/ {
    model = "Generic ARM System";
    compatible = "generic-arm-system";
};
  • modelcompatible 属性描述了设备树的目标平台信息,通常用来匹配设备树与内核的兼容性。
3. 子节点(Child Nodes)

根节点下面会有多个子节点,这些节点描述了具体的硬件设备。每个设备的节点都包含了该设备的相关信息,如配置、地址、大小等。

  • 节点名称是设备的标识符(如 cpu@0, memory@80000000 等),这些名称通常由硬件架构的规范或设备的硬件地址决定。

例如:

dts

cpu@0 {
    compatible = "arm,cortex-a57";
    reg = <0x0>;
    device_type = "cpu";
};
  • cpu@0 这个节点描述了一个CPU设备。
  • compatible 表示设备的类型或品牌。
  • reg 是设备的硬件地址。
4. 属性(Properties)

每个节点下可以有多个属性,属性用来描述设备的详细信息。

  • 字符串属性:如 compatiblemodel
  • 数值属性:如 reg(表示设备的地址和大小),interrupts(表示中断信息)。
  • 位域属性:如 #address-cells#size-cells(描述节点中的地址和大小的单元数)。

例如:

dts

memory@80000000 {
    compatible = "ram";
    reg = <0x80000000 0x20000000>; /* Start address and size */
};
  • compatible 指示该节点描述的是一个内存(RAM)。
  • reg 指定了内存的起始地址和大小。
5. 设备的中断描述

设备树允许通过 interrupts 属性来描述设备的中断信息。例如:

dts

uart@101f1000 {
    compatible = "arm,pl011";
    reg = <0x101f1000 0x1000>;
    interrupts = <0 29 4>;
    clock-frequency = <115200>;
};
  • interrupts 指定该设备的中断号和相关信息(例如,0 29 4 表示中断号和中断触发模式)。
  • clock-frequency 表示设备的时钟频率。
6. 设备树中的特殊符号
  • &符号:设备树源文件中的 & 符号用于引用其他节点的地址或资源。通常用于在其他地方引用共享的资源。 例如:
    dts
    
  • uart: serial@101f1000 {
        compatible = "arm,pl011";
        reg = <0x101f1000 0x1000>;
        interrupts = <0 29 4>;
        clocks = <&uart_clk>;
    };
    
7. 节点中的 #address-cells#size-cells

这些属性用于定义节点下描述地址和大小的单元数。

  • #address-cells = <1> 指定该节点下地址属性使用一个单元来描述。
  • #size-cells = <1> 指定该节点下大小属性也使用一个单元来描述。
8. 设备树的“兼容性”
  • compatible 是一个非常重要的属性,它用于描述设备与驱动程序的兼容性。内核通过该字段来确定是否应该加载适当的驱动。

示例分析

dts

cpu@0 {
    compatible = "arm,cortex-a57";
    reg = <0x0>;
    device_type = "cpu";
};
  • 该节点描述了一个 cpu 设备,硬件地址是 0x0,它是 arm,cortex-a57 架构的处理器。

dts

memory@80000000 {
    compatible = "ram";
    reg = <0x80000000 0x20000000>; /* Start address and size */
};
  • 该节点描述了内存(RAM)设备,起始地址是 0x80000000,大小是 0x20000000

设备树源文件的基本语法

  • 节点:由节点名称(如 cpu@0)和大括号包围的内容组成。每个节点描述一个硬件设备。
  • 属性:节点内的属性键值对,如 compatible = "arm,cortex-a57"
  • 引用:节点中的引用(如 &)用来访问其他节点或资源。

二、 设备树文件结构

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

设备树源文件的结构分为header、fill_area、dt_struct及dt_string四个区域。fill_area区域填充数值0。节点(node)信息使用struct fdt_node_header结构体描述。属性信息使用struct fdt_property结构体描述。各个结构体信息如下:scripts/dtc/libfdt/fdt.h

struct fdt_node_header {
	fdt32_t tag;
	char name[];
};

struct fdt_property {
	fdt32_t tag;
	fdt32_t len;
	fdt32_t nameoff;
	char data[];
};

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

#define FDT_BEGIN_NODE	0x1		/* Start node: full name */
#define FDT_END_NODE	0x2		/* End node */
#define FDT_PROP	      0x3		/* Property: name off, size, content */
#define FDT_NOP		0x4		/* nop */
#define FDT_END		0x9

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

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

例如:

compatible = "rockchip,rk3568-evb1-ddr4-v10", "rockchip,rk3568";

compatible是属性名称, "rockchip,rk3568-evb1-ddr4-v10", "rockchip,rk3568"是属性值。

compatible属性名称字符串存放的区域是dt_string。

"rockchip,rk3568-evb1-ddr4-v10", "rockchip,rk3568"存放的位置是fdt_property.data后面。

因此fdt_property.data指向该属性值。

fdt_property.tag的值为属性标识,len为属性值的长度(包括‘\0’,单位:字节),此处len = 29。

nameoff为compatible字符串的位置相对于off_dt_strings的偏移地址,即&compatible = nameoff + off_dt_strings。 

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

三、 kernel解析设备树


设备树文件结构描述就以上struct fdt_header、struct fdt_node_header及struct fdt_property三个结构体描述。kernel会根据Device Tree的结构解析出kernel能够使用的struct property结构体。kernel根据Device Tree中所有的属性解析出数据填充struct property结构体。struct property结构体描述如下: include/linux/of.h

struct property {
	char	*name;
	int	length;
	void	*value;
	struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
	unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
	unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
	struct bin_attribute attr;
#endif
};

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


kernel中究竟是如何解析设备树的呢?下面分析函数解析过程。函数调用过程如下图所示。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保存了系统内存相关的信息。

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

 include/linux/of.h

struct device_node {
	const char *name;              /* node的名称,取最后一次“/”和“@”之间子串 */
	const char *type;              /* device_type的属性名称,没有为<NULL> */
	phandle phandle;               /* phandle属性值 */
	const char *full_name;        /* 指向该结构体结束的位置,存放node的路径全名,例如:/chosen */
	struct fwnode_handle fwnode;
 
	struct	property *properties;  /* 指向该节点下的第一个属性,其他属性与该属性链表相接 */
	struct	property *deadprops;   /* removed properties */
	struct	device_node *parent;   /* 父节点 */
	struct	device_node *child;    /* 子节点 */
	struct	device_node *sibling;  /* 姊妹节点,与自己同等级的node */
	struct	kobject kobj;            /* sysfs文件系统目录体现 */
	unsigned long _flags;          /* 当前node状态标志位,见/include/linux/of.h #if 
	void	*data;
defined(CONFIG_SPARC)
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};
 
/* flag descriptions (need to be visible even when !CONFIG_OF) */
#define OF_DYNAMIC        1 /* node and properties were allocated via kmalloc */
#define OF_DETACHED       2 /* node has been detached from the device tree*/
#define OF_POPULATED      3 /* device already created for the node */
#define OF_POPULATED_BUS 4 /* of_platform_populate recursed to children of this node */

struct device_node结构体中的每个成员作用已经备注了注释信息,下面分析以上信息是如何得来的。设备树的解析首先从unflatten_device_tree()开始,代码列出如下:drivers/of/fdt.c

/**
 * unflatten_device_tree - create tree of device_nodes from flat blob
 *
 * unflattens the device-tree passed by the firmware, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used.
 */
void __init unflatten_device_tree(void)
{
	__unflatten_device_tree(initial_boot_params, NULL, &of_root,
				early_init_dt_alloc_memory_arch, false);

	/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
	of_alias_scan(early_init_dt_alloc_memory_arch);

	unittest_unflatten_overlay_base();
}

 
/**
 * __unflatten_device_tree - create tree of device_nodes from flat blob
 *
 * unflattens a device-tree, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used.
 * @blob: The blob to expand
 * @mynodes: The device_node tree created by the call
 * @dt_alloc: An allocator that provides a virtual address to memory
 * for the resulting tree
 */
void *__unflatten_device_tree(const void *blob,
			      struct device_node *dad,
			      struct device_node **mynodes,
			      void *(*dt_alloc)(u64 size, u64 align),
			      bool detached)
{
	int size;
	void *mem;
	int ret;

	if (mynodes)
		*mynodes = NULL;

	pr_debug(" -> unflatten_device_tree()\n");

	if (!blob) {
		pr_debug("No device tree pointer\n");
		return NULL;
	}

	pr_debug("Unflattening device tree:\n");
	pr_debug("magic: %08x\n", fdt_magic(blob));
	pr_debug("size: %08x\n", fdt_totalsize(blob));
	pr_debug("version: %08x\n", fdt_version(blob));

	if (fdt_check_header(blob)) {
		pr_err("Invalid device tree blob header\n");
		return NULL;
	}

	/* First pass, scan for size */
	size = unflatten_dt_nodes(blob, NULL, dad, NULL);
	if (size <= 0)
		return NULL;

	size = ALIGN(size, 4);
	pr_debug("  size is %d, allocating...\n", size);

	/* Allocate memory for the expanded device tree */
	mem = dt_alloc(size + 4, __alignof__(struct device_node));
	if (!mem)
		return NULL;

	memset(mem, 0, size);

	*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);

	pr_debug("  unflattening %p...\n", mem);

	/* Second pass, do actual unflattening */
	ret = unflatten_dt_nodes(blob, mem, dad, mynodes);

	if (be32_to_cpup(mem + size) != 0xdeadbeef)
		pr_warn("End of tree marker overwritten: %08x\n",
			be32_to_cpup(mem + size));

	if (ret <= 0)
		return NULL;

	if (detached && mynodes && *mynodes) {
		of_node_set_flag(*mynodes, OF_DETACHED);
		pr_debug("unflattened tree is detached\n");
	}

	pr_debug(" <- unflatten_device_tree()\n");
	return mem;
}

分析以上代码,在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()函数,第一次是为了得到设备树转换成struct device_node和struct property结构体需要分配的内存大小,第二次调用才是具体填充每一个struct device_node和struct property结构体。unflatten_dt_node()代码列出如下:drivers/of/fdt.c

/**
 * unflatten_dt_nodes - Alloc and populate a device_node from the flat tree
 * @blob: The parent device tree blob
 * @mem: Memory chunk to use for allocating device nodes and properties
 * @dad: Parent struct device_node
 * @nodepp: The device_node tree created by the call
 *
 * Return: The size of unflattened device tree or error code
 */
static int unflatten_dt_nodes(const void *blob,
			      void *mem,
			      struct device_node *dad,
			      struct device_node **nodepp)
{
	struct device_node *root;
	int offset = 0, depth = 0, initial_depth = 0;
#define FDT_MAX_DEPTH	64
	struct device_node *nps[FDT_MAX_DEPTH];
	void *base = mem;
	bool dryrun = !base;
	int ret;

	if (nodepp)
		*nodepp = NULL;

	/*
	 * We're unflattening device sub-tree if @dad is valid. There are
	 * possibly multiple nodes in the first level of depth. We need
	 * set @depth to 1 to make fdt_next_node() happy as it bails
	 * immediately when negative @depth is found. Otherwise, the device
	 * nodes except the first one won't be unflattened successfully.
	 */
	if (dad)
		depth = initial_depth = 1;

	root = dad;
	nps[depth] = dad;

	for (offset = 0;
	     offset >= 0 && depth >= initial_depth;
	     offset = fdt_next_node(blob, offset, &depth)) {
		if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH - 1))
			continue;

		if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
		    !of_fdt_device_is_available(blob, offset))
			continue;

		ret = populate_node(blob, offset, &mem, nps[depth],
				   &nps[depth+1], dryrun);
		if (ret < 0)
			return ret;

		if (!dryrun && nodepp && !*nodepp)
			*nodepp = nps[depth+1];
		if (!dryrun && !root)
			root = nps[depth+1];
	}

	if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
		pr_err("Error %d processing FDT\n", offset);
		return -EINVAL;
	}

	/*
	 * Reverse the child list. Some drivers assumes node order matches .dts
	 * node order
	 */
	if (!dryrun)
		reverse_nodes(root);

	return mem - base;
}

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


四、 platform_device和device_node绑定


经过以上解析,设备树的数据已经全部解析出具体的struct device_node和struct property结构体,下面需要和具体的device进行绑定。首先讲解platform_device和device_node的绑定过程。在arch/arm/kernel/setup.c文件中,customize_machine()函数负责填充struct platform_device结构体。函数调用过程如下图所示。

代码分析如下:drivers/of/platform.c

const struct of_device_id of_default_bus_match_table[] = {
	{ .compatible = "simple-bus", },
	{ .compatible = "simple-mfd", },
#ifdef CONFIG_ARM_AMBA
	{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
	{} /* Empty terminated list */
};
 
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;
 
	/* 为根节点下面的每一个节点创建platform_device结构体 */
	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_node flag标志位 */
	of_node_set_flag(root, OF_POPULATED_BUS);
 
	of_node_put(root);
	return rc;
}
 
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;
 
	/* 只有包含"compatible"属性的node节点才会生成相应的platform_device结构体 */
	/* Make sure it has a compatible property */
	if (strict && (!of_get_property(bus, "compatible", NULL))) {
		return 0;
	}
	/* 省略部分代码 */
	/* 
	 * 针对节点下面得到status = "ok" 或者status = "okay"或者不存在status属性的
	 * 节点分配内存并填充platform_device结构体
	 */
	dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
	if (!dev || !of_match_node(matches, bus))
		return 0;
 
	/* 递归调用节点解析函数,为子节点继续生成platform_device结构体,前提是父节点
	 * 的“compatible” = “simple-bus”,也就是匹配of_default_bus_match_table结构体中的数据
	 */
	for_each_child_of_node(bus, 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_populate()函数执行完毕,kernel就为设备树中所有包含compatible属性名的第一级node创建platform_device结构体,并向平台设备总线注册设备信息。如果第一级node的compatible属性值等于“simple-bus”、“simple-mfd”或者"arm,amba-bus"的话,kernel会继续为当前node的第二级包含compatible属性的node创建platform_device结构体,并注册设备。Linux系统下的设备大多都是挂载在平台总线下的,因此在平台总线被注册后,会根据of_root节点的树结构,去寻找该总线的子节点,所有的子节点将被作为设备注册到该总线上。


五、 i2c_client和device_node绑定


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

drivers/i2c/i2c-core-base.c

/**
 * __i2c_add_numbered_adapter - i2c_add_numbered_adapter where nr is never -1
 * @adap: the adapter to register (with adap->nr initialized)
 * Context: can sleep
 *
 * See i2c_add_numbered_adapter() for details.
 */
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
	int id;

	mutex_lock(&core_lock);
	id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1, GFP_KERNEL);
	mutex_unlock(&core_lock);
	if (WARN(id < 0, "couldn't get idr"))
		return id == -ENOSPC ? -EBUSY : id;

	return i2c_register_adapter(adap);
}

 

static int i2c_register_adapter(struct i2c_adapter *adap)
{
	int res = -EINVAL;

	/* Can't register until after driver model init */
	if (WARN_ON(!is_registered)) {
		res = -EAGAIN;
		goto out_list;
	}

	/* Sanity checks */
	if (WARN(!adap->name[0], "i2c adapter has no name"))
		goto out_list;

	if (!adap->algo) {
		pr_err("adapter '%s': no algo supplied!\n", adap->name);
		goto out_list;
	}

	if (!adap->lock_ops)
		adap->lock_ops = &i2c_adapter_lock_ops;

	adap->locked_flags = 0;
	rt_mutex_init(&adap->bus_lock);
	rt_mutex_init(&adap->mux_lock);
	mutex_init(&adap->userspace_clients_lock);
	INIT_LIST_HEAD(&adap->userspace_clients);

	/* Set default timeout to 1 second if not already set */
	if (adap->timeout == 0)
		adap->timeout = HZ;

	/* register soft irqs for Host Notify */
	res = i2c_setup_host_notify_irq_domain(adap);
	if (res) {
		pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n",
		       adap->name, res);
		goto out_list;
	}

	dev_set_name(&adap->dev, "i2c-%d", adap->nr);
	adap->dev.bus = &i2c_bus_type;
	adap->dev.type = &i2c_adapter_type;
	res = device_register(&adap->dev);
	if (res) {
		pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
		goto out_list;
	}

	res = i2c_setup_smbus_alert(adap);
	if (res)
		goto out_reg;

	device_enable_async_suspend(&adap->dev);
	pm_runtime_no_callbacks(&adap->dev);
	pm_suspend_ignore_children(&adap->dev, true);
	pm_runtime_enable(&adap->dev);

	res = i2c_init_recovery(adap);
	if (res == -EPROBE_DEFER)
		goto out_reg;

	dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

#ifdef CONFIG_I2C_COMPAT
	res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
				       adap->dev.parent);
	if (res)
		dev_warn(&adap->dev,
			 "Failed to create compatibility class link\n");
#endif

	/* create pre-declared device nodes */
	of_i2c_register_devices(adap);
	i2c_acpi_install_space_handler(adap);
	i2c_acpi_register_devices(adap);

	if (adap->nr < __i2c_first_dynamic_bus_num)
		i2c_scan_static_board_info(adap);

	/* Notify drivers */
	mutex_lock(&core_lock);
	bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
	mutex_unlock(&core_lock);

	return 0;

out_reg:
	init_completion(&adap->dev_released);
	device_unregister(&adap->dev);
	wait_for_completion(&adap->dev_released);
out_list:
	mutex_lock(&core_lock);
	idr_remove(&i2c_adapter_idr, adap->nr);
	mutex_unlock(&core_lock);
	return res;
}

drivers/i2c/i2c-core-of.c

void of_i2c_register_devices(struct i2c_adapter *adap)
{
	struct device_node *bus, *node;
	struct i2c_client *client;

	/* Only register child devices if the adapter has a node pointer set */
	if (!adap->dev.of_node)
		return;

	dev_dbg(&adap->dev, "of_i2c: walking child nodes\n");

	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_err(&adap->dev,
				 "Failed to create I2C device for %pOF\n",
				 node);
			of_node_clear_flag(node, OF_POPULATED);
		}
	}

	of_node_put(bus);
}
static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,
						 struct device_node *node)
{
	struct i2c_client *client;
	struct i2c_board_info info;
	int ret;

	dev_dbg(&adap->dev, "of_i2c: register %pOF\n", node);

	ret = of_i2c_get_board_info(&adap->dev, node, &info);
	if (ret)
		return ERR_PTR(ret);

	client = i2c_new_client_device(adap, &info);
	if (IS_ERR(client))
		dev_err(&adap->dev, "of_i2c: Failure registering %pOF\n", node);

	return client;
}

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


六、 Device_Tree与sysfs


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属性就是一个二进制属性文件。

drivers/of/base.c

void __init of_core_init(void)
{
	struct device_node *np;

	of_platform_register_reconfig_notifier();

	/* Create the kset, and register existing nodes */
	mutex_lock(&of_mutex);
	of_kset = kset_create_and_add("devicetree", NULL, firmware_kobj);
	if (!of_kset) {
		mutex_unlock(&of_mutex);
		pr_err("failed to register existing nodes\n");
		return;
	}
	for_each_of_allnodes(np) {
		__of_attach_node_sysfs(np);
		if (np->phandle && !phandle_cache[of_phandle_cache_hash(np->phandle)])
			phandle_cache[of_phandle_cache_hash(np->phandle)] = np;
	}
	mutex_unlock(&of_mutex);

	/* Symlink in /proc as required by userspace ABI */
	if (of_root)
		proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base");
}

逐步分析这段代码的每个部分:

1. of_platform_register_reconfig_notifier()

of_platform_register_reconfig_notifier();

这一行代码注册了一个通知机制,用于在设备树发生变化时通知平台设备。这通常用于动态修改设备树的情景,例如设备树重新配置时(例如通过热插拔设备或重新加载设备树时)。

2. 创建 kset 并注册现有节点

mutex_lock(&of_mutex);
of_kset = kset_create_and_add("devicetree", NULL, firmware_kobj);
if (!of_kset) {
    mutex_unlock(&of_mutex);
    pr_err("failed to register existing nodes\n");
    return;
}
  • mutex_lock(&of_mutex):锁住 of_mutex,这是一个用于保护设备树操作的互斥锁,防止多线程并发修改设备树或设备树节点时出现竞态条件。
  • kset_create_and_add("devicetree", NULL, firmware_kobj):创建一个名为 "devicetree" 的 kset,并将其添加到 firmware_kobj(这是一个内核对象,通常代表固件信息)。kset 是 Linux 内核中的一个对象集合,用于组织和管理设备对象及其属性。在这里,它用于管理与设备树相关的节点信息。
  • 如果 kset_create_and_add 失败(即返回 NULL),那么会释放锁并输出错误信息。

3. 遍历所有设备树节点并附加 sysfs 属性

for_each_of_allnodes(np) {
    __of_attach_node_sysfs(np);
    if (np->phandle && !phandle_cache[of_phandle_cache_hash(np->phandle)])
        phandle_cache[of_phandle_cache_hash(np->phandle)] = np;
}
  • for_each_of_allnodes(np):这是一个宏,用于遍历设备树中的所有节点。np 是当前遍历到的设备树节点。
  • __of_attach_node_sysfs(np):这个函数会将设备树节点 np 附加到 sysfs 中,即在 /sys/firmware/devicetree/ 下为每个设备树节点创建一个相应的目录和文件,以便用户空间可以通过 sysfs 访问设备树的相关信息。
  • if (np->phandle && !phandle_cache[of_phandle_cache_hash(np->phandle)]):如果当前节点具有 phandle(一个设备树中节点的标识符),并且该 phandle 没有出现在 phandle_cache 中,就会将该 phandle 存入 phandle_cache。这是一种优化机制,用于缓存已处理过的设备树节点的 phandle

4. 解锁 of_mutex

mutex_unlock(&of_mutex);

解锁 of_mutex,释放对设备树操作的锁,允许其他线程进行设备树的操作。

5. 创建 /proc 中的符号链接

if (of_root)
    proc_symlink("device-tree", NULL, "/sys/firmware/devicetree/base");

如果 of_root 存在(即设备树根节点有效),就会在 /proc 下创建一个符号链接,指向 /sys/firmware/devicetree/base。这个符号链接为用户空间提供了设备树的接口,可以通过 /proc/device-tree 访问设备树的相关内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值