飞道的博客

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

1417人阅读  评论(0)

    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.全局变量的初始化


  
  1. void start_armboot (void)
  2. {
  3. init_fnc_t **init_fnc_ptr; //①
  4. char *s;
  5. #ifdef CONFIG_HAS_SLAVE
  6. char *e;
  7. #endif
  8. #if defined(CONFIG_VFD) || defined(CONFIG_LCD)
  9. unsigned long addr;
  10. #endif
  11. /* Pointer is writable since we allocated a register for it */
  12. gd = ( gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof( gd_t)); //②
  13. /* compiler optimization barrier needed for GCC >= 3.4 */
  14. __asm__ __volatile__( "": : : "memory");
  15. memset (( void*)gd, 0, sizeof ( gd_t)); //③
  16. gd->bd = ( bd_t*)(( char*)gd - sizeof( bd_t));
  17. memset (gd->bd, 0, sizeof ( bd_t));
  18. gd->flags |= GD_FLG_RELOC; //④
  19. 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

结构体的内容为:


  
  1. typedef struct global_data {
  2. bd_t *bd;
  3. unsigned long flags;
  4. unsigned long baudrate;
  5. unsigned long have_console; /* serial_init() was called */
  6. unsigned long env_addr; /* Address of Environment struct */
  7. unsigned long env_valid; /* Checksum of Environment valid? */
  8. unsigned long fb_base; /* base address of frame buffer */
  9. #ifdef CONFIG_VFD
  10. unsigned char vfd_type; /* display type */
  11. #endif
  12. #ifdef CONFIG_FSL_ESDHC
  13. unsigned long sdhc_clk;
  14. #endif
  15. #if 0
  16. unsigned long cpu_clk; /* CPU clock in Hz! */
  17. unsigned long bus_clk;
  18. phys_size_t ram_size; /* RAM size */
  19. unsigned long reset_status; /* reset status register at boot */
  20. #endif
  21. void **jt; /* jump table */
  22. } gd_t;
  • 这里定义了开发板信息指针,全局变量标签,波特率,终端,环境变量地址,环境变量是否可用,时钟,内存大小等信息
  • 板子信息结构体

  
  1. typedef struct bd_info {
  2. int bi_baudrate; /* serial console baudrate */
  3. unsigned long bi_ip_addr; /* IP Address */
  4. struct environment_s *bi_env;
  5. ulong bi_arch_number; /* unique id for this board */
  6. ulong bi_boot_params; /* where this board expects params */
  7. struct /* RAM configuration */
  8. {
  9. ulong start;
  10. ulong size;
  11. } bi_dram[CONFIG_NR_DRAM_BANKS];
  12. } 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.初始化硬件及软件数据结构


  
  1. for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
  2. if ((*init_fnc_ptr)() != 0) {
  3. hang ();
  4. }
  5. }
  •  init_sequence 是一个函数指针数组,里面存了很多的函数指针,init_fnc_ptr是一个二重指针,*init_fnc_ptr表示init_sequence中的函数指针指向的地址,
  • 如果为空NULL,结束for循环。init_sequence函数指针里面定义了各种各样的函数初始化函数

  
  1. init_fnc_t *init_sequence[] = {
  2. #if defined(CONFIG_ARCH_CPU_INIT)
  3. arch_cpu_init, /* basic arch cpu dependent setup */
  4. #endif
  5. timer_init, /* initialize timer before usb init */
  6. board_init, /* basic board dependent setup */ //①
  7. #if defined(CONFIG_USE_IRQ)
  8. interrupt_init, /* set up exceptions */
  9. #endif
  10. // timer_init, /* initialize timer */
  11. #ifdef CONFIG_FSL_ESDHC
  12. get_clocks,
  13. #endif
  14. env_init, /* initialize environment */ //②
  15. init_baudrate, /* initialze baudrate settings */ //③
  16. serial_init, /* serial communications setup */ //④
  17. console_init_f, /* stage 1 init of console */ //⑤
  18. display_banner, /* say that we are here */ //⑥
  19. #if defined(CONFIG_DISPLAY_CPUINFO)
  20. print_cpuinfo, /* display cpu info (and speed) */
  21. #endif
  22. #if defined(CONFIG_DISPLAY_BOARDINFO)
  23. checkboard, /* display board info */
  24. #endif
  25. #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
  26. init_func_i2c,
  27. #endif
  28. dram_init, /* configure available RAM banks */ //⑦
  29. #if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
  30. arm_pci_init,
  31. #endif
  32. /* display_dram_config */
  33. NULL,
  34. };
  • 这里面的所有编译宏在海思设备中都没有定义,没有使用到
  • 实际使用到的是
  • timer_init: 
  • 时钟初始化,定义在\u-boot-2010.06\arch\arm\cpu\hi3521a\hi3521a\timer.c
  • 时钟参数的配置在y\u-boot-2010.06\include\configs\hi3521a.h

①board_init:(这里很重要)


  
  1. int board_init(void)
  2. {
  3. unsigned long reg;
  4. /* set uart clk from XTAL OSC 24M */
  5. reg = readl(CRG_REG_BASE + PERI_CRG33);
  6. reg &= ~UART_CKSEL_MASK;
  7. reg |= UART_CKSEL_24M;
  8. writel(reg, CRG_REG_BASE + PERI_CRG33);
  9. DECLARE_GLOBAL_DATA_PTR;
  10. gd->bd->bi_arch_number = MACH_TYPE_HI3521A;
  11. gd->bd->bi_boot_params = CFG_BOOT_PARAMS;
  12. gd->flags = 0;
  13. boot_flag_init();
  14. return 0;
  15. }
  • 开发板的一些参数定义,
  • 该函数定义定义在:\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()启动类型判断


  
  1. void boot_flag_init(void)
  2. {
  3. unsigned int regval, device_type;
  4. /* get boot device type */
  5. regval = __raw_readl(SYS_CTRL_REG_BASE + REG_SYSSTAT);
  6. device_type = (regval >> 8) & 0x1;
  7. switch (device_type) {
  8. /* spi nor device */
  9. case 0:
  10. boot_media = BOOT_MEDIA_SPIFLASH;
  11. break;
  12. /* spi nand device */
  13. case 1:
  14. boot_media = BOOT_MEDIA_NAND;
  15. break;
  16. default:
  17. boot_media = BOOT_MEDIA_SPIFLASH;
  18. break;
  19. }
  20. }
  •  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堆管理器


  
  1. /* armboot_start is defined in the board-specific linker script */
  2. mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN,
  3. 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驱动初始化


  
  1. #ifdef CONFIG_CMD_SF
  2. #if (defined CONFIG_ARCH_HI3559 || defined CONFIG_ARCH_HI3556)
  3. if(get_boot_media() == BOOT_MEDIA_SPIFLASH) {
  4. #endif
  5. spi_flash_probe( 0, 0, 0, 0); //①
  6. #if (defined CONFIG_ARCH_HI3559 || defined CONFIG_ARCH_HI3556)
  7. }
  8. #endif
  9. #endif
  10. /* it is not needed in A7 in A17-A7 */
  11. #ifdef CONFIG_CMD_NAND
  12. #if (defined CONFIG_ARCH_HI3559 || defined CONFIG_ARCH_HI3556)
  13. if(get_boot_media() == BOOT_MEDIA_NAND) {
  14. #endif
  15. nand_init(); /* go init the NAND */ //②
  16. #if (defined CONFIG_ARCH_HI3559 || defined CONFIG_ARCH_HI3556)
  17. }
  18. #endif
  19. #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)这个函数
  • 从终端可以打印出我设备的一些信息:

  
  1. Check Flash Memory Controller v100 ... Found
  2. SPI Nor(cs 0) ID: 0xc2 0x20 0x19
  3. Block:64KB Chip:32MB Name: "MX25L(256/257)XX"
  4. SPI Nor total size: 32MB
  • nand_init()函数,定义在\u-boot-2010.06\drivers\mtd\nand\nand.c
  • 进行SPI Nand flash 初始化,我设备上的spi nand flash 信息如下:

  
  1. SPI Nand(cs 1) ID: 0xc2 0x12 Name: "MX35LF1GE4AB"
  2. Block:128KB Page:2KB Chip:128MB*1 OOB:64B ECC:4bit/512 
  3. ECC provided by Flash Memory Controller
  4. SPI Nand total size: 128MB

5.环境变量重定向

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

6.输入输出初始化


  
  1. #ifdef CONFIG_VFD
  2. /* must do this after the framebuffer is allocated */
  3. drv_vfd_init();
  4. #endif /* CONFIG_VFD */
  5. #ifdef CONFIG_SERIAL_MULTI
  6. serial_initialize();
  7. #endif
  8. /* IP Address */
  9. gd->bd->bi_ip_addr = getenv_IPaddr ( "ipaddr"); //①
  10. stdio_init (); /* get the devices list going. */ //②
  11. jumptable_init (); //③
  12. #if defined(CONFIG_API)
  13. /* Initialize API */
  14. api_init ();
  15. #endif
  16. console_init_r (); /* fully init console as a device */ //④
  17. #if defined(CONFIG_ARCH_MISC_INIT)
  18. /* miscellaneous arch dependent initialisations */
  19. arch_misc_init ();
  20. #endif
  21. #if defined(CONFIG_MISC_INIT_R)
  22. /* miscellaneous platform dependent initialisations */
  23. misc_init_r (); //⑤
  24. #endif
  25. /* enable exceptions */
  26. 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.其它初始化


  
  1. /* Perform network card initialisation if necessary */
  2. #ifdef CONFIG_DRIVER_TI_EMAC
  3. /* XXX: this needs to be moved to board init */
  4. extern void davinci_eth_set_mac_addr (const u_int8_t *addr);
  5. if (getenv ( "ethaddr")) {
  6. uchar enetaddr[ 6];
  7. eth_getenv_enetaddr( "ethaddr", enetaddr);
  8. davinci_eth_set_mac_addr(enetaddr);
  9. }
  10. #endif
  11. #if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
  12. /* XXX: this needs to be moved to board init */
  13. if (getenv ( "ethaddr")) { //①
  14. uchar enetaddr[ 6];
  15. eth_getenv_enetaddr( "ethaddr", enetaddr);
  16. smc_set_mac_addr(enetaddr);
  17. }
  18. #endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */
  19. /* Initialize from environment */
  20. if ((s = getenv ( "loadaddr")) != NULL) { //②
  21. load_addr = simple_strtoul (s, NULL, 16);
  22. }
  23. #if defined(CONFIG_CMD_NET)
  24. if ((s = getenv ( "bootfile")) != NULL) { //③
  25. copy_filename (BootFile, s, sizeof (BootFile));
  26. }
  27. #endif
  28. #ifdef BOARD_LATE_INIT
  29. board_late_init ();
  30. #endif
  31. #ifdef CONFIG_BITBANGMII
  32. bb_miiphy_init();
  33. #endif
  34. #if defined(CONFIG_CMD_NET)
  35. #if defined(CONFIG_NET_MULTI)
  36. puts ( "Net: ");
  37. #endif
  38. eth_initialize(gd->bd); //④
  39. #if defined(CONFIG_RESET_PHY_R)
  40. debug ( "Reset Ethernet PHY\n");
  41. reset_phy();
  42. #endif
  43. #endif
  44. #if defined(CONFIG_BOOTROM_SUPPORT) && (!defined(CONFIG_ARCH_HI3559) && !defined(CONFIG_ARCH_HI3556))
  45. extern void download_boot(const int (*handle)(void));
  46. download_boot( NULL);
  47. #endif
  48. #ifdef CONFIG_HAS_SLAVE
  49. e = getenv( "slave_autostart");
  50. if (e && (*e == '1'))
  51. slave_start();
  52. #endif
  53. #ifdef CONFIG_DDR_TRAINING_V300
  54. check_ddr_training();
  55. #endif /* CONFIG_DDR_TRAINING_V300 */
  56. product_control(); //⑤
  57. #ifdef CONFIG_SNAPSHOT_BOOT
  58. /* example */
  59. /* set_mtdparts_info("mtdparts=hinand:1M(boot),3M(kernel),64M(rootfs),2M(param),16M(hibernate)"); */
  60. extern void comp_save_snapshot_image(void);
  61. comp_save_snapshot_image();
  62. #endif
  • 从环境变量中获取网卡的MAC地址ethaddr,然后将它解析
  • loadaddr这个环境变量默认是没有被定义
  • bootfile是指启动kernel文件的类型,在海思设备,默认是设置为uImage。
  • 初始化网卡设备
  • 这个是对ddr做等长匹配,对于hi3251a,这个工作在第一阶段的start.S文件中就已近做过了,在实际上这个函数相当于空。

8.uboot命令处理    


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

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

 

 


转载:https://blog.csdn.net/li_wen01/article/details/103347598
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场