Start-up process analysis of transplanting uboot (2) _uboot from 0

Keywords: Linux REST network C

After the configuration of the previous article, we can compile a uboot.bin by making, but this is not enough. First of all, the uboot at this time does not meet the format requirements of the bootloader for Samsung chips. At the same time, uboot.bin is not configured with our development board, and can not be used. The prerequisite for such personalized configuration is to have a good understanding of uboot boot process and compiling system. This paper mainly discusses the former. In Samsung's SoC, the start-up process can be divided into three stages: BL0, BL1, BL2, BL3. Samsung's own manuals have different interpretations of BL1. One is to sum up the programs running in iRAM as BL1; the other is to use Samsung's encrypted code bl1.bin as BL1 and the rest of iRAM as BL2.

  • BL0: The starting addresses of ARM are all zero addresses. Samsung chips usually map 0 addresses to iROM. BL0 is the startup code solidified in iROM. It is mainly responsible for loading BL1.
  • BL1: Samsung's encryption code for bootloader, bl1.bin, is placed on the head of uboot.bin in the peripheral and loaded into iRAM with some uboot.bin.
  • BL2: After removing bl1.bin from the code in iRAM from the maximum 14K header of uboot.bin copied in nand/sd/usb, it is responsible for setting CPU as SVC mode, closing MMU, interrupt, iCache, watchdog, initializing DRAM, initializing clock, initializing serial port, setting stack, verifying BL2 and moving it to the high address of DRAM, relocating it to DRAM to execute BL3.
  • BL3: The complete code of uboot executed in memory after code redirection. It is responsible for initializing peripherals, updating vector tables, clearing BSS, preparing kernel startup parameters, loading and running OS kernel.

This process can be understood with the help of the following figure

We often say that uboot is a two-stage bootloader, which means that the above BL2 and BL3. BL2 mainly do hardware-related initialization and compile in assembly; BL3 mainly prepares for the operation environment of the operating system, mainly in C. Here, we take the ARM platform as an example to analyze its start-up process. Here are the main files involved in the startup process

arch/arm/cpu/armv7/start.S
board/samsung/myboard/lowlevel_init.S
arch/arm/lib/crt0.S
arch/arm/lib/board.c
arch/samsung/myboard/myboard.c

BL2

The main files and tasks of BL2 are as follows

arch/arm/cpu/armv7/start.S
           1. Set up CPU by SVC Pattern
           2. Close MMU
           3. Close Cache
           4. Jump To lowlevel_init.S low_level_init
board/samsung/origen/lowlevel_init.S
           5. Initialization Clock
           6. Initialize memory
           7. Initialize Serial Port
           8. Close the watchdog
           9. Jump To crt0.S _main
arch/arm/lib/crt0.S
           10. Setup stack
           11. Initialization C Running environment
           12. call board_init_f()
arch/arm/lib/board.c
           13. board_init_f Global information GD Structural filling
arch/arm/lib/crt0.S
           14. Code relocation------------BL2 Final work, Enter after execution DRAM implement BL2

start.S

 39 .globl _start
 40 _start: b       reset
 41         ldr     pc, _undefined_instruction
 42         ldr     pc, _software_interrupt
 43         ldr     pc, _prefetch_abort
 44         ldr     pc, _data_abort
 45         ldr     pc, _not_used
 46         ldr     pc, _irq
 47         ldr     pc, _fiq

--40 - > Exception Vector Table Settings

126 reset:
127         bl      save_boot_params
131         mrs     r0, cpsr
132         bic     r0, r0, #0x1f
133         orr     r0, r0, #0xd3
134         msr     cpsr,r0

- 126 - > Set CPU to SVC mode

The following three lines of code are very important and are the intersection of the BL2 startup process

154         bl      cpu_init_cp15
155         bl      cpu_init_crit
158         bl      _main

- 154 - > jump to execute cpu_init_cp15, that is, initialize CP15 coprocessor
- 155 - > jump to execute cpu_init_crit,
- 158 - > Jump Execution_main

287 ENTRY(cpu_init_cp15)
291         mov     r0, #0                  @ set up for MCR
292         mcr     p15, 0, r0, c8, c7, 0   @ invalidate TLBs
293         mcr     p15, 0, r0, c7, c5, 0   @ invalidate icache
294         mcr     p15, 0, r0, c7, c5, 6   @ invalidate BP array
295         mcr     p15, 0, r0, c7, c10, 4  @ DSB
296         mcr     p15, 0, r0, c7, c5, 4   @ ISB
297 
301         mrc     p15, 0, r0, c1, c0, 0
302         bic     r0, r0, #0x00002000     @ clear bits 13 (--V-)
303         bic     r0, r0, #0x00000007     @ clear bits 2:0 (-CAM)
304         orr     r0, r0, #0x00000002     @ set bit 1 (--A-) Align
305         orr     r0, r0, #0x00000800     @ set bit 11 (Z---) BTB
307         bic     r0, r0, #0x00001000     @ clear bit 12 (I) I-cache
311         mcr     p15, 0, r0, c1, c0, 0
312         mov     pc, lr                  @ back to my caller
313 ENDPROC(cpu_init_cp15)

- 291 - > Close Cache
301 - > Close MMU

324 ENTRY(cpu_init_crit)
331         b       lowlevel_init           @ go setup pll,mux,memory
332 ENDPROC(cpu_init_crit)

- 331 - > jump to lowlevel_init at board/samsung/origen/lowlevel_init.S for board-level related settings.

lowlevel_init.S

This is the initialization file located in the directory. It mainly completes the initialization of the specific development board, including clock, memory and serial port.

 82         bl system_clock_init
 85         bl mem_ctrl_asm_init
 87 1:
 88         /* for UART */
 89         bl uart_asm_init
 90         bl tzpc_init
 91         pop     {pc}
114 system_clock_init:
329 uart_asm_init:
357 tzpc_init:

- 82 - > Initialize the system clock, that is, jump to 114 lines
- 85 - > Initialize system memory
89 - > Initialize UART serial port, that is, jump to 329 lines
- 90 - > Initialize TrustZone Protector Controller, which jumps to 357 lines

After executing lowlevel_init.S, according to the above three lines of code, the execution process should go back to start.S, execute 156 lines and jump to _main.

crt0.S

The first task is to set up the stack and prepare the environment for running C language.

 96 _main:
102 #if defined(CONFIG_NAND_SPL)
103         /* deprecated, use instead CONFIG_SPL_BUILD */
104         ldr     sp, =(CONFIG_SYS_INIT_SP_ADDR)
105 #elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
106         ldr     sp, =(CONFIG_SPL_STACK)
107 #else
108         ldr     sp, =(CONFIG_SYS_INIT_SP_ADDR)
109 #endif
110         bic     sp, sp, #7      /* 8-byte alignment for ABI compliance */
111         sub     sp, #GD_SIZE    /* allocate one GD above SP */
112         bic     sp, sp, #7      /* 8-byte alignment for ABI compliance */
113         mov     r8, sp          /* GD is above SP */
114         mov     r0, #0
115         bl      board_init_f

_main
- 104 - > Initialize SP to prepare for C
- 110 - > Save 128B and put GD structure to store global information.
The address of 111 - > GD is in r8.
- 115 - > Jump to board_init_f(), the first C code executed in the entire initialization process

board.c

The following function is the first C function executed during the uboot initialization process, which can be regarded as the entry function of this file. One of the most important is to prepare the global information GD structure, from which the kernel startup parameters come.

209 typedef int (init_fnc_t) (void);
243 init_fnc_t *init_sequence[] = {
244         arch_cpu_init,          /* basic arch cpu dependent setup */
245         mark_bootstage,
246 #ifdef CONFIG_OF_CONTROL
247         fdtdec_check_fdt,
            ...
277 void board_init_f(ulong bootflag)
278 {
        ...
291         gd->mon_len = _bss_end_ofs;
292 #ifdef CONFIG_OF_EMBED
293         /* Get a pointer to the FDT */
294         gd->fdt_blob = _binary_dt_dtb_start;
295 #elif defined CONFIG_OF_SEPARATE
296         /* FDT is at end of image */
297         gd->fdt_blob = (void *)(_end_ofs + _TEXT_BASE);
298 #endif
299         /* Allow the early environment to override the fdt address */
300         gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
301                                                 (uintptr_t)gd->fdt_blob);
302 
303         for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
304                 if ((*init_fnc_ptr)() != 0) {
305                         hang ();
306                 }
307         }
        ...

board_init_f()
--243--> Global array of function pointers, each pointer is int (*ptr)(void)Type.
--291-->mon_len You can see from the link script that what's stored is uboot Code size;
--294-->fdt_blob Number and address of storage equipment;
--303--Traversal function pointer array init_sequence Each member of the array executes every initialization function once, which can be used for reference.

crt0.S

When the function board_init_f() returns, we continue to execute the part after 115 lines in crt0.S. The main task is to perform code relocation. After that, we find the following sentences which are most interesting.

163         /* call board_init_r(gd_t *id, ulong dest_addr) */
164         mov     r0, r8                  /* gd_t */
165         ldr     r1, [r8, #GD_RELOCADDR] /* dest_addr */
166         /* call board_init_r */
167         ldr     pc, =board_init_r       /* this is auto-relocated! */

- 167 - > jump to board_init_r function to execute, this jump out of the file statement will be executed, will not come back, start to execute BL3.

BL3

The documents and tasks involved in this phase are as follows

arch/arm/lib/crt0.S
arch/arm/lib/board.c
           1. board_init_r()Is the entrance to the customized board directory
common/main.c
           2. main_loop()Close interrupts, execute commands, and load boot kernels

board.c

This is also the last time to jump to this file. The execution function is as follows

519 void board_init_r(gd_t *id, ulong dest_addr)
520 {
521         ulong malloc_start;
522 #if !defined(CONFIG_SYS_NO_FLASH)
523         ulong flash_size;
524 #endif
525 
526         gd->flags |= GD_FLG_RELOC;      /* tell others: relocation done */
527         bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");
528 
529         monitor_flash_len = _end_ofs;
530 
531         /* Enable caches */
532         enable_caches();
533 
534         debug("monitor flash len: %08lX\n", monitor_flash_len);
535         board_init();   /* Setup chipselects */
        ...
650          /* set up exceptions */
651         interrupt_init();
652         /* enable exceptions */
653         enable_interrupts(); 
667         eth_initialize(gd->bd);
        ...
701         /* main_loop() can return to retry autoboot, if so just run it again. */
702         for (;;) {
703                 main_loop();
704         }
705 

board_init_r()
--532-->A lot of urgent work has been done and can be opened. cache 了
--535-->crux!!!This is the customized file we are looking for. xxx.c Entry function!!!
--651-->Interrupt initialization
--653-->Enable interruption
--667-->Network Card Initialization, Function Implementation in net/eth.c,Callback board level xxx.c Medium board_eth_init()
--703-->**implement main_loop(),Realization in common/main.c,Its main function is to cycle the detection of input commands and execution, one of the environment variables. bootdelay(Self starting)The settings determine whether to start the kernel, if the delay is greater than or equal to zero,And no keys were received during the delay.,Boot the kernel

main.c

This file is the file where main_loop() is located. Whether it is to start the kernel, print information or input commands, it is executed in this function. We are mainly concerned about how this function starts the kernel. The calling relationship of the function is as follows:

main_loop()
           ├──getenv
           │           └──getenv_f
           │                      ├──env_get_char
           │                      └──envmatch
           └──run_command_list
                      └──builtin_run_command_list
                                 └──builtin_run_command
                                            ├──process_macros
                                            ├──parse_line
                                            └──cmd_process
                                                       ├──find_cmd
                                                       ├──cmd_call
                                                       │           └──cmdtp->cmd//Find the command do_bootm
                                                       │                      └──bootm_start
                                                       │                                 └──boot_start_lmb
                                                       │                                            └──arch_lmb_reserve(struct lmb *lmb)
                                                       │                                                       └──cleanup_before_linux
                                                       │                                                                  └──disable_interrupts //Closing interrupt
                                                       └──cmd_usage
                                                                             └──bootm_load_os

This is the last workflow that uboot did before starting Linux. Uoot did so much to boot the OS kernel. For ARM Linux, its environment requirements before starting are described in the kernel documents'/ Documentation/kernel-parameters.txt'and'Documentation/arm/Booting', which can explain why uboot did the above work. Starting the Linux kernel in RM platform, we make the following six requirements, which we explain one by one:

Setup and initialise the RAM.

RAM here refers to DRAM, because the Linux kernel needs to run in DRAM, and DRAM must be initialized. This part of the work is completed in BL2.

Initialise one serial port.

Initialize a serial port, bootloader should initialize a serial port, so that the kernel serial driver can automatically detect which serial port is used for the console
bootloader can use console = in taglist to specify a serial port, which is also done in BL2.

Detect the machine type.

To detect the type of board, the Linux kernel needs to know the type of board it runs on. This part of the work is also done by bootloader. After bootloader obtains the type of board in some ways, according to the description in the "arch/arm/tools/mach-types" of the kernel source code, the type number of the board is stored in the r1 register.

Setup the kernel tagged list.

Establish tag list, which is the kernel parameters, and use a struct tag to describe both the kernel source code and the uboot source code. The data structure tag and tag_header are defined in the header file "include/asm/setup.h" of the Linux kernel source code:

  1. Effective tagged list must start with ATAG_CORE and end with ATAG_NONE. The size domain of empty ATAG_CORE is 0x00002, and the size domain of ATAG_NONE is 0.
  2. Any number of tags can be placed in the list. The consequences of tags with the same name are undefined. The final value may be before or after.
  3. bootloader passes at least the location of system memory, the size of memory, and the location of the root file system to the kernel.
  4. The location of taggedlist cannot conflict with the bootp program of the kernel self-decompression area or initrd to prevent rewriting. The recommended address is the first 16KB of RAM.
  5. bootloader must place dtb in 64 bit aligned initialized memory. The format of dtb is in "Documentation/devicetree/booting-without-of.txt", which defines the size of the device tree.

    struct boot_param_header {
    __be32 magic;                   //Device Tree Magic Number, Fixed to 0xd00dfeed
    __be32 totalsize;               //Size of the entire device tree
    __be32 off_dt_struct;           //The offset of the saved block in the entire device tree
    __be32 off_dt_strings;          //The offset of saved string blocks in the device tree
    __be32 off_mem_rsvmap;          //Reserve memory area, which retains memory space that cannot be dynamically allocated by the kernel
    __be32 version;                 //Device Tree Version
    __be32 last_comp_version;       //Downward compatible version number
    __be32 boot_cpuid_phys;         //The physical id of the main cpu for startup in a multicore processor
    __be32 dt_strings_size;         //String block size
    __be32 dt_struct_size;          //Block size
    };

    In the agreement on startup parameters of the kernel, it considers that the address in R2 may be the address of the device tree or the address of tagged list. Therefore, after getting this address, the primary task of the kernel is to determine what is in the end. The basis of the determination is to determine whether the magic number of the device tree stored on its first 32 bits is 0xd00dfeed or ATAGS_CORE. See the kernel "arch/arm/kernel/head-common.h", here I have a question, since R2 may store the address of dtb, then how can the kernel find tagged list???? In practical development, the address of tagged list is usually put in r2. The kernel recommends placing DTB at 128MB at the beginning of RAM

Load initramfs.

Loading ramfs, ramfs recommended just above the device tree

Calling the kernel image

Start the kernel image. If you use zImage in flash, bootloader can load zImage directly into memory and execute it. The Linux kernel has stricter requirements on the address of the non-zImage kernel image - the image must be loaded into PAGE_OFFSET+TEXT_OFFSET. PAGE_OFFSET defines the starting address of the kernel space in the virtual address space, which is at 3GB in the 32bit system. TEXT_OFFSET denotes the size of the page table (i.e. the PGD of process 0), bootload and a piece of space for kernels to transfer parameters in the kernel space. For arm, TEXT_OFFSET is 32kB. Because the first 896MB(3GB~3GB+896MB) of the kernel space is mapped one by one, loading the kernel to the physical address 0x40000000 is actually loading to the virtual address space at the 3GB. Considering that the above 32KB is used to save page tables, kernel parameters and so on, we need to decompress the kernel to run at 0x40008000, that is, the virtual address (3GB+32KB). If we use uImage, this parameter is written into the header when making uImage. Usually we do not specify the boot parameters of uboot to load to this address, because the kernel will move to 0x40008000 after decompression, if we specify this address, then the kernel will first move to another place, decompressed and then moved back to execute, to prevent the decompressed part from overwriting and causing errors during decompression.

Whatever boot mode, the following conditions must be met when the kernel boots:

  1. r0=0;r1 = board type number; r2 = tagged list or device tree address in the kernel
  2. All IRQ FIQ must be closed
  3. Must be ARM state, SVC mode
  4. MMU must be turned off
  5. iCache can be turned off or not
  6. dCache must be turned off
  7. The DMA device must be turned off

We've analyzed the entire Uboot startup framework, and in detail, Uboot must do all of the above to meet Linux startup requirements.

Posted by rab on Thu, 11 Apr 2019 00:54:31 -0700