设备树的节点和属性
设备树dts文件由节点和属性构成。每个节点都是一个设备的抽象。
节点的格式:
标签: 节点名称@单元地址{ 属性1 = ... 属性2 = ... 子节点... }
标签:为节点提供了一个唯一的标识符。这个标识符用于在设备树的其他节点属性引用本节点。标签不是必须的,如果没有标签,也可以通过“节点名称@单元地址”来引用当前节点。
节点名称:使用字母开头(根节点用\表示)
单元地址:@为分隔符,后面是实际的单元地址,它的值与reg属性的第一个地址一致,若没有reg属性值,则可以省略单元地址
节点属性:节点的大括号{ }中包含的内容是节点属性, 一个节点可以包含多个属性信息,属性包括自定义属性和标准属性。
比如:
/ { cpus { #address-cells = <1>; #size-cells = <0>; cpu0: cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; clock-latency = <61036>; } } }
标准属性
model属性:用于指定设备的制造商和型号,多个字符串使用“,”分隔开。
compatible属性:由一个或多个字符串组成,可以用来与驱动程序支持的设备列表的compatible字段匹配。
status属性:用于指示设备的操作状态,通过status可以禁用或启用设备。
reg属性:描述设备资源在其父总线定义的地址空间内的地址,通常用于表示一块寄存器的起始地址和长度。
#address-cells 和 #size-cells:这两个属性同时存在,前者决定了子节点reg属性中地址信息所占用的字长,后者决定了长度信息所占的字长。
ranges属性:是一个地址映射/转换表,由子地址、父地址和地址空间长度这三部分组成:
- child-bus-address:子总线地址空间的物理地址, 由父节点的#address-cells 确定此物理地址所占用的字长;
- parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长;
- length:子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长;
属性的取值
属性的取值可以是:没有值,字符串,32位数字,引用其他节点
字符串用“”表示;
32位数字和引用其他节点用<>表示;
cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; memory = <&memory@0 1>; };
linux使用设备树
生成platform设备列表
查找machine_desc
每个单板都会定义自己的struct machine_desc结构体,如下所示:
#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,
#endif
static const char *imx6ul_dt_compat[] __initconst = {
"fsl,imx6ul",
"fsl,imx6ull",
NULL,
};
DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,
.dt_compat = imx6ul_dt_compat,
MACHINE_END
在setup_machine_fdt函数中,会轮询所有定义在.arch.info.init字段的struct machine_desc结构体,将它的dt_compat字段与设备树的根节点的compatible字段作比较,找到最匹配的struct machine_desc结构体。
调用过程:start_kernel -> setup_arch -> setup_machine_fdt
解析dtb
dtb是dts文件编译后生成的,无法直接给内核使用,所以内核需要先解析dtb,生成设备树的树形结构。
unflatten_device_tree函数会解析dtb文件,生成设备树列表,存储在struct device_node *of_root中。
调用过程:start_kernel -> setup_arch -> unflatten_device_tree
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, &of_root,
early_init_dt_alloc_memory_arch);
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
}
解析设备树
找到匹配的machine_desc之后,则通过调用machine_desc->init_machine函数来解析设备树。调用时机是在init进程当中调用arch_initcall定义的customize_machine函数,如下所示:
static int __init customize_machine(void)
{
/*
* customizes platform devices, or adds new ones
* On DT based machines, we fall back to populating the
* machine from the device tree, if no callback is provided,
* otherwise we would always need an init_machine callback.
*/
of_iommu_init();
if (machine_desc->init_machine)
machine_desc->init_machine(); //解析设备树入口
#ifdef CONFIG_OF
else
of_platform_populate(NULL, of_default_bus_match_table,
NULL, NULL);
#endif
return 0;
}
arch_initcall(customize_machine);
customize_machine的调用过程:kernel_init -> kernel_init_freeable -> do_basic_setup -> do_initcalls
machine_desc->init_machine()会调用到 of_platform_populate函数去解析设备树,如上面定义的函数imx6ul_init_machine:
static void __init imx6ul_init_machine(void)
{
struct device *parent;
parent = imx_soc_device_init();
if (parent == NULL)
pr_warn("failed to initialize soc device\n");
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
imx6ul_enet_init();
imx_anatop_init();
imx6ul_pm_init();
}
of_platform_populate函数会轮询of_root的每个节点,注册platform设备,生成设备列表
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("/"); //会查找到全局指针of_root
if (!root)
return -EINVAL;
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true); //为每个节点注册platfom设备
if (rc)
break;
}
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}