Post

boot up of u-boot

boot up of u-boot

Preface

U-Boot (subtitled “the Universal Boot Loader” and often shortened to U-Boot) is an open-source boot loader used in embedded devices to perform various low-level hardware initialization tasks and boot the device’s operating system kernel. It is available for a number of computer architectures, including M68000, ARM, Blackfin, MicroBlaze, AArch64, MIPS, Nios II, SuperH, PPC, RISC-V and x86.

  U-boot作为很流行的开源bootloader项目,它可以用作first stage bootloader(u-boot SPL),也可以做second stage bootloader。所谓first stage bootloader,是从ROM出来的第一段程序,一般运行在sram中,整体size比较小,用来做基本硬件的初始化,比如clock,DDR,等DDR初始化好了,还会把后面的binaries从flash上搬运到DDR上,在ARMv8以上的系统中一般会run在EL3。而second stage bootloader主要运行在DDR中,为操作系统的启动做准备。考虑到first stage bootloader一般都不开源(也会有二般情况),而U-boot是GPL license的,这种情况就要考虑其他的开源bootloader了。U-boot还提供了CLI和各种command,并提供了简单的方法添加自定义command,也可以用来做各种工具软件,比如diagnostic程序。

Boot of U-boot

Board: QEMU
U-boot Version: v2023.07.02。

  首先找到Entry Point,一般是定义在lds(Linker Description Script)中,ARMv8的lds就放在它的目录下arch/arm/cpu/armv8/u-boot.lds,它指定的entry为_start
  本来想找一个tool导出从入口函数的call graph的,试用了几个开源的都不大行,比如callgragh-gencallycrabviz啥的,它们在处理汇编和一些宏的时候都有问题,导致call graph联系不起来,很凌乱。手动整理的call graph及解析如下:

  • _start
    • reset
      • save_boot_params
        • save_boot_params_ret
          • pie fix up if CONFIG_POSITION_INDEPENDENT
            这个选项打开的时候,只要放在4KB对齐的地址,都可以relocate跑起来。
          • switch exception level
            U-boot可以运行在ARM的各个exception level。
          • set vbar if SPL
          • initialize CNTFRQ if EL3 and CONFIG_COUNTER_FREQUENCY defined
            如果U-boot之前没有设过generic timer,U-boot要设好frequency并enable
        • apply_core_errata
        • lowlevel_init
          • gic_init (gic distributor and cpu interface)
        • spin_table_secondary_jump (if SMP and spintable defined)
        • select SP_ELx
        • _main
          • board_init_f_alloc_reserve
          • board_init_f_init_reserve
          • board_init_f
            • initcall_run_list
              循环调用定义在static const init_fnc_t init_sequence_f[]中的函数。
              • setup_mon_len
              • fdtdec_setup
              • initf_malloc
              • log_init
              • initf_bootstage
              • event_init
              • arch_cpu_init
                默认是空函数,有的会在这里打开MMU(MPU for Cortex-M)增加performance。
              • mach_cpu_init
              • initf_dm
                初始化U-boot driver model,DM是类Linux的driver model。
              • board_early_init_f 可以在这里设pinmux,设置GPIO状态,等等
              • timer_init
                之前提到的generic timer也可以在这里初始化,有些SoC还有特别的寄存器去enable timer(见link中的注释),也可以在这里执行。
              • env_init
              • init_baud_rate
                一般用uart做console,baud rate默认为115200。
              • serial_init
                用DM的情况下,从fdt中拿到baud rate的值,更新gd和env(上面那步看来只在非DM下有用)。
              • console_init_f
              • display_options
                打印version_string和build tag Example:
                1
                
                U-Boot 2023.07.02 (Nov 02 2024 - 16:40:26 +0800)
                
              • dram_init
                这个函数比较重要,是必须实现的函数,主要是从dtb或者其他地方拿到可用的DRAM的region,最好是连续的,然后赋值给gd->ram_size和gd_rambase。
              • post_init_f
                这个函数很少用,但看了下,发现它做了很多test,如果遇到问题,可以打开它来test一下。主要test函数定义在
              • testdram
                U-boot还单独定义一个宏和函数做dram test,为啥不合并到上一步?
              • setup_dest_addr
                这个函数里设定gd里的ram_base,ram_top啥的,需要注意的是,get_effective_memsize和board_get_usable_ram_top都是weak function,通过override这两个函数可以重新界定U-boot运行所在内存区域,而dram_init里拿到的全部memory可以看作需要设MMU做map的memory。那多出来map的memory有啥用?其他硬件有可能用啊。
              • reserve_xxxxxx 接下来一堆reserve啥啥的函数,除了计算U-boot relocate的地址,还预留出诸如new_gd,heap,stack,trace buffer等等的memory。以qemu为例,大致的memory layout如下:
                1
                2
                3
                4
                5
                6
                7
                8
                9
                10
                11
                12
                13
                14
                15
                16
                17
                
                end       0x82100000 -------
                                     | TLB table from 0x81ae0000 to 0x820f2000
                TLB addr  0x81ae0000 -------
                                     | 1138k for U-boot at: 0x819c3000
                relocaddr 0x819c3000 -------
                                     | 16640k for malloc() at: 0x80983000
                malloc    0x80983000 -------
                                     | 13b Bytes for Board Info at: 0x80982f70
                bd_info   0x80982f70 -------
                                     | 448 Bytes for Global Data at: 0x80982db0
                gd        0x80982db0 -------
                                     | 9184 Bytes for FDT at: 0x809809d0
                fdt_blob  0x809809d0 -------
                                     |
                sp start  0x809809c0 -------
                                     | 
                start     0x40000000 -------
                
              • reloc_fdt
                把fdt copy到新的位置。
              • setup_reloc
                更新gd和copy gd。
          • relocate_code
            把U-boot的code copy到新的地址,这里就是0x819c3000。这里用的是b,没有用bl,但这个函数最后用了ret,这是因为lr在调用这个函数之前经过计算已经设为relocate过后的地址了,汇编如下。
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            
            	adr	lr, relocation_return
            	/* Add in link-vs-runtime offset */
              adrp	x0, _start		/* x0 <- Runtime value of _start */
              add	x0, x0, #:lo12:_start
              ldr	x9, _TEXT_BASE		/* x9 <- Linked value of _start */
              sub	x9, x9, x0		/* x9 <- Run-vs-link offset */
              add	lr, lr, x9
              ......
              /* Add in link-vs-relocation offset */
              ldr	x9, [x18, #GD_RELOC_OFF]	/* x9 <- gd->reloc_off */
              add	lr, lr, x9	/* new return address after relocation */
            

            ret回来就已经运行在新地址上了。

          • clear_bss
          • board_init_r
            它也和board_init_f一样定义了init_sequence_r
            • initr_trace
            • initr_caches
              cache在这里enable了。
            • initr_malloc
              调用mem_malloc_init把heap pool创建好。
            • log_init
            • initr_dm
            • board_init
              可以做些board specific的初始化操作。
            • initr_binman
            • initr_dm_devices
            • stdio_init_tables
            • serial_initialize
            • arch_early_init_r
            • power_init_board
            • initr_xxx 各种device的init,如flash,mmc等等。
            • console_init_r /* fully init console as a device */
            • interrupt_init
            • board_late_init
              注意,early和late init都有宏控制。
            • run_main_loop
              • main_loop
                • cli_init
                • run_preboot_environment_command 跑定义的preboot command。
                • process_button_cmds
                  跑定义的button command。注意,这里只能跑一个就退出了。
                • bootdelay_process
                • autoboot_command
                • cli_loop
                  • cli_simple_loop 如果前面没有进入到特定的flow,最终就进入command line模式了。

  嗯,U-boot的boot过程基本就是上面的步骤,中间省略了一些无关紧要的调用。

Reference

Das U-boot
u-boot github

This post is licensed under CC BY 4.0 by the author.

© sfeng. Some rights reserved.

Using the Chirpy theme for Jekyll.