1 背景
在ARM device tree出现之前,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节。Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称“this whole ARM thing is a f*cking pain in
the ass”,随后Linaro社区开始考虑采用PowerPC等其他体系架构下已经使用的Flattened
Device Tree(FDT)。
Bootloader向Linux内核跳转的时候需要向内核传递一张描述整个硬件系统扁平设备树的的表,来描述设备、总线以及中断的信息等等。这个表被称为设备树块(device-tree block),也就是我们提供的二进制dtb文件。
具体DT语法可以参考官方网站:http://devicetree.org/Device_Tree_Usage
2 Device Tree在kernel的初始化
Bootloader传递给kernel的device tree是平面的(flattend),需要kernel将其转换成tree的形式,kernel对DT初始化的流程如下:
start_kernel
-> setup_arch
-> setup_machine_fdt
-> unflatten_device_tree
其中setup_machine_fdt是为了初始化machine desc和machine type,以及DT的基本属性;unflatten_device_tree则是生成device tree的过程。
setup_arch中对bootloader传入的参数处理方法是这样的:
-
mdesc = setup_machine_fdt(__atags_pointer);
-
if (!mdesc)
-
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
-
machine_desc = mdesc;
-
machine_name = mdesc->name;
__atags_pointer 是bootloader传递给kernel信息的物理地址,fdt会逐渐替代老式的atags传递方法,因此先调用setup_machine_fdt,失败以后再用setup_machine_tags。
setup_machine_fdt主要从__atags_pointer中获取一些基本信息,确定机器类型。
-
struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
-
{
-
......
-
devtree = phys_to_virt(dt_phys); //将bootloader传递的物理地址转换成虚拟地址
-
/* Search the mdescs for the 'best' compatible value match */
-
initial_boot_params = devtree;
-
dt_root = of_get_flat_dt_root(); //找到dt根节点
-
for_each_machine_desc(mdesc) { //遍历所有的machine_desc,找到与dt最匹配的
-
score = of_flat_dt_match(dt_root, mdesc->dt_compat);
-
//分数是根据匹配compatible的字符串个数决定的,匹配越多分数越高
-
if (score > 0 && score < mdesc_score) {
-
mdesc_best = mdesc;
-
mdesc_score = score;
-
}
-
}
-
......
-
/* Retrieve various information from the /chosen node */
-
//从DT中取出kernel启动参数
-
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
-
/* Initialize {size,address}-cells info */
-
//初始化根节点的size cell与addree cell
-
of_scan_flat_dt(early_init_dt_scan_root, NULL);
-
//根据memory节点添加memory block
-
/* Setup memory, calling early_init_dt_add_memory_arch */
-
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
-
......
-
}
unflatten_device_tree则完成了“tree”的构建,把flattened信息以tree的形式存放在内存中,方便后续查找使用
-
void __init unflatten_device_tree(void)
-
{
-
//构建device tree,根节点地址存放在of_allnodes中
-
__unflatten_device_tree(initial_boot_params, &of_allnodes,
-
early_init_dt_alloc_memory_arch);
-
//处理两个特殊的节点,/chosen和/aliasas
-
/* Get pointer to "/chosen" and "/aliasas" nodes for use everywhere */
-
of_alias_scan(early_init_dt_alloc_memory_arch);
-
}
至此,完成的device tree已经建议,后续就可以通过OF API查找使用各个节点的信息了,例如:
of_find_compatible_node
of_property_read_u32_array
of_property_read_string
3 Device Tree的应用举例
3.1 获取可用CPU
在ARM多核系统中,可用的CPU数量由全局变量nr_cpu_ids表示
int nr_cpu_ids __read_mostly = NR_CPUS;
系统启动之初,这个值由编译选项CONFIG_NR_CPUS设置
#define NR_CPUS CONFIG_NR_CPUS
随后在start_kernel之中,nr_cpu_ids会根据可用CPU的位图cpu_possible_mask的最后一位来决定
start_kernel
-> setup_nr_cpu_ids
-> nr_cpu_ids =
find_last_bit(cpumask_bits(cpu_possible_mask),NR_CPUS) + 1;
可用CPU的位图cpu_possible_mask正是由DT中的/cpu节点来决定的,具体代码如下
start_kernel
-> setup_arch
-> arm_dt_init_cpu_maps
-
void __init arm_dt_init_cpu_maps(void)
-
{
-
......
-
for_each_child_of_node(cpus, cpu) {
-
if (of_node_cmp(cpu->type, "cpu"))
-
continue;
-
-
pr_debug(" * %s...\n", cpu->full_name);
-
/*
-
* A device tree containing CPU nodes with missing "reg"
-
* properties is considered invalid to build the
-
* cpu_logical_map.
-
*/
-
//任何一个CPU节点的reg为空则说明DT存在问题直接退出
-
if (of_property_read_u32(cpu, "reg", &hwid)) {
-
pr_debug(" * %s missing reg property\n",
-
cpu->full_name);
-
return;
-
}
-
-
/*
-
* 8 MSBs must be set to 0 in the DT since the reg property
-
* defines the MPIDR[23:0].
-
*/
-
if (hwid & ~MPIDR_HWID_BITMASK)
-
return;
-
-
/*
-
* Duplicate MPIDRs are a recipe for disaster.
-
* Scan all initialized entries and check for
-
* duplicates. If any is found just bail out.
-
* temp values were initialized to UINT_MAX
-
* to avoid matching valid MPIDR[23:0] values.
-
*/
-
for (j = 0; j < cpuidx; j++)
-
if (WARN(tmp_map[j] == hwid, "Duplicate /cpu reg "
-
"properties in the DT\n"))
-
return;
-
/*
-
* Build a stashed array of MPIDR values. Numbering scheme
-
* requires that if detected the boot CPU must be assigned
-
* logical id 0. Other CPUs get sequential indexes starting
-
* from 1. If a CPU node with a reg property matching the
-
* boot CPU MPIDR is detected, this is recorded so that the
-
* logical map built from DT is validated and can be used
-
* to override the map created in smp_setup_processor_id().
-
*/
-
// boot CPU必须放在掩码第0位
-
if (hwid == mpidr) {
-
i = 0;
-
bootcpu_valid = true;
-
} else {
-
i = cpuidx++;
-
}
-
......
-
tmp_map[i] = hwid;
-
}
-
......
-
//小于cpuidx的都是有效CPU,cpuidx~nr_cpu_ids之间的不可用
-
for (i = 0; i < nr_cpu_ids; i++) {
-
if (i < cpuidx) {
-
set_cpu_possible(i, true);
-
cpu_logical_map(i) = tmp_map[i];
-
pr_debug("cpu logical map 0x%x\n", cpu_logical_map(i));
-
} else {
-
set_cpu_possible(i, false);
-
}
-
}
-
}
3.2 I2C设备创建
一般的ARM SoC通常device tree会有一个/soc节点,用来描述板级设备信息,例如UART、I2C、eTSEC、FEC、PIC等设备。这里以高通SOC I2C设备为例,看看如何通过DT创建设备。
-
&soc {
-
#address-cells = <1>;
-
#size-cells = <1>;
-
ranges = <0 0 0 0xffffffff>;
-
compatible = "simple-bus";
-
i2c@0,0 {
-
compatible = "qcom,i2c-msm-v2";
-
rtc@58 {
-
compatible = "maxim,ds1338";
-
};
-
};
-
};
以上为例,/soc节点下面挂载了i2c总线,i2c总线下挂载了rtc设备。
首先看i2c总线的创建过程,通常各类SoC总线设备的创建时通过SoC对应的machine的.init_machine成员函数中调用of_platform_populate或者of_platform_bus_probe来以此创建。以高通8916 SoC为例,i2c总线的创建过程如下:
arch_initcall(customize_machine);
-> machine_desc->init_machine
-> msm8916_init
-> of_platform_populate
-> of_platform_bus_create
-> of_platform_device_create_pdata
-> of_device_add
以上看出与传统的各个总线设备调用platform_device_register创建设备有较大差异。
接下来看i2c总线上的各个设备是如何创建的,还是以高通I2C驱动为例,在i2c_msm_v2.c中,注册了以"qcom,i2c-msm-v2"为compatible的platform_driver,与之前创建的platform_device相匹配,于是有以下流程:
i2c_msm_probe
-> i2c_msm_frmwrk_reg
-> of_i2c_register_devices
-> i2c_new_device
从而完成了挂在各个i2c总线上设备的创建。
阅读(1841) | 评论(0) | 转发(0) |