在 uboot 第一阶段启动完成后将会调用 start_armboot 开始第二阶段的启动流程,这个阶段的代码由 c 语言编写,分析如下:
一、基础数据结构
第二阶段主要用到了两个数据结构即 gd_t 和 bd_t,其定义如下:
- /* 全局数据结构 */
- 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; /* 环境参数地址 */
- unsigned long env_valid; /* 环境参数 CRC 校验有效标志 */
- unsigned long fb_base; /* fb 起始地址 */
- #ifdef CONFIG_VFD
- unsigned char vfd_type; /* 显示器类型(VFD代指真空荧光屏) */
- #endif
- #if 0
- unsigned long cpu_clk; /* cpu 频率*/
- unsigned long bus_clk; /* bus 频率 */
- phys_size_t ram_size; /* ram 大小 */
- unsigned long reset_status; /* reset status register at boot */
- #endif
- void **jt; /* 跳转函数表 */
- } gd_t;
- /* Global Data Flags */
- #define GD_FLG_RELOC 0x00001 /* 代码已经转移到 RAM */
- #define GD_FLG_DEVINIT 0x00002 /* 设备已经完成初始化 */
- #define GD_FLG_SILENT 0x00004 /* 静音模式 */
- #define GD_FLG_POSTFAIL 0x00008 /* Critical POST test failed */
- #define GD_FLG_POSTSTOP 0x00010 /* POST seqeunce aborted */
- #define GD_FLG_LOGINIT 0x00020 /* Log Buffer has been initialized */
- #define GD_FLG_DISABLE_CONSOLE 0x00040 /* Disable console (in & out) */
- /* 定义一个寄存器变量,占用寄存器r8,作为 gd_t 的全局指针 */
- #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
- /* 板级信息结构 */
- 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; /* 板子的 id */
- ulong bi_boot_params; /* 板子的启动参数 */
- struct /* RAM 配置 */
- {
- ulong start;
- ulong size;
- } bi_dram[CONFIG_NR_DRAM_BANKS];
- } bd_t;
- /**************************************************************************
- *
- * 每个环境变量以形如"name=value"的字符串存储并以'\0'结尾,环境变量的尾部以两个'\0'结束。
- * 新增的环境变量都依次添加在尾部,如果删除一个环境变量需要将其后面的向前移动
- * 如果替换一个环境变量需要先删除再新增。
- *
- * 环境变量采用 32 bit CRC 校验.
- *
- **************************************************************************
- */
- /* 我们平台定义了8kB大小的环境变量存储区,地址空间位于4M ~ 6M */
- #define CONFIG_ENV_IS_IN_NAND
- #define CONFIG_ENV_OFFSET 0x00400000 /* 4M ~ 6M */
- #define CONFIG_ENV_SIZE 8192 /* 8KB */
- #define CONFIG_ENV_RANGE 0x00200000
- #define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)
- /* 环境变量结构 */
- typedef struct environment_s {
- uint32_t crc; /* CRC32 over data bytes */
- #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
- unsigned char flags; /* active/obsolete flags */
- #endif
- unsigned char data[ENV_SIZE]; /* Environment data */
- } env_t;
这两个类型变量记录了刚启动时的信息,还将记录作为引导内核和文件系统的参数,如 bootargs 等,并且将来还会在启动内核时,由 uboot 交由 kernel 时会有所用。
二、启动流程
1、init_sequence
start_armboot 首先为全局数据结构和板级信息结构分配内存,代码如下:
- #define CONFIG_UNCONTINUOUS_MEM
- #define CONFIG_SYS_MALLOC_END (MDDR_BASE_ADDR + 0x00600000)
- #define CONFIG_SYS_MALLOC_LEN 0x00100000 /* 1MB */
- gd = (gd_t*)(CONFIG_SYS_MALLOC_END - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
- __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; /* 标记为代码已经转移到 RAM */
可以看到 bd_t 、gd_t 以及 MALLOC 区域是紧挨着的。然后依次调用 init_sequence数组中的函数指针完成各部分的初始化,代码如下:
- typedef int (init_fnc_t) (void);
- int print_cpuinfo (void);
- init_fnc_t *init_sequence[] = {
- #if defined(CONFIG_ARCH_CPU_INIT)
- arch_cpu_init, /* 与处理器架构相关的初始化 */
- #endif
- board_init, /* 板级特殊设备初始化 */
- #if defined(CONFIG_USE_IRQ)
- interrupt_init, /* 初始化中断 */
- #endif
- timer_init, /* 初始化定时器 */
- env_init, /* 初始化环境变量 */
- init_baudrate, /* 初始化波特率 */
- serial_init, /* 初始化串口 */
- console_init_f, /* 控制台初始化第一阶段 */
- display_banner, /* 打印uboot版本信息 */
- #if defined(CONFIG_DISPLAY_CPUINFO)
- print_cpuinfo, /* 打印cpu信息及各总线频率 */
- #endif
- #if defined(CONFIG_DISPLAY_BOARDINFO)
- checkboard, /* 打印板级信息 */
- #endif
- #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
- init_func_i2c, /* 初始化i2c */
- #endif
- dram_init, /* 配置有效的内存区 */
- #if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
- arm_pci_init,
- #endif
- display_dram_config, /* 打印内存区配置信息 */
- NULL,
- };
- for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
- if ((*init_fnc_ptr)() != 0) {
- hang ();
- }
- }
- void hang (void)
- {
- puts ("### ERROR ### Please RESET the board ###\n");
- for (;;);
- }
在我们平台比较重要的初始化函数有 board_init 以及 env_init,代码如下:
- /* 板级特殊设备初始化 */
- int board_init(void)
- {
- uint32_t val;
- uint32_t adc_vol;
- uint32_t adc_per_vol;
- uint32_t adc_per_vol_res;
- mmu_cache_on(memory_map); /* 初始化mmu */
- clock_init(); /* 初始化时钟 */
- calibrate_delay(); /* 延时校准 */
- /*enable power */
- xx_request_gpio(GPIO_PMU_WAKEUP);
- xx_set_gpio_direction(GPIO_PMU_WAKEUP, 0);
- xx_gpio_set(GPIO_PMU_WAKEUP,0);
- xx_request_gpio(GPIO_PMU_MODE);
- xx_set_gpio_direction(GPIO_PMU_MODE, 0);
- xx_gpio_set(GPIO_PMU_MODE,0);
- i2c_init();
- adc_init();
- pmu_init();
- keypad_init();
- /* arch number of board */
- gd->bd->bi_arch_number = MACH_TYPE_XXX;
- /* adress of boot parameters */
- gd->bd->bi_boot_params = CONFIG_ATAG_ADDR;
- return 0;
- }
- /* 初始化环境变量 */
- int env_init(void)
- {
- gd->env_addr = (ulong)&default_environment[0]; /* 设定默认的环境变量 */
- gd->env_valid = 1;
- return (0);
- }
在环境变量 default_environment 中我们设置了很多参数,列表如下:
- uchar default_environment[] = {
- /* #define CONFIG_UBOOT_OFFSET 0x00200000 */
- "uboot-nandoff="MK_STR(CONFIG_UBOOT_OFFSET) "\0"
- /* #define CONFIG_UBOOT_LADDR 0x88007e00 */
- "uboot-laddr=" MK_STR(CONFIG_UBOOT_LADDR) "\0"
- /* #define CONFIG_BOOTARGS_SD "console=ttyS0,921600n8 console=ttyMTD androidboot.console=ttyS0 \
- mtdparts=atxx_nd:32M(boot),2M(ttyMTD),-(system) quiet" */
- "bootargs_sd=" CONFIG_BOOTARGS_SD "\0"
- /* #define CONFIG_BOOTCOMMAND_SD "fatload mmc 1 0x89807e00 kboot.img; hdcvt 89807e00; bootm 89807fc0" */
- "bootcmd_sd=" CONFIG_BOOTCOMMAND_SD "\0"
- /* 各总线时钟频率
- * #define CONFIG_CLK_ARM 1001000000
- * #define CONFIG_CLK_AXI 312000000
- * #define CONFIG_CLK_APP 104000000
- * #define CONFIG_CLK_MDDR 201000000
- * #define CONFIG_CLK_GCLK 312000000
- * #define CONFIG_CLK_VPCLK 156000000
- * #define CONFIG_CLK_VSCLK 403000000
- */
- "clk-arm=" MK_STR(CONFIG_CLK_ARM) "\0"
- "clk-axi=" MK_STR(CONFIG_CLK_AXI) "\0"
- "clk-app=" MK_STR(CONFIG_CLK_APP) "\0"
- "clk-mddr=" MK_STR(CONFIG_CLK_MDDR) "\0"
- "clk-gclk=" MK_STR(CONFIG_CLK_GCLK) "\0"
- "clk-vpclk=" MK_STR(CONFIG_CLK_VPCLK) "\0"
- "clk-vsclk=" MK_STR(CONFIG_CLK_VSCLK) "\0"
- /* #define CONFIG_BOOTARGS "console=ttyS0,921600n8 console=ttyMTD androidboot.console=ttyS0 mtdparts=\
- atxx_nd:32M(boot),2M(ttyMTD),-(system) init=/init ubi.mtd=2 root=ubi0:rootfs rootfstype=ubifs ro" */
- "bootargs=" CONFIG_BOOTARGS "\0"
- /* #define CONFIG_BOOTCOMMAND "nand read 89807e00 600000 200000; hdcvt 89807e00; bootm 89807fc0" */
- "bootcmd=" CONFIG_BOOTCOMMAND "\0"
- "clocks_in_mhz=1\0"
- #ifdef CONFIG_EXTRA_ENV_SETTINGS
- CONFIG_EXTRA_ENV_SETTINGS
- #endif
- "\0"
- };
2、start_armboot
start_armboot 在接下来的流程中还做了如下操作:
- void start_armboot (void)
- {
- init_fnc_t **init_fnc_ptr;
- char *s;
- unsigned long addr;
- mem_malloc_init (CONFIG_SYS_MALLOC_END - CONFIG_SYS_MALLOC_LEN, CONFIG_SYS_MALLOC_LEN);
- ...
- /* board init may have inited fb_base */
- if (!gd->fb_base) {
- /*
- * reserve memory for LCD display (always full pages)
- */
- /* bss_end is defined in the board-specific linker script */
- addr = CONFIG_SYS_MALLOC_END;
- lcd_setmem (addr);
- gd->fb_base = addr;
- }
- nand_init(); /* 初始化 NAND */
- env_relocate (); /* 重定位环境变量,将其从 NAND 拷贝到内存中 */
- serial_initialize(); /* 初始化串口 */
- gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr"); /* IP Address */
- stdio_init (); /* 初始化外设 */
- jumptable_init (); /* 初始化跳转函数表 */
- console_init_r (); /* 控制台初始化第二阶段 */
- misc_init_r (); /* 杂项设备初始化, eg:battery */
- enable_interrupts (); /* enable exceptions */
- /* #define CONFIG_SYS_LOAD_ADDR (MDDR_BASE_ADDR + 0x00807e00) */
- /* 如果存在则从环境变量中读取装载地址,其默认为 ulong load_addr = CONFIG_SYS_LOAD_ADDR; */
- if ((s = getenv ("loadaddr")) != NULL) {
- load_addr = simple_strtoul (s, NULL, 16);
- }
- /* main_loop() can return to retry autoboot, if so just run it again. */
- for (;;) {
- main_loop (); /* 进入主循环 common/main.c */
- }
- /* NOTREACHED - no way out of command loop except booting */
- }
- int stdio_init (void)
- {
- /* 初始化设备链表 */
- INIT_LIST_HEAD(&(devs.list));
- drv_lcd_init (); /* 初始化 lcd,显示 Logo */
- drv_system_init (); /* 初始化stdio设备系统 */
- serial_stdio_init (); /* 初始化串口 */
- return (0);
- }
- void main_loop (void)
- {
- static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
- int len;
- int rc = 1;
- int flag;
- char *s;
- int bootdelay;
- s = getenv ("bootdelay");
- bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; /* 获取超时信息 */
- s = getenv ("bootcmd"); /* 获取启动命令 */
- /* abortboot会判断用户选择的启动模式,如果是命令模式就会返回1,如果是其他模式就不再返回 */
- if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
- s = getenv ("bootcmd");
- run_command (s, 0);
- }
- for (;;) {
- len = readline (CONFIG_SYS_PROMPT); /* 读取输入 */
- flag = 0;
- if (len > 0)
- strcpy (lastcommand, console_buffer); /* 将输入保存到历史记录中 */
- else if (len == 0)
- flag |= CMD_FLAG_REPEAT; /* 如果没有输入则重复上次 */
- if (len == -1)
- puts ("<INTERRUPT>\n");
- else
- rc = run_command (lastcommand, flag); /* 执行命令 */
- if (rc <= 0) { /* 执行错误的命令从历史记录中删除 */
- lastcommand[0] = 0;
- }
- }
- }
附:启动命令解析
在 uboot 进入主循环后默认会进入 nand 启动模式,会依次执行 3 个命令:
- bootcmd=nand read 89807e00 600000 200000; hdcvt 89807e00; bootm 89807fc0
该命令用于进行各种 nand 操作,用法如下:
- nand - NAND sub-system
- Usage:
- nand info - show available NAND devices
- nand device [dev] - show or set current device
- nand read - addr off|partition size
- nand write - addr off|partition size
- read/write 'size' bytes starting at offset 'off'
- to/from memory address 'addr', skipping bad blocks.
- nand bad - show bad blocks
- nand dump[.oob] off - dump page
- nand scrub - really clean NAND erasing bad blocks (UNSAFE)
- nand markbad off [...] - mark bad block(s) at offset (UNSAFE)
- nand biterr off - make a bit error at offset (UNSAFE)
- nand read 89807e00 600000 200000;
- /* we have 1 bank of DRAM */
- #define CONFIG_NR_DRAM_BANKS 1
- #define MDDR_BASE_ADDR 0x88000000 /* 物理内存的起始地址 */
- #define CONFIG_SYS_MALLOC_END (MDDR_BASE_ADDR + 0x00600000) /* MALLOC 区结束地址 */
- #define CONFIG_SYS_MALLOC_LEN 0x00100000 /* MALLOC 区长度 1MB */
- /* u-boot run address */
- #define CONFIG_SYS_UBOOT_BASE (MDDR_BASE_ADDR + 0x00008000) /* uboot 加载的起始地址 */
- #define CONFIG_UBOOT_LADDR 0x88007e00
- /* default load address for reading (i.e. kernel zImage with header) */
- #define CONFIG_SYS_LOAD_ADDR (MDDR_BASE_ADDR + 0x00807e00) /* kernel 加载的起始地址 */
- #define CONFIG_SYS_KERN_ADDR (MDDR_BASE_ADDR + 0x00808000) /* kernel 入口地址 */
- #define CONFIG_ATAG_ADDR (MDDR_BASE_ADDR + 0x01800100) /* kernel 启动参数地址 */
- /* xloader 存储的起始地址为 0,长度为 2M */
- #define CONFIG_XLOADER_OFFSET 0x00000000 /* 0M ~ 2M */
- #define CONFIG_XLOADER_MSIZE 0x00200000
- /* uboot 存储的起始地址为 2M,长度为 2M */
- #define CONFIG_UBOOT_OFFSET 0x00200000 /* 2M ~ 4M */
- #define CONFIG_UBOOT_MSIZE 0x00200000
- /* 环境变量存储的起始地址为 4M,默认长度为 8k */
- #define CONFIG_ENV_OFFSET 0x00400000 /* 4M ~ 6M */
- #define CONFIG_ENV_SIZE 8192 /* 8KB */
- #define CONFIG_ENV_RANGE 0x00200000
- /* kernel 存储的起始地址为 6M,长度为 3M */
- #define CONFIG_KERNEL_OFFSET 0x00600000 /* 6M ~ 9M */
- #define CONFIG_KERNEL_MSIZE 0x00300000
2、hdcvt 命令
自动启动中用到的第二个命令是:
- hdcvt 89807e00
- Usage:
- hdcvt usage: hdcvt addr_from [addr_to] [body_addr]
- /* 平台镜像头,长度512Byte,填充在kernel的加载地址与入口地址之间的区域:0x89807e00 ~ 0x89808000 */
- typedef struct xx_image_header {
- unsigned char iv[IV_SIZE];
- unsigned int boot_signature;
- unsigned int load_address; /* 加载地址 = 镜像入口 - 标准镜像头长度(64Byte) */
- unsigned int run_address; /* 镜像入口 */
- unsigned int firm_size; /* 镜像长度 */
- unsigned int nand_offset;
- unsigned int image_type; /* 镜像类型 */
- unsigned char board_name[16];
- unsigned char reserved[40];
- unsigned char certificate[CERT_SIZE];
- unsigned char signature[SIGE_SIZE];
- } xx_image_header_t;
- /* 标准镜像头,长度64Byte,填充在kernel入口地址之前:0x89807fc0 ~ 0x89808000 */
- typedef struct image_header {
- uint32_t ih_magic; /* 魔数,用于检测是否存在镜像头*/
- uint32_t ih_hcrc; /* 镜像头校验和*/
- uint32_t ih_time; /* 镜像创建时间 */
- uint32_t ih_size; /* 镜像大小 */
- uint32_t ih_load; /* 镜像加载地址 */
- uint32_t ih_ep; /* 镜像入口地址 */
- uint32_t ih_dcrc; /* 镜像校验和 */
- uint8_t ih_os; /* 系统类型 */
- uint8_t ih_arch; /* 处理器类型 */
- uint8_t ih_type; /* 镜像类型 */
- uint8_t ih_comp; /* 压缩类型 */
- uint8_t ih_name[IH_NMLEN]; /* 镜像名称 */
- } image_header_t;
bootm 用于启动 Linux,参数为镜像的加载地址:
- bootm 89807fc0
可以看到加载地址为 0x89807fc0,这个地址在镜像头中保存,计算方法:ih_load = ih_ep - sizeof(struct image_header) 即:0x89807fc0 = 0x89808000 - 0x40。该命令执行时串口信息如下:
- Booting kernel from Legacy Image at 89807fc0 ...
- Image Name: Linux-2.6.32.9
- Image Type: ARM Linux Kernel Image (uncompressed)
- Data Size: 1964376 Bytes = 1.9 MB
- Load Address: 89807fc0
- Entry Point: 89808000
- Verifying Checksum ... OK
- XIP Kernel Image ... OK
- OK
- Starting kernel ...