前言
最近在移植一款蓝牙芯片的过程中用到了设备树,一开始在研究kernel是怎么解析DTB的,后来就很好奇kernel是怎么找到DTB的,所以就有了这篇文章,纯粹记录一下自己的学习过程吧。
正文
下面我就从两个阶段来讲述,第一个阶段就是uboot是怎么将DTB的地址传递给kernel;第二个阶段就是kernel怎么根据DTB的地址,并解析DTB的。
uboot阶段
uboot的启动流程我就不在这里具体分析,网上很多的文章,这里只分析在uboot是怎么传递DTB的地址的。在uboot启动的最后阶段,会去执行bootcmd中的命令,其中就是使用下面的命令来启动kernel:
bootm ${loadaddr} //loadaddr是kernel的地址
熟悉uboot的人会知道,这条命令最终会执行到某个具体的函数:
U_BOOT_CMD( bootm, CONFIG_SYS_MAXARGS, 1, do_bootm, "boot application image from memory", bootm_help_text );
所以最终执行到do_bootm函数
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { ... nRet = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START | BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER | BOOTM_STATE_LOADOS | #if defined(CONFIG_PPC) || defined(CONFIG_MIPS) BOOTM_STATE_OS_CMDLINE | #endif BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO, &images, 1); ... }
继续跟踪到do_bootm_states()里面
//uboot\common\bootm.c int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[], int states, bootm_headers_t *images, int boot_progress) { ... if (!ret && (states & BOOTM_STATE_FINDOTHER)) { ret = bootm_find_other(cmdtp, flag, argc, argv); //找到fdt的最终地址 argc = 0; /* consume the args */ } ... boot_fn = bootm_os_get_boot_func(images->os.os); //跳转到kernel ... }
这里我们只关心两个主要的函数,分别是
(1)bootm_find_other:找到fdt的最终地址
(2)bootm_os_get_boot_func:找到跳转kernel的函数,并执行它boot_fn()
1、
我们先看一下怎么找到fdt地址的。
//uboot\common\bootm.c bootm_find_other bootm_find_ramdisk_fdt bootm_find_fdt
跟踪到最终的 bootm_find_fdt()函数,我们可以看到fdt的地址最终设置到images.ft_addr里面。
/* *uboot\common\bootm.c */ #if defined(CONFIG_OF_LIBFDT) static int bootm_find_fdt(int flag, int argc, char * const argv[]) { int ret; /* find flattened device tree */ #ifdef CONFIG_DTB_MEM_ADDR unsigned long long dtb_mem_addr = -1; char *ft_addr_bak; ulong ft_len_bak; if (getenv("dtb_mem_addr")) dtb_mem_addr = simple_strtoul(getenv("dtb_mem_addr"), NULL, 16); else dtb_mem_addr = CONFIG_DTB_MEM_ADDR; ft_addr_bak = (char *)images.ft_addr; ft_len_bak = images.ft_len; images.ft_addr = (char *)map_sysmem(dtb_mem_addr, 0); images.ft_len = fdt_get_header(dtb_mem_addr, totalsize); #endif printf("load dtb from 0x%lx ......\n", (unsigned long)(images.ft_addr)); ... if (ret) { puts("Could not find a valid device tree\n"); return 1; } set_working_fdt_addr(images.ft_addr); return 0; } #endif
2、
上面既然找到了合法的fdt地址,后面肯定就是看怎么传递给kernel了,所以我们再回到bootm_os_get_boot_func()。看一下里面做了什么动作。
/* * uboot\common\bootm_os.c */ boot_os_fn *bootm_os_get_boot_func(int os) { ... return boot_os[os]; }
其实,boot_os是一个函数数组,会根据传递进来的os值,调用对应的函数
/* * uboot\common\bootm_os.c */ static boot_os_fn *boot_os[] = { ... #ifdef CONFIG_BOOTM_LINUX [IH_OS_LINUX] = do_bootm_linux, #endif ... }
所以最终调用到do_bootm_linux()函数里面,并最终调用到kernel_entry(),并去执行kernel。
/* *uboot\arch\arm\lib\bootm.c */ do_bootm_linux boot_jump_linux(images, flag); kernel_entry(images->ft_addr, NULL, NULL, NULL); //从uboot跳到执行kernel,第一个参数就是fdt的地址
kernel阶段
分析完上面的uboot阶段后,我们已经知道fdt的地址是作为参数1传递到kernel。下面看一下kernel阶段怎么获取这个地址值的。
大致看一下head.S里面的内容吧,全是汇编,只能懂个大概(囧~)。下面贴出来的代码可以看出来,uboot的传过来的地址是怎么传给kernel来的,其它的汇编代码可以参考我给出的链接。
/*
* kernel\arch\arm64\kernel\head.S
* 可以参考下面两篇文章:
* https://www.cnblogs.com/keanuyaoo/p/3299536.html
* http://www.wowotech.net/215.html
*/
ENTRY(stext)
mov x21, x0 // x21=FDT,uboot阶段我们知道fdt的地址放在了寄存器x0,这里赋值给x21
bl el2_setup // Drop to EL1, w20=cpu_boot_mode
bl __calc_phys_offset // x24=PHYS_OFFSET, x28=PHYS_OFFSET-PAGE_OFFSET
bl set_cpu_boot_mode_flag
mrs x22, midr_el1 // x22=cpuid
mov x0, x22
bl lookup_processor_type
mov x23, x0 // x23=current cpu_table
cbz x23, __error_p // invalid processor (x23=0)?
bl __vet_fdt
bl __create_page_tables // x25=TTBR0, x26=TTBR1
...
__switch_data:
.quad __mmap_switched
.quad __bss_start // x6
.quad _end // x7
.quad processor_id // x4
.quad __fdt_pointer // x5,用来指向FDT的首地址
.quad memstart_addr // x6
.quad init_thread_union + THREAD_START_SP // sp
/*
* The following fragment of code is executed with the MMU on in MMU mode, and
* uses absolute addresses; this is not position independent.
*/
__mmap_switched:
adr x3, __switch_data + 8
ldp x6, x7, [x3], #16
1: cmp x6, x7
b.hs 2f
str xzr, [x6], #8 // Clear BSS
b 1b
2:
ldp x4, x5, [x3], #16
ldr x6, [x3], #8
ldr x16, [x3]
mov sp, x16
str x22, [x4] // Save processor ID
str x21, [x5] // Save FDT pointer,保存FDT的地址到x5指向的地址
str x24, [x6] // Save PHYS_OFFSET
mov x29, #0
b start_kernel //跳转到C代码继续执行
ENDPROC(__mmap_switched)
在汇编的阶段,大概可以看出来用变量__fdt_pointer指向FDT的首地址,执行完汇编的阶段就会调到C代码的流程里面了。
asmlinkage void __init start_kernel(void) { ... setup_arch(&command_line); //设置架构相关的内容 ... }
我们只关心读取fdt的相关内容,直接进到setup_ arch()函数。
void __init setup_arch(char cmdline_p) { /* * Unmask asynchronous aborts early to catch possible system errors. */ local_async_enable(); setup_processor(); setup_machine_fdt(__fdt_pointer); //根据fdt地址,进行fdt的扫描 }
终于看到fdt的地址被用起来了,传递进了setup_machine_fdt()函数里面。
static void __init setup_machine_fdt(phys_addr_t dt_phys) { if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys))) { early_print("\n" "Error: invalid device tree blob at physical address 0x%p (virtual address 0x%p)\n" "The dtb must be 8-byte aligned and passed in the first 512MB of memory\n" "\nPlease check your bootloader.\n", dt_phys, phys_to_virt(dt_phys)); while (true) cpu_relax(); } machine_name = of_flat_dt_get_machine_name(); }
因为传进来的fdt地址是物理地址,所以用phys_to_virt()函数转换为虚拟地址,并进入到early_init_dt_scan()进行fdt的扫描
bool __init early_init_dt_scan(void *params) { if (!params) return false; /* Setup flat device-tree pointer */ initial_boot_params = params; /* check device tree validity */ if (be32_to_cpu(initial_boot_params->magic) != OF_DT_HEADER) { initial_boot_params = NULL; return false; } /* 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); return true; }
具体的扫描函数就不分析了,有兴趣的同学可以继续跟踪代码。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/211166.html原文链接:https://javaforall.net
