参考内容点此跳转
在第一阶段,清 BSS 段之后,CPU 跳转到 sdram 里的 start_armboot() 函数,本文,分析 uboot 启动流程的第二阶段。
start_armboot函数在lib_arm/board.c中定义,是U-Boot第二阶段代码的入口。第二阶段的主要工作是进行单板级别的初始化,初始化 nandflash 、norflash 、初始化串口、设置环境变量、最终跳转到 main_loop 里,接收串口传递进来的各种命令。
一、gd和bd结构体
-
- gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
-
- memset ((void*)gd, 0, sizeof (gd_t));
- gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
- memset (gd->bd, 0, sizeof (bd_t));
_armboot_start 是 start.S 中的汇编语言的标号
- .globl _armboot_start
- _armboot_start:
- .word _start
汇编中我们可以通过不同的指令获得一个标号的地址或者标号地址处存放的数据,在 C 中,直接访问一个标号表示获取标号地址处存放的数据,一个无符号整形变量
在include/asm-arm/u-boot-arm.h中:
- extern ulong _armboot_start;
- extern ulong _bss_start;
前边前一篇文章我们分析过堆栈的划分,为 gd 结构体 留出了128K 的空间,根据上边的代码我们可以知道这 128K 有高到低先放一个 gd_t 类型的结构体,紧挨着又放了一个它的成员 bd_t 类型的结构。
① U-Boot使用了一个结构体gd_t来存储全局数据区的数据,这个结构体在include/asm-arm/global_data.h中定义;
bd_t在include/asm/u-boot.h中定义。
- typedef struct global_data {
- bd_t *bd;
- unsigned long flags;
- unsigned long baudrate;
- unsigned long have_console;
- unsigned long reloc_off;
- unsigned long env_addr;
- unsigned long env_valid;
- unsigned long fb_base;
- #ifdef CONFIG_VFD
- unsigned char vfd_type;
- #endif
- #if 0
- unsigned long cpu_clk;
- unsigned long bus_clk;
- unsigned long ram_size;
- unsigned long reset_status;
- #endif
- void **jt;
- } gd_t;
-
- typedef struct bd_info {
- int bi_baudrate;
- unsigned long bi_ip_addr;
- unsigned char bi_enetaddr[6];
- struct environment_s *bi_env; /* 环境变量开始地址*/
- ulong bi_arch_number;
- ulong bi_boot_params;
- struct
- {
- ulong start;
- ulong size;
- } bi_dram[CONFIG_NR_DRAM_BANKS];
- #ifdef CONFIG_HAS_ETH1
-
- unsigned char bi_enet1addr[6];
- #endif
- } bd_t;
② 前边的 gd 是个全局变量:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
这个声明也避免编译器把r8分配给其它的变量。
任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。
根据U-Boot内存使用图中可以计算gd的值:
gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)
③ U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表。第一阶段调用start_armboot指向C语言执行代码区,首先它要从内存上的重定位数据获得不完全配置的全局数据表格和板级信息表格,即获得gd_t和bd_t,这两个类型变量记录了刚启动时的信息,并将要记录作为引导内核和文件系统的参数,如bootargs等等,并且将来还会在启动内核时,由uboot交由kernel时会有所用。
二、初始化工作
前边分配完了 gd 结构体之后,就开始一系列的初始化工作,初始化函数定义在 init_sequence 中
U-Boot使用一个数组init_sequence来存储对于大多数开发板都要执行的初始化函数的函数指针。
- for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
- if ((*init_fnc_ptr)() != 0) {
- hang ();
- }
- }
- init_fnc_t *init_sequence[] = {
- cpu_init,
- board_init,
- interrupt_init,
- env_init,
- init_baudrate,
- serial_init,
- console_init_f,
- display_banner,
- #if defined(CONFIG_DISPLAY_CPUINFO)
- print_cpuinfo,
- #endif
- #if defined(CONFIG_DISPLAY_BOARDINFO)
- checkboard,
- #endif
- dram_init,
- display_dram_config,
- NULL,
- };
2.1 cpu_init
cpu/arm920t/cpu.c 初始化irq和fiq模式的栈
- int cpu_init (void)
- {
-
-
-
- #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;
- }
如果使用 irq 的话,将这两个宏指向之前分配的栈空间
2.2 board_init
board/smdk2410/smdk2410.c 初始化GPIO
- int board_init (void)
- {
-
- S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
-
-
- S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();
-
-
- clk_power->LOCKTIME = 0xFFFFFF;
-
-
-
- clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);
-
-
- delay (4000);
-
-
- clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);
-
-
- delay (8000);
-
-
- gpio->GPACON = 0x007FFFFF;
- gpio->GPBCON = 0x00044555;
- gpio->GPBUP = 0x000007FF;
- gpio->GPCCON = 0xAAAAAAAA;
- gpio->GPCUP = 0x0000FFFF;
- gpio->GPDCON = 0xAAAAAAAA;
- gpio->GPDUP = 0x0000FFFF;
- gpio->GPECON = 0xAAAAAAAA;
- gpio->GPEUP = 0x0000FFFF;
- gpio->GPFCON = 0x000055AA;
- gpio->GPFUP = 0x000000FF;
- gpio->GPGCON = 0xFF95FFBA;
- gpio->GPGUP = 0x0000FFFF;
- gpio->GPHCON = 0x002AFAAA;
- gpio->GPHUP = 0x000007FF;
-
-
- gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
-
-
-
- gd->bd->bi_boot_params = 0x30000100;
-
-
- icache_enable();
- dcache_enable();
-
- return 0;
- }
重点工作,向 gd 结构体中记录了 机器ID 以及 tag 的存放地址,这俩都是要传递给内核的!!!
2.3 interrupt_init
cpu/arm920t/s3c24x0/interrupt.c 初始化定时器-PWM的初始化,无关紧要
2.4 env_init
common/env_flash.c 检查flash上的环境参数是否有效
- typedef struct environment_s {
- unsigned long crc;
- unsigned char flags;
- unsigned char data[ENV_SIZE];
- } env_t;
- env_t *env_ptr = (env_t *)CFG_ENV_ADDR;
- static env_t *flash_addr = (env_t *)CFG_ENV_ADDR;
环境变量用一个 environment_s 结构来描述,它被放置在 norflash 的 0x70000 起始的地址处,crc 是环境变量的校验和,全部的环境变量都以字符串的形式存放在 data 数组中,两个环境变量之间用 “\0” 隔开。
- int env_init(void)
- {
- if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
- gd->env_addr = (ulong)&(env_ptr->data);
- gd->env_valid = 1;
- return(0);
- }
-
- gd->env_addr = (ulong)&default_environment[0];
- gd->env_valid = 0;
- return (0);
- }
现在env_ptr指向 Norfalsh 中的0x070000,进行校验,判断环境变量是否可用。
1、uboot 第一次启动,那么 norflash 这个地址处并没有任何东西,校验失败,则使用默认的环境变量,使全局指针 gd->env_addr 指向内存中的默认环境变量,并设置标志位 gd->env_valid 为 0 。
2、uboot 非第一次启动,那么校验成功,将全局指针 gd->env_addr 指向环境变量,并使标志位 gd->env_valid 置一。
默认环境变量的 定义 CONFIG_BOOTARGS 等宏在 Smdk2410.h (include\configs)
- uchar default_environment[] = {
- #ifdef CONFIG_BOOTARGS
- "bootargs=" CONFIG_BOOTARGS "\0"
- #endif
- #ifdef CONFIG_BOOTCOMMAND
- "bootcmd=" CONFIG_BOOTCOMMAND "\0"
- #endif
- #ifdef CONFIG_RAMBOOTCOMMAND
- "ramboot=" CONFIG_RAMBOOTCOMMAND "\0"
- #endif
- #ifdef CONFIG_NFSBOOTCOMMAND
- "nfsboot=" CONFIG_NFSBOOTCOMMAND "\0"
- #endif
- #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
- "bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0"
- #endif
- #if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
- "baudrate=" MK_STR(CONFIG_BAUDRATE) "\0"
- #endif
- #ifdef CONFIG_LOADS_ECHO
- "loads_echo=" MK_STR(CONFIG_LOADS_ECHO) "\0"
- #endif
- #ifdef CONFIG_ETHADDR
- "ethaddr=" MK_STR(CONFIG_ETHADDR) "\0"
- #endif
- #ifdef CONFIG_ETH1ADDR
- "eth1addr=" MK_STR(CONFIG_ETH1ADDR) "\0"
- #endif
- #ifdef CONFIG_ETH2ADDR
- "eth2addr=" MK_STR(CONFIG_ETH2ADDR) "\0"
- #endif
- #ifdef CONFIG_ETH3ADDR
- "eth3addr=" MK_STR(CONFIG_ETH3ADDR) "\0"
- #endif
- #ifdef CONFIG_IPADDR
- "ipaddr=" MK_STR(CONFIG_IPADDR) "\0"
- #endif
- #ifdef CONFIG_SERVERIP
- "serverip=" MK_STR(CONFIG_SERVERIP) "\0"
- #endif
- #ifdef CFG_AUTOLOAD
- "autoload=" CFG_AUTOLOAD "\0"
- #endif
- #ifdef CONFIG_PREBOOT
- "preboot=" CONFIG_PREBOOT "\0"
- #endif
- #ifdef CONFIG_ROOTPATH
- "rootpath=" MK_STR(CONFIG_ROOTPATH) "\0"
- #endif
- #ifdef CONFIG_GATEWAYIP
- "gatewayip=" MK_STR(CONFIG_GATEWAYIP) "\0"
- #endif
- #ifdef CONFIG_NETMASK
- "netmask=" MK_STR(CONFIG_NETMASK) "\0"
- #endif
- #ifdef CONFIG_HOSTNAME
- "hostname=" MK_STR(CONFIG_HOSTNAME) "\0"
- #endif
- #ifdef CONFIG_BOOTFILE
- "bootfile=" MK_STR(CONFIG_BOOTFILE) "\0"
- #endif
- #ifdef CONFIG_LOADADDR
- "loadaddr=" MK_STR(CONFIG_LOADADDR) "\0"
- #endif
- #ifdef CONFIG_CLOCKS_IN_MHZ
- "clocks_in_mhz=1\0"
- #endif
- #if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0)
- "pcidelay=" MK_STR(CONFIG_PCI_BOOTDELAY) "\0"
- #endif
- #ifdef CONFIG_EXTRA_ENV_SETTINGS
- CONFIG_EXTRA_ENV_SETTINGS
- #endif
- "\0"
- };
以 "bootargs=" CONFIG_BOOTARGS "\0" 为例:
#define CONFIG_BOOTARGS "root=ramfs devfs=mount console=ttySA0,9600"
确实如同前边说所,data 里边放置的都是一个个字符串,两个环境变量之间 通过 “\0”隔开。
2.5 init_baudrate 设置波特率 首先从环境变量中读取,如果没有则设置为 115200
2.6 serial_init 串口初始化
- int serial_init (void)
- {
- serial_setbrg ();
-
- return (0);
- }
- void serial_setbrg (void)
- {
- S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);
- int i;
- unsigned int reg = 0;
-
-
- reg = get_PCLK() / (16 * gd->baudrate) - 1;
-
-
- uart->UFCON = 0x07;
- uart->UMCON = 0x0;
-
- uart->ULCON = 0x3;
-
-
-
-
- uart->UCON = 0x245;
- uart->UBRDIV = reg;
-
- #ifdef CONFIG_HWFLOW
- uart->UMCON = 0x1;
- #endif
- for (i = 0; i < 100; i++);
- }
串口控制寄存器的设置.....以前移植的时候遇到一个问题,get_PCLK() 这个函数需要修改,2410 和 2440 不一样。
2.7 console_init_f 控制台初始化,无关紧要
2.8 display_banner 打印代码段 BSS段等地址信息
2.9 dram_init
- int dram_init (void)
- {
- gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
- gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
-
- return 0;
- }
在全局指针 gd 中标记内存范围
2.A display_dram_config 打印 sdram 信息
2.B norflash 初始化
- #ifndef CFG_NO_FLASH
-
- size = flash_init ();
2.C nandflash 初始化
- #if (CONFIG_COMMANDS & CFG_CMD_NAND)
- puts ("NAND: ");
- nand_init();
2.D 环境变量重定位
- void env_relocate (void)
- {
- DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
- gd->reloc_off);
-
-
-
- env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
- DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
-
-
-
-
- env_get_char = env_get_char_memory;
-
- if (gd->env_valid == 0)
- puts ("*** Warning - bad CRC, using default environment\n\n");
- SHOW_BOOT_PROGRESS (-1);
-
-
- if (sizeof(default_environment) > ENV_SIZE)
- {
- puts ("*** Error - default environment is too large\n\n");
- return;
- }
-
- memset (env_ptr, 0, sizeof(env_t));
- memcpy (env_ptr->data,
- default_environment,
- sizeof(default_environment));
- #ifdef CFG_REDUNDAND_ENVIRONMENT
- env_ptr->flags = 0xFF;
- #endif
- env_crc_update ();
- gd->env_valid = 1;
- }
- else {
-
- env_relocate_spec ();
- }
- gd->env_addr = (ulong)&(env_ptr->data);
-
- #ifdef CONFIG_AMIGAONEG3SE
- disable_nvram();
- #endif
- }
-
- #ifdef CONFIG_AUTO_COMPLETE
- void env_relocate_spec (void)
- {
-
-
- memcpy (env_ptr, (void*)flash_addr, CFG_ENV_SIZE);
- }
重定位之后,我们在uboot命令行读取到的环境变量都是内存中的,修改后要写回 norflash
- #ifdef CMD_SAVEENV
-
- int saveenv(void)
- {
- int len, rc;
- ulong end_addr;
- ulong flash_sect_addr;
-
- uchar *env_buffer = (uchar *)env_ptr;
-
- int rcode = 0;
-
- flash_sect_addr = (ulong)flash_addr;
- len = CFG_ENV_SIZE;
-
- end_addr = flash_sect_addr + len - 1;
-
-
- debug ("Protect off %08lX ... %08lX\n",
- (ulong)flash_sect_addr, end_addr);
-
- if (flash_sect_protect (0, flash_sect_addr, end_addr))
- return 1;
-
- puts ("Erasing Flash...");
- if (flash_sect_erase (flash_sect_addr, end_addr))
- return 1;
-
- puts ("Writing to Flash... ");
- rc = flash_write((char *)env_buffer, flash_sect_addr, len);
- if (rc != 0) {
- flash_perror (rc);
- rcode = 1;
- } else {
- puts ("done\n");
- }
-
-
- (void) flash_sect_protect (1, flash_sect_addr, end_addr);
- return rcode;
- }
2.E 从环境变量中获取 ip mac 地址放入全局 gd 结构
-
- gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
-
-
- {
- 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;
- }
-
- #ifdef CONFIG_HAS_ETH1
- i = getenv_r ("eth1addr", tmp, sizeof (tmp));
- s = (i > 0) ? tmp : NULL;
-
- for (reg = 0; reg < 6; ++reg) {
- gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
- if (s)
- s = (*e) ? e + 1 : e;
- }
有意思的是,这里用了一个{ },大概是为了防止变量名字冲突?
2.F devices_init (); 设备初始化
2.G 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;
- #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 (CONFIG_COMMANDS & CFG_CMD_I2C)
- gd->jt[XF_i2c_write] = (void *) i2c_write;
- gd->jt[XF_i2c_read] = (void *) i2c_read;
- #endif /* CFG_CMD_I2C */
在全局结构 gd 中记录,函数调用地址登记。
2.H console_init_r 后期控制台初始化
2.H enable_interrupts 在 cpsr 中使能 irq fiq
- void enable_interrupts (void)
- {
- unsigned long temp;
- __asm__ __volatile__("mrs %0, cpsr\n"
- "bic %0, %0, #0x80\n"
- "msr cpsr_c, %0"
- : "=r" (temp)
- :
- : "memory");
- }
2.I cs8900_get_enetaddr (gd->bd->bi_enetaddr) 网卡初始化
- #if (CONFIG_COMMANDS & CFG_CMD_NET)
- #if defined(CONFIG_NET_MULTI)
- puts ("Net: ");
- #endif
- eth_initialize(gd->bd);
2.J 跳转到 uboot 菜单
- for (;;) {
- main_loop ();
- }
总结:
