linux内核解析设备树节点
准备
使用的源码包为华为官方的ascend200AI加速模块的SDK,其下载地址位于:点击跳转
使用的固件与驱动版本为:1.0.9.alpha
压缩包名称为:A200-3000-sdk_20.2.0.zip
将A200-3000-sdk_20.2.0.zip解压后可以看到Ascend310-source-minirc.tar.gz压缩包,这个压缩包里有ascend200AI加速模块的linux内核源码包、设备树及驱动文件等。
DTB文件分析
DTB文件内容布局
- struct ftd_header:用来表明各个分部的偏移地址,整个文件的大小,版本号等;
- memory reservationblock:在设备树中使用
/memreserve/
定义的保留内存信息; - structure block:保存节点的信息,节点的结构;
- strings block:保存属性的名字,单独作为字符串保存;
struct ftd_header
表示整个设备树的块和地址信息。
struct fdt_header {
uint32_t magic;
uint32_t totalsize;
uint32_t off_dt_struct;
uint32_t off_dt_strings;
uint32_t off_mem_rsvmap;
uint32_t version;
uint32_t last_comp_version;
uint32_t boot_cpuid_phys;
uint32_t size_dt_strings;
uint32_t size_dt_struct;
};
例如:
00000000: D0 0D FE ED 00 00 25 40 00 00 00 38 00 00 21 C8 P.~m..%@...8..!H
00000010: 00 00 00 28 00 00 00 11 00 00 00 10 00 00 00 00 ...(............
00000020: 00 00 03 78 00 00 21 90 00 00 00 00 00 00 00 00 ...x..!.........
00000030: 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 ................
00000040: 00 00 00 03 00 00 00 04 00 00 00 00 00 00 00 01 ................
00000050: 00 00 00 03 00 00 00 04 00 00 00 0F 00 00 00 01 ................
00000060: 00 00 00 03 00 00 00 0F 00 00 00 1B 78 6C 6E 78 ............xlnx
00000070: 2C 7A 79 6E 71 2D 37 30 30 30 00 00 00 00 00 01 ,zynq-7000......
00000080: 63 68 6F 73 65 6E 00 00 00 00 00 03 00 00 00 16 chosen..........
00000090: 00 00 00 26 63 6F 6E 73 6F 6C 65 3D 74 74 79 50 ...&console=ttyP
000000a0: 53 30 2C 31 31 35 32 30 30 00 00 00 00 00 00 02 S0,115200.......
000000b0: 00 00 00 01 61 6C 69 61 73 65 73 00 00 00 00 03 ....aliases.....
000000c0: 00 00 00 18 00 00 00 2F 2F 61 6D 62 61 2F 65 74 .......//amba/et
- magic:模数,必须为0xd00dfeed
- totalsize:整个dtb文件的大小,如上为00 00 25 40,即为9536字节
- off_dt_struct,即structure block的偏移地址,如上00 00 00 38,即56字节
- off_dt_strings,即strings block的偏移地址;,如上00 00 21 C8,即8648字节
- off_mem_rsvmap,即memory reservation block的偏移地址;00 00 00 28,即40字节,后面8个字节表示保留内存的address,8个字节表示保留内存的size
- size_dt_strings:strings block的的大小
- size_dt_struct:structure block的大小
memory reservationblock
设备树的memreserve信息,只有16个字节,8个字节表示保留内存的address,8个字节表示保留内存的size,他在内核解析设备树时告诉内核要保留哪里的内存在存放设备树。
structure block
存放所有属性的值,每个节点以0x00000001开始,后面接节点名字,以0x00000002结束,0x00000003表示后面要跟属性值了,后面依次是4字节的val的长度,4字节val的名字在string block的偏移,然后跟val的字符。整个 structure block以0x00000009结束。在dts文件中会有节点的别名,编译为dtb文件是没有的,外部别名添加在信息会全部汇总在节点里。
例如:
dts:
#size-cells = <0x1>;
compatible = "xlnx,zynq-7000";
chosen {
bootargs = "console=ttyPS0,115200";
};
aliases {
ethernet0 = "/amba/ethernet@e000b000";
serial0 = "/amba/serial@e0001000";
serial1 = "/amba/serial@e0000000";
spi0 = "/amba/spi@e000d000";
spi1 = "/amba/spi@e0006000";
spi2 = "/amba/spi@e0007000";
};
memory {
device_type = "memory";
reg = <0x0 0x10000000>;
};
cpus {
dtb:
00000070: 2C 7A 79 6E 71 2D 37 30 30 30 00 00 00 00 00 01 ,zynq-7000......
00000080: 63 68 6F 73 65 6E 00 00 00 00 00 03 00 00 00 16 chosen..........
00000090: 00 00 00 26 63 6F 6E 73 6F 6C 65 3D 74 74 79 50 ...&console=ttyP
000000a0: 53 30 2C 31 31 35 32 30 30 00 00 00 00 00 00 02 S0,115200.......
000000b0: 00 00 00 01 61 6C 69 61 73 65 73 00 00 00 00 03 ....aliases.....
000000c0: 00 00 00 18 00 00 00 2F 2F 61 6D 62 61 2F 65 74 .......//amba/et
000000d0: 68 65 72 6E 65 74 40 65 30 30 30 62 30 30 30 00 hernet@e000b000.
000000e0: 00 00 00 03 00 00 00 16 00 00 00 39 2F 61 6D 62 ...........9/amb
000000f0: 61 2F 73 65 72 69 61 6C 40 65 30 30 30 31 30 30 a/serial@e000100
00000100: 30 00 00 00 00 00 00 03 00 00 00 16 00 00 00 41 0..............A
00000110: 2F 61 6D 62 61 2F 73 65 72 69 61 6C 40 65 30 30 /amba/serial@e00
00000120: 30 30 30 30 30 00 00 00 00 00 00 03 00 00 00 13 00000...........
00000130: 00 00 00 49 2F 61 6D 62 61 2F 73 70 69 40 65 30 ...I/amba/spi@e0
00000140: 30 30 64 30 30 30 00 00 00 00 00 03 00 00 00 13 00d000..........
00000150: 00 00 00 4E 2F 61 6D 62 61 2F 73 70 69 40 65 30 ...N/amba/spi@e0
00000160: 30 30 36 30 30 30 00 00 00 00 00 03 00 00 00 13 006000..........
00000170: 00 00 00 53 2F 61 6D 62 61 2F 73 70 69 40 65 30 ...S/amba/spi@e0
00000180: 30 30 37 30 30 30 00 00 00 00 00 02 00 00 00 01 007000..........
00000190: 6D 65 6D 6F 72 79 00 00 00 00 00 03 00 00 00 07 memory..........
000001a0: 00 00 00 58 6D 65 6D 6F 72 79 00 00 00 00 00 03 ...Xmemory......
000001b0: 00 00 00 08 00 00 00 64 00 00 00 00 10 00 00 00 .......d........
首先0x00000001+63 68 6F 73 65 6E 00(chosen),表示chosen节点开始,00 00 00 03表示后面要跟一个属性,00 00 00 16表示val有22个字节,即console=ttyPS0,115200,00 00 00 26表示在string block的偏移地址,然后跟val,灾后00 00 00 00 02结束该节点。
strings block
存放设备树所有的属性名字,例如model、compatible等。
000021a0: 00 00 00 00 00 00 00 1F 00 00 00 02 00 00 00 00 ................
000021b0: 00 00 00 20 00 00 00 02 00 00 00 02 00 00 00 02 ................
000021c0: 00 00 00 02 00 00 00 09 23 61 64 64 72 65 73 73 ........#address
000021d0: 2D 63 65 6C 6C 73 00 23 73 69 7A 65 2D 63 65 6C -cells.#size-cel
000021e0: 6C 73 00 63 6F 6D 70 61 74 69 62 6C 65 00 62 6F ls.compatible.bo
000021f0: 6F 74 61 72 67 73 00 65 74 68 65 72 6E 65 74 30 otargs.ethernet0
00002200: 00 73 65 72 69 61 6C 30 00 73 65 72 69 61 6C 31 .serial0.serial1
00002210: 00 73 70 69 30 00 73 70 69 31 00 73 70 69 32 00 .spi0.spi1.spi2.
00002220: 64 65 76 69 63 65 5F 74 79 70 65 00 72 65 67 00 device_type.reg.
00002230: 63 6C 6F 63 6B 73 00 63 6C 6F 63 6B 2D 6C 61 74 clocks.clock-lat
00002240: 65 6E 63 79 00 63 70 75 30 2D 73 75 70 70 6C 79 ency.cpu0-supply
00002250: 00 6F 70 65 72 61 74 69 6E 67 2D 70 6F 69 6E 74 .operating-point
00002260: 73 00 69 6E 74 65 72 72 75 70 74 73 00 69 6E 74 s.interrupts.int
00002270: 65 72 72 75 70 74 2D 70 61 72 65 6E 74 00 72 65 errupt-parent.re
00002280: 67 75 6C 61 74 6F 72 2D 6E 61 6D 65 00 72 65 67 gulator-name.reg
00002290: 75 6C 61 74 6F 72 2D 6D 69 6E 2D 6D 69 63 72 6F ulator-min-micro
按照顺序存放字符,以0x00(字符‘.’)来区分单词
machine_desc
这个machine_desc主要是匹配根节点的compatible属性。这个结构体会描述和硬件相关的一些信息。
mdesc = setup_machine_fdt(__atags_pointer),__atags_pointer是uboot传入的参数存放的位置。
- 一般内核处理根节点compatible属性的过程:
内核会根据根节点compatible中string的顺序去找合适的machine_desc,每个machine_desc是表明这个内核能支持哪些设备。例如arch/arm/mach_zynq/common.c
static const char * const zynq_dt_match[] = {
"xlnx,zynq-7000",
NULL
};
DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
/* 64KB way size, 8-way associativity, parity disabled */
.l2c_aux_val = 0x00400000,
.l2c_aux_mask = 0xffbfffff,
.smp = smp_ops(zynq_smp_ops),
.map_io = zynq_map_io,
.init_irq = zynq_irq_init,
.init_machine = zynq_init_machine,
.init_late = zynq_init_late,
.init_time = zynq_timer_init,
.dt_compat = zynq_dt_match,
.reserve = zynq_memory_init,
MACHINE_END
这个dt_compat表示能匹配到"xlnx,zynq-7000",表示本内核支持设备树根节点为"xlnx,zynq-7000"的设备。DT_MACHINE_START是一个__attribute__的宏,他将名字为__mach_desc_XILINX_EP107的结构体放到名为.arch.info.init段中。linux内核将所有用MACHINE_START定义的结构体都被组织到.arch.info.init段中,和内核模块注册的机制一样,同时定义了__arch_info_begin和__arch_info_end 两个空的machine_desc结构体对象,在链接脚本中会将.arch.info.init段链接在这两个变量中间,这样就能通过__arch_info_begin变量地址遍历到整个.arch.info.init段了
函数调用过程:
start_kernel
-->setup_arch
//如果设备树正确则使用设备树来设置machine
-->mdesc = setup_machine_fdt(__atags_pointer);
//找到最匹配的machine_desc并且返回
-->mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
-->get_next_compat
//按顺序遍历.arch.info.init段的内容
-->arch_get_next_mach
{
static const struct machine_desc *mdesc = __arch_info_begin;
const struct machine_desc *m = mdesc;
if (m >= __arch_info_end)
return NULL;
mdesc++;
*match = m->dt_compat;
return m;
}
//如果设备树解析出错,则使用uboot传入的参数
-->mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
- 华为昇腾310内核
设备树节点:
/memreserve/ 0x0000000000000000 0x0000000000010000;
/ {
compatible = "hisilicon,hi1910-asic", "hisilicon,mini";
hisi,boardid = <1 0 0 4>;
去内核源码中grep"hisilicon,hi1910-asic", “hisilicon,mini”;是找不到的,然后就跳进去看了下
start_kernel
-->setup_arch // arch/arm64/kernel/setup.c
//注意这里是没有返回值的
-->setup_machine_fdt(__fdt_pointer);
-->of_flat_dt_get_machine_name();//Clinux-4.19/drivers/of/fdt.c
{
const char *name;
//返回0
unsigned long dt_root = of_get_flat_dt_root();
name = of_get_flat_dt_prop(dt_root, "model", NULL);
if (!name)
name = of_get_flat_dt_prop(dt_root, "compatible", NULL);
return name;
}
这里直接找的是根节点下的model属性,找不到的话找compatible,然后pr_info(“Machine model: %s\n”, name);打印了一下信息,然后就没了,也没有machine_desc的返回值。
后面的一些内核和硬件架构相关的初始化代码好像也没有用到这个machine_desc,所以有没有这个machine_desc因该没有关系。
dtb转化为device_node
setup_machine_fdt
start_kernel
-->setup_arch // arch/arm64/kernel/setup.c
-->setup_machine_fdt(__fdt_pointer);
-->early_init_dt_scan
//检查设备树的魔数是否为D0 0D FE ED 00 00 25 40
//版本version是否正确,这样好像是早期的版本dtb文件分布不同
//将设备树的首地址传给initial_boot_params,后面经常用到
-->early_init_dt_verify
//解析设备树的部分节点
//1.chosen节点下的bootargs节点
//2.{size,address}-cells节点
//3.memory,reg,linux,usable-memory等和内存相关的节点信息
-->early_init_dt_scan_nodes
{
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
of_scan_flat_dt(early_init_dt_scan_root, NULL);
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
arm_memblock_init
和内存初始化相关的一些操作,没有整明白,网上查了下大概是使用设备树中的memreserve节点的信息,将这个地方保留存放设备树,不要让内核使用。
unflatten_device_tree
start_kernel
-->setup_arch // arch/arm64/kernel/setup.c
-->unflatten_device_tree
// initial_boot_params存放的是dtb文件的起始地址 of_root是device_node结构体,根节点会保存在里面
-->__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
//unflatten_dt_nodes第一次调用,计算出整个设备树需要多少个device_node结构体
//然后返回需要的大小size
-->size = unflatten_dt_nodes(blob, NULL, dad, NULL);
//根据上面的size申请内存
-->mem = dt_alloc(size + 4, __alignof__(struct device_node));
//unflatten_dt_nodes第二次调用,传入mem,真正的开始将dtb文件构造为device_node
-->unflatten_dt_nodes(blob, mem, dad, mynodes);
//遍历能用的节点,如果没有status则返回true
//如果status属性为ok或者okay则返回true
//否则返回false
//按顺序遍历能用的节点
...fdt_next_node()...
-->of_fdt_device_is_available
{
const char *status = fdt_getprop(blob, node, "status", NULL);
if (!status)
return true;
if (!strcmp(status, "ok") || !strcmp(status, "okay"))
return true;
return false;
}
-->populate_node
{
//获得节点名字,长度存在l
pathp = fdt_get_name(blob, offset, &l);
//申请内存,此时申请的内存为一个device_node结构体加上节点名字的长度
allocl = ++l;
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));
//这个full的指针指向的是sizeof(struct device_node)后面的节点名字
np->full_name = fn = ((char *)np) + sizeof(*np);
//设置parent、sibling、child指针
if (dad != NULL) {
np->parent = dad;
np->sibling = dad->child;
dad->child = np;
}
//构造本届点device_node中的properties
//properties结构体存放属性名、值,通过next指针串成链表
populate_properties(blob, offset, mem, np, pathp, dryrun);
//如果有name和device_type则设置device_node的name和device_type,没有则为null
np->name = of_get_property(np, "name", NULL);
np->type = of_get_property(np, "device_type", NULL);
if (!np->name)
np->name = "<NULL>";
if (!np->type)
np->type = "<NULL>";
}
总结
device_node结构体内容如下所示:
struct device_node {
const char *name;
const char *type;
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;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
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
};
跟节点下每一个大括号都会被转化为1个device_node结构体:
- name不是设备树下子节点的名字,而是它的name属性,如果没有name属性,则他为空
- type来自于节点中device_type属性,没有则为空
- full_name才是设备树节点的名字,比如led@0x01
- properties节点的属性
- parent指向父节点的device_node结构体
- child指向子节点的device_node结构体
- sibling指向同级节点
property 结构体:
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
};
- name:属性名
- value:属性的值
- length:按字节计算value的长度
- next:指向下一个property属性,串成一个链表
device_node转化为platform_device
哪些节点会被转化为platform_device
- 节点必须有compatible属性
- 必须是根节点的子节点
- 如果根节点的子节点有simple-bus、simple-mfd或者isa等属性,它的子节点也能被转化为platform_device
- 如果是子节点下的子节点,则如父节点的probe函数来处理,例如spi节点下的子节点,会在spi_register_master函数内被处理,被转化为一个spi_device结构体,i2c则是被转化为一个i2c_client结构体
函数调用过程
start_kernel
-->rest_init
-->pid = kernel_thread(kernel_init, NULL, CLONE_FS);
-->kernel_init
-->kernel_init_freeable
-->do_basic_setup
-->do_initcalls
{
//内核initcall机
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
//drivers/of/platform.c,内核initcall机制,会放到指定的段中按顺序指向段内的函数
arch_initcall_sync(of_platform_default_populate_init);
#define arch_initcall_sync(fn) __define_initcall(fn, 3s)
of_platform_default_populate_init
//处理特殊的节点,没用过
-->for_each_matching_node(node, reserved_mem_matches)
-->of_platform_device_create(node, NULL, NULL);
-->node = of_find_node_by_path("/firmware");
//处理除上以外所有的节点
-->of_platform_default_populate
//of_default_bus_match_table表中的子节点也能被转化为platform_device
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 */
};
-->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);
//开始创建platform_device
-->of_platform_device_create_pdata
//将io、中断和reg属性转化为resources资源。
//里面还会处理dev结构体的其他内容,都是根据device_node来的
-->of_device_alloc
//如果该节点的compatible不含有of_default_bus_match_table里的内容,则返回
//否则处理它的子节点
if (!dev || !of_match_node(matches, bus))
return 0;
}
}
}
总结
platform_device 结构体如下:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
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;
};
- num_resources表示有多少个resources,一般是设备树节点下的reg属性和interrupts属性和io资源
- resource是一个指针,指向设备树节点的资源,会从device_node得到
- dev结构体里的内容都是根据device_node来的
- dev->of_node指向的是它的device_node
- dev->bus默认为platform_bus_type
platform总线和设备匹配过程
//drivers/base/platform.c
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总线和设备匹配优先级:
- platform_dev.driver_override 和 platform_driver.drv->name
- platform_dev.dev.of_node的compatible属性 和 platform_driver.drv->of_match_table
- platform_dev.name 和 platform_driver.id_table
- platform_dev.name 和 platform_driver.drv->name