海思(Hi3521a)uboot详细分析(6)——uboot启动第二阶段start_armboot函数分析

    uboot 在start.S中完成了第一阶段启动后,由汇编语言调用C语言函数start_armboot进入第二阶段的启动,在海思给的Uboot代码中,该函数定义在/u-boot-2010.06/arch/arm/lib/board.c。它主要完成了:

  1. 全局变量的初始化
  2. 初始化硬件及软件数据结构
  3. 初始化uboot堆管理器
  4. flash驱动初始化
  5. 环境变量重定向
  6. 输入输出初始化
  7. 其它初始化
  8. uboot命令处理

说明:对于官方默认没有配置的功能都直接跳过,不做详细的介绍。

1.全局变量的初始化

void start_armboot (void)
{
	init_fnc_t **init_fnc_ptr;  //①
	char *s;
#ifdef CONFIG_HAS_SLAVE
	char *e;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
	unsigned long addr;
#endif

	/* Pointer is writable since we allocated a register for it */
	gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)); //②
	/* 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));

	gd->flags |= GD_FLG_RELOC;  //④

	monitor_flash_len = _bss_start - _armboot_start; //⑤
  • typedef int (init_fnc_t) (void);  是一个函数,不是函数指针
  • 在hi3521a中 CONFIG_HAS_SLAVE,CONFIG_VFD,CONFIG_LCD 都没有初始化
  • _armboot_start 是在\u-boot-2010.06\arch\arm\cpu\hi3521a\start.S 的汇编语言中被定义,表示uboot代码段在ddr中的开始地址,其值为0x80800000  
  • ddr可以使用的地址是从80000000开始,也就是说uboot的代码是被拷贝到ddr开始往后的8M空间
  • 在这里CONFIG_SYS_MALLOC_LEN=0x60000=(384K)
  • gd指向的地址为:0x80800000 - 0x60000 - 0x20 = 0x8079FFE0
  • gd->bd指向的地址为:0x8079FFE0 - 0x1c = 0x8079FFC4
  • 接下来是将gd和gd->bd指向的内存空间清零。其实gd是一个全局的变量,在uboot中很多地方会使用到,gd->bd是一个板子的信息结构体。详细介绍如下:
  • 两个关键结构体介绍:
  • gd_t 是一个全局的结构体,在uboot的很多地方都有使用它所在的位置是:\u-boot-2010它所在的位置是:\u-boot-2010.06\arch\arm\include\asm\global_data.h

结构体的内容为:

typedef	struct	global_data {
	bd_t		*bd;
	unsigned long	flags;
	unsigned long	baudrate;
	unsigned long	have_console;	/* serial_init() was called */
	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
#ifdef CONFIG_FSL_ESDHC
	unsigned long	sdhc_clk;
#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;
  • 这里定义了开发板信息指针,全局变量标签,波特率,终端,环境变量地址,环境变量是否可用,时钟,内存大小等信息
  • 板子信息结构体
typedef struct bd_info {
    int			bi_baudrate;	/* serial console baudrate */
    unsigned long	bi_ip_addr;	/* IP Address */
    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];
} bd_t;
  • 定义波特率,IP地址,环境变量指针,机器码,启动参数,内存配置。
  • 机器码:机器码是设备的唯一编号,在Uboot和kernel中都有软件维护这个机器码,如果机器码不匹配将不会被启动
  • 启动参数:这里是uboot传递给kernel启动参数的内存地址。uboot传参数到kernel的过程是这样的:uboot将事先准备好的kernel启动参数存放到bi_boot_params指向的这个地址(实际上是将bi_boot_params这个值存放到了r0 r1 r2寄存器中去),kernel起来后,去读取寄存器r0,r1,r2中的值,得到bi_boot_params这个参数的值,也就是启动参数的地址,然后再去这个地址将内核中的启动参数读取出来。
  • 内存配置:CONFIG_NR_DRAM_BANKS是在\u-boot-2010.06\include\configs\hi3521.h中定义,表示ddr的个数,海思系列的这个值都是1,表示只有一个ddr;在bi_dram中定义了ddr的开始地址和长度
  • GD_FLG_RELOC;是全局变量的一个状态,表示为代码已经从flash中拷贝到了内存中去。它定义在:\u-boot-2010.06\arch\arm\include\asm\global_data.h
  • _bss_start是bss的开始地址,_armboot_start是Uboot的开始代码地址,这里monitor_flash_len得到的是整个Uboot的长度

2.初始化硬件及软件数据结构

	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { 
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}
  •  init_sequence 是一个函数指针数组,里面存了很多的函数指针,init_fnc_ptr是一个二重指针,*init_fnc_ptr表示init_sequence中的函数指针指向的地址,
  • 如果为空NULL,结束for循环。init_sequence函数指针里面定义了各种各样的函数初始化函数
init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT)
	arch_cpu_init,		/* basic arch cpu dependent setup */
#endif
	timer_init,		/* initialize timer before usb init */
	board_init,		/* basic board dependent setup */    //①
#if defined(CONFIG_USE_IRQ)
	interrupt_init,		/* set up exceptions */
#endif
//	timer_init,		/* initialize timer */
#ifdef CONFIG_FSL_ESDHC
	get_clocks,
#endif
	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 */ //⑦
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
	arm_pci_init,
#endif
/*	display_dram_config */
	NULL,
};
  • 这里面的所有编译宏在海思设备中都没有定义,没有使用到
  • 实际使用到的是
  • timer_init: 
  • 时钟初始化,定义在\u-boot-2010.06\arch\arm\cpu\hi3521a\hi3521a\timer.c
  • 时钟参数的配置在y\u-boot-2010.06\include\configs\hi3521a.h

①board_init:(这里很重要)

int board_init(void)
{
	unsigned long reg;
	/* set uart clk from XTAL OSC 24M */
	reg = readl(CRG_REG_BASE + PERI_CRG33);
	reg &= ~UART_CKSEL_MASK;
	reg |= UART_CKSEL_24M;
	writel(reg, CRG_REG_BASE + PERI_CRG33);

	DECLARE_GLOBAL_DATA_PTR;

	gd->bd->bi_arch_number = MACH_TYPE_HI3521A;
	gd->bd->bi_boot_params = CFG_BOOT_PARAMS;
	gd->flags = 0;

	boot_flag_init();

	return 0;
}
  • 开发板的一些参数定义,
  • 该函数定义定义在:\u-boot-2010.06\board\hi3521a\board.c
  • UART时钟频率设置
  • DECLARE_GLOBAL_DATA_PTR宏申明了全局变量 register volatile gd_t *gd asm ("r8")这里全局变量名字叫gd,这个全局变量是一个指针类型,占4字节。用volatile修饰表示可变的,用register修饰表示这个变量要尽量放到寄存器中,后面的asm("r8")是gcc支持的一种语法,意思就是要把gd放到寄存器r8中。
  • MACH_TYPE_HI3521A是设备的机器码,值为8000,海思所有的设备都是这个值
  • CFG_BOOT_PARAMSuboot传递给kernel的启动参数存放的地址值为:MEM_BASE_DDR=DDR_MEM_BASE=0x80000000;CFG_BOOT_PARAMS=MEM_BASE_DDR+0x0100=0x80000100 

boot_flag_init()启动类型判断

void boot_flag_init(void)
{
	unsigned int regval, device_type;

	/* get boot device type */
	regval = __raw_readl(SYS_CTRL_REG_BASE + REG_SYSSTAT);
	device_type = (regval >> 8) & 0x1;

	switch (device_type) {
	/* spi nor device */
	case 0:
		boot_media = BOOT_MEDIA_SPIFLASH;
		break;
	/* spi nand device */
	case 1:
		boot_media = BOOT_MEDIA_NAND;
		break;
	default:
		boot_media = BOOT_MEDIA_SPIFLASH;
		break;
	}
}
  •  SYS_CTRL_REG_BASE=0x12050000 
  • REG_SYSSTAT=0x008C
  • SYSSTAT 为系统状态寄存器,它的第8位表示SPI FLASH 器件类型
  • 从这里可以判断外接的是spi nandflash 还是spi norflash
  • 如果是spi nandflash 将它标记为nandflash启动
  • 可以使用get_boot_media函数来获取外接的flash启动类型

②env_init

  • 定义在\u-boot-2010.06\common\env_common_func.c
  • 针对不同的存储介质设置环境变量的操作接口 

③init_baudrate

  • 波特率设置,先去环境变量中查询是否有设置波特率,
  • 如果没有设置则使用配置文件中的CONFIG_BAUDRATE波特率
  • 海思的串口设置在\u-boot-2010.06\drivers\serial\serial_pl01x.c文件

④serial_init

  • 海思hi3521a在配置文件中有配置宏CONFIG_PL011_SERIAL,所以串口的初始化函数实际调用的是\u-boot-2010.06\drivers\serial\serial_pl01x.c中的函数

⑤console_init_f

  • 定义使用端口,_f表示是第一阶段的初始化

⑥display_banner

  • 在终端打印出uboot的版本号,代码段的开始位置,ssb的开始和结束位置
  • code: 80800000 ->   BSS: -> 8089EE00
  • _armboot_start = 80800000
  • _bss_start = 0x8084A384
  • _bss_end   = 0x8089EE00

⑦dram_init

  • 定义在\u-boot-2010.06\board\hi3521a\board.c
  • 配置ddr的个数和地址
  • CFG_DDR_PHYS_OFFSET 的物理地址
  • CFG_DDR_SIZE ddr的大小默认大小为:512 * 1024 * 1024UL 512Mb=64MB

3.初始化uboot堆管理器

	/* armboot_start is defined in the board-specific linker script */
	mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN,
			CONFIG_SYS_MALLOC_LEN);
  • 堆空间的分配:
  • _armboot_start=80800000;
  • CONFIG_ENV_SIZE=0x40000
  • CONFIG_SYS_MALLOC_LEN=CONFIG_ENV_SIZE + 128*1024= 256K+128K = 384K
  • mem_malloc_start=80800000-384K
  • mem_malloc_end = 80800000
  • mem_malloc_brk = 80800000

4.flash驱动初始化

#ifdef CONFIG_CMD_SF
#if (defined CONFIG_ARCH_HI3559 || defined CONFIG_ARCH_HI3556)
    if(get_boot_media() == BOOT_MEDIA_SPIFLASH) {
#endif
	spi_flash_probe(0, 0, 0, 0);//①
#if (defined CONFIG_ARCH_HI3559 || defined CONFIG_ARCH_HI3556)
    }
#endif
#endif

/* it is not needed in A7 in A17-A7  */
#ifdef CONFIG_CMD_NAND
#if (defined CONFIG_ARCH_HI3559 || defined CONFIG_ARCH_HI3556)
    if(get_boot_media() == BOOT_MEDIA_NAND) {
#endif
	nand_init();		/* go init the NAND */ //②
#if (defined CONFIG_ARCH_HI3559 || defined CONFIG_ARCH_HI3556)
    }
#endif
#endif
  • spi_flash_probe函数定义在\u-boot-2010.06\drivers\mtd\spi\spi_compatible.c
  • 在海思设备中,配置的是CONFIG_HIFMC_SPI_NOR 和CONFIG_HIFMC_SPI_NAND
  • 所以在这里调用的是hifmc100_spi_nor_probe(&spiinfo_ex)这个函数
  • 从终端可以打印出我设备的一些信息:
Check Flash Memory Controller v100 ... Found
SPI Nor(cs 0) ID: 0xc2 0x20 0x19
Block:64KB Chip:32MB Name:"MX25L(256/257)XX"
SPI Nor total size: 32MB
  • nand_init()函数,定义在\u-boot-2010.06\drivers\mtd\nand\nand.c
  • 进行SPI Nand flash 初始化,我设备上的spi nand flash 信息如下:
SPI Nand(cs 1) ID: 0xc2 0x12 Name:"MX35LF1GE4AB"
Block:128KB Page:2KB Chip:128MB*1 OOB:64B ECC:4bit/512 
ECC provided by Flash Memory Controller
SPI Nand total size: 128MB

5.环境变量重定向

env_relocate
  • 环境变量重定向,关于环境变量,可以参考博客《uboot环境变量》,这里主要实现的功能是将环境变量从flash中复制到DDR中去,后面环境变量的操作都是在内存中操作,直到使用了saveenv才会将内存中的环境变量写回到flash中实现环境变量的更新。

6.输入输出初始化

#ifdef CONFIG_VFD
	/* must do this after the framebuffer is allocated */
	drv_vfd_init();
#endif /* CONFIG_VFD */

#ifdef CONFIG_SERIAL_MULTI
	serial_initialize();
#endif

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

	stdio_init ();	/* get the devices list going. */ //②

	jumptable_init (); //③

#if defined(CONFIG_API)
	/* Initialize API */
	api_init ();
#endif

	console_init_r ();	/* fully init console as a device */  //④

#if defined(CONFIG_ARCH_MISC_INIT)
	/* miscellaneous arch dependent initialisations */
	arch_misc_init ();				
#endif
#if defined(CONFIG_MISC_INIT_R)
	/* miscellaneous platform dependent initialisations */
	misc_init_r ();				//⑤				
#endif

	/* enable exceptions */
	enable_interrupts ();		//⑥
  • 从环境变量中提取IP地址信息
  • 初始化标准输入输出"stdin", "stdout", "stderr"
  • 跳转表初始化,没有查到有哪里会使用
  • 终端设备的第二阶段初始化
  • 杂项初始化,这里其它的功能都没有定义,只是设置了一个环境变量verify,设它的值为n
  • 中断初始化,这个函数定义在\test\uboot_study\u-boot-2010.06\arch\arm\lib\interrupts.c ,在海思的uboot中CONFIG_USE_IRQ宏没有被定义,所以中断在整个Uboot过程中都是关闭的,enable_interrupts函数的实现是是个空函数。

7.其它初始化

	/* Perform network card initialisation if necessary */
#ifdef CONFIG_DRIVER_TI_EMAC
	/* XXX: this needs to be moved to board init */
extern void davinci_eth_set_mac_addr (const u_int8_t *addr);
	if (getenv ("ethaddr")) {
		uchar enetaddr[6];
		eth_getenv_enetaddr("ethaddr", enetaddr);
		davinci_eth_set_mac_addr(enetaddr);
	}
#endif

#if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
	/* XXX: this needs to be moved to board init */
	if (getenv ("ethaddr")) {					//①
		uchar enetaddr[6];
		eth_getenv_enetaddr("ethaddr", enetaddr);
		smc_set_mac_addr(enetaddr);
	}
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */

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

#ifdef BOARD_LATE_INIT
	board_late_init ();
#endif

#ifdef CONFIG_BITBANGMII
	bb_miiphy_init();
#endif
#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_BOOTROM_SUPPORT) && (!defined(CONFIG_ARCH_HI3559) && !defined(CONFIG_ARCH_HI3556))
	extern void download_boot(const int (*handle)(void));
	download_boot(NULL);
#endif
#ifdef CONFIG_HAS_SLAVE
	e = getenv("slave_autostart");
	if (e && (*e == '1'))
		slave_start();
#endif

#ifdef CONFIG_DDR_TRAINING_V300
	check_ddr_training();
#endif /* CONFIG_DDR_TRAINING_V300 */
	product_control();    					//⑤

#ifdef CONFIG_SNAPSHOT_BOOT
	/* example */
	/* set_mtdparts_info("mtdparts=hinand:1M(boot),3M(kernel),64M(rootfs),2M(param),16M(hibernate)"); */

	extern void comp_save_snapshot_image(void);
	comp_save_snapshot_image();
#endif
  • 从环境变量中获取网卡的MAC地址ethaddr,然后将它解析
  • loadaddr这个环境变量默认是没有被定义
  • bootfile是指启动kernel文件的类型,在海思设备,默认是设置为uImage。
  • 初始化网卡设备
  • 这个是对ddr做等长匹配,对于hi3251a,这个工作在第一阶段的start.S文件中就已近做过了,在实际上这个函数相当于空。

8.uboot命令处理    

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

    在这里的最后一步就是进入了一个死循环main_loop(),等待用户命令的输入。如果uboot超时没有接收到用户输入命令,则执行环境变量bootcmd中的命令,使用bootcmd环境变量中的命令来引导内核的启动。关于uboot命令解析,可以查看博客《bootm启动命令解析》,其它uboot内容可以查看博客《序言与目录》

发布了164 篇原创文章 · 获赞 229 · 访问量 62万+

猜你喜欢

转载自blog.csdn.net/li_wen01/article/details/103347598