uboot 启动流程分析(二) — 第二阶段

来源:互联网 发布:sql临时表 编辑:程序博客网 时间:2024/05/21 22:43

在 uboot 第一阶段启动完成后将会调用 start_armboot 开始第二阶段的启动流程,这个阶段的代码由 c 语言编写,分析如下:

一、基础数据结构

第二阶段主要用到了两个数据结构即 gd_t bd_t,其定义如下:

/* 全局数据结构 */typedefstructglobal_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_VFDunsigned char   vfd_type;       /* 显示器类型(VFD代指真空荧光屏) */#endif#if 0unsigned long   cpu_clk;        /* cpu 频率*/unsigned long   bus_clk;        /* bus 频率 */phys_size_t     ram_size;       /* ram 大小 */unsigned long   reset_status;   /* reset status register at boot */#endifvoid            **jt;           /* 跳转函数表 */} gd_t;/* Global Data Flags */#defineGD_FLG_RELOC0x00001/* 代码已经转移到 RAM */#defineGD_FLG_DEVINIT0x00002/* 设备已经完成初始化 */#defineGD_FLG_SILENT0x00004/* 静音模式 */#defineGD_FLG_POSTFAIL0x00008/* Critical POST test failed */#defineGD_FLG_POSTSTOP0x00010/* POST seqeunce aborted */#defineGD_FLG_LOGINIT0x00020/* Log Buffer has been initialized*/#define GD_FLG_DISABLE_CONSOLE0x00040/* 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)/* 环境变量结构 */typedefstruct environment_s {uint32_t  crc;                 /* CRC32 over data bytes */#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENTunsigned char flags;           /* active/obsolete flags */#endifunsigned 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,          /* 与处理器架构相关的初始化 */#endifboard_init,             /* 板级特殊设备初始化 */#if defined(CONFIG_USE_IRQ)interrupt_init,         /* 初始化中断 */#endiftimer_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 */#endifdram_init,              /* 配置有效的内存区 */#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)arm_pci_init,#endifdisplay_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_SETTINGSCONFIG_EXTRA_ENV_SETTINGS#endif"\0"};
我们可以在 uboot 命令行模式下输入 printenv 命令查看当前的环境变量值。

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 */}
其中工作比较多的是 stdio_init 完成了部分外设的初始化:
int stdio_init (void){/* 初始化设备链表 */INIT_LIST_HEAD(&(devs.list));drv_lcd_init ();       /* 初始化 lcd,显示 Logo */drv_system_init ();    /* 初始化stdio设备系统 */serial_stdio_init ();  /* 初始化串口 */return (0);}
start_armboot 最终进入 main_loop 函数,首先判断用户选择的启动模式,如果是命令模式则等待输入命令然后执行,代码如下:
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");elserc = run_command (lastcommand, flag);  /* 执行命令 */if (rc <= 0) {  /* 执行错误的命令从历史记录中删除 */lastcommand[0] = 0;}}}

附:启动命令解析

在 uboot 进入主循环后默认会进入 nand 启动模式,会依次执行 3 个命令:

bootcmd=nand read 89807e00 600000 200000; hdcvt 89807e00; bootm 89807fc0
1、nand 命令

该命令用于进行各种 nand 操作,用法如下:

nand - NAND sub-systemUsage:nand info - show available NAND devicesnand device [dev] - show or set current devicenand read - addr off|partition sizenand 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 blocksnand dump[.oob] off - dump pagenand 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;
即从 nand 偏移为 0x600000 的地方读取长度为 0x200000 字节(2M)的数据到内存 0x89807e00 地址处。对于这几个参数的解释需要了解 内存地址空间 nand 地址空间 是怎么分配的:
/* 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 */#defineCONFIG_SYS_UBOOT_BASE           (MDDR_BASE_ADDR + 0x00008000)   /* uboot 加载的起始地址 */#defineCONFIG_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 */#defineCONFIG_XLOADER_OFFSET           0x00000000    /* 0M ~ 2M */#defineCONFIG_XLOADER_MSIZE            0x00200000/* uboot 存储的起始地址为 2M,长度为 2M */#defineCONFIG_UBOOT_OFFSET             0x00200000    /* 2M ~ 4M */#defineCONFIG_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 */#defineCONFIG_KERNEL_OFFSET            0x00600000    /* 6M ~ 9M */#defineCONFIG_KERNEL_MSIZE             0x00300000
在烧录镜像到 nand 的时候程序会按照上表将 xloader、uboot、kernel 分别烧录到其对应空间,这段 nand 空间是以 1M 为单位对齐的。

2、hdcvt 命令

自动启动中用到的第二个命令是:

hdcvt 89807e00
该命令将位于内存 0x89807e00 的平台镜像头转换为标准的镜像头,用法介绍如下:

Usage:hdcvt usage: hdcvt addr_from [addr_to] [body_addr]
生成的标准镜像头存储起始地址为:addr_to = addr_from - sizeof(xx_image_header_t) - sizeof(image_header_t)。这里涉及到两个新的结构体struct xx_image_header 和 struct image_header:前者是平台的镜像头,后者为标准镜像头,定义如下:
/* 平台镜像头,长度512Byte,填充在kernel的加载地址与入口地址之间的区域:0x89807e00 ~ 0x89808000 */typedef struct xx_image_header {unsigned chariv[IV_SIZE];unsigned intboot_signature;unsigned intload_address;             /* 加载地址 = 镜像入口 - 标准镜像头长度(64Byte) */unsigned intrun_address;              /* 镜像入口 */unsigned intfirm_size;                /* 镜像长度 */unsigned intnand_offset;unsigned intimage_type;               /* 镜像类型 */unsigned charboard_name[16];unsigned charreserved[40];unsigned char certificate[CERT_SIZE];unsigned char signature[SIGE_SIZE];} xx_image_header_t;/* 标准镜像头,长度64Byte,填充在kernel入口地址之前:0x89807fc0 ~ 0x89808000 */typedef struct image_header {uint32_tih_magic;/* 魔数,用于检测是否存在镜像头*/uint32_tih_hcrc;/* 镜像头校验和*/uint32_tih_time;/* 镜像创建时间 */uint32_tih_size;/* 镜像大小 */uint32_tih_load;/* 镜像加载地址 */uint32_tih_ep;/* 镜像入口地址 */uint32_tih_dcrc;/* 镜像校验和 */uint8_tih_os;/* 系统类型 */uint8_tih_arch;/* 处理器类型 */uint8_tih_type;/* 镜像类型 */uint8_tih_comp;/* 压缩类型 */uint8_tih_name[IH_NMLEN];/* 镜像名称 */} image_header_t;
3、bootm 命令

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 ... OKOKStarting kernel ...

原创粉丝点击