Linux ARM32内嵌DTB实现
大多数方案是将dtb独立,uboot启动kernel时通过r2传递dtb的地址。kernel读取到__atags_pointer,后续解析dtb内容。
内核保留了.dtb.init.rodata用于保存dtb内容,借此实现ARM32下dtb内嵌功能。
1 ARM 32启动__atags_pointer处理流程
Linux启动的dtb地址是由uboot传入的:
/* Subcommand: GO */ static void boot_jump_linux(bootm_headers_t *images, int flag) { #ifdef CONFIG_ARM64 ... #else... if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) r2 = (unsigned long)images->ft_addr; else r2 = gd->bd->bi_boot_params; if (!fake) { kernel_entry(0, machid, r2);--r2保存dtb地址,跳转到Kernel执行。 } #endif }
Linux启动后从r2寄存器获取dtb地址。启动流程如下:
stext--内核运行的起始点。
-->__enable_mmu
-->__turn_mmu_on
-->__map_switched--此时MMU已经打开,进行一些数据初始化后跳转到start_kernel启动。
-->__mmap_switched_data--MMMU打开后,跳转到start_kernel之前保存的数据。
-->start_kernel--进入C执行环境。
-->setup_arch
-->setup_machine_fdt--最开始使用DTB的地方。
-->setup_machine_tags
__mmap_switched_data保存了start_kernel所需要的数据。
__mmap_switched_data: .long __bss_start @ r0 .long __bss_stop @ r1--BSS段起始和结束地址。 .long init_thread_union + THREAD_START_SP @ sp--栈地址。 .long processor_id @ r0 .long __machine_arch_type @ r1 .long __atags_pointer @ r2--DTB地址。 .long cr_alignment @ r3 ... .size __mmap_switched_data, . - __mmap_switched_data
__mmap_switched在MMU打开后,进入start_kernel()之前主要做两件事:
- 初始化BSS段为0。
- 初始化processor_id、__machine_arch_type、__atags_pointer、cr_alignment四个变量。
__mmap_switched: mov r7, r1 mov r8, r2 mov r10, r0 adr r4, __mmap_switched_data--r4此时指向_bss_tart。 mov fp, #0 ARM( ldmia r4!, {r0, r1, sp} )--r0/r1/sp分别指向__bss_start、__bss_stop、init_thread_union + THREAD_START_SP,执行后r4指向processor_id。 THUMB( ldmia r4!, {r0, r1, r3} ) THUMB( mov sp, r3 ) sub r2, r1, r0 mov r1, #0 bl memset @ clear .bss--对.bss段清零。 ldmia r4, {r0, r1, r2, r3}--r0/r1/r2/r3分别指向processor_id、__machine_arch_type、__atags_pointer、cr_alignment,下面则分别初始化。
str r9, [r0] @ Save processor ID
str r7, [r1] @ Save machine type
str r8, [r2] @ Save atags pointer--将r8暂存的dtb地址写入__atags_pointers,是物理地址。
cmp r3, #0
strne r10, [r3] @ Save control register values
mov lr, #0
b start_kernel--跳转到C执行环境。
ENDPROC(__mmap_switched)
2 dtb内嵌启动实现
2.1 dtb编译后内嵌到kernel
2.1.1 链接脚本vmlinux.lds生成流程以及.dtb.init.rodata段
vmlinux.lds.S经过编译生成vmlinux.lds文件:
内嵌dtb文件位于.dtb.init.rodata段中:
vmlinux.lds.S
-->INIT_DATA_SECTION
-->INIT_DATA
-->KERNEL_DTB
#define KERNEL_DTB() \
STRUCT_ALIGN(); \
__dtb_start = .; \--dtb的起始地址为__dtb_start。
KEEP(*(.dtb.init.rodata)) \
__dtb_end = .;
编译结果为:
. = ALIGN(32); __dtb_start = .; KEEP(*(.dtb.init.rodata)) __dtb_end = .;
2.1.2 增加配置将dtb内嵌到kernel
大致连接流程:xxx.dtb.S -> xxx.dtb.o -> arch/arm/boot/dts/built-in.a -> core-y -> vmlinux。关于LinuxMakefile说明参考《Linux Kernel Makefiles》。
修改arch/arm/Kconfig增加CONFIG_USE_BUILTIN_DTB选项:
menu "Builtin dtb" config USE_BUILTIN_DTB bool "Use kernel builtin dtb" default y config BUILTIN_DTB_NAME--指向dtb文件。 string "Kernel builtin dtb name" endmenu
KBUILD_VMLINUX_OBJS决定了哪些文件可以编译进内核,KBUILD_VMLINUX_LIBS决定了哪些库文件编译进内核。两者共同决定了vmlinux所有连接文件。lds文件由KBUILD_LDS指定。
export KBUILD_VMLINUX_OBJS := $(head-y) $(init-y) $(core-y) $(libs-y2) \ $(drivers-y) $(net-y) $(virt-y) export KBUILD_VMLINUX_LIBS := $(libs-y1) export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
其中core-y是内核core部分obj列表:
core-y := $(patsubst %/, %/built-in.a, $(core-y))
将dtb加入到这部分,修改arch/arm/Makefile增加core-y对arch/arm/boot/dts/built-in.a的链接:
core-$(CONFIG_USE_BUILTIN_DTB) += arch/arm/boot/dts
修改arch/arm/boot/dts/Makefile编译文件,生成dtb对应的dtb.S文件;然后编译生成dtb.o文件,进而打包成arch/arm/boot/dts/built-in.a文件。
ifdef CONFIG_USE_BUILTIN_DTB builtindtb-y := $(patsubst "%",%,$(CONFIG_BUILTIN_DTB_NAME)) dtb-y += $(builtindtb-y).dtb obj-y += $(builtindtb-y).dtb.o .SECONDARY: $(obj)/$(builtindtb-y).dtb.S--.dtb.S将作为中间文件被创建,但是不立即删除。更多参考《Special Targets: .secondary》。 endif
编译的数据位于__dtb_start和__dtb_end之间,__dtb_start就类似于__atags_pointer。
生成dtb.S的脚本:
quiet_cmd_dt_S_dtb= DTB $@ cmd_dt_S_dtb= \ { \ echo '\#include <asm-generic/vmlinux.lds.h>'; \ echo '.section .dtb.init.rodata,"a"'; \--经过编译后dtb的数据就插入.dtb.init.rodata段中。 echo '.balign STRUCT_ALIGNMENT'; \ echo '.global __dtb_$(subst -,_,$(*F))_begin'; \--每个dtb文件还有自己的开始和结束符号。 echo '__dtb_$(subst -,_,$(*F))_begin:'; \ echo '.incbin "$<" '; \--插入dtb文件。 echo '__dtb_$(subst -,_,$(*F))_end:'; \ echo '.global __dtb_$(subst -,_,$(*F))_end'; \ echo '.balign STRUCT_ALIGNMENT'; \ } > $@ $(obj)/%.dtb.S: $(obj)/%.dtb FORCE $(call if_changed,dt_S_dtb)
2.2 修改启动阶段dtb初始化流程
在setup_arch()的使用__atags_pointer之前,添加如下代码:
#ifdef CONFIG_USE_BUILTIN_DTB if(!__atags_pointer) __atags_pointer = virt_to_phys(__dtb_start);--此处__atags_pointer为由uboot传入的物理地址;而__dtb_start是虚拟地址,需要virt_to_phys将虚拟地址转换为物理地址。后面使用的时候再通过phys_to_virt转换为虚拟地址。
#endif
如果使用内嵌dtb,则启动linux是bootz kernel_addr - dtb_addr的dtb_addr为0。
2.3 uboot修改
uboot启动linux的命令为bootz <kernel addr> - <dtb addr>。如果使用外部地址则将<dtb addr>指向加载的dtb内存;否则<dtb addr>为0,则使用builtin dtb。
bootz <kernel addr> - 0