Note: This paper analyzes the u-boot source code under ARM architecture. U-boot version: 4.1.15
gd can be seen everywhere in the u-boot source code, so let's take a look at the mysterious gd
This article contains two contents:
(1) How is gd defined
(2)gd_t structure definition
1, How is gd defined
In the source code of U-Boot, register r9 is used to represent the global data structure gd. The following code snippet is from
u-boot Dir/arch/arm/include/asm/global_data.h
#ifdef __clang__ #define DECLARE_GLOBAL_DATA_PTR #define gd get_gd() static inline gd_t *get_gd(void) { gd_t *gd_ptr; #ifdef CONFIG_ARM64 /* * Make will already error that reserving x18 is not supported at the * time of writing, clang: error: unknown argument: '-ffixed-x18' */ __asm__ volatile("mov %0, x18\n" : "=r" (gd_ptr)); #else __asm__ volatile("mov %0, r9\n" : "=r" (gd_ptr)); #endif return gd_ptr; } #else #ifdef CONFIG_ARM64 #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18") #else #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9") #endif #endif
As shown in the above code, when it is not ARM64 and clang, the essence of the code is:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
So how to define a register variable? According to the GCC user manual, the definitions are as follows:
register int *foo asm ("reg");
The register keyword is required, and asm ("reg") is an embedded assembly. Therefore, register volatile gd_t *gd asm ("r9") means that the gd pointer is stored in the r9 register, which is related to the CPU architecture. It should be noted that qualifiers such as static const and volatile cannot be used, otherwise it may be contrary to the expected assumption. It should be noted that volatile, even if added, can not prevent the compiler from optimizing (because the definition in U-Boot does not comply with the GCC compiler specification. If you use the grep DECLARE_GLOBAL_DATA_PTR -nr command to search in the U-Boot source code, you will find that some definitions do not have volatile).
To define register variables, the above definition alone is not enough. Because although it is declared to use Reg to represent foo pointers, the compiler will still use reg for other purposes (parameter passing, etc.). If you want the compiler not to use the reg register for other purposes, you also need to use the [- ffixed reg] compilation option to explicitly tell the compiler that the reg register is a fixed purpose register.
Under the u-boot source code, the following variables are defined in the arch/arm/config.mk file. You can find the '- ffixed reg' option (u-boot uses r9 register). Therefore, when compiling, the compiler treats r9 register as a fixed register.
PLATFORM_RELFLAGS += -ffunction-sections -fdata-sections
-fno-common -ffixed-r9
If gd is a global pointer variable, the variable points to gd_t structure type data. However, for gd variable, it is not initialized at the time of definition, that is, it is not given an initial value. Where is the initial value given to gd variable? Continue with the analysis below
##2, gd_t structure definition
The gd variable is a pointer to gd_ Pointer to T structure, gd_ The T structure is defined as follows, and the code fragment comes from
u-boot dir/include/asm-generic/global_data.h
typedef struct global_data { bd_t *bd; unsigned long flags; unsigned int baudrate; unsigned long cpu_clk; /* CPU clock in Hz! */ unsigned long bus_clk; /* We cannot bracket this with CONFIG_PCI due to mpc5xxx */ unsigned long pci_clk; unsigned long mem_clk; #if defined(CONFIG_LCD) || defined(CONFIG_VIDEO) unsigned long fb_base; /* Base address of framebuffer mem */ #endif #if defined(CONFIG_POST) || defined(CONFIG_LOGBUFFER) unsigned long post_log_word; /* Record POST activities */ unsigned long post_log_res; /* success of POST test */ unsigned long post_init_f_time; /* When post_init_f started */ #endif #ifdef CONFIG_BOARD_TYPES unsigned long board_type; #endif unsigned long have_console; /* serial_init() was called */ #ifdef CONFIG_PRE_CONSOLE_BUFFER unsigned long precon_buf_idx; /* Pre-Console buffer index */ #endif unsigned long env_addr; /* Address of Environment struct */ unsigned long env_valid; /* Checksum of Environment valid? */ unsigned long ram_top; /* Top address of RAM used by U-Boot */ unsigned long relocaddr; /* Start address of U-Boot in RAM */ phys_size_t ram_size; /* RAM size */ #ifdef CONFIG_SYS_MEM_RESERVE_SECURE #define MEM_RESERVE_SECURE_SECURED 0x1 #define MEM_RESERVE_SECURE_MAINTAINED 0x2 #define MEM_RESERVE_SECURE_ADDR_MASK (~0x3) /* * Secure memory addr * This variable needs maintenance if the RAM base is not zero, * or if RAM splits into non-consecutive banks. It also has a * flag indicating the secure memory is marked as secure by MMU. * Flags used: 0x1 secured * 0x2 maintained */ phys_addr_t secure_ram; #endif unsigned long mon_len; /* monitor len */ unsigned long irq_sp; /* irq stack pointer */ unsigned long start_addr_sp; /* start_addr_stackpointer */ unsigned long reloc_off; struct global_data *new_gd; /* relocated global data */ #ifdef CONFIG_DM struct udevice *dm_root; /* Root instance for Driver Model */ struct udevice *dm_root_f; /* Pre-relocation root instance */ struct list_head uclass_root; /* Head of core tree */ #endif #ifdef CONFIG_TIMER struct udevice *timer; /* Timer instance for Driver Model */ #endif const void *fdt_blob; /* Our device tree, NULL if none */ void *new_fdt; /* Relocated FDT */ unsigned long fdt_size; /* Space reserved for relocated FDT */ struct jt_funcs *jt; /* jump table */ char env_buf[32]; /* buffer for getenv() before reloc. */ #ifdef CONFIG_TRACE void *trace_buff; /* The trace buffer */ #endif #if defined(CONFIG_SYS_I2C) int cur_i2c_bus; /* current used i2c bus */ #endif #ifdef CONFIG_SYS_I2C_MXC void *srdata[10]; #endif unsigned long timebase_h; unsigned long timebase_l; #ifdef CONFIG_SYS_MALLOC_F_LEN unsigned long malloc_base; /* base address of early malloc() */ unsigned long malloc_limit; /* limit address */ unsigned long malloc_ptr; /* current address */ #endif #ifdef CONFIG_PCI struct pci_controller *hose; /* PCI hose for early use */ phys_addr_t pci_ram_top; /* top of region accessible to PCI */ #endif #ifdef CONFIG_PCI_BOOTDELAY int pcidelay_done; #endif struct udevice *cur_serial_dev; //Current serial device struct arch_global_data arch; /* architecture-specific data */ #ifdef CONFIG_CONSOLE_RECORD struct membuff console_out; /* console output */ struct membuff console_in; /* console input */ #endif #ifdef CONFIG_DM_VIDEO ulong video_top; /* Top of video frame buffer area */ ulong video_bottom; /* Bottom of video frame buffer area */ #endif } gd_t;
For gd_t structure type, pay attention to two points:
(1) Each architecture has its own private data fields.
(2) The data defined above will be placed in the memory available early after system startup, such as DPRAM on MPC8xx/MPC82xx or locked data cache. So that the smallest set of global variables can be stored during system initialization before the memory controller of the system is set.
3, How is gd assigned during u-boot
As shown in the figure above, the code comes from the... / arch/arm/lib/crt0.S file. In line 89, assign the value in r0 to r9. Because r9 stores the gd variable, the gd variable is given the initial value at this moment. Then, on line 90, call the function board_init_f_init_reserve adjusts the gd pointing space and reserves the memory space for gd pointing data.
board_ init_ f_ init_ The reserve function is defined in... / common / init / board_ The init. C file is also independent of the specific architecture. The following code snippet shows:
void board_init_f_init_reserve(ulong base) { struct global_data *gd_ptr; #ifndef _USE_MEMCPY int *ptr; #endif /* * clear GD entirely and set it up. * Use gd_ptr, as gd may not be properly set yet. */ gd_ptr = (struct global_data *)base; /* zero the area */ #ifdef _USE_MEMCPY memset(gd_ptr, '\0', sizeof(*gd)); #else for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); ) *ptr++ = 0; #endif /* set GD unless architecture did it already */ #if !defined(CONFIG_ARM) arch_setup_gd(gd_ptr); #endif /* next alloc will be higher by one GD plus 16-byte alignment */ base += roundup(sizeof(struct global_data), 16); /* * record early malloc arena start. * Use gd as it is now properly set for all architectures. */ #if defined(CONFIG_SYS_MALLOC_F) /* go down one 'early malloc arena' */ gd->malloc_base = base; /* next alloc will be higher by one 'early malloc arena' size */ base += CONFIG_SYS_MALLOC_F_LEN; #endif }