嵌入式Linux -- uboot启动之第二阶段分析总结

​ 上文uboot第一阶段为汇编阶段,主要负责初始化SoC内部的部件,而uboot第二阶段为C语言阶段,主要负责初始化SoC外部的硬件以及uboot本身的一些命令和环境变量等。下面解析第二阶段的主要函数start_armboot。

1、start_armboot解析第一部分

1.1、init_fnc_t

typedef int (init_fnc_t) (void);

​ 在uboot/lib_arm/Board.c中的开始部分用typedef定义了一个函数类型。

	init_fnc_t **init_fnc_ptr;
	char *s;
	int mmc_exist = 0;

​ 在start_armboot函数中首先定义了几个变量,而init_fnc_ptr是一个二重指针,用来指向一个函数指针数组,具体数组内容在之后引用,在此先不做解析。

1.2、uboot的全局变量gd和开发板的全局变量bd

1.2.1、全局变量gd
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

​ 因为uboot的全局变量在整个uboot的过程都都需要大量用到,所以我们在uboot/include/asm-arm/global_data.h中做了一个宏定义,将DECLARE_GLOBAL_DATA_PTR定义为一个指向gd_t类型的指针变量并在uboot/lib_arm/Board.c

中声明引用这个宏定义。全局变量gd用typedef定义的结构体类型如下,下面对其进行相应解析:

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;

(1)bd_t *bd; bd是一个指向bd_t的指针变量,bd_t是存放开发板的板相关的信息的一种结构体。

(2)unsigned long flags; flags是存放标志位的。

(3)unsigned long baudrate; baudrate是控制台的通信波特率。

(4)unsigned long have_console; 表示是否有控制台,是一个布尔类型的变量。

(5)unsigned long reloc_off; 是重定位的偏移量。

(6)unsigned long env_addr; 是环境变量结构体的地址+偏移量。

(7)unsigned long env_valid; 检测DDR中的环境变量是否可以使用。

(8)unsigned long fb_base; 缓存frame buffer的起始地址。

(9)unsigned char vfd_type; 打印显示器类型,我们这里没用到。

(10)unsigned long cpu_clk; CPU时钟,这里没用到。

(11)unsigned long bus_clk; 总线时钟,这里没用到

(12)phys_size_t ram_size; SRAM的大小,这里没用到。

(13)unsigned long reset_status; 复位状态寄存器,这里没用到。

(14)void **jt; 用来指向跳转表的二重指针。

1.2.2、开发板全局变量bd

​ 在uboot\include\asm-armu-boot.h用typedef定义了一个结构体类型,下面来解析其内容:

typedef struct bd_info {
    int			bi_baudrate;	/* serial console baudrate */
    unsigned long	bi_ip_addr;	/* IP Address */
    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];
#ifdef CONFIG_HAS_ETH1	//未定义
    /* second onboard ethernet port */
    unsigned char   bi_enet1addr[6];
#endif
} bd_t;

(1)int bi_baudrate; 开发板硬件设置的波特率。

(2)unsigned long bi_ip_addr; 开发板的ip地址。

(3)unsigned char bi_enetaddr[6]; 开发板的net地址。

(4)ulong bi_arch_number; 开发板的机器码。

(5)ulong bi_boot_params; 开发板启动参数的地址。

(6)bi_dram[CONFIG_NR_DRAM_BANKS]; 存放DDR信息的结构体。

(7)unsigned char bi_enet1addr[6]; 第二块板子的net地址,这里没用到。

1.3、分配内存

​ 宏定义DECLARE_GLOBAL_DATA_PTR是一个指针,我们要为它分配内存从而使用gd和bd,下面是分配内存的代码。

#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
	ulong gd_base;

	gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
#ifdef CONFIG_USE_IRQ
	gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif
	gd = (gd_t*)gd_base;
#else
	gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
#endif

	/* 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;

(1)首先了解我们的内存排布,在uboot区中CFG_UBOOT_BASE是起始地址,CFG_UBOOT_SIZE是uboot的大小;在堆区中,CFG_MALLOC_LEN是堆的长度;在栈区中,CFG_STACK_SIZE是栈的长度;全局变量gd的长度是sizeof(gd_t);全局变量bd的长度是sizeof(bd_t);

(2)我们定义了CONFIG_MEMORY_UPPER_CODE,故此满足此条件编译,定义了gd的基地址的变量,并且初始化它的值为uboot的开始地址加上uboot的整体尺寸然后减去堆区、栈区以及环境变量的大小。接着用强制类型转换将此地址实例化,即由指针指向变量。

(3)因为GCC3.4以上的版本优化容易造成错误,所以我们使用内嵌汇编产生内存间隔,防止编译器优化造成错误。

(4)最后初始化环境变量,并且设置monitor_flash_len。

1.4、init_sequence

init_fnc_t *init_sequence[] = {
	cpu_init,		/* basic cpu dependent setup */
#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 */
	display_banner,		/* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
	print_cpuinfo,		/* display cpu info (and speed) */
#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 */
	display_dram_config,
	NULL,
};

​ init_sequence内容如上,它是一个函数指针数组,里面存放了许多初始化相关的函数,是板级硬件的初始化,在此我们用二重指针init_fnc_ptr来访问它,用for循环来遍历执行这个函数指针数组中的函数。

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

​ 首先解析此循环遍历的代码,我们依次访问这个函数指针数组中的每个函数,直到二重指针init_fnc_ptr指向一个NULL从而结束循环,其中如果二重指针指向的函数指针指向的那个函数返回值不为0,则使用hang函数进行挂起。

1.4.1、挂起函数hang
void hang (void)
{
	puts ("### ERROR ### Please RESET the board ###\n");
	for (;;);
}

​ 这个挂起函数的功能是首先打印"### ERROR ### Please RESET the board ###"的字样,然后进入死循环。

1.4.2、init_sequence的cpu_init
int cpu_init (void)
{
	/*
	 * setup up stacks if necessary
	 */
#ifdef CONFIG_USE_IRQ
	IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
	FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
	return 0;
}

​ 此函数是用来初始化CPU的,但是第一阶段已经初始化过CPU了,这里也没有满足此条件编译,故此函数的内容是空的。

1.4.3、init_sequence的reloc_init
#ifdef CONFIG_SKIP_RELOCATE_UBOOT
static int reloc_init(void)
{
	gd->flags |= GD_FLG_RELOC;
	return 0;
}
#endif

​ 用来关闭重定位的标志位,但是这里我们没有满足这个条件编译,所以不执行这个函数。

1.4.4、init_sequence的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->bd->bi_arch_number = MACH_TYPE;
	gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);

	return 0;
}

​ 此函数在uboot/board/samsung/x210x210.c中,来配置开发板的相关信息。首先声明了gd环境变量,我们在配置文件中定义了网卡相关的宏,满足CONFIG_DRIVER_DM9000的条件编译,故执行dm9000_pre_init函数来对DM9000网卡进行初始化,对网卡的GPIO和端口进行配置。

​ 下面的两行代码是令环境变量bd中的机器码bi_arch_number赋值为配置文件中的MACH_TYPE;令bd中给内核启动时传参的内存地址bi_boot_params赋值为第一片内存的物理地址PHYS_SDRAM_1加上0x100,最后返回0。

1.4.5、init_sequence的interrupt_init
int interrupt_init(void)
{

	S5PC11X_TIMERS *const timers = S5PC11X_GetBase_TIMERS();

	/* use PWM Timer 4 because it has no output */
	/* prescaler for Timer 4 is 16 */
	timers->TCFG0 = 0x0f00;
	if (timer_load_val == 0) {
		/*
		 * for 10 ms clock period @ PCLK with 4 bit divider = 1/2
		 * (default) and prescaler = 16. Should be 10390
		 * @33.25MHz and  @ 66 MHz
		 */
		timer_load_val = get_PCLK() / (16 * 100);
	}

	/* load value for 10 ms timeout */
	lastdec = timers->TCNTB4 = timer_load_val;
	/* auto load, manual update of Timer 4 */
	timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | TCON_4_UPDATE;
	/* auto load, start Timer 4 */
	timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | COUNT_4_ON;
	timestamp = 0;


	return (0);
}

typedef struct {
	S5PC11X_REG32	TCFG0;
	S5PC11X_REG32	TCFG1;
	S5PC11X_REG32	TCON;
	S5PC11X_TIMER	ch[4];
	S5PC11X_REG32	TCNTB4;
	S5PC11X_REG32	TCNTO4;
} /*__attribute__((__packed__))*/ S5PC11X_TIMERS;

​ 此函数在uboot/cpu/s5pc11x/interrupts.c中,用来初始化定时器Timer4。Timer4没有中断支持,主要实现的功能是bootdelay相关的定时,此定时器的基准时间是由二级时钟分频器决定,我们把定时时间除以基准时间所得到的数值放入TCNTB4中,再读取TCNTO4的数值,如果为0就说明我们的定时时间已经达到。

​ 这段代码使用类型为S5PC11X_TIMERS的指针timers指向定时器寄存器的基地址,用结构体中的偏移量来访问每个寄存器,从而初始化Timer4。

1.4.6、init_sequence的env_init
int env_init(void)
{
#if defined(ENV_IS_EMBEDDED)
	ulong total;
	int crc1_ok = 0, crc2_ok = 0;
	env_t *tmp_env1, *tmp_env2;

	total = CFG_ENV_SIZE;

	tmp_env1 = env_ptr;
	tmp_env2 = (env_t *)((ulong)env_ptr + CFG_ENV_SIZE);

	crc1_ok = (crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc);
	crc2_ok = (crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc);

	if (!crc1_ok && !crc2_ok)
		gd->env_valid = 0;
	else if(crc1_ok && !crc2_ok)
		gd->env_valid = 1;
	else if(!crc1_ok && crc2_ok)
		gd->env_valid = 2;
	else {
		/* both ok - check serial */
		if(tmp_env1->flags == 255 && tmp_env2->flags == 0)
			gd->env_valid = 2;
		else if(tmp_env2->flags == 255 && tmp_env1->flags == 0)
			gd->env_valid = 1;
		else if(tmp_env1->flags > tmp_env2->flags)
			gd->env_valid = 1;
		else if(tmp_env2->flags > tmp_env1->flags)
			gd->env_valid = 2;
		else /* flags are equal - almost impossible */
			gd->env_valid = 1;
	}

	if (gd->env_valid == 1)
		env_ptr = tmp_env1;
	else if (gd->env_valid == 2)
		env_ptr = tmp_env2;
#else /* ENV_IS_EMBEDDED */
	gd->env_addr  = (ulong)&default_environment[0];
	gd->env_valid = 1;
#endif /* ENV_IS_EMBEDDED */

	return (0);
}

​ 此函数在uboot/common/env_movi.c中,用来初始化环境变量。这个函数只对内存中那一份uboot的环境变量做了基本的判定,判定内存里面有没有能用的环境变量。因为我们还没进行环境变量从启动介质向内存中的重定位,因此是不可以用的,在之后调用env_relocate函数才可以在DDR中读取环境变量,在此之前只能从启动介质中读取。

1.4.7、init_sequence的init_baudrate
static int init_baudrate (void)
{
	char tmp[64];	/* long enough for environment variables */
	int i = getenv_r ("baudrate", tmp, sizeof (tmp));
	gd->bd->bi_baudrate = gd->baudrate = (i > 0)
			? (int) simple_strtoul (tmp, NULL, 10)
			: CONFIG_BAUDRATE;

	return (0);
}

​ 这个函数用来初始化串口通信的波特率,用getenv_r函数读取波特率的值并存入tmp中,判定读取的值是否正确,如果正确则令串口和开发板的波特率都等于tmp,但是此时tmp是字符串,所以用simple_strtoul函数将其转换为int型数据。如果读取的值错误,就令串口和开发板的波特率都等于配置文件设置的波特率。

1.4.8、init_sequence的serial_init
int serial_init(void)
{
	serial_setbrg();

	return (0);
}

void serial_setbrg(void)
{
	DECLARE_GLOBAL_DATA_PTR;

	int i;
	for (i = 0; i < 100; i++);
}

​ 此函数在uboot/cpu/s5pc11x/serial.c中,调用了serial_setbrg函数后返回0,而serial_setbrg函数只做了一个gd的声明,然后进行了一个空循环,即serial_init什么事情都没有做。

1.4.9、init_sequence的console_init_f
int console_init_f (void)
{
	gd->have_console = 1;

#ifdef CONFIG_SILENT_CONSOLE
	if (getenv("silent") != NULL)
		gd->flags |= GD_FLG_SILENT;
#endif

	return (0);
}

​ 此函数在uboot/common/console.c中,仅仅是对gd->have_console设置为1,是console的第一阶段初始化。

1.4.10、init_sequence的display_banner
static int display_banner (void)
{
	printf ("\n\n%s\n\n", version_string);
	debug ("U-Boot code: %08lX -> %08lX  BSS: -> %08lX\n",
	       _armboot_start, _bss_start, _bss_end);
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
	debug("\t\bMalloc and Stack is above the U-Boot Code.\n");
#else
	debug("\t\bMalloc and Stack is below the U-Boot Code.\n");
#endif
#ifdef CONFIG_MODEM_SUPPORT
	debug ("Modem Support enabled\n");
#endif
#ifdef CONFIG_USE_IRQ
	debug ("IRQ Stack: %08lx\n", IRQ_STACK_START);
	debug ("FIQ Stack: %08lx\n", FIQ_STACK_START);
#endif
	open_backlight();//lqm.
	//open_gprs();

	return (0);
}

​ 主要作用是用来打印uboot的版本信息,open_backlight用来打开背光。

1.4.11、init_sequence的print_cpuinfo
int print_cpuinfo(void)
{
	uint set_speed;
	uint tmp;
	uchar result_set;

#if defined(CONFIG_CLK_533_133_100_100)
	set_speed = 53300;
#elif defined(CONFIG_CLK_667_166_166_133)
	set_speed = 66700;
#elif defined(CONFIG_CLK_800_200_166_133)
	set_speed = 80000;
#elif defined(CONFIG_CLK_1000_200_166_133)
	set_speed = 100000;
#elif defined(CONFIG_CLK_1200_200_166_133)
	set_speed = 120000;
#else
	set_speed = 100000;
	printf("Any CONFIG_CLK_XXX is not enabled\n");
#endif
	tmp = (set_speed / (get_ARMCLK()/1000000));

	if((tmp < 105) && (tmp > 95)){
		result_set = 1;
	} else {
		result_set = 0;
	}

#ifdef CONFIG_MCP_SINGLE
	printf("\nCPU:  S5PV210@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" : "FAIL"));
#else
	printf("\nCPU:  S5PC110@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" : "FAIL"));
#endif
	printf("        APLL = %ldMHz, HclkMsys = %ldMHz, PclkMsys = %ldMHz\n",
			get_FCLK()/1000000, get_HCLK()/1000000, get_PCLK()/1000000);
#if 1
	printf("	MPLL = %ldMHz, EPLL = %ldMHz\n",
			get_MPLL_CLK()/1000000, get_PLLCLK(EPLL)/1000000);
	printf("		       HclkDsys = %ldMHz, PclkDsys = %ldMHz\n",
			get_HCLKD()/1000000, get_PCLKD()/1000000);
	printf("		       HclkPsys = %ldMHz, PclkPsys = %ldMHz\n",
			get_HCLKP()/1000000, get_PCLKP()/1000000);
	printf("		       SCLKA2M  = %ldMHz\n", get_SCLKA2M()/1000000);
#endif
	puts("Serial = CLKUART ");

	return 0;
}

​ 此函数在uboot/cpu/s5pc11x/s5pc110/speed.c中,用来打印CPU的型号、时钟频率等信息。

1.4.12、init_sequence的checkboard
int checkboard(void)
{
#ifdef CONFIG_MCP_SINGLE
#if defined(CONFIG_VOGUES)
	printf("\nBoard:   VOGUESV210\n");
#else
	printf("\nBoard:   X210\n");
#endif //CONFIG_VOGUES
#else
	printf("\nBoard:   X210\n");
#endif
	return (0);
}

​ 此函数在uboot/board/samsung/x210/x210.c中,用来检测使用的是哪个开发板并打印处开发板的名字。

1.4.13、init_sequence的init_func_i2c
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
static int init_func_i2c (void)
{
	puts ("I2C:   ");
	i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
	puts ("ready\n");
	return (0);
}
#endif

​ 用来初始化I2C的函数,但是未满足编译条件,没有执行。

1.4.14、init_sequence的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;
}

​ 初始化DDR,将DDR的配置信息记录在bd中的bi_dram[ i ]中,以便uboot中使用。

1.4.15、init_sequence的display_dram_config
static int display_dram_config (void)
{
	int i;

#ifdef DEBUG
	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);
}

​ 此函数用来打印DDR的配置信息。

2、start_armboot解析第二部分

2.1、CFG_NO_FLASH

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

​ 用来打印“Flash: 8MB”的信息,和其他配置有关系,去掉会导致编译出错。

2.2、mem_malloc_init

#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
	mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
#else
	mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
#endif


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);
}

​ 此函数用来初始化uboot的堆管理器,这里满足条件编译,所以执行第二行的代码,将uboot的大小减去堆和栈的大小作为堆的内存大小并初始化。

2.3、MMC初始化

#if defined(CONFIG_X210)

	#if defined(CONFIG_GENERIC_MMC)
		puts ("SD/MMC:  ");
		mmc_exist = mmc_initialize(gd->bd);	//MMC相关初始化
		if (mmc_exist != 0)
		{
			puts ("0 MB\n");
#ifdef CONFIG_CHECK_X210CV3
			check_flash_flag=0;//check inand error!
#endif
		}
#ifdef CONFIG_CHECK_X210CV3
		else
		{
			check_flash_flag=1;//check inand ok! 
		}
#endif
	#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

#endif /* CONFIG_X210 */

int board_mmc_init(bd_t *bis) __attribute__((weak, alias("__def_mmc_init")));
int cpu_mmc_init(bd_t *bis)
{
#ifdef CONFIG_S3C_HSMMC
	setup_hsmmc_clock();		//SD卡控制器时钟的初始化
	setup_hsmmc_cfg_gpio();		//SD卡控制器的GPIO初始化
	return smdk_s3c_hsmmc_init();	//驱动代码
#else
	return 0;
#endif
}

int mmc_initialize(bd_t *bis)
{
	struct mmc *mmc;
	int err;

	INIT_LIST_HEAD(&mmc_devices);
	cur_dev_num = 0;

	if (board_mmc_init(bis) < 0)
		cpu_mmc_init(bis);

#if defined(DEBUG_S3C_HSMMC)
	print_mmc_devices(',');
#endif

#ifdef CONFIG_CHECK_X210CV3
	mmc = find_mmc_device(1);//lqm
#else
	mmc = find_mmc_device(0);
#endif
	if (mmc) {
		err = mmc_init(mmc);
		if (err)
			err = mmc_init(mmc);
		if (err) {
			printf("Card init fail!\n");
			return err;
		}
	}
	printf("%ldMB\n", (mmc->capacity/(1024*1024/(1<<9))));
	return 0;
}

​ mmc_initialize用来初始化SoC内部的SD/MMC控制器,此函数在uboot\drivers\mmc.c中,通过调用board_mmc_init和cpu_mmc_init函数来对MMC进行初始化,cpu_mmc_init函数用来进行SD卡控制器时钟、GPIO的初始化以及调用驱动程序。此函数最终打印出MMC的容量信息。

2.4、环境变量重定位函数env_relocate

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);
	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);
	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
		puts ("*** Warning - bad CRC, using default environment\n\n");
		show_boot_progress (-60);
#endif
		set_default_env();
	}
	else {
		env_relocate_spec ();
	}
	gd->env_addr = (ulong)&(env_ptr->data);

#ifdef CONFIG_AMIGAONEG3SE
	disable_nvram();
#endif
}

​ 此函数在uboot/common/env_common.c中,其中ENV_IS_EMBEDDED的条件编译未通过,执行else里面的env_relocate_spec函数,下面来对此函数进行解析。

void movi_read_env(ulong addr)
{
	movi_read(raw_area_control.image[2].start_blk,
		  raw_area_control.image[2].used_blk, addr);
}

void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)
	uint *magic = (uint*)(PHYS_SDRAM_1);

	if ((0x24564236 != magic[0]) || (0x20764316 != magic[1]))
		movi_read_env(virt_to_phys((ulong)env_ptr));

	if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
		return use_default();
#endif /* ! ENV_IS_EMBEDDED */
}

​ 通过代码和实际分析可以看出本次uboot尝试去SD卡的env分区读取环境变量时失败,这段代码选择使用uboot内部代码中设置的一套默认的环境变量。env_relocate_spec函数是通过movi_read_env函数进行实现的,而movi_read_env中使用movi_read函数将原始分区信息表raw_area_control中的起始扇区raw_area_control.image[2].start_blk以及已经使用的部分raw_area_control.image[2].used_blk作为参数,将这套默认的环境变量读取到DDR中的环境变量中以供使用。

2.5、IP地址和MAC地址

	/* IP Address */
	gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

	/* 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;
		}
     
IPaddr_t getenv_IPaddr (char *var)
{
	return (string_to_ip(getenv(var)));
}
        
int getenv_r (char *name, char *buf, unsigned len)
{
	int i, nxt;

	for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
		int val, n;

		for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
			if (nxt >= CFG_ENV_SIZE) {
				return (-1);
			}
		}
		if ((val=envmatch((uchar *)name, i)) < 0)
			continue;
		/* found; copy out */
		n = 0;
		while ((len > n++) && (*buf++ = env_get_char(val++)) != '\0')
			;
		if (len == n)
			*buf = '\0';
		return (n);
	}
	return (-1);
}        

​ 用getenv_IPaddr函数获取开发板的IP地址并赋值给bd中的bi_ip_addr作为开发板的IP地址信息,在getenv_IPaddr中使用string_to_ip将字符串格式的IP地址转换成字符串格式的点分十进制格式。同样的使用getenv_r函数获取开发板的MAC地址信息,并将其赋值给bd中的bi_enetaddr[reg]。

2.6、devices_init

int devices_init (void)
{
#ifndef CONFIG_ARM     /* already relocated for current ARM implementation */
	ulong relocation_offset = gd->reloc_off;
	int i;

	/* relocate device name pointers */
	for (i = 0; i < (sizeof (stdio_names) / sizeof (char *)); ++i) {
		stdio_names[i] = (char *) (((ulong) stdio_names[i]) +
						relocation_offset);
	}
#endif

	/* Initialize the list */
	devlist = ListCreate (sizeof (device_t));

	if (devlist == NULL) {
		eputs ("Cannot initialize the list of devices!\n");
		return -1;
	}
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
	i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
#endif
#ifdef CONFIG_LCD
	drv_lcd_init ();
#endif
#if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE)
	drv_video_init ();
#endif
#ifdef CONFIG_KEYBOARD
	drv_keyboard_init ();
#endif
#ifdef CONFIG_LOGBUFFER
	drv_logbuff_init ();
#endif
	drv_system_init ();
#ifdef CONFIG_SERIAL_MULTI
	serial_devices_init ();
#endif
#ifdef CONFIG_USB_TTY
	drv_usbtty_init ();
#endif
#ifdef CONFIG_NETCONSOLE
	drv_nc_init ();
#endif

	return (0);
}

​ 此函数在uboot/common/devices.c中,作用是开发板上的各种硬件驱动设备的初始化。

2.7、jumptable_init

void jumptable_init (void)
{
	int i;

	gd->jt = (void **) malloc (XF_MAX * sizeof (void *));
	for (i = 0; i < XF_MAX; i++)
		gd->jt[i] = (void *) dummy;

	gd->jt[XF_get_version] = (void *) get_version;
	gd->jt[XF_malloc] = (void *) malloc;
	gd->jt[XF_free] = (void *) free;
	gd->jt[XF_getenv] = (void *) getenv;
	gd->jt[XF_setenv] = (void *) setenv;
	gd->jt[XF_get_timer] = (void *) get_timer;
	gd->jt[XF_simple_strtoul] = (void *) simple_strtoul;
	gd->jt[XF_udelay] = (void *) udelay;
	gd->jt[XF_simple_strtol] = (void *) simple_strtol;
	gd->jt[XF_strcmp] = (void *) strcmp;
#if defined(CONFIG_I386) || defined(CONFIG_PPC)
	gd->jt[XF_install_hdlr] = (void *) irq_install_handler;
	gd->jt[XF_free_hdlr] = (void *) irq_free_handler;
#endif	/* I386 || PPC */
#if defined(CONFIG_CMD_I2C)
	gd->jt[XF_i2c_write] = (void *) i2c_write;
	gd->jt[XF_i2c_read] = (void *) i2c_read;
#endif
}

​ 此函数在uboot/common/Exports.c中,跳转表本身是一个函数指针数组,它实现的功能是函数指针到具体函数的映射关系,将来通过跳转表中的函数指针就可以执行具体的函数。

2.8、console_init_r

int console_init_r (void)
{
	device_t *inputdev = NULL, *outputdev = NULL;
	int i, items = ListNumItems (devlist);

#ifdef CONFIG_SPLASH_SCREEN
	/* suppress all output if splash screen is enabled and we have
	   a bmp to display                                            */
	if (getenv("splashimage") != NULL)
		gd->flags |= GD_FLG_SILENT;
#endif

	/* Scan devices looking for input and output devices */
	for (i = 1;
	     (i <= items) && ((inputdev == NULL) || (outputdev == NULL));
	     i++
	    ) {
		device_t *dev = ListGetPtrToItem (devlist, i);

		if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
			inputdev = dev;
		}
		if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
			outputdev = dev;
		}
	}

	/* Initializes output console first */
	if (outputdev != NULL) {
		console_setfile (stdout, outputdev);
		console_setfile (stderr, outputdev);
	}

	/* Initializes input console */
	if (inputdev != NULL) {
		console_setfile (stdin, inputdev);
	}

	gd->flags |= GD_FLG_DEVINIT;	/* device initialization completed */

#ifndef CFG_CONSOLE_INFO_QUIET
	/* Print information */
	puts ("In:      ");
	if (stdio_devices[stdin] == NULL) {
		puts ("No input devices available!\n");
	} else {
		printf ("%s\n", stdio_devices[stdin]->name);
	}

	puts ("Out:     ");
	if (stdio_devices[stdout] == NULL) {
		puts ("No output devices available!\n");
	} else {
		printf ("%s\n", stdio_devices[stdout]->name);
	}

	puts ("Err:     ");
	if (stdio_devices[stderr] == NULL) {
		puts ("No error devices available!\n");
	} else {
		printf ("%s\n", stdio_devices[stderr]->name);
	}
#endif /* CFG_CONSOLE_INFO_QUIET */

#ifndef	CONFIG_X210
	/* Setting environment variables */
	for (i = 0; i < 3; i++) {
		setenv (stdio_names[i], stdio_devices[i]->name);
	}
#endif

#if 0
	/* If nothing usable installed, use only the initial console */
	if ((stdio_devices[stdin] == NULL) && (stdio_devices[stdout] == NULL))
		return (0);
#endif

	return (0);
}

​ 此函数在uboot/common/Console.c中,纯软件架构相关的初始化,就是给console相关的数据结构中填充相应的值,并没有实质上的作用。

2.9、enable_interrupts

void enable_interrupts(void)
{
	return;
}

​ 此函数在uboot/cpu/s5pc11x/interrupts.c中,本来应有CPSR总中断标志位的使能的作用,但是没有满足条件编译,即uboot中没用中断,所以此函数是空函数。

2.10、环境变量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));
	}

​ 初始化了loadaddr、bootfile这两个环境变量,他们是内核启动有关的环境变量,在启动Linux内核时会参考这两个环境变量的值。

2.11、board_late_init

#ifdef BOARD_LATE_INIT
#if defined(CONFIG_BOOT_NAND)
int board_late_init (void)
{
	uint *magic = (uint*)(PHYS_SDRAM_1);
	char boot_cmd[100];

	if ((0x24564236 == magic[0]) && (0x20764316 == magic[1])) {
		sprintf(boot_cmd, "nand erase 0 40000;nand write %08x 0 40000", PHYS_SDRAM_1 + 0x8000);
		magic[0] = 0;
		magic[1] = 0;
		printf("\nready for self-burning U-Boot image\n\n");
		setenv("bootdelay", "0");
		setenv("bootcmd", boot_cmd);
	}

	return 0;
}
#elif defined(CONFIG_BOOT_MOVINAND)
int board_late_init (void)
{
	uint *magic = (uint*)(PHYS_SDRAM_1);
	char boot_cmd[100];
	int hc;

	hc = (magic[2] & 0x1) ? 1 : 0;

	if ((0x24564236 == magic[0]) && (0x20764316 == magic[1])) {
		sprintf(boot_cmd, "movi init %d %d;movi write u-boot %08x", magic[3], hc, PHYS_SDRAM_1 + 0x8000);
		magic[0] = 0;
		magic[1] = 0;
		printf("\nready for self-burning U-Boot image\n\n");
		setenv("bootdelay", "0");
		setenv("bootcmd", boot_cmd);
	}

	return 0;
}
#else
int board_late_init (void)
{
	return 0;
}
#endif
#endif

​ 此函数在uboot/board/samsung/x210/x210.c中,是开发板级别的一些晚期初始化,对于我们的开发板来说,执行的是其中的空函数。

2.12、网卡和IDE初始化

#if defined(CONFIG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
	puts ("Net:   ");
#endif
	eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)
	debug ("Reset Ethernet PHY\n");
	reset_phy();
#endif
#endif

#if defined(CONFIG_CMD_IDE)
	puts("IDE:   ");
	ide_init();
#endif
2.12.1、网卡初始化

​ eth_initialize是网卡本身初始化的函数,对于DM9000来说,这个函数是空的,其真正的初始化函数在init_sequence里的board_init函数中。

2.12.2、IDE初始化

​ 这里先打印一个“IDE: ”,然后用ide_init函数去初始化IDE。

2.13、开发板logo

#ifdef CONFIG_MPAD
	extern int x210_preboot_init(void);
	x210_preboot_init();
#endif

​ 调用x210_preboot_init函数初始化LCD并显示开发板启动时的logo信息。

2.14、检查是否有自动更新并进入死循环

	/* check menukey to update from sd */
	extern void update_all(void);
	if(check_menu_update_from_sd()==0)//update mode
	{
		puts ("[LEFT DOWN] update mode\n");
		run_command("fdisk -c 0",0);
		update_all();
	}
	else
		puts ("[LEFT UP] boot mode\n");

	/* 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 */
}

​ 可以将要升级的镜像放到SD卡的固定目录中,开机时在uboot启动的最后阶段检查升级标志,如果标志被启动就进入update mode从而使uboot自动从SD卡读取镜像然后烧录到iNand中,如果标志未启动则进入boot mode,正常执行下面的流程。最后调用main_loop函数进入主循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值