Understanding the Cortex-A9 U-Boot Startup Code

This article is a record of Teacher Peng’s first live broadcast on Bilibili, where he took notes while streaming. The video has been uploaded to Bilibili.

Now it has been organized into this article. If you want to learn about the detailed process of U-Boot startup code, you can enter my Bilibili space to learn together with the video.

Bilibili Username: YikouLinux

For ARM series articles, please click the following summary link:

Learn ARM from Scratch Collection

Introduction

In the previous ARM series courses, we have explained the ARM architecture, assembly instructions, exceptions, and the drivers for commonly used peripherals. Thus, we have acquired the basic skills for developing ARM series products.

This article will introduce a commonly used bootloader: U-Boot. Through the introduction of U-Boot and detailed analysis of its source code, we aim to integrate all previous ARM-related knowledge points.

1. U-Boot

1. Concept

U-Boot is a bootloader primarily used for embedded systems, supporting various computer architectures including PPC, ARM, AVR32, MIPS, x86, 68k, Nios, and MicroBlaze. It is also a free software released under the GNU General Public License.

U-Boot not only supports the booting of embedded Linux systems but also supports NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS, and Android embedded operating systems. Currently, it aims to support target operating systems such as OpenBSD, NetBSD, FreeBSD, 4.4BSD, Linux, SVR4, Esix, Solaris, Irix, SCO, Dell, NCR, VxWorks, LynxOS, pSOS, QNX, RTEMS, ARTOS, and Android.

2. Basic Functions of U-Boot

The main function list supported by U-Boot includes:

  • System boot supports NFS mount and RAMDISK (compressed or uncompressed) root filesystems; supports NFS mount and booting the kernel from FLASH (compressed or uncompressed);
  • Basic auxiliary function with powerful operating system interface capabilities; can flexibly set and pass multiple key parameters to the operating system, suitable for debugging requirements at different stages of system development and product release, with the strongest support for Linux; supports multiple storage methods for target board environment parameters, such as FLASH, NVRAM, EEPROM;
  • CRC32 check can verify whether the kernel and RAMDISK image files in FLASH are intact;
  • Device drivers for serial port, SDRAM, FLASH, Ethernet, LCD, NVRAM, EEPROM, keyboard, USB, PCMCIA, PCI, RTC, etc.;
  • Power-on self-test function for automatic detection of SDRAM and FLASH size; SDRAM fault detection; CPU model.

3. Common Commands

There are many U-Boot commands; below are only the commands needed for network boot:

Command Meaning
printenv Print U-Boot environment variables
setenv Set U-Boot environment variables
ipaddr Local IP address
serverip TFTP server’s IP address
ethaddr Ethernet MAC address
netmask Ethernet network mask
gateway Ethernet gateway
bootcmd Command to execute on automatic startup
bootargs Boot parameters passed to the Linux kernel
bootm Boots the program image stored in memory. This memory includes RAM and Flash that can be permanently saved.
bootdelay Wait seconds for executing automatic startup (bootcmd commands)
baudrate Baud rate of serial console

4. Example Configuration Parameters

Below is an example of downloading the kernel over the network and mounting NFS.

1) Ubuntu Environment

Ubuntu IP: 192.168.6.186

NFS Configuration:

Configuration file as follows:

/etc/exports

Configuration information as follows:

Understanding the Cortex-A9 U-Boot Startup Code
NFS

2) Development Board Settings

Development board IP: 192.168.6.187

Configuration commands:

setenv ipaddr 192.168.6.187      ; Board's IP
setenv serverip 192.168.6.186    ; Virtual machine's IP
setenv gatewayip 192.168.1.1     ; Gateway
saveenv                          ; Save configuration
  • Load kernel and device tree
setenv bootcmd tftp 41000000 uImage
tftp 42000000 exynos4412-fs4412.dtbootm 41000000 - 42000000

bootcmd: After U-Boot starts, it first executes this parameter to perform the subsequent commands.

Download the kernel image uImage from the TFTP server to address 41000000, and the device tree file exynos4412-fs4412.dtb to 42000000, and use the command bootm to load and start the kernel.

  • Mount NFS
setenv bootargs root=/dev/nfs nfsroot=192.168.6.186:/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.6.187

Mount NFS filesystem

  • root=/dev/nfs
  • nfsroot=192.168.6.186:/rootfs NFS server address 192.168.6.186, directory is /rootfs,
  • rw File system operation permission is writable
  • console=ttySAC2,115200 Serial port name and baud rate
  • init=/linuxrc The process to run after the kernel starts is linuxrc
  • ip=192.168.6.187 Development board address

2. Exynos-4412 SoC Boot Sequence

To understand the boot sequence of Exynos-4412, we first need to understand the memory layout of the SoC.

1. Exynos-4412 Memory Layout

Typically, the memory of a SoC is predetermined by the manufacturer during design, and users cannot change it.

Understanding the Cortex-A9 U-Boot Startup CodeWe only care about one address related to startup,

  1. iROM Inside the SoC, the manufacturer has solidified specific programs at factory time, and the program in iROM is unchangeable for users.
  1. iRAM Inside the SoC, faster speed but smaller space.
  1. DMC RAM controller, located inside the SoC, used to drive RAM. Large capacity RAM needs to be connected to this controller.

2. Booting Sequence

The booting sequence varies among different manufacturers. This article mainly uses Samsung’s Exynos-4412 SoC as a basis to explain the U-Boot boot sequence based on this board.

Understanding the Cortex-A9 U-Boot Startup Code

According to the above figure, the approximate order of system startup is as follows:

  • iROM is inside the SoC, a 64KB ROM that pools some necessary functions for system startup, such as: clock, stack.
  • iROM is responsible for loading the BL1 image from special startup peripherals into the 256KB SRAM inside the SoC. The startup peripherals are determined by the operation buttons. Depending on the value of different keys, iROM will perform different checks on the BL1 image.
  • BL1 initializes the system clock and DRAM controller, and then loads the OS image into DRAM from the startup peripherals. Depending on the value of the startup buttons, BL1 will perform different checks on the OS.
  • After startup is complete, BL1 jumps to the operating system (kernel).

iROM will select different startup devices based on the different OM pins, and the corresponding OM registers need to provide the corresponding startup information.

3. Overview of Kernel Startup Process

1. Overview of Kernel Startup Process

Understanding the Cortex-A9 U-Boot Startup Code
U-Boot startup process

As shown in the figure above:

  1. After powering on the device, it first executes the factory code in iROM, performing necessary hardware initialization and then executing U-Boot,
  1. Usually, the kernel and device tree files are placed in FLASH.
  1. After the program starts, it often first boots from FLASH, running U-Boot.
  1. Step 1: Perform hardware initialization (svc mode stack, clock, memory, serial port) Step 2: Self-relocation: Copy U-Boot from FLASH to RAM, jump to RAM to execute the remaining U-Boot code. Step 3: Copy the kernel to RAM, execute the kernel, and hand over control to the kernel.

2. Detailed Kernel Startup Process

Understanding the Cortex-A9 U-Boot Startup Code
The process from powering on the development board to starting the kernel

4. Detailed Analysis of U-Boot Startup Process Code

1. lds File

To understand the entire code flow of the U-Boot project, one must first understand the link script [Link script reference “7. Learn ARM-GNU pseudo instructions from scratch, using lds”].

This file determines the final generated image file of U-Boot and the layout of each segment.

The U-Boot link script is as follows:

u-boot-2013.01/arch/arm/cpu/u-boot.lds

File content:

26 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
27 OUTPUT_ARCH(arm)
28 ENTRY(_start)
29 SECTIONS
30 {
31     . = 0x00000000;
32 
33     . = ALIGN(4);
34     .text :
35     {
36         __image_copy_start = .;
37         CPUDIR/start.o (.text*)
38         *(.text*)
39     }
40 
41     . = ALIGN(4);
42     .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
43 
44     . = ALIGN(4);
45     .data : {
46         *(.data*)
47     }
48 
49     . = ALIGN(4);
50 
51     . = .;
52 
53     . = ALIGN(4);
54     .u_boot_list : {
55     #include <u-boot.lst>
56     }
57 
58     . = ALIGN(4);
59 
60     __image_copy_end = .;
61 
62     .rel.dyn : {
63         __rel_dyn_start = .;
64         *(.rel*)
65         __rel_dyn_end = .;
66     }
67 
68     .dynsym : {
69         __dynsym_start = .;
70         *(.dynsym)
71     }
72 
73     _end = .;
74 
75     /*
76      * Deprecated: this MMU section is used by pxa at present but
77      * should not be used by new boards/CPUs.
78      */
79     . = ALIGN(4096);
80     .mmutable : {
81         *(.mmutable)
82     }
83 
84     .bss __rel_dyn_start (OVERLAY) : {
85         __bss_start = .;
86         *(.bss*)
87          . = ALIGN(4);
88         __bss_end__ = .;
89     }
90 
91     /DISCARD/ : { *(.dynstr*) }
92     /DISCARD/ : { *(.dynamic*) }
93     /DISCARD/ : { *(.plt*) }
94     /DISCARD/ : { *(.interp*) }
95     /DISCARD/ : { *(.gnu*) }
96 }
97 

Core content explanation:

27 OUTPUT_ARCH(arm)       :    This image runs on ARM architecture hardware
28 ENTRY(_start)          :    The entry point of the program is _start
29 SECTIONS
30 {
31  . = 0x00000000;      :   The linking address of the program, not the running address [U-Boot must be position-independent code]
34     .text :
35     {
36         __image_copy_start = .;    : Macro corresponding to the address of the entire program after compilation, initial position of self-relocation code
37         CPUDIR/start.o (.text*)    : The code segment in the first target file CPUDIR/start.o
38         *(.text*)                  : The code segments of the remaining target files
39     }
60     __image_copy_end = .;          : End of self-relocation code

BSS global uninitialized variables and global variables initialized to 0 are located in the segment:

84     .bss __rel_dyn_start (OVERLAY) : {
85         __bss_start = .;
86         *(.bss*)
87          . = ALIGN(4);
88         __bss_end__ = .;
89     }

2. Overview of U-Boot Startup Code Flow

The code is analyzed only up to the U-Boot command line, at the position of function main_loop().

Understanding the Cortex-A9 U-Boot Startup Code

3. Detailed Analysis of Startup Code

The _start entry is located in the following file:

u-boot-2013.01/arch/arm/cpu/armv7/start.S

Phase One:

Understanding the Cortex-A9 U-Boot Startup Code

Phase Two

The second phase code starts from _main:

Understanding the Cortex-A9 U-Boot Startup Code

For detailed explanations of the above code, please learn synchronously with the Bilibili video.

5. Several Key Knowledge Points of U-Boot Startup

  1. How to determine the position of the first machine instruction?

The link script determines the memory layout.

The U-Boot link script is as follows:

u-boot-2013.01/arch/arm/cpu/u-boot.lds

File content:

28 ENTRY(_start)
29 SECTIONS
30 {
31     . = 0x00000000;
32 

The entry point of U-Boot is _start

The linking address is 0x00000000

  1. How does U-Boot relocate code?

The code is located at:

u-boot-2013.01/arch/arm/cpu/armv7/start.S

The relocation code is as follows:

ENTRY(relocate_code)
 mov r4, r0 /* save addr_sp */
 mov r5, r1 /* save addr of gd */
 mov r6, r2 /* save addr of destination */

 adr r0, _start
 cmp r0, r6
 moveq r9, #0  /* no relocation. relocation offset(r9) = 0 */
 beq relocate_done  /* skip relocation */
 mov r1, r6   /* r1 <- scratch for copy_loop */
 ldr r3, _image_copy_end_ofs
 add r2, r0, r3  /* r2 <- source end address     */

copy_loop:
 ldmia r0!, {r9-r10}  /* copy from source address [r0]    */
 stmia r1!, {r9-r10}  /* copy to   target address [r1]    */
 cmp r0, r2   /* until source end address [r2]    */
 blo copy_loop

For details, refer to Chapter 4, Section 3.

  1. How does U-Boot determine whether the boot is from a power-off state or from a sleep state?
board/samsung/fs4412/lowlevel_init.S

The code is as follows:

 41   lowlevel_init:
 54     /* AFTR wakeup reset */
 55     ldr r2, =S5P_CHECK_DIDLE
 56     cmp r1, r2
 57     beq exit_wakeup
 58 
 59     /* LPA wakeup reset */
 60     ldr r2, =S5P_CHECK_LPA
 61     cmp r1, r2
 62     beq exit_wakeup
 63 
 64     /* Sleep wakeup reset */
 65     ldr r2, =S5P_CHECK_SLEEP
 66     cmp r1, r2
 67     beq wakeup_reset

 112 wakeup_reset:
 113     bl system_clock_init
 114     bl mem_ctrl_asm_init
 115     bl tzpc_init
 116 
 117 exit_wakeup:
 118     /* Load return address and jump to kernel */
 119     ldr r0, =(EXYNOS4_POWER_BASE + INFORM0_OFFSET)
 120 
 121     /* r1 = physical address of exynos4210_cpu_resume function */
 122     ldr r1, [r0]
 123 
 124     /* Jump to kernel*/
 125     mov pc, r1

From the above, we can see that when a mobile phone enters sleep for various reasons, it protects the context of the currently executing program and writes specific data to some PMIC registers to indicate why it entered sleep.

However, the phone is not completely powered off but is in a low-power mode, so the RAM still has data. Therefore, after this startup, it only needs to read the corresponding values from special registers to know the reason for the previous sleep and restore the context before sleep.

  1. After U-Boot code is moved to RAM, the running address of the code changes. How to ensure that the program jump does not go wrong?

In addition to ensuring that U-Boot code is position-independent, .rel.dyn helps us solve this, mainly because the compiler does a lot of work for us.

For position-independent code, refer to “15. Learn ARM from Scratch – What is Position-Independent Code?”

  1. When the device starts, it may boot directly from RAM. How to know whether it started from FLASH or RAM?

File:

board/samsung/fs4412/lowlevel_init.S

Code:

lowlevel_init:
85     /*
86      * If U-Boot is already running in RAM, no need to relocate U-Boot.
87      * Memory controller must be configured before relocating U-Boot
88      * in RAM.
89      */
90     ldr r0, =0x0ffffff      /* r0 <- Mask Bits*/
91     bic r1, pc, r0      /* pc <- current addr of code */
92                     /* r1 <- unmasked bits of pc */
93     ldr r2, _TEXT_BASE      /* r2 <- original base addr in RAM */
94     bic r2, r2, r0      /* r2 <- unmasked bits of r2*/
95     cmp r1, r2          /* compare r1, r2 */
96     beq 1f          /* r0 == r1 then skip SDRAM init */

The principle is: The RAM address space is: 0x40000000-0xA0000000 0xA0000000-0x00000000 and the iROM/iRAM address’s bit:28-31 are all 0, so we only need to read the value of PC when executing lowlevel_init to determine whether the bit:28-31 is 0 to know if the code is running in RAM.

The source code, datasheet, and cross-compilation tools used in this article can be obtained by replying “uboot2013” in the WeChat public account.

Recommended Reading

[1] 18. Detailed Explanation of Cortex-A9 SPI, MCP2515 Must Read
[2] Detailed Explanation of Network/Command Line Packet Capture Tool tcpdump
[3]13. Understand Cortex-A9 RTC in One Article Must Read
[4]DNS Principles Beginner’s Guide
[5]14. Learn ARM Cortex-A9 Watchdog from Scratch
[6] What is the difference between apt and apt-get? Must Read
[7] 16. Learn ARM from Scratch, Detailed Explanation of Cortex-A9 ADC Bare Metal Driver
[8]17. Detailed Explanation of Cortex-A9, i2c PeripheralMust Read
[9][Fan Q&A 8] Implementing a Host Computer for CC2530 in C Language under Linux – 1
[10]CAN Embedded CAN Bus Beginner’s Article (Bottom Details)Must Read

Understanding the Cortex-A9 U-Boot Startup Code

To join the group, please add Yikoujun’s personal WeChat to guide you in embedded learning.

Reply “1024” in the public account to get free learning materials, looking forward to your attention~

Leave a Comment