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";
};
model
和compatible
属性描述了设备树的目标平台信息,通常用来匹配设备树与内核的兼容性。
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)
每个节点下可以有多个属性,属性用来描述设备的详细信息。
- 字符串属性:如
compatible
,model
。 - 数值属性:如
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
访问设备树的相关内容。