kernel解析dtb为节点
kernel解析dtb为节点
head.s入口传递
回顾
看以前的笔记 kernel(二)源码浅析
先来回顾下以前uboot
是怎么传递参数的?
R0 | 一般设置为0 |
---|---|
R1 | machine id (设备树不使用) |
R2 | ATAGS(设备树使用为DTB地址) |
kernel
的入口点是``arch\arm\kernel\head.S,以前的流程是根据这个
machine id去匹配到具体的单板,然后使用
ATAGS`构造相应的启动参数
使用机器id可以找到类似如下的结构体
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
具体这个结构如下
struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head-armv.S
*/
unsigned int nr; /* architecture number */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/* IO mapping function */
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
};
设备树启动浅析
head.s
会调用head-common.S
a. __lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
b. __vet_atags : 判断是否存在可用的ATAGS或DTB
c. __create_page_tables : 创建页表, 即创建虚拟地址和物理地址的映射关系
d. __enable_mmu : 使能MMU, 以后就要使用虚拟地址了
e. __mmap_switched : 上述函数里将会调用__mmap_switched
.long __bss_start @ r0
.long __bss_stop @ r1
.long init_thread_union + THREAD_START_SP @ sp
.long processor_id @ r0
.long __machine_arch_type @ r1
.long __atags_pointer @ r2
f. 把bootloader传入的r2参数, 保存到变量__atags_pointer中
g. 调用C函数start_kernel
start_kernel
先记住这个
.long processor_id @ r0
.long __machine_arch_type @ r1
.long __atags_pointer @ r2
大概的流程是这样的
mdesc = setup_machine_fdt(__atags_pointer);
// 头部检查
early_init_dt_verify
//找到最匹配的machine_desc
of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
if (!mdesc) //按照以前使用atag的方式
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
// 找到匹配的machine_desc,后处理了
machine_desc = mdesc;
machine_name = mdesc->name;
dump_stack_set_arch_desc("%s", mdesc->name);
// 把命令行启动参数存起来
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
// 保留dtb 本身的内存 以及指定的 reserve的内存
arm_memblock_init(mdesc);
启动参数以及内存解析
-
/chosen节点中bootargs
属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来,存在boot_command_line
-
/memory
中的reg
属性指定了不同板子内存的大小和起始地址,调用memblock_add
-
根节点的
#address-cells和#size-cells
属性指定属性参数的位数
代码浅析
setup_machine_fdt
mdesc_best = &__mach_desc_GENERIC_DT; 这个应该是机器描述符段的起始地址
early_init_dt_verify
// 头部校验
fdt_check_header
// 把这个 地址又存一遍 initial_boot_params
initial_boot_params = params;
//计算crc到 of_fdt_crc32
of_fdt_crc32=crc32_be(...)
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
// arch_get_next_mach 获取下一个机器描述
// 寻找下一个机器描述的id
// get_next_compat=arch_get_next_mach
// 最终找到最匹配的机器描述
while ((data = get_next_compat(&compat)))
{
score = of_flat_dt_match(dt_root, compat);
// initial_boot_params 就是上面存档的dtb地址
of_fdt_match(initial_boot_params, node, compat);
of_fdt_is_compatible
// 寻找 compatible 属性来匹配 单板
fdt_getprop(blob, node, "compatible", &cplen);
// 找到最匹配的
while (cplen > 0)
of_compat_cmp
if (score > 0 && score < best_score)
{
best_data = data;
best_score = score;
}
}
early_init_dt_scan_nodes
// 找到启动命令参数
/* Retrieve various information from the /chosen node */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
early_init_dt_scan_chosen // 寻找到 chosen 的 bootargs
p = of_get_flat_dt_prop(node, "bootargs", &l);
// 找到reg的描述
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
// 内存相关设置
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
early_init_dt_scan_memory
if( ! of_get_flat_dt_prop(node, "linux,usable-memory", &l);)
else of_get_flat_dt_prop(node, "reg", &l);
....
//
of_get_flat_dt_prop(node, "hotpluggable", NULL);
内存保留
uboot把设备树DTB文件随便放到内存的某一个地方就可以使用,内核不会去覆盖DTB所占用的那块内存呢.在设备树文件中,可以使用/memreserve/
指定一块内存,这块内存就是保留的内存,内核不会占用它。即使你没有指定这块内存,当我们内核启动时,他也会把设备树所占用的区域保留下来.
setup_arch
// dtb 判断,机器id查找
setup_machine_fdt
// 内存保留
arm_memblock_init(mdesc);
// dtb 自身的内存空间
early_init_fdt_reserve_self();
early_init_dt_reserve_memory_arch(
__pa(initial_boot_params), //initial_boot_params 是以前存储的dtb地址
fdt_totalsize(initial_boot_params), //头部指示的大小
0);
memblock_reserve(....)
// dtb 描述中的 reserve 内存
early_init_fdt_scan_reserved_mem();
fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
early_init_dt_reserve_memory_arch
memblock_reserve(....)
小结
综上,我们到此为止解析了如下
chosen/bootargs | 启动命令行boot_command_line |
---|---|
memory | 保留内存 |
dtb本身内存 | |
initial_boot_params | dtb地址,这个是个全局变量 |
节点的解析
这段代码的入口是这里
setup_arch
// 找到匹配的机器描述
setup_machine_fdt
// 内存保留
arm_memblock_init(mdesc);
// 节点解析
unflatten_device_tree();
unflatten_device_tree()
{
// 第一次解析,这里最后一个参数是false,不会分配内存,会计算所需的内存大小
__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();
}
这段代码还是比较复杂了,暂时不去分析了这里没有使用递归,按我的理解应该是类似这样的
for(next_node(xxx))-----这里的next_node 会记录树的深度,也就是应该会有父兄的记录
{
// 解析status
// 解析属性
if(is_not_属性)
continue;
}
数据结构
节点描述
struct device_node {
const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL"
const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL"
phandle phandle;
const char *full_name; // 节点的名字, node-name[@unit-address]
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
};
属性描述
struct property {
char *name; // 属性名字, 指向dtb文件中的字符串
int length; // 属性值的长度
void *value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储
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
};
具体的描述看下老师的图,很容易理解,就是一个比较大的链表,注意其中节点名字指向节点本身最后的内存