我们知道设备树只是起到信息传递的作用,所以对配置信息的处理还是相对比较简单的,
设备树只不过从dtb文件中把信息给提取出来付给内核中的某个变量就可以了,
- 下面这条命令,就是内核启动时的命令行参数:
chosen {
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};
root=/dev/mtdblock4:指定了跟文件系统在哪里;
init=/linuxrc:指定第一个运行的应用程序是哪一个;
console=ttySAC0:指定内核的打印信息从哪个设备打印出来;
- memory节点用来描述硬件内存布局的。如果有多块内存,既可以通过多个memory节点表示,也可以通过一个memory节点的reg属性的多个元素支持。不同的板子内存的起始地址和大小可能不一样,我们需要在设备树中把内存的起始地址和大小告诉内核。
memory@30000000 {
device_type = "memory";
reg = <0x30000000 0x4000000>;
};
#address-cells = <1>; //1个32bit 表示内存的起始地址
#size-cells = <1>; //1个32bit 表示内存的大小
内存起始地址和大小用多少个32bit表示,通过设置#address-cells和#size-cells来指定,譬如:
#address-cells = <n>; //n个32bit 表示内存的起始地址
#size-cells = <m> //个32bit 表示内存的大小
memory@0 {
device_type = "memory";
reg = <0x000000000 0x00000000 0x00000000 0x80000000
0x000000001 0x00000000 0x00000001 0x00000000>;
};
两个memory节点的形式如下:
memory@0 {
device_type = "memory";
reg = <0x000000000 0x00000000 0x00000000 0x80000000>;
};
memory@100000000 {
device_type = "memory";
reg = <0x000000001 0x00000000 0x00000001 0x00000000>;
};
所以在解析memory@30000000节点之前需要解析#address-cells和#size-cells,
下面分析设备树文件节点信息是怎样传给内核的:
- /chosen节点中bootargs属性的值, (怎么提取出来)怎么存入全局变量得?
解析:在之前提起过,head.S会把DTB的位置保存在变量__atags_pointer里,最后调用start_kernel。
head.S中的部分内容:
str r9, [r0] @ Save processor ID
str r7, [r1] @ Save machine type
str r8, [r2] @ Save atags pointer
cmp r3, #0
strne r10, [r3] @ Save control register values
mov lr, #0
b start_kernel
start_kernel函数调用过程:
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
/* 使用dtb文件设置machine */
mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/setup.
mdesc_best = &__mach_desc_GENERIC_DT; //选择最合适的mach_desc
early_init_dt_scan_nodes(); //早期的初始化扫描节点
/* Retrieve various information from the /chosen node */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
主要分析early_init_dt_scan_nodes内的函数
void __init early_init_dt_scan_nodes(void)
{
/* 扫描 /chosen node,保存运行时参数(bootargs)到boot_command_line */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
/* 扫描根节点,获取 {size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中 */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* 扫描DTB中的memory node,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息 */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
of_scan_flat_dt函数扫描整个设备树,实际的动作是在回调函数中完成的。
第4行是对chosen节点操作,该行代码的作用是将节点下的bootargs属性的字符串拷贝到boot_command_line指向的内存中。boot_command_line是内核的一个全局变量,在内核的多处都会用到。
第8行是根据根节点的#address-cells属性和#size-cells属性初始化全局变量dt_root_size_cells和dt_root_addr_cells,如果没有设置属性的话就用默认值,这些都在early_init_dt_scan_root函数中实现。
第12行是对内存进行初始化。
/**
* of_scan_flat_dt - scan flattened tree blob and call callback on each.
* @it: callback function
* @data: context data pointer
*
* This function is used to scan the flattened device-tree, it is
* used to extract the memory information at boot before we can
* unflatten the tree
*/
int __init of_scan_flat_dt(int (*it)(unsigned long node,
const char *uname, int depth,
void *data),
void *data)
{
const void *blob = initial_boot_params;
const char *pathp;
int offset, rc = 0, depth = -1;
if (!blob)
return 0;
/* 取出设备树文件中的每一个节点 */
for (offset = fdt_next_node(blob, -1, &depth);
offset >= 0 && depth >= 0 && !rc;
offset = fdt_next_node(blob, offset, &depth)) {
pathp = fdt_get_name(blob, offset, NULL);
if (*pathp == '/')
pathp = kbasename(pathp);
rc = it(offset, pathp, depth, data); //调用该函数来处理,rc值为非0值时
} //表示处理完成
return rc;
}
从函数的注释我们可以看到it参数是传入的函数指针,data是给it参数指向的函数
使用的, 函数的作用是取出每一个节点,然后调用it指向的函数来处理,如果it返回的值
不为0的话就退出这个循环(返回非0值表示处理完)。对于这个函数我们主要关心it参数指向的函数就可以了。
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
int depth, void *data)
{
int l;
const char *p;
pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);
if (depth != 1 || !data ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0; ------------------------------------<1>
early_init_dt_check_for_initrd(node);------------------------------------<2>
/* Retrieve command line */
p = of_get_flat_dt_prop(node, "bootargs", &l);------------------------------------<3>
if (p != NULL && l > 0)
strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));
/*
* CONFIG_CMDLINE is meant to be a default in case nothing else
* managed to set the command line, unless CONFIG_CMDLINE_FORCE
* is set in which case we override whatever was found earlier.
*/
#ifdef CONFIG_CMDLINE
#if defined(CONFIG_CMDLINE_EXTEND)
strlcat(data, " ", COMMAND_LINE_SIZE);
strlcat(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#else
/* No arguments from boot loader, use kernel's cmdl*/
if (!((char *)data)[0])
strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif
#endif /* CONFIG_CMDLINE */
pr_debug("Command line is: %s\n", (char*)data);
/* break now */
return 1; ------------------------------------<4>
}
<1>、上面我们说过,early_init_dt_scan_chosen会为设备树中的每一个节点而调用一次,因此,为了效率,不是选择节点的节点我们必须赶紧闪人。由于所选节点是根节点的子节点,因此其深度必须是1。这里深度不是1的节点,节点名字不是“选择”或者选择@ 0和我们毫无关系,立刻返回。
<2>、解析选择节点中的initrd的信息
<3>、解析选择节点中的bootargs(命令行参数)并将其复制到boot_command_line。
<4>、除非设置了CONFIG_CMDLINE_FORCE,否则CONFIG_CMDLINE意味着是默认值,除非设置了CONFIG_CMDLINE_FORCE,否则我们会覆盖之前找到的内容。
/**
* early_init_dt_scan_root - fetch the top level address and size cells
*/
int __init early_init_dt_scan_root(unsigned long node, const char *uname,
int depth, void *data)
{
const __be32 *prop;
if (depth != 0)
return 0;
dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
------------------------------------<1>
prop = of_get_flat_dt_prop(node, "#size-cells", NULL); --------------------<2>
if (prop)
dt_root_size_cells = be32_to_cpup(prop);
pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);
prop = of_get_flat_dt_prop(node, "#address-cells", NULL); --------------------<3>
if (prop)
dt_root_addr_cells = be32_to_cpup(prop);
pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);
/* break now */
return 1;
}
<1>、dt_root_size_cells和dt_root_addr_cells分别用来保存和设备树文件中#address-cells和#size-cells的值,刚开始设置默认值都是1。
<2>、获得"#size-cells"属性的值(获取到的格式是大段格式),通过函数be32_to_cpup处理转化为小段模式赋值给dt_root_size_cells全局变量。
<3>、获得"#address-cells"属性的值(获取到的格式是大段格式),通过函数be32_to_cpup处理转化为小段模式赋值给dt_root_addr_cells全局变量。
/**
* early_init_dt_scan_memory - Look for and parse memory nodes
*/
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
int depth, void *data)
{
const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
const __be32 *reg, *endp;
int l;
bool hotpluggable;
/* We are scanning "memory" nodes only */
if (type == NULL || strcmp(type, "memory") != 0)
return 0; --------------------------<1>
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL)
reg = of_get_flat_dt_prop(node, "reg", &l);
if (reg == NULL)
return 0;----------------------<2>
endp = reg + (l / sizeof(__be32));
hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);
pr_debug("memory scan node %s, reg size %d,\n", uname, l);
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
base = dt_mem_next_cell(dt_root_addr_cells, ®);
size = dt_mem_next_cell(dt_root_size_cells, ®); ----------------------<3>
if (size == 0)
continue;
pr_debug(" - %llx , %llx\n", (unsigned long long)base,
(unsigned long long)size);
early_init_dt_add_memory_arch(base, size);
if (!hotpluggable)
continue;
if (early_init_dt_mark_hotplug_memory_arch(base, size))
pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
base, base + size);
}
return 0;
}
<1>、如果该memory node是root node的子节点的话,那么它一定是有device_type属性并且其值是字符串”memory”。不是的话就可以返回了。
<2>、该memory node的物理地址信息保存在"linux,usable-memory"或者"reg"属性中(reg是我们常用的)。
<3>、解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);