The second stage of uboot source code analysis (based on S5PV210)

uboot系列文章所用源码(我的开发板厂商提供的)

链接:https://pan.baidu.com/s/1gzjMjV5gUFlwKSnOfPwX_A 
提取码:kw31 
--来自百度网盘超级会员V6的分享

1. Introduction to start_armboot function

1. A very long function

(1) This function starts from line 444 to line 908 of uboot/lib_arm/board.c.

(2) These 464 lines are not all, because other functions are called in it.

(3) Why is such a long function not divided into two or three functions?
  Mainly because this function constitutes the second stage of uboot startup.

2. A function constitutes the second stage of uboot

  From the assembly stage to the C language stage, the first stage is basically implemented using assembly code, and the second stage begins to use C language for development.

3. Macro analysis: what should be done in the second stage of uboot

(1) Generally speaking, the first stage of uboot is mainly to initialize some components inside the SoC (such as watchdog, clock), and then initialize DDR and complete relocation.

(2) From the perspective of macro analysis, the second stage of uboot is to initialize the remaining uninitialized hardware. Mainly SoC external hardware (such as iNand, network card chip...), uboot itself (uboot commands, environment variables, etc....). Then finally initialize the necessary things and enter the uboot command line to prepare to accept commands.

4. Thinking: Where does the second stage of uboot end?

(1) After uboot starts, it automatically runs and prints out a lot of information (this information is the information printed out when uboot is continuously initialized in the first and second stages). Then uboot enters the countdown to bootdelay seconds and executes the startup command corresponding to bootcmd.

(2) If the user does not intervene, it will execute bootcmd to enter the process of automatically starting the kernel (uboot will die); at this time, the user can press the Enter key (some uboot may be other keys, this is not fixed, you can click on Modify in the uboot code) Interrupt the automatic startup of uboot and enter the command line of uboot. Then uboot has been working under the command line, parsing the input of the command line and executing different commands.

(3) The command line of uboot is an infinite loop, and the loop body repeats continuously: receiving commands, parsing commands, and executing commands. This is the ultimate destination of uboot.

Two, start_armboot analysis

insert image description here

1、init_fnc_t

(1)typedef int (init_fnc_t) (void);	这是一个函数类型

(2)init_fnc_ptr是一个二重函数指针,二重指针的作用有2个:其中一个是用来指向一重指
针,一个是用来指向指针数组。因此这里的init_fuc_ptr可以用来指向一个函数指针数组。

2、DECLARE_GLOBAL_DATA_PTR

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

定义了一个全局变量名字叫gd,这个全局变量是一个指针类型,占4字节。用volatile修饰
表示可变的,用register修饰表示这个变量要尽量放到寄存器中,后面的asm("r8")是gcc
支持的一种语法,意思就是要把gd放到寄存器r8中。

  Comprehensive analysis, DECLARE_GLOBAL_DATA_PTR defines a global variable to be placed in register r8, the name is gd, and the type is a pointer to a gd_t type variable.

  Why should it be defined as register?
  Because this global variable gd (short for global data) is a very important global variable in uboot ( accurately speaking, this global variable is a structure with many contents in it, and the structure composed of these contents is commonly used in uboot All global variables ), this gd is often accessed in the program, so it is placed in the register to improve efficiency. Therefore, it is purely a consideration of operational efficiency and has nothing to do with functional requirements. It is not necessary.

typedef	struct	global_data {
    
    
	bd_t		*bd;           //硬件相关的信息
	unsigned long	flags;     //标志位
	unsigned long	baudrate;  //通信的波特率(控制台的((console))
	unsigned long	have_console;	/* serial_init() was called ,bool类型的变量,console:控制台,其是基于串口的,没有时,串口只能简单的工作,建立后串口即可printf,控制台是标准输入输出控制的*/
	unsigned long	reloc_off;	/* Relocation Offset ,重定位时的偏移量*/
	unsigned long	env_addr;	/* Address  of Environment struct,环境变量相关的地址 */
	unsigned long	env_valid;	/* Checksum of Environment valid?在内存的环境变量当前是否可以使用,是个bool类型变量,表明校验和是否成功 */
	unsigned long	fb_base;	/* base address of frame buffer ,缓存的起始地址*/
#ifdef CONFIG_VFD
	unsigned char	vfd_type;	/* display type */
#endif
#if 0
	unsigned long	cpu_clk;	/* CPU clock in Hz!		*/
	unsigned long	bus_clk;
	phys_size_t	ram_size;	/* RAM size */
	unsigned long	reset_status;	/* reset status register at boot */
#endif
	void		**jt;		/* jump table */
} gd_t;

  gd_t is defined in include/asm-arm/global_data.h . Many global variables are defined in gd_t, which are used by the entire uboot; there is a bd_t type pointer pointing to a bd_t type variable, this bd is the structure of the board-level information of the development board, and there are many hardware-related Parameters, such as baud rate, IP address, machine code, DDR memory distribution.

typedef struct bd_info {
    
    /*开发板的相关信息*/
    int			bi_baudrate;	/* serial console baudrate,硬件开发板的波特率 */
    unsigned long	bi_ip_addr;	/* IP Address开发板IP地址 */
    unsigned char	bi_enetaddr[6]; /* Ethernet adress,开发板网卡地址 */
    struct environment_s	       *bi_env;/*环境变量的指针*/
    ulong	        bi_arch_number;	/* unique id for this board,机器码 */
    ulong	        bi_boot_params;	/* where this board expects params,启动参数(uboot传递给内核的)的地址 */
    struct				/* RAM configuration */
    {
    
    
	ulong start;
	ulong size;
    }			bi_dram[CONFIG_NR_DRAM_BANKS];/*该结构体存储当前开发板的DDR的信息,DDR的配置*/
#ifdef CONFIG_HAS_ETH1
    /* second onboard ethernet port */
    unsigned char   bi_enet1addr[6];
#endif
} bd_t;
(1)DECLARE_GLOBAL_DATA_PTR只能 定义 了一个指针,也就是说gd里的这些全局变量并没
有被分配内存,我们在使用gd之前要给他分配内存,否则gd也只是一个野指针而已。

(2)gd和bd需要内存,内存当前没有被人管理(因为没有操作系统统一管理内存故而无法使用
malloc函数去申请内存,裸机程序也没有这个函数可以用),大片的DDR内存散放着可以随意
使用(只要使用内存地址直接去访问内存即可)。但是因为uboot中后续很多操作还需要大片
的连着内存块,因此这里使用内存要本着够用就好,紧凑排布的原则。所以我们在uboot中需
要有一个整体规划。

内存排布:
(1)uboot区	CFG_UBOOT_BASE-xx(长度为uboot的实际长度)
(2)堆区		长度为CFG_MALLOC_LEN,实际为912KB
(3)栈区		长度为CFG_STACK_SIZE,实际为512KB
(4)gd		长度为sizeof(gd_t),实际36字节
(5)bd		长度为sizeof(bd_t),实际为44字节左右
(6)内存间隔		为了防止高版本的gcc的优化造成错误。

普通内存使用是向上增的,gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); 

insert image description here

3. The for loop executes init_sequence

insert image description here
(1) init_sequence is an array of function pointers. Many function pointers are stored in the array. The functions pointed to by these are all of type init_fnc_t (the characteristic is that the receiving parameter is void type and the return value is int).

(2) init_sequence is initialized at the same time when it is defined, and the initialized function pointers are all function names. (The essence of the function name is a pointer)

(3) init_fnc_ptr is a dual function pointer that can point to the init_sequence array of function pointers .

(4) Using the for loop definitely wants to traverse the array of function pointers (the purpose of traversing is also to execute all the functions in the array of function pointers in sequence).

  Thinking: How to traverse an array of function pointers?
  There are two methods: the first and the most commonly used one, use subscripts to traverse, and use the number of array elements to stop. The second is not commonly used, but it works. It is to put a mark at the end of the effective elements of the array, and traverse to the standard in turn to stop (a bit similar to the idea of ​​a string, the last digit of the string is \0).

  We use the second way of thinking here. Because all the function pointers are stored in the array, we choose NULL as the symbol. When we traverse, we proceed sequentially from the beginning until we see the NULL flag. The advantage of this method is that there is no need to count the number of elements in the array in advance.

(5) The return values ​​of these functions of init_fnc_t are defined in the same way, all of which are: return 0 when the function is executed correctly, and return -1 when it is incorrect. So we check the function return value during traversal. If there is a function return value not equal to 0 in the traversal, hang() hangs . From the analysis of the hang function, it can be known that no errors can occur when initializing the board-level hardware during the uboot startup process. As long as there is an error, the entire startup will be terminated. There is no way except to restart the development board.

(6) These functions in init_sequence are various hardware initializations at the board level.
insert image description here

4、cpu_init、board_init

(1) cpu_init looks at the name. This function should be the initialization inside the cpu, so it is empty here.

(2) board_init is in uboot/board/samsung/x210/x210.c, this is the initialization related to the x210 development board from the name.

int board_init(void)
{
    
    
	DECLARE_GLOBAL_DATA_PTR;
#ifdef CONFIG_DRIVER_SMC911X
	smc9115_pre_init();
#endif

#ifdef CONFIG_DRIVER_DM9000
	dm9000_pre_init();
#endif

	gd->bd->bi_arch_number = MACH_TYPE;
	gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);

	return 0;
}

(3) DECLARE_GLOBAL_DATA_PTR is declared for the convenience of using gd later. It can be seen that the reason for defining the gd declaration as a macro is that we need to use gd everywhere, so we have to declare it everywhere, and it is more convenient to define it as a macro. In addition to declaring this method, you can also use the header file to include, but it is more troublesome when it comes to the path. Different versions have different paths

(4) Network card initialization. The CONFIG_DRIVER_DM9000 macro is defined in x210_sd.h. This macro is used to configure the network card of the development board. The dm9000_pre_in5it function is the initialization function of the corresponding DM9000 network card. When transplanting uboot to the development board, if you want to transplant the network card, the main work is here.

(5) This function is mainly the configuration of the GPIO and port of the network card, not the driver. Because the drivers of the network card are ready-made and correct, the driver does not need to be changed when transplanting. The key is the basic initialization here. Because these basic initializations are hardware dependent.

5、gd->bd->bi_arch_number

(1) bi_arch_number is an element in board_info, meaning: the machine code of the development board. The so-called machine code is a unique number defined by uboot for this development board.

(2) The main function of the machine code is to compare and adapt between uboot and the linux kernel.

(3) The hardware of each device in the embedded device is customized and cannot be used universally. Due to the high degree of customization of embedded devices, hardware and software cannot be adapted and used casually. This tells us: the kernel image transplanted from this development board must not be downloaded to another development board, otherwise it cannot be started, and even if it is started, it will not work normally, and there are many hidden dangers.

  So linux made a setting: give each development board a unique number (machine code), and then there is a software-maintained machine code number in uboot and linux kernel. Then the development board, uboot, and linux compare the machine code. If the machine code matches, it will start, otherwise it will not start (because the software thinks that I am not suitable for this hardware).

(4) MACH_TYPE is defined in x210_sd.h, and the value is 2456, which has no special meaning, but the number corresponding to the current development board. This number represents the machine code of the x210 development board. In the future, the machine code in the Linux kernel transplanted on this development board must also be 2456, otherwise it will not start.

(5) The machine code configured in uboot will be passed to the linux kernel as part of the parameters passed by uboot to the linux kernel. During the kernel startup process, the received machine code will be compared with its own machine code. If it is equal, it will start, if it does not want to wait, it will not start.

(6) Theoretically speaking, the machine code of a development board cannot be determined arbitrarily (for example, if multiple development boards use the same number, it will be messed up). Theoretically speaking, only uboot official has the right to issue this machine code, so after we have made a development board and transplanted uboot, theoretically, it should be submitted to uboot official for review and release the machine code (it seems to be free).

  However, there are basically no applications for domestic development boards (mainly because domestic developers are not good at English and have little contact with foreign open source communities), and they are all numbered by themselves. The problem with random numbering is that it may conflict with other people's numbers, but as long as the numbers in uboot and kernel are consistent, it will not affect the startup of your own development board.

6、gd->bd->bi_boot_params

(1) Another main element in bd_info, bi_boot_params indicates the memory address of uboot to pass parameters when starting the linux kernel.

  That is to say, when uboot passes parameters to the linux kernel, it passes in this way: uboot puts the prepared parameters (string, that is, bootargs) in an address of the memory in advance (bi_boot_params is the address value), and then uboot starts. Kernel (uboot actually passes parameters directly through registers r0, r1, and r2 when starting the kernel, and one of the registers is bi_boot_params). After the kernel starts, read bi_boot_params from the register to know where the parameters passed to me by uboot are in the memory. Then go to the place in the memory to find the bootargs.

(2) After calculation, it is known that the value of bi_boot_params in X210 is 0x30000100, and this memory address is allocated for kernel parameter passing. So pay attention when using memory in other places of uboot, and don't dare to flood this place.
insert image description here

背景:关于DDR的配置:
(1)board_init中除了网卡的初始化之外,剩下的2行用来初始化DDR。

(2)注意:这里的初始化DDR和汇编阶段lowlevel_init中初始化DDR是不同的。当时是硬件的
初始化,目的是让DDR可以开始工作。现在是软件结构中一些DDR相关的属性配置、地址设置的
初始化,是纯软件层面的。

(3)软件层次初始化DDR的原因:对于uboot来说,他怎么知道开发板上到底有几片DDR内存,
每一片的起始地址、长度这些信息呢?在uboot的设计中采用了一种简单直接有效的方式:
程序员在移植uboot到一个开发板时,程序员自己在x210_sd.h中使用宏定义去配置出来板子
上DDR内存的信息,然后uboot只要读取这些信息即可。(实际上还有另外一条思路:就是
uboot通过代码读取硬件信息来知道DDR配置,但是uboot没有这样。实际上PC的BIOS采用的
是这种)

(4)x210_sd.h的496行到501行中使用了标准的宏定义来配置DDR相关的参数。主要配置了
这么几个信息:有几片DDR内存、每一片DDR的起始地址、长度。这里的配置信息我们在
uboot代码中使用到内存时就可以从这里提取使用(uboot中使用到内存的地方都不是
直接用地址数字的,都是用宏定义的)

7、interrupt_init

(1) The name function is related to interrupt initialization, but it is not. In fact, this function is used to initialize the timer (the actual use is Timer4).

(2) There are 5 PWM timers in 210. Among them, Timer0-timer3 has a corresponding PWM signal output pin. However, Timer4 has no pins and cannot output PWM waveforms. Timer4 was not designed to output PWM waveforms (no pins, no TCMPB register), this timer is designed for timing.

(3) When Timer4 is used for timing, two registers are used: a number is stored in TCNTB, and this number is the number of timings (each time is determined by the clock, which is actually determined by the 2-level clock divider) . When we are timing, we only need to set the timing time/reference time = number, and put this number into TCNTB; we can read whether the time has been reduced to 0 through the TCNTO register. After reading 0, we know that the set time has passed. arrive.

(4) Use Timer4 for timing, because there is no interrupt support, so the CPU cannot do other things at the same time, the CPU can only use the polling method to continuously check the TCNTO register to know whether the timing is up. Because the timing of Timer4 cannot achieve microscopic parallelism. The timing in uboot is realized through Timer4. So you can't do other things during timing in uboot (consider, the typical one is bootdelay, timing is implemented in bootdelay and checking user input is implemented by polling) (5) The interrupt_init function sets timer4 to timing 10ms
insert image description here
. The key part is the get_PCLK function to obtain the PCLK_PSYS clock frequency set by the system, then set TCFG0 and TCFG1 (the default value is used if no setting is used) for frequency division, and then calculate the value that needs to be written to TCNTB when it is set to 10ms, and write it Enter TCNTB, then set it to auto reload mode, then start the timer to start timing and it will be gone.

Summary: When learning this function, the focus is to learn: access registers by defining structures, and automatically calculate setting values ​​​​through functions to set timers.

8、env_init

(1) env_init, you can tell from the name that it is an initialization related to environment variables.

(2) Why there are many env_init functions, the main reason is that uboot supports various boot media (such as norflash, nandflash, inand, sd card...), we usually put the environment variable env into where.
insert image description here
 &eemsp;The methods of accessing and operating env of various media are different. Therefore, uboot supports the operation methods of env in various media. So there are many c files starting with env_xx. Which one is actually used depends on the storage medium used by your own development board (only one of these env_xx.c will work at the same time, and the others cannot be entered, and who is included is determined by the macro configured in x210_sd.h ), for x210, we should look at the functions in env_movi.c.

(3) After basic analysis, this function is only a basic initialization or judgment of the uboot env maintained in the memory (judging whether there are any usable environment variables in it) . Currently, because we have not yet relocated the environment variable from the SD card to the DDR, the current environment variable cannot be used .

(4) Call env_relocate in the start_armboot function (line 776) to relocate the environment variable from the SD card to the DDR. After relocation, environment variables can only be fetched from DDR. Before relocation, environment variables can only be read from SD card .

9、init_baudrate

(1) init_baudrate looks at the name to initialize the baud rate of serial communication.

static int init_baudrate (void)
{
    
    
	char tmp[64];	/* long enough for environment variables */
	int i = getenv_r ("baudrate", tmp, sizeof (tmp));//返回值表示是否执行成功,一般返回0表示成功,
	gd->bd->bi_baudrate = gd->baudrate = (i > 0)//其他值(小于0,如-1)则发生错误,这里是返回的值大于0执行成功,小于0错误
			? (int) simple_strtoul (tmp, NULL, 10)
			: CONFIG_BAUDRATE;

	return (0);
}

(2) The getenv_r function is used to read the value of the environment variable. Use the getenv function to read the value of "baudrate" in the environment variable (note that the read is not an int type but a string type ), and then use the simple_strtoul function to convert the string into a digital format baud rate.

(3) The rule when baudrate is initialized is: first go to the environment variable to read the value of the environment variable "baudrate". If the read is successful, use this value as an environment variable, recorded in gd->baudrate and gd->bd->bi_baudrate; if the read is unsuccessful, use the value of CONFIG_BAUDRATE in x210_sd.h as the baud rate. It can be seen from this: the priority of environment variables is very high .

10、serial_init

(1) serial_init looks at the name to initialize the serial port. (Question: The lowlevel_init.S called in start.S has already used assembly to initialize the serial port. How can it be initialized here again? Are these two initializations repeated or are they different?)

(2) It can be seen from SI that there are many serial_init functions in uboot, and we use the serial_init function in uboot/cpu/s5pc11x/serial.c.

(3) After coming in, I found that the serial_init function actually did nothing (only a for loop, similar to a delay). Because the serial port has been initialized in the assembly stage, the initialization of the hardware registers is no longer performed here.

11、console_init_f

(1) console_init_f is the first stage initialization of console (console). _f means the first stage initialization, _r means the second stage initialization. Sometimes the initialization function cannot be completed together at one time, and some code must be mixed in the middle, so the initialization of a complete module is divided into two stages. (line 826 of start_armboot in our uboot initializes console_init_r)

(2) console_init_f is in uboot/common/console.c, just set gd->have_console to 1, nothing else is done.

12、display_banner

(1) display_banner is used for serial port output to display the uboot logo

(2) The printf function is used in display_banner to output the string version_string to the serial port. Then the above analysis shows that console_init_f has not initialized the console, how can printf it?

(3) By tracking the implementation of printf, it is found that printf->puts, and the puts function will judge whether the console in the current uboot has been initialized. If the console is initialized, call fputs to complete the serial port transmission (this line is the console); if the console has not been initialized, it will call serial_puts (and then call serial_putc to directly operate the serial port register to send the content).

(4) The console is also output through the serial port, and the non-console is also output through the serial port. What exactly is a console? And the difference without a console?

  In fact, analyzing the code will reveal that the console is a device virtualized by software. This device has a set of dedicated communication functions (send, receive...), and the communication functions of the console will eventually be mapped to the communication functions of the hardware. achieve . In fact, the communication function of the console in uboot is directly mapped to the communication function of the hardware serial port, that is to say, there is no essential difference between using or not using the controller in uboot.

(5) But in other systems, when the communication function of the console is mapped to the hardware communication function, some intermediate optimization can be done by software, such as the buffer mechanism . (The console in the operating system uses a buffering mechanism, so sometimes we printf the content but do not see the output information on the screen, because it is buffered. The information we output is only in the buffer of the console, and the buffer has not yet is refreshed to the hardware output device, especially when the output device is an LCD screen)
insert image description here
(6) The components of version_string: U_BOOT_VERSION cannot be found in the uboot source code, this variable is actually defined in the makefile , and then implemented with a macro definition in include/version_autogenerated.h generated at compile time.

13、print_cpuinfo

(1) During uboot startup:

CPU:  S5PV210@1000MHz(OK)
        APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
        MPLL = 667MHz, EPLL = 96MHz
                       HclkDsys = 166MHz, PclkDsys = 83MHz
                       HclkPsys = 133MHz, PclkPsys = 66MHz
                       SCLKA2M  = 200MHz
Serial = CLKUART 
这些信息都是print_cpuinfo打印出来的。

(2) Refer to the data sheet of the chip, compare the methods of calculating various clocks in the functions called here, and slowly analyze and understand the principles and implementation methods of these codes by yourself. This is learning.

14. Checkboard, init_func_i2c, uboot learning practice

(1) The name of checkboard means to check and confirm the development board. The function of this function is to check which development board the current development board is and print out the name of the development board.

(2) The function init_func_i2c is not actually executed, and I2C is not used in the uboot of X210. If my development board needs to expand I2C to connect external hardware in the future, configure the corresponding macro in x210_sd.h to enable it.

(3) uboot learning practice

(1)对uboot源代码进行完修改(修改内容根据自己的理解和分析来修改)
(2)make distclean然后make x210_sd_config然后make
(3)编译完成得到u-boot.bin,然后去烧录,具体烧录方式参考自己的开发板配套的烧录教程。
(4)总结:uboot就是个庞大点复杂点的裸机程序而已,我们完全可以对他进行调试。调试的
方法就是按照上面步骤,根据自己对代码的分析和理解对代码进行更改,然后重新编译烧录
运行,根据运行结果来学习。

15、dram_init

int dram_init(void)
{
    
    
	DECLARE_GLOBAL_DATA_PTR;

	gd->bd->bi_dram[0].start = PHYS_SDRAM_1;//第一片内存的起始地址
	gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;//第一片内存的大小

#if defined(PHYS_SDRAM_2)
	gd->bd->bi_dram[1].start = PHYS_SDRAM_2;//第二片内存的起始地址
	gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;//第二片内存的大小
#endif

#if defined(PHYS_SDRAM_3)
	gd->bd->bi_dram[2].start = PHYS_SDRAM_3;
	gd->bd->bi_dram[2].size = PHYS_SDRAM_3_SIZE;
#endif

	return 0;
}

(1) The name of dram_init is about the initialization of DDR. Question: DDR has been initialized in the assembly stage, otherwise it cannot be relocated to the second part to run. How to initialize DDR here again?

(2) dram_init assigns values ​​to the global variables of the DDR configuration part in gd->bd, so that gd->bd data records the configuration information of the DDR of the current development board, so that the memory can be used in uboot.

(3) From the point of view of the code, it is actually to initialize the structure array of gd->bd->bi_dram.

16、display_dram_config

static int display_dram_config (void)
{
    
    
	int i;

#ifdef DEBUG//文件开头处定义了:#undef DEBUG,故而执行else下的语句
	puts ("RAM Configuration:\n");

	for(i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
    
    
		printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
		print_size (gd->bd->bi_dram[i].size, "\n");
	}
#else
	ulong size = 0;

	for (i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
    
    
		size += gd->bd->bi_dram[i].size;
	}

	puts("DRAM:    ");
	print_size(size, "\n");
#endif

	return (0);
}

(1) The meaning of the name is to print and display the configuration information of the dram.

(2) In the startup information: (DRAM: 512 MB) is printed out in this function.

(3) Thinking: How to know the DDR configuration information of uboot during uboot operation? There is a command in uboot called bdinfo. This command can print out the values ​​of all hardware-related global variables recorded in gd->bd, so you can know the configuration information of DDR.

DRAM bank   = 0x00000000
-> start    = 0x30000000
-> size     = 0x10000000
DRAM bank   = 0x00000001
-> start    = 0x40000000
-> size     = 0x10000000

(4) init_sequence summary: it is the initialization of the board-level hardware and the initialization of the data structure in gd, gd->bd. For example: network card initialization, machine code (gd->bd->bi_arch_number), kernel parameter transfer DDR address (gd->bd->bi_boot_params), Timer4 initialization is 10ms once, baud rate setting (gd->bd->bi_baudrate and gd->baudrate), the first stage of console initialization (gd->have_console is set to 1), print uboot startup information, print cpu-related setting information, check and print the current development board name, DDR configuration information initialization (gd-> bd->bi_dram), print the total capacity of DDR.

17. Go back to the start_armboot function, CFG_NO_FLASH

insert image description here
(1) Although NandFlash and NorFlash are both Flash, generally NandFlash will be referred to as Nand instead of Flash. Generally speaking, Flash refers to Norflash. Here 2 lines of code are Norflash related.

(2) flash_init executes the initialization of the corresponding NorFlash in the development board, and display_flash_config prints the configuration information of NorFlash (Flash: 8 MB is printed here). But in fact there is no Norflash in X210. So the two lines of code can be removed (I don't know why it wasn't removed?

  Guess the reason may be that removing two lines of code will cause other places to work abnormally. It takes time to transplant and debug, and then the transplanter is too lazy to do it. In fact, if you don’t remove it, there is no other impact except that it shows 8MB Flash, which is actually useless) Samsung’s official development board has norflash, and the development board I bought from a domestic manufacturer has no flash in order to save costs.

(3) Code practice, remove Flash to see if there will be errors.

  Conclusion: After adding the CONFIG_NOFLASH macro, there is a compilation error, indicating that the code is not well transplanted, and the inclusion of that file is not controlled by this macro. So the people who transplanted just let it go.

  CONFIG_VFD and CONFIG_LCD are related to the display. This is the software architecture of the LCD display in uboot. But in fact, we use LCD instead of the software architecture set in uboot, and we added an LCD display part later.

18、mem_malloc_init

(1) The mem_malloc_init function is used to initialize the uboot heap manager. (Tell the heap manager where the memory you manage starts and ends)

static void mem_malloc_init (ulong dest_addr)
{
    
    
	mem_malloc_start = dest_addr;
	mem_malloc_end = dest_addr + CFG_MALLOC_LEN;
	mem_malloc_brk = mem_malloc_start;

	memset ((void *) mem_malloc_start, 0,
			mem_malloc_end - mem_malloc_start);//将堆内存清零
}

(2) Uboot maintains a section of heap memory by itself, and must have a set of codes to manage this heap memory. With these things in uboot, you can also use this mechanism of malloc and free (not unique to the operating system, it is also possible with the heap manager uboot, the heap manager is actually a bunch of codes, the essence is to transplant the malloc function, and truly maintain A piece of memory) to apply for memory and release memory. We mentioned above that 896KB of memory is reserved for the heap in the DDR memory.

19. Unique initialization of the development board: mmc initialization

补充学习:
https://jingyan.baidu.com/article/03b2f78cd810115ea237ae2d.html

(1) The start_armboot function is unique to the initialization of the development board from 536 to 768. It means that Samsung uses a set of uboot to satisfy several series of development boards at the same time, and then writes here some initializations unique to different development boards. Use #if conditional compilation with CONFIG_xxx macros to select a specific development board.

(2) X210-related configurations are from line 599 to line 632.
insert image description here
(3) mmc_initialize looks at the name and it should be some basic initialization related to MMC. In fact, it is used to initialize the SD/MMC controller inside the SoC. The function is in uboot/drivers/mmc/mmc.c .

(4) The operation of hardware in uboot (such as network card, SD card...) is realized by borrowing the driver in the linux kernel. There is a drivers folder under the uboot root directory, and all the files in it are from linux Various driver source files transplanted from the kernel.
Originally, the bare metal cannot be driven by linux, and uboot has been transplanted accordingly
.

(5) mmc_initialize is an MMC initialization function independent of the specific hardware architecture. All codes that use this architecture use this function to complete the MMC initialization.

  In mmc_initialize, board_mmc_init and cpu_mmc_init are called to complete the initialization of the specific hardware MMC controller. (When we initialize an SD card controller in the hardware, we first consider the level of the development board. If this level is initialized successfully, it indicates that the SD card controller is at the level of the development board. The integrated or externally extended SD card controller chip, which means In this case, the initialization should be completed in board_mmc_init, but the 210 chip itself has a built-in SD card controller, so the MMC initialization is completed in cpu_mmc_init, so there are two levels, and one level works. Here is the work of cpu_mmc_init)

(6) cpu_mmc_init is in uboot/cpu/s5pc11x/cpu.c, which indirectly calls the driver code in drivers/mmc/s3c_mmcxxx.c to initialize the hardware MMC controller. There are many layers in it, and you must have a layered thinking, otherwise you will be completely confused.

summary: In fact, the initialization of the MMC controller has already been done in the irom, because the SD card needs to read BL1 when starting up, and the initialization is done again here because the internal initialization of the irom is relatively simple, and the external one is relatively complete and complete, and it also involves initializing the external SD card device, etc.

20、env_relocate

(1) env_relocate is the relocation of environment variables, and completes the task of reading environment variables from SD card to DDR.

(2) Where do environment variables come from?

  There are some independent sectors in the SD card as the environment variable storage area. But when we burned/deployed the system, we only burned the uboot partition, kernel partition and rootfs partition, and never burned the env partition at all. So when we burn the system and start the first time, the ENV partition is empty. This time, when we start uboot and try to read the environment variables from the ENV partition of the SD card, it fails (it fails when the CRC check is performed after reading back). Choose to use a set of default environment variables set in uboot internal code (this is the default environment variable);

  This set of default environment variables will be read into the environment variables in DDR during this run, and then written (it may also be written when you saveenv, or it may be uboot designed to read the default environment for the first time Variables are written to) the ENV partition of the SD card. Then the next time you boot up again, uboot will read the environment variables from the ENV partition of the SD card to the DDR, and this time the reading will not fail. (Because the CRC was saved when saving last time, there will be no error in this verification)

(3) The real code to relocate ENV from SD card to DDR is done in movi_read_env inside env_relocate_spec.

void env_relocate (void)
{
    
    
	DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
		gd->reloc_off);

#ifdef CONFIG_AMIGAONEG3SE
	enable_nvram();
#endif

#ifdef ENV_IS_EMBEDDED//环境变量是内嵌到设备里边的,这个在这里是没有被定义的,通过查找
	/*
	 * The environment buffer is embedded with the text segment,
	 * just relocate the environment pointer
	 */
	env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off);//环境变量相对于代码段首地址的偏移量:gd->reloc_off
	DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#else
	/*
	 * We must allocate a buffer for the environment
	 */
	env_ptr = (env_t *)malloc (CFG_ENV_SIZE);//之前初始化了堆管理器,故而这里可以使用malloc函数了
	DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#endif

	if (gd->env_valid == 0) {
    
    //表明校验和失败
#if defined(CONFIG_GTH)	|| defined(CFG_ENV_IS_NOWHERE)	/* Environment not changable */
		puts ("Using default environment\n\n");
#else//这次执行的是else下的
		puts ("*** Warning - bad CRC, using default environment\n\n");
		show_boot_progress (-60);//uboot启动的程度,到了%多少
#endif
		set_default_env();
	}
	else {
    
    
		env_relocate_spec ();//完成从SD卡到DDR的重定位
	}
	gd->env_addr = (ulong)&(env_ptr->data);//malloc申请的那段内存的首地址

#ifdef CONFIG_AMIGAONEG3SE
	disable_nvram();
#endif
}

21. Determination of IP address and MAC address

insert image description here
(1) The IP address of the development board is maintained in gd->bd, which comes from the environment variable ipaddr. The getenv function is used to obtain the IP address in string format, and then use string_to_ip to convert the IP address in string format into dotted decimal format in string format.

(2) The IP address is composed of 4 numbers between 0-255, so the easiest way to store an IP address in the program is an unsigend int. But it is not this type that is easy for humans to understand, but the dotted decimal type (192.168.1.2). These two types can be converted to each other.

22、devices_init

(1) devices_init looks at the name is the initialization of the device. The device here refers to the hardware device on the development board. (Some devices have been initialized before, here are the remaining ones that have not been initialized) The devices initialized here are all driver devices, and this function is originally derived from the driver framework.

  The drivers of many devices in uboot are directly transplanted into the linux kernel (such as network cards, SD cards), and the drivers in the linux kernel have corresponding device initialization functions. The linux kernel has a devices_init (the name is not necessarily correct, but almost) during the startup process, and its function is to centrally execute the init functions of various hardware drivers.

(2) This function of uboot is actually ported from the linux kernel, and its function is to execute all the initialization functions of the hardware drivers inherited from the linux kernel.

23、jumptable_init

(1) The jumptable jump table itself is an array of function pointers, which records the function names of many functions. Realize a mapping relationship from a function pointer to a specific function. In the future, the specific function can be executed through the function pointer in the jump table. This is actually implementing object-oriented programming in C language. There are many such tricks in the linux kernel.

(2) Through analysis, it is found that the jump table is only assigned and never referenced, so the jump table is not used in uboot at all.

24、console_init_r

(1) console_init_f is the first stage initialization of the console, and console_init_r is the second stage initialization. In fact, the first phase of initialization did not have any substantive work, and the second phase of initialization did substantive work.

(2) There are many functions with the same name in uboot. When using the SI (SourceInsight) tool to index, the wrong function is often indexed (recall that when lowlevel_init.S was found in start.S, the automatic index found it was wrong, the real Instead, they were not found at all.) Sometimes only one is found, but not necessarily only one, so you need to search for confirmation. The one found is not necessarily correct.

  Pay attention to whether the file name corresponds to or is related to what you want. Sometimes some macros are defined, but in files we don't use, so pay attention. Sometimes, although it is also compiled in the file we use, this macro may be in another conditional compilation, and it is not compiled in this conditional compilation.

(3) console_init_r is the initialization of the pure software architecture of the console (to put it bluntly, it is to fill the corresponding value in the data structure related to the console), so it belongs to the initialization of the pure software configuration type.

(4) The console of uboot does not actually do meaningful conversion, it is a function of serial communication that is directly called. So there is actually no difference between using the console or not. (The console in linux can provide a buffer mechanism and other things that cannot be achieved without the console).

25、enable_interrupts

  Searching through SI tools, there are multiple ones, some of which are free and have actual functions. The free ones are to meet the requirements of some codes, so that the compilation does not report errors, but because this function is not actually needed, it is set to empty. In addition to setting empty functions, you can also use macros by using conditional compilation, so that certain codes do not include it or choose to include empty ones, so that compilation will not report errors.

(1) Looking at the name, it should be the interrupt initialization code. This refers to the enablement of the total interrupt flag in CPSR.

(2) Because we do not use interrupts in uboot, the CONFIG_USE_IRQ macro is not defined, so our function here is an empty shell.
insert image description here
(3) A situation that often occurs in uboot is to conditionally compile and decide whether to call the code inside a function according to whether a macro is defined.

  There are 2 solutions in uboot to handle this situation: Option 1: Use conditional compilation at the calling function, and then the function body actually provides the code completely. Solution 2: Call directly at the calling function, and then provide 2 function bodies at the function body, one is an entity and the other is an empty shell, and use macro definition conditional compilation to determine which function to compile in actual compilation.

26. loadaddr, bootfile environment variable, board_late_init function

(1) These two environment variables are related to kernel startup, and the values ​​of these two environment variables will be referred to when starting the linux kernel.

(2) Looking at the name, this function is relatively late among some initializations at the development board level, which is late initialization. So in the late stage, all the things that should be initialized before have been initialized, and the rest that must be initialized later are here. The side shows that the hardware and software initialization at the development board level has come to an end.

(3) For X210, this function is empty.
insert image description here

27. eth_initialize, x210_preboot_init (LCD and logo display)

(1) Looking at the name, it should be the initialization related to the network card. This is not the initialization of the SoC when the SoC is connected to the network card chip, but some initialization of the network card chip itself.

(2) For X210 (DM9000), this function is empty. The network card of X210 is initialized in the board_init function, and the network card chip is initialized in the driver.

(3) Some display-related initialization before the x210 development board starts up, and the logo display on the LCD screen. This function is in /board/samsung/x210/x210.c

28、check menukey to update from sd

(1) An automatic update function is designed in the final stage of uboot startup. That is: we can put the image to be upgraded in the fixed directory of the SD card, and then check the upgrade flag at the last stage of uboot startup when booting (it is a button. The button marked "LEFT" among the buttons, if this button is pressed It means update mode, if it is not pressed at startup, it means boot mode).

  If you enter the update mode, uboot will automatically read the image file from the SD card and burn it into iNand; if you enter the boot mode, uboot will not execute the update, and will directly start the normal operation.

(2) This mechanism can help us quickly burn the system, and is often used for system burning and deployment with SD cards during mass production.

29. The main_loop function of the dead loop uboot/common/main.c

(1) Parser

(2) The countdown is automatically executed when the power is turned on

(3) Command completion (there is this function but it is useless, so there is no such function, it can also be available by using this function)


/****************************************************************************/

void main_loop (void)
{
    
    
#ifndef CFG_HUSH_PARSER      
	static char lastcommand[CFG_CBSIZE] = {
    
     0, };
	int len;
	int rc = 1;
	int flag;
#endif

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)//自动倒数执行相关
	char *s;
	int bootdelay;
#endif

#ifdef CONFIG_PREBOOT
	char *p;
#endif

#ifdef CONFIG_BOOTCOUNT_LIMIT
	unsigned long bootcount = 0;
	unsigned long bootlimit = 0;
	char *bcs;
	char bcs_set[16];
#endif /* CONFIG_BOOTCOUNT_LIMIT */

#if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO)
	ulong bmp = 0;		/* default bitmap */
	extern int trab_vfd (ulong bitmap);

	#ifdef CONFIG_MODEM_SUPPORT
	if (do_mdm_init)
		bmp = 1;	/* alternate bitmap */
	#endif
	trab_vfd (bmp);
#endif	/* CONFIG_VFD && VFD_TEST_LOGO */

#ifdef CONFIG_BOOTCOUNT_LIMIT
	bootcount = bootcount_load();
	bootcount++;
	bootcount_store (bootcount);
	sprintf (bcs_set, "%lu", bootcount);
	setenv ("bootcount", bcs_set);
	bcs = getenv ("bootlimit");
	bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */

#ifdef CONFIG_MODEM_SUPPORT
	debug ("DEBUG: main_loop:   do_mdm_init=%d\n", do_mdm_init);
	if (do_mdm_init) {
    
    
		char *str = strdup(getenv("mdm_cmd"));
		setenv ("preboot", str);  /* set or delete definition */
		if (str != NULL)
			free (str);
		mdm_init(); /* wait for modem connection */
	}
#endif  /* CONFIG_MODEM_SUPPORT */

#ifdef CONFIG_VERSION_VARIABLE
	{
    
    
		extern char version_string[];

		setenv ("ver", version_string);  /* set version variable */
	}
#endif /* CONFIG_VERSION_VARIABLE */

#ifdef CFG_HUSH_PARSER     //PARSER 解析器
	u_boot_hush_start ();
#endif

#ifdef CONFIG_AUTO_COMPLETE//命令自动补全
	install_auto_complete();
#endif

#ifdef CONFIG_FASTBOOT//不要去屏蔽掉 CONFIG_FASTBOOT,否则fastboot 功能也会被屏蔽掉
    if (fastboot_preboot())//决定是否自动进入fastboot烧写模式
        run_command("fastboot", 0);//不想自动进入修改或屏蔽345-346代码即可
#endif

#ifdef CONFIG_PREBOOT
	if ((p = getenv ("preboot")) != NULL) {
    
    
	#ifdef CONFIG_AUTOBOOT_KEYED
		int prev = disable_ctrlc(1);	/* disable Control C checking */
	#endif

	#ifndef CFG_HUSH_PARSER
		run_command (p, 0);//执行命令的函数
	#else
		parse_string_outer(p, FLAG_PARSE_SEMICOLON |
				    FLAG_EXIT_FROM_LOOP);//解析字符串
	#endif

	#ifdef CONFIG_AUTOBOOT_KEYED
		disable_ctrlc(prev);	/* restore Control C checking */
	#endif
	}
#endif /* CONFIG_PREBOOT */

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
	s = getenv ("bootdelay");
	bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

	debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);

# ifdef CONFIG_BOOT_RETRY_TIME
	init_cmd_timeout ();
# endif	/* CONFIG_BOOT_RETRY_TIME */

#ifdef CONFIG_POST
	if (gd->flags & GD_FLG_POSTFAIL) {
    
    
		s = getenv("failbootcmd");
	}
	else
#endif /* CONFIG_POST */

#ifdef CONFIG_BOOTCOUNT_LIMIT
	if (bootlimit && (bootcount > bootlimit)) {
    
    
		printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
		        (unsigned)bootlimit);
		s = getenv ("altbootcmd");
	}
	else
#endif /* CONFIG_BOOTCOUNT_LIMIT */
		s = getenv ("bootcmd");

	debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

	if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
    
    
#ifdef CONFIG_AUTOBOOT_KEYED
		int prev = disable_ctrlc(1);	/* disable Control C checking */
#endif

#ifndef CFG_HUSH_PARSER
		run_command (s, 0);
#else
		parse_string_outer(s, FLAG_PARSE_SEMICOLON |
				    FLAG_EXIT_FROM_LOOP);//解析字符串
#endif

#ifdef CONFIG_AUTOBOOT_KEYED
		disable_ctrlc(prev);	/* restore Control C checking */
#endif
	}

#ifdef CONFIG_MENUKEY
	if (menukey == CONFIG_MENUKEY) {
    
    
	    s = getenv("menucmd");
	    if (s) {
    
    
#ifndef CFG_HUSH_PARSER
		run_command (s, 0);
#else
		parse_string_outer(s, FLAG_PARSE_SEMICOLON |
				    FLAG_EXIT_FROM_LOOP);
#endif
	    }
	}
#endif /* CONFIG_MENUKEY */
#endif	/* CONFIG_BOOTDELAY */

#ifdef CONFIG_AMIGAONEG3SE
	{
    
    
	    extern void video_banner(void);
	    video_banner();
	}
#endif

	/*
	 * Main Loop for Monitor Command Processing
	 */
#ifdef CFG_HUSH_PARSER
	parse_file_outer();
	/* This point is never reached */
	for (;;);
#else
	for (;;) {
    
    
	#ifdef CONFIG_BOOT_RETRY_TIME
		if (rc >= 0) {
    
    
			/* Saw enough of a valid command to
			 * restart the timeout.
			 */
			reset_cmd_timeout();
		}
	#endif
		len = readline (CFG_PROMPT);

		flag = 0;	/* assume no special flags for now */
		if (len > 0)
			strcpy (lastcommand, console_buffer);
		else if (len == 0)
			flag |= CMD_FLAG_REPEAT;
#ifdef CONFIG_BOOT_RETRY_TIME
		else if (len == -2) {
    
    
			/* -2 means timed out, retry autoboot
			 */
			puts ("\nTimed out waiting for command\n");

	#ifdef CONFIG_RESET_TO_RETRY
			/* Reinit board to run initialization code again */
			do_reset (NULL, 0, 0, NULL);
	#else
			return;		/* retry autoboot */
	#endif
		}
#endif

		if (len == -1)
			puts ("<INTERRUPT>\n");
		else
			rc = run_command (lastcommand, flag);

		if (rc <= 0) {
    
    
			/* invalid command or not repeatable, forget it */
			lastcommand[0] = 0;
		}
	}
#endif /*CFG_HUSH_PARSER*/
}

3. Summary of the second stage of uboot startup

1. Startup process review, key functions marked

(1) The second stage is mainly to initialize the hardware and software data structures at the development board level.

init_sequence
	cpu_init	空的
	board_init	网卡、机器码、内存传参地址
		dm9000_pre_init			网卡
		gd->bd->bi_arch_number	机器码
		gd->bd->bi_boot_params	内存传参地址
	interrupt_init	定时器
	env_init
	init_baudrate	gd数据结构中波特率
	serial_init		空的
	console_init_f	空的
	display_banner	打印启动信息
	print_cpuinfo	打印CPU时钟设置信息
	checkboard		检验开发板名字
	dram_init		gd数据结构中DDR信息
	display_dram_config	打印DDR配置信息表
mem_malloc_init		初始化uboot自己维护的堆管理器的内存
mmc_initialize		inand/SD卡的SoC控制器和卡的初始化
env_relocate		环境变量重定位
gd->bd->bi_ip_addr	gd数据结构赋值
gd->bd->bi_enetaddr	gd数据结构赋值
devices_init		空的
jumptable_init		不用关注的
console_init_r		真正的控制台初始化
enable_interrupts	空的
loadaddr、bootfile 	环境变量读出初始化全局变量
board_late_init		空的
eth_initialize		空的
x210_preboot_init	LCD初始化和显示logo
check_menu_update_from_sd	检查自动更新
main_loop			主循环

2. Summary of the characteristics of the startup process

(1) The first stage is the assembly stage (very versatile, many CPUs of different development boards are shared, and the original CPU will be adjusted, using tools such as jtag), the second stage is the C stage (this is The part we mainly look at when transplanting)

(2) The first stage is in SRAM, and the second stage is in DRAM

(3) The first stage focuses on the interior of the SoC, and the second stage focuses on the interior of the SoC external Board

3. Points to note when transplanting

(1) Macro definitions in the x210_sd.h header file

(2) The location of the initialization function of a specific hardware (such as a network card), and knowing the whole process can accurately locate and quickly modify when an error occurs.

Note: Most of this information is compiled from the course notes of Mr. Zhu's Internet of Things Lecture, citing Baidu Encyclopedia, some other people's blog content and combining with his own actual development experience. If there is any infringement, please contact to delete! The level is limited, if there are mistakes, welcome to communicate in the comment area.

Guess you like

Origin blog.csdn.net/weixin_45842280/article/details/126816422