uboot移植之启动过程详解2

/*******************************************************************************
    
    uboot启动过程之第二阶段的分析(board.c的分析)


    时间:201812月中旬
    者:cryil_先森
    以朱有鹏课程中uboot-samsung-dev为分析对象
    
*********************************************************************************/

                                          宏观分析:


    初始化第一阶段结束还没被初始化的硬件;SOC的外部硬件(inand,网卡等);uboot本身的一些东西(uboot的命令,环境变量),最终初始化完进入uboot的命令行准备接受命令。
    在这个C语言程序之中,大部分是对于环境变量的初始化,对于函数的定义和初始化,真正这些变量和函数使用的地方是在void start_armboot(void) 函数之中,统筹了板子的硬件的初始化。

uboot的启动概述:


    主要是对开发板级别的硬件的初始化、软件的数据结构进行初始化。uboot在启动完后打印出很多的信息,然后进入倒数bootdelay秒后执行bootcmd对应的启动命令。如果用户没有进行干涉则会执行bootcmd进入自动启动内核流程(此时uboot就死掉了),此时用户可以按下回车键打断uboot的自启动过程进入uboot的命令行下,然后uboot就会一直工作在命令行下。而uboot的命令行就是一个死循环,循环体内不断地重复:接受命令,解析命令,执行命令。
    

   ***1.一个重要的函数指针数组init_sequence[]:

    cpu_init,                空的
    reloc_init,        
    board_init,                网卡,机器码,内存传参地址
    interrupt_init,            定时器
    env_init,        
    init_baudrate,            波特率数据结构
    serial_init,            串口初始化(空的)
    console_init_f,            空的
    display_banner,            打印启动信息
    print_cpuinfo,            打印出CPU时钟设置信息
    checkboard,                检验开发板名字
    init_func_i2c,
    dram_init,                初始化gd数据结构的
    display_dram_config,    打印出DDR的配置信息

  
***2.其他

    mem_malloc_init        初始化堆内存
    mmc_initialize            inand/SD卡的SOC控制器和卡的初始化

    
    env_relocate ();        环境变量的重定位
    
    devices_init ();        基本为空的
    jumptable_init ();        无用的
    
    console_init_r()        真正的控制台初始化
    
    enable_interrupts        基本为空的
    
    loadaddr和bootfile        环境变量的读出,初始化全局变量
    
    board_late_init()        最后的补充初始化函数,基本为空
    
    eth_initialize()        网卡芯片本身的一些硬件初始化
    
    main_loop()                uboot工作的死循环。


*******************************************************************************************************************************************

                                           具体的分析如下:

                                           ***1.引用了一个全局变量:

DECLARE_GLOBAL_DATA_PTR

这个全局变量在Global_data.h头文件之中被定义

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

DECLARE_GLOBAL_DATA_PTR其实是一个全局变量gd,这个全局变量在asm寄存器r8位被使用  同时这个全局变量是一个指向gd_t类型变量的指针。

                   ***2.引出了两个结构体gd_t和bd_t (Global_data.h中)

  **1.gd_t

        typedef	struct	global_data {
		bd_t		*bd;
		unsigned long	flags;
		unsigned long	baudrate;
		unsigned long	have_console;	/* serial_init() was called */
		unsigned long	reloc_off;	/* Relocation Offset */
		unsigned long	env_addr;	/* Address  of Environment struct */
		unsigned long	env_valid;	/* Checksum of Environment valid? */
		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;	
	

分析:这个全局变量实际上是一个结构体,结构体内是uboot中常用的所有全局变量。而bd_t 则是一个和开发板版级信息直接相关的结构体,结构体内包含:板子的硬件信息,IP地址,机器码,
以及DDR内存分布情况的结构体bi_dram[CONFIG_NR_DRAM_BANKS]等。

 

**2.bd_t:

        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 */
	定义了内核的传参地址
		struct				/* RAM configuration */
		{
		ulong start;
		ulong size;
		}			bi_dram[CONFIG_NR_DRAM_BANKS];
	定义了一个关于DDR的结构体
	#ifdef CONFIG_HAS_ETH1
		/* second onboard ethernet port */
		unsigned char   bi_enet1addr[6];
	#endif
	} bd_t;

 

***3.一系列初始化函数的定义:(在后面的程序之中会给出初始化函数的执行顺序)

**1.version_string[]数组:

const char version_string[] =
	U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;	

分析:定义了一个字符串数组,这个数组内记录了板子的信息和烧录信息。baudratebaudrateversion_string 这个参数的一部分是主Makefile传过来的,代表板子的版本号;    " (" __DATE__ " - " __TIME__ ")"是烧录时的时间信息;CONFIG_IDENT_STRING是板子对应的芯片信息,而这个字符串数组在之后被引用 display_banner, /* say that we are here */用串口打印显示出uboot的logo,(此时控制台还未完全初始化完成,通过一系列函数的调用直接控制串口寄存器,打印输出uboot的logo)

static int init_baudrate (void)  波特率的初始化函数
	static int display_banner (void)  串口打印初始化函数

    **2一个重要的函数指针数组init_sequence[]   

       大致进行一下操作:板级硬件的初始化,gd、gd-bd的初始化,网卡的初始化,机器码(gd-bd-bi_arch_number),内核传参DDR的地址(gd->bd->bi_boot_params),波特率,打印出CPU的启动信息,打印出CPU的相关设置信息,检查并打印当前开发板名字,DDR的配置信息的初始化。
       定义了一个init_fnc_t类型的数组,数组内存放的是指针(实际上是数组内函数的首地址)
      数组内储存了很多个函数指针,这些指向的函数都是init_fnc_t类型的(接受void类型参数返回int型参数)。init_sequence在定义之初同时被初始化,初始化的函数指针都是一些函数名(函数名可以做指针使用),而这些函数都是board级别的硬件初始化函数。

typedef int (init_fnc_t) (void);
	int print_cpuinfo (void); /* test-only */
	init_fnc_t *init_sequence[] = {
	cpu_init,		/* basic cpu dependent setup */

   初始化CPU

#if defined(CONFIG_SKIP_RELOCATE_UBOOT)
		reloc_init,		/* Set the relocation done flag, must
					   do this AFTER cpu_init(), but as soon
					   as possible */
	#endif
		
		board_init,		/* basic board dependent setup */

  详见下面

interrupt_init,		/* set up exceptions */

  初始化定时器

env_init,		/* initialize environment */

    初始化环境变量

init_baudrate,		/* initialze baudrate settings */
		serial_init,		/* serial communications setup */
		
		console_init_f,		/* stage 1 init of console */

    控制台初始化
    注:在初始化时不能一次初始化完成时分两步进行,_f第一阶段  _r第二阶段    

display_banner,		/* say that we are here */	

  打印出version_string

#if defined(CONFIG_DISPLAY_CPUINFO)
		print_cpuinfo,		/* display cpu info (and speed) */

  打印出CPU的信息(波特率)

#endif
	#if defined(CONFIG_DISPLAY_BOARDINFO)
		checkboard,		/* display board info */
	#endif

  打印出板子的信息

	#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
		init_func_i2c,
	#endif
		dram_init,		/* configure available RAM banks */

    关于DDR的初始化,其实在汇编阶段已经进行了一次初始化(从硬件的角度)
    查看原函数可知此次的初始化规定了各个内存块的起始地址和大小

display_dram_config,
		NULL,
	};

   打印显示DDR的配置信息    

**3.板子的初始化细究(board_init):

	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这个结构体,主要完成网卡的GPIO和端口的配置,而不是驱动,移植时驱动不用修改,只用改动初始化函数。

gd->bd->bi_arch_number = MACH_TYPE;		

分析:bi_arch_number:开发板的机器码,是board.info的一个元素。在uboot和Linux内核之间进行比对和配置。由于嵌入式设备的高度定制化,导致软件和硬件不能随意适配使用。板子的机器码和uboot、Linux的内核进行比对,然后决定是否启动。uboot中配置的机器码会作为参数传给Linux内核,内核启动时会进行再次比对决定是否启动。

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

		return 0;
		}

      分析:bi_boot_params是uboot传给Linux的参数,代表Linux内核启动时的内存地址,之后uboot就能启动内核(启动过程其实是寄存器R0 R1 R2来直接传递参数的) uboot事先准备好参数bootargs(字符串)放在内存之中,通过uboot的传参,内核启动时就会知道bootargs的内存地址(0x20000100)。
       注:初始化DDR(和汇编阶段lowlever_init中的初始化DDR是不同的,当时只是进行硬件的初始化,使得DDR能够开始工作),进行软件结构中的一些DDR相关的属性配置、地址设置的初始化。
       在uboot设计时,开发者在***.h(板载头文件)中使用宏定义去配置出来DDR的内存信息,然后uboot只需读取这些信息即可知道DDR的内存信息。
      PHYS_SDRAM_1是DDR的地址信息,同时在它定义的地方之后也定义了DDR的个数,大小。

同时也对DDR的各个分区做出了定义,定义了DDR各个分区大小,起始地址

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

		return 0;
	}

           ***4.板子初始化启动过程的整体架构  void start_armboot (void)

 

**1.定义了一个二重函数指针init_fnc_ptr:

(这里用来指向一个函数指针数组)


	init_fnc_t **init_fnc_ptr;
	typedef int (init_fnc_t) (void);  
	
		char *s;
		int mmc_exist = 0;

**2.一系列的判断是否初始化:

#if !defined(CFG_NO_FLASH) || defined (CONFIG_VFD) || defined(CONFIG_LCD)
		ulong size;
	#endif

	#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
		unsigned long addr;
	#endif

	#if defined(CONFIG_BOOT_MOVINAND)
		uint *magic = (uint *) (PHYS_SDRAM_1);
	#endif

		/* Pointer is writable since we allocated a register for it */
	#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
		ulong gd_base;

**3.uboot的内存排布:

gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);

        由于gd和bd都是一个指针类型的全局变量,gd和bd变量在使用前需要被分配内存,而此时是裸机状态,系统不会自动的进行内存的分配,只能人为的为其指定内存区域。而gd_base规定了我们指针开始访问的地址,为指针找到了一块安全的内存空间。(详见文件夹内的图片)
   
    分析:gd_base是gd这段内存存放在内存之中的起始地址。
    CFG_UBOOT_BASE:uboot代码的起始地址。(uboot实际大小为200kb左右)
    CFG_UBOOT_SIZE:系统为uboot分配的内存空间(2MB)
    CFG_MALLOC_LEN:堆区,长度为912KB
    CFG_STACK_SIZE:栈区,长度为512kb
    sizeof(gd_t):gd全局变量的内存空间,长度为36字节
    bd:bd全局变量的内存空间,长度为字节。
     综上所述:gd的内存地址在uboot起始地址+600kb左右的空间内,而一般情况下uboot只有200kb左右,两者之间有着400kb左右的间隔。

	
	#ifdef CONFIG_USE_IRQ
		gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
	#endif
		gd = (gd_t*)gd_base;	

分析:将gd_base代表的数字强制类型转化为指针类型然后赋值给gd,此时gd就指向了这段存放gd全局变量的内存地址。
    

#else   //CONFIG_USE_IRQ
		gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
	#endif   //CONFIG_MEMORY_UPPER_CODE

		/* compiler optimization barrier needed for GCC >= 3.4 */
		__asm__ __volatile__("": : :"memory");

		memset ((void*)gd, 0, sizeof (gd_t));
		
		gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
		memset (gd->bd, 0, sizeof (bd_t));

		monitor_flash_len = _bss_start - _armboot_start;

   分析:在使用之前清除栈中的变量。

**4.板子初始化的真正开始:

	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
			if ((*init_fnc_ptr)() != 0) {
				hang ();
			}
		}	
	

for循环内从第一个函数cpu_init开始,遍历整个函数数组,从而依次调用每一个函数,而数组内的每一个函数的返回值都是0,依次将每一个函数的返回值和0进行判断,从而决定是否执行hang ()。

而hang ()函数为:

	void hang (void)
	{
		puts ("### ERROR ### Please RESET the board ###\n");
		for (;;);
	}

   hang ()是一个启动过程错误判断函数,如果某一个函数执行错误,uboot的启动就会中止,然后打印错误。

                                ***5.一些零碎的初始化、定义。

**1.CFG_NO_FLASH

#ifndef CFG_NO_FLASH
		/* configure available FLASH banks */
		size = flash_init ();
		display_flash_config (size);
	#endif /* CFG_NO_FLASH */

  分析:在启动时打印显示出nand的内存大小。

**2.CONFIG_VFD和CONFIG_LCD都是显示相关的

    分析:uboot中自带的lcd显示架构,但是在启动时实际没有使用,我们在后面自己添加了一个lcd显示的部分。

    **3.DDR堆的初始化

mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);

分析:具体看mem_malloc_init函数可知,括号内传入的参数是堆的起始地址,在初始化函数中申请了堆内存,并规定了堆的大小。从而达到自己维护堆内存的目的。

    

**4.判断适配出和自己板子相同的文件然后进行下一步的操作。

	#if defined(CONFIG_SMDKC110)

		#if defined(CONFIG_GENERIC_MMC)
			puts ("SD/MMC:  ");
			mmc_exist = mmc_initialize(gd->bd);
			if (mmc_exist != 0)
			{
				puts ("0 MB\n");
			}
		#endif

		#if defined(CONFIG_MTD_ONENAND)
			puts("OneNAND: ");
			onenand_init();
			/*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/
		#else
			//puts("OneNAND: (FSR layer enabled)\n");
		#endif

		#if defined(CONFIG_CMD_NAND)
			puts("NAND:    ");
			nand_init();
		#endif

   分析:mmc_exist = mmc_initialize(gd->bd);
    初始化SOC内部的SD/MMC控制器,实际上再次调用了board_mmc_init和cpu_mmu_init函数来完成具体的硬件MMC控制器的初始化。
    注:uboot对硬件的初始化操作(网卡,SD卡..)都是通过借用Linux内核中的驱动来实现的在uboot/drivers内都是从内核移植过来的各种驱动的源代码。

**5.环境变量的导入

	#ifdef CONFIG_HAS_DATAFLASH
		AT91F_DataflashInit();
		dataflash_print_info();
	#endif

		/* initialize environment */
		env_relocate ();
	

分析:由于uboot中使用的是SD卡,不是DATAFLASH,所以直接进行endif的语句,进行环境变量的重定位,将SD卡的环境变量读取到DDR中。

#ifdef CONFIG_VFD和#ifdef CONFIG_SERIAL_MULT均与我们的板子无关

**6.IP地址和MAC地址的获取

	/* IP Address */
	gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
		
		IPaddr_t getenv_IPaddr (char *var)
	{
		return (string_to_ip(getenv(var)));
	}

   分析:开发板的IP地址用环境变量gd->bd->bi_ip_addr来表示。
    getenv_IPaddr函数用来获取字符串格式的IP地址,然后用string_to_ip函数字符串格式的IP地址转换成点分式十进制格式。(也就是通常意义的IP地址)

/* MAC Address */
	{
		int i;
		ulong reg;
		char *s, *e;
		char tmp[64];

		i = getenv_r ("ethaddr", tmp, sizeof (tmp));
		s = (i > 0) ? tmp : NULL;

		for (reg = 0; reg < 6; ++reg) {
			gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
			if (s)
				s = (*e) ? e + 1 : e;
		}

分析:MAC Address是网卡的硬件地址。 用环境变量gd->bd->bi_enetaddr来表示。
    可以看出来,网卡的硬件地址由六部分构成,由函数getenv_r获取,在经过非零判断最后将其赋值给gd->bd->bi_enetaddr。

    **7.驱动设备的初始化    

devices_init ();	/* get the devices list going. */

分析:直接从驱动架构框架衍生出来的,来源于Linux内核中驱动的源码,作用就是集中执行各种硬件驱动的init函数。

    **8.定义了一个跳转表

jumptable_init ();

分析:其实本身是一个函数指针数组,里面记录了很多函数名,将来可以使用跳转表内的函数指针执行相应的函数
(相当于init_fnc_t *init_sequence[])这就实现了用C语言完成面向对象的编程。但是分析可知跳转表只是被赋值,并没有被使用过。

**9.控制台的第二阶段的初始化

	#if !defined(CONFIG_SMDK6442)
		console_init_r ();	/* fully init console as a device */
	#endif

分析:console_init_r是console(控制台)纯软件配置架构方面的初始化,属于纯软件配置类型的初始化。他直接调用串口通讯的函数。

    **10.中断的初始化函数

enable_interrupts ();

  分析:CPSR总中断标志位的使能。

    **11.两个环境变量loadaddr和bootfile

	/* Initialize from environment */
		if ((s = getenv ("loadaddr")) != NULL) {
			load_addr = simple_strtoul (s, NULL, 16);
		}
	#if defined(CONFIG_CMD_NET)
		if ((s = getenv ("bootfile")) != NULL) {
			copy_filename (BootFile, s, sizeof (BootFile));
		}
	#endif

分析:内核启动有关的环境变量,在启动内核时会参考这两个环境变量的值。

**12.开发板级别的最后部分的初始化

	#ifdef BOARD_LATE_INIT
		board_late_init ();
	#endif

**13.网卡芯片本身的一些硬件初始化

eth_initialize(gd->bd);

分析:这里的初始化不是SOC与网卡连接时SOC的初始化,只是本身的初始化。

**14.关于IDE的初始化

	#if defined(CONFIG_CMD_IDE)
		puts("IDE:   ");
		ide_init();
	#endif

**15.启动成功,uboot进入死循环

	/* main_loop() can return to retry autoboot, if so just run it again. */
		for (;;) {
			main_loop ();
		}

		/* NOTREACHED - no way out of command loop except booting */
	}

   分析:uboot进入死循环,不间断的进行检测命令,执行命令的操作。
    main_loop的功能:解析器;开机倒数执行;命令的补全

**16.启动错误打印函数

		void hang (void)
	{
		puts ("### ERROR ### Please RESET the board ###\n");
		for (;;);
	}
	

分析:在之前的start_armboot函数中有使用,当启动过程有任意一个初始化出错时,就会打印出### ERROR ### Please RESET the board ###,一些初始化归零,启动失败。

注:DDR内的一部分内存分布情况:

猜你喜欢

转载自blog.csdn.net/qq_41464499/article/details/85033975