LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

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()之前主要做两件事:

  1. 初始化BSS段为0。
  2. 初始化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

posted on 2023-12-15 23:59  ArnoldLu  阅读(447)  评论(0)    收藏  举报

导航