【U-Boot 启动过程及简单代码分析这里以 AR7241 的 U-Boot 为例
MIPS 架构简介MIPS 地址空间:
这里说的是地址空间,不是内存空间。内存只是映射在一部分地址空间上而已。
内存分为4段 (Kuseg、Kseg0、Kseg1、Kseg2),其中 Kseg0 (0x80000000 ~ 0x9fffffff) 为缓存段,直接映射在物理地址段上。
Kseg1 (0xa0000000 ~ 0xbfffffff) 为非缓存段,直接映射在物理地址段上。
Kuseg 和 Kseg2 需要经过地址转换才能访问。
在 CPU 复位时,缓存未被初始化,只有 Kseg1 能够被直接访问。
AR71XX 物理段:
内存、Flash、IO寄存器等都被映射在物理段上
范围为 0~0x1fffffff,访问时需要通过宏 KSEG0ADDR(_addr) 来通过 Kseg0 访问或 KSEG1ADDR(_addr) 来通过 Kseg1 访问。
物理地址一般不能直接访问,都需要通过 Kseg0 或 Kseg1 来访问。
主要的映射范围:
DDR:0~0x0fffffff (这一段直接映射物理内存,一般通过带缓存的 Kseg0 来访问,以便加快速度)
I/O空间:0x10000000 ~ 0x1dffffff (这一段直接控制硬件,必须通过 Kseg1 来访问)
SPI Flash 空间:0x1f000000 ~ 0x1fffffff (这一段映射闪存的前 16MB 数据)
AR71XX 启动过程CPU 上电时,物理段中,SPI Flash 的前4MB 被循环映射在 0x1f000000 上,因此会被重复 4 次。
执行地址为 KSEG1ADDR(0x1fc00000),即 0xbfc00000。
*注意:U-Boot 在编译时,实际设置的基址,即启动地址为 KSEG0ADDR(0x1f000000),即 0x9f000000。下面会讲到为什么要这么设置。
U-Boot 中的起始代码在 cpu/mips/start.S 中:
- _start:
- #ifndef COMPRESSED_UBOOT
- RVECENT(reset,0) /* U-boot entry point */
- RVECENT(reset,1) /* software reboot */
- RVECENT(romReserved,2)
- ----------------------------------------------------省略----------------------------------------------------
- RVECENT(romReserved,125)
- RVECENT(romReserved,126)
- RVECENT(romReserved,127)
- /* We hope there are no more reserved vectors!
- * 128 * 8 == 1024 == 0x400
- * so this is address R_VEC+0x400 == 0xbfc00400
- */
复制代码以上代码设置异常向量,不需要理解
第一句 RVECENT(reset,0) 为一个跳转,直接转到下面的代码进行运行
- .align 4
- reset:
- /* set GPIO_OE -- added by lsz to fix the LED issue 09.03.25 */
- li a1, AR7100_GPIO_OE
- lw v1, 0(a1)
- li v0, 0x3fdff
- or v1, v1, v0
- sw v1, 0(a1)
- lw v0, 0(a1)
- lw v0, 0(a1)
- ----------------------------------------------------省略----------------------------------------------------
- /* CONFIG0 register */
- li t0, CONF_CM_UNCACHED
- mtc0 t0, CP0_CONFIG
复制代码以上代码前半部分通过设置 GPIO 来点亮路由的 LED。后半部分设置 CPU 相关参数,不需要理解
以上代码没有包含使用相对地址的指令,也就是说,无论这段代码在 CPU 地址空间的何种位置,都能够正常执行因此虽然 CPU 的执行地址为 0xbfc00000,以上代码依然能执行- /* Initialize GOT pointer.*/
- bal 1f
- nop
- .word _GLOBAL_OFFSET_TABLE_
- 1:
- move gp, ra
- lw t1, 0(ra)
- move gp, t1
复制代码以上代码加载了用于 U-Boot 重定位的 GOT 指针,它是 U-Boot 在内存中进行自身移动的关键
- la t9, lowlevel_init
- jalr t9
- nop
复制代码以上代码进行 CPU 频率设置,超频就是在这里实现的
- la t0, rel_start
- j t0
- nop
- rel_start:
复制代码以上代码很关键,它实现了 U-Boot 的执行地址的转移
假设当前 CPU 的执行地址在 0xbfc00000 + x
那么执行此段代码后,CPU 执行地址将跳转到 0x9f000000 + x 处进行执行
前面提到了 CPU 在复位时,物理段 0x1f000000 处循环映射 4 次 Flash 的前 4MB,而 U-Boot 大小只有100KB多,
因此 0x1f000000 跟 0x1fc00000 处的数据是一样的,这就实现了运行地址的转移。
至于为什么不跳转到 0xbf000000 + x 处运行,是因为 AR71XX CPU 在复位时,缓存可用,因此通过 Kseg0 段执行,可以加快速度。
- /* REMAP_DISABLE */
- li a0, AR7100_SPI_CLOCK
- li t0, 0x43
- sw t0, 0(a0)
复制代码以上代码的作用是禁用物理段 0x1f000000 的 4MB Flash 数据循环映射,以便让 0x1f000000 能够映射 16MB 的 Flash 数据
- /* Initialize caches...
- */
- /* 初始化缓存 */
- la t9, simple_mips_cache_reset
- jalr t9
- nop
- /* ... and enable them.
- */
- /* 启用缓存 */
- li t0, CONF_CM_CACHABLE_NONCOHERENT
- mtc0 t0, CP0_CONFIG
- ----------------------------------------------------省略----------------------------------------------------
- #if defined(CONFIG_AR7100) || defined(CONFIG_AR7240)
- /* 将缓存锁定在 Kseg0 低端处 */
- la t9, mips_cache_lock_24k
- jalr t9
- nop
- #endif
- #endif /* #ifndef COMPRESSED_UBOOT */
- /* 将栈指针指向缓存 */
- li t0, CFG_SDRAM_BASE + CFG_INIT_SP_OFFSET
- la sp, 0(t0)
- /* 进入 C 环境 */
- la t9, board_init_f
- j t9
- nop
复制代码这段代码的作用是初始化缓存,并将缓存锁定在 Kseg0 上,用作临时的栈空间
此时内存并未初始化, Kseg0 对应的 0x80000000 ~ 0x8fffffff 还不能被访问,因此需要用缓存代替。
然后跳转到 board_init_f 进行执行
下面开始在 C 环境中运行
文件:lib_mips/board.c
- void board_init_f(ulong bootflag)
- {
- gd_t gd_data, *id;
- bd_t *bd;
- init_fnc_t **init_fnc_ptr;
- ulong addr, addr_sp, len = (ulong)&uboot_end - CFG_MONITOR_BASE;
- ulong *s;
- #ifdef COMPRESSED_UBOOT
- char board_string[50];
- #endif
- #ifdef CONFIG_PURPLE
- void copy_code (ulong);
- #endif
- /* Pointer is writable since we allocated a register for it.
- */
- gd = &gd_data;
- /* compiler optimization barrier needed for GCC >= 3.4 */
- __asm__ __volatile__("": : :"memory");
- memset ((void *)gd, 0, sizeof (gd_t));
- /* 依次运行初始化函数 */
- for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
- if ((*init_fnc_ptr)() != 0) {
- hang ();
- }
- }
- #ifdef COMPRESSED_UBOOT
- checkboard(board_string);
- printf("%s\n\n",board_string);
- gd->ram_size = bootflag;
- puts ("DRAM: ");
- print_size (gd->ram_size, "\n");
- #endif
- /*
- * Now that we have DRAM mapped and working, we can
- * relocate the code and continue running from DRAM.
- */
- addr = CFG_SDRAM_BASE + gd->ram_size;
- /* We can reserve some RAM "on top" here.
- */
- /* round down to next 4 kB limit.
- */
- addr &= ~(4096 - 1);
- debug ("Top of RAM usable for U-Boot at: %08lx\n", addr);
- ----------------------------------------------------省略----------------------------------------------------
- /*
- * Save local variables to board info struct
- */
- /* 记录内存基址及大小、串口波特率 */
- bd->bi_memstart = CFG_SDRAM_BASE; /* start of DRAM memory */
- bd->bi_memsize = gd->ram_size; /* size of DRAM memory in bytes */
- bd->bi_baudrate = gd->baudrate; /* Console Baudrate */
- memcpy (id, (void *)gd, sizeof (gd_t));
- /* On the purple board we copy the code in a special way
- * in order to solve flash problems
- */
- #ifdef CONFIG_PURPLE
- copy_code(addr);
- #endif
- /* 执行代码转移 */
- relocate_code (addr_sp, id, addr);
- /* NOTREACHED - relocate_code() does not return */
- }
复制代码board_init_f 的作用为进行内存、时钟、串口等设备的初始化,并为 U-Boot 的运行划分内存。
初始化函数位于 board_init_f 函数上方
- init_fnc_t *init_sequence[] = {
- #ifndef COMPRESSED_UBOOT
- timer_init, /* 初始化时钟 */
- #endif
- env_init, /* initialize environment */ /* 初始化环境变量 */
- #ifdef CONFIG_INCA_IP
- incaip_set_cpuclk, /* set cpu clock according to environment variable */
- #endif
- init_baudrate, /* initialze baudrate settings */
- #ifndef COMPRESSED_UBOOT
- serial_init, /* serial communications setup */ /* 初始化串口,这样 TTL 才有输出 */
- #endif
- console_init_f, /* 初始化控制台环境 */
- display_banner, /* say that we are here */ /* 显示 U-Boot 1.1.4 ..... */
- #ifndef COMPRESSED_UBOOT
- checkboard, /* 显示 AP99 (ar7241 - Virian) U-boot */
- init_func_ram, /* 初始化内存 */
- #endif
- NULL,
- };
复制代码以上为初始化函数列表,依次运行
在初始化完成后,board_init_f 调用 relocate_code 将 U-Boot 代码从 Flash 中复制到内存中,再从内存中执行
回到 cpu/mips/start.S
- .globl relocate_code
- .ent relocate_code
- relocate_code:
- move sp, a0 /* Set new stack pointer */
- li t0, CFG_MONITOR_BASE
- la t3, in_ram
- lw t2, -12(t3) /* t2 <-- uboot_end_data */
- move t1, a2
- /*
- * Fix GOT pointer:
- *
- * New GOT-PTR = (old GOT-PTR - CFG_MONITOR_BASE) + Destination Address
- */
- /* 根据传递的参数计算移动距离 */
- move t6, gp
- sub gp, CFG_MONITOR_BASE
- add gp, a2 /* gp now adjusted */
- sub t6, gp, t6 /* t6 <-- relocation offset */
- /*
- * t0 = source address
- * t1 = target address
- * t2 = source end address
- */
- /* 复制代码到内存 */
- 1:
- lw t3, 0(t0)
- sw t3, 0(t1)
- addu t0, 4
- ble t0, t2, 1b
- addu t1, 4 /* delay slot */
- /* If caches were enabled, we would have to flush them here.
- */
- /* Jump to where we've relocated ourselves.
- */
- /* 跳转到移动后的代码继续执行 */
- addi t0, a2, in_ram - _start
- j t0
- nop
- .word uboot_end_data
- .word uboot_end
- .word num_got_entries
- in_ram:
- /* 此时已经在内存中了 */
- /* Now we want to update GOT.
- */
- lw t3, -4(t0) /* t3 <-- num_got_entries */
- addi t4, gp, 8 /* Skipping first two entries. */
- li t2, 2
- /* GOT 表重定位 */
- 1:
- lw t1, 0(t4)
- beqz t1, 2f
- add t1, t6
- sw t1, 0(t4)
- 2:
- addi t2, 1
- blt t2, t3, 1b
- addi t4, 4 /* delay slot */
- /* Clear BSS.
- */
- lw t1, -12(t0) /* t1 <-- uboot_end_data */
- lw t2, -8(t0) /* t2 <-- uboot_end */
- add t1, t6 /* adjust pointers */
- add t2, t6
- /* 清除 BSS 段,即全局变量清零 */
- sub t1, 4
- 1: addi t1, 4
- bltl t1, t2, 1b
- sw zero, 0(t1) /* delay slot */
- /* 移交控制权到 board_init_r */
- move a0, a1
- la t9, board_init_r
- j t9
- move a1, a2 /* delay slot */
- .end relocate_code
复制代码relocate_code 的作用是将 U-Boot 代码移动到内存的最高端,并清空 BSS 段,设置栈指针,创造一个完整的 C 环境。
然后 C 环境就绪,进入 board_init_r 继续运行,此时 SPI Flash 不再被使用,U-Boot 完全在内存中运行了
回到 lib_mips/board.c:
- void board_init_r (gd_t *id, ulong dest_addr)
- {
- cmd_tbl_t *cmdtp;
- ulong size;
- extern void malloc_bin_reloc (void);
- #ifndef CFG_ENV_IS_NOWHERE
- extern char * env_name_spec;
- #endif
- char *s, *e;
- bd_t *bd;
- int i;
- gd = id;
- gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */
- debug ("Now running in RAM - U-Boot at: %08lx\n", dest_addr);
- gd->reloc_off = dest_addr - CFG_MONITOR_BASE; /* 记录 U-Boot 重定位时移动的偏移量 */
- monitor_flash_len = (ulong)&uboot_end_data - dest_addr;
- /*
- * We have to relocate the command table manually
- */
- /* U-Boot 命令表,重定位所有字符串地址和函数地址 */
- for (cmdtp = &__u_boot_cmd_start; cmdtp != &__u_boot_cmd_end; cmdtp++) {
- ulong addr;
- addr = (ulong) (cmdtp->cmd) + gd->reloc_off;
- #if 0
- printf ("Command \"%s\": 0x%08lx => 0x%08lx\n",
- cmdtp->name, (ulong) (cmdtp->cmd), addr);
- #endif
- cmdtp->cmd =
- (int (*)(struct cmd_tbl_s *, int, int, char *[]))addr;
- addr = (ulong)(cmdtp->name) + gd->reloc_off;
- cmdtp->name = (char *)addr;
- if (cmdtp->usage) {
- addr = (ulong)(cmdtp->usage) + gd->reloc_off;
- cmdtp->usage = (char *)addr;
- }
- #ifdef CFG_LONGHELP
- if (cmdtp->help) {
- addr = (ulong)(cmdtp->help) + gd->reloc_off;
- cmdtp->help = (char *)addr;
- }
- #endif
- }
- /* there are some other pointer constants we must deal with */
- #ifndef CFG_ENV_IS_NOWHERE
- env_name_spec += gd->reloc_off;
- #endif
- /* 熄灭路由的 LED */
- /* turn off switch LED added by tiger 20091225 */
- ar7240_gpio_sw_led();
- ----------------------------------------------------省略----------------------------------------------------
- /* configure available FLASH banks */
- /* 初始化 Flash,显示 Flash:XXXX (X MB) */
- size = flash_init();
- display_flash_config (size);
- bd = gd->bd;
- bd->bi_flashstart = CFG_FLASH_BASE;
- bd->bi_flashsize = size;
- #if CFG_MONITOR_BASE == CFG_FLASH_BASE
- bd->bi_flashoffset = monitor_flash_len; /* reserved area for U-Boot */
- #else
- bd->bi_flashoffset = 0;
- #endif
- /* initialize malloc() area */
- /* 初始化内存管理器,以便能够使用 malloc 等函数 */
- mem_malloc_init();
- malloc_bin_reloc();
- /* relocate environment function pointers etc. */
- /* 重定位环境变量相关函数和数据 */
- env_relocate();
- /* board MAC address */
- s = getenv ("ethaddr");
- for (i = 0; i < 6; ++i) {
- bd->bi_enetaddr[i] = s ? simple_strtoul (s, &e, 16) : 0;
- if (s)
- s = (*e) ? e + 1 : e;
- }
- /* IP Address */
- bd->bi_ip_addr = getenv_IPaddr("ipaddr");
- #if defined(CONFIG_PCI)
- /*
- * Do pci configuration
- */
- /* PCI 设备初始化 */
- pci_init();
- #endif
- /** leave this here (after malloc(), environment and PCI are working) **/
- /* Initialize devices */
- devices_init ();
- jumptable_init ();
- /* Initialize the console (after the relocation and devices init) */
- console_init_r ();
- /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/
- /* Initialize from environment */
- if ((s = getenv ("loadaddr")) != NULL) {
- load_addr = simple_strtoul (s, NULL, 16);
- }
- #if (CONFIG_COMMANDS & CFG_CMD_NET)
- if ((s = getenv ("bootfile")) != NULL) {
- copy_filename (BootFile, s, sizeof (BootFile));
- }
- #endif /* CFG_CMD_NET */
- #if defined(CONFIG_MISC_INIT_R)
- /* miscellaneous platform dependent initialisations */
- misc_init_r ();
- #endif
- #if (CONFIG_COMMANDS & CFG_CMD_NET)
- /* 初始化网络 */
- #if defined(CONFIG_NET_MULTI)
- puts ("Net: ");
- #endif
- eth_initialize(gd->bd);
- #endif
- /* main_loop() can return to retry autoboot, if so just run it again. */
- /* 进入命令主循环,永不返回 */
- for (;;) {
- main_loop ();
- }
- /* NOTREACHED - no way out of command loop except booting */
- }
复制代码到此为止 U-Boot 就开始正常运行了
在处理交互命令前,U-Boot 会检查有没有启动命令
文件:common/main.c
- void main_loop (void)
- {
- ----------------------------------------------------省略----------------------------------------------------
- #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
- char *s;
- int bootdelay;
- #endif
- ----------------------------------------------------省略----------------------------------------------------
- #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
- /* 获取启动延时 */
- s = getenv ("bootdelay");
- bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
- // debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
- # ifdef CONFIG_BOOT_RETRY_TIME
- init_cmd_timeout ();
- # endif /* CONFIG_BOOT_RETRY_TIME */
- ----------------------------------------------------省略----------------------------------------------------
- /* 获取启动命令 */
- s = getenv ("bootcmd");
- if (!s) {
- /* 如果没有启动命令,就设置默认的命令 */
- #ifdef CONFIG_ROOTFS_FLASH
- /* XXX if rootfs is in flash, expect uImage to be in flash */
- #ifdef CONFIG_AR7100
- /* 这里设置的即为 AR71XX 的默认固件启动命令 */
- setenv ("bootcmd", "bootm 0xbf200000");
- #else
- setenv ("bootcmd", "bootm 0xbf450000");
- #endif /* CONFIG_AR7100 */
- #else
- setenv ("bootcmd", "tftpboot 0x8022c090 uImage; bootm 0x8022c090");
- #endif
- }
- /* 重新获取启动命令 */
- s = getenv ("bootcmd");
- // debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
- if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
- # ifdef CONFIG_AUTOBOOT_KEYED
- int prev = disable_ctrlc(1); /* disable Control C checking */
- # endif
- # ifndef CFG_HUSH_PARSER
- /* 执行启动命令 */
- run_command (s, 0);
- # else
- parse_string_outer(s, FLAG_PARSE_SEMICOLON |
- FLAG_EXIT_FROM_LOOP);
- # endif
- # ifdef CONFIG_AUTOBOOT_KEYED
- disable_ctrlc(prev); /* restore Control C checking */
- # endif
- }
- ----------------------------------------------------省略----------------------------------------------------
- #endif /* CONFIG_BOOTDELAY */
- #ifdef CONFIG_AMIGAONEG3SE
- {
- extern void video_banner(void);
- video_banner();
- }
- #endif
- /* 启动命令执行完毕,则开始处理交互命令 */
- /*
- * Main Loop for Monitor Command Processing
- */
- #ifdef CFG_HUSH_PARSER
- parse_file_outer();
- /* This point is never reached */
- for (;;);
- #else
- for (;;) {
- #ifdef CONFIG_BOOT_RETRY_TIME
- if (rc >= 0) {
- /* Saw enough of a valid command to
- * restart the timeout.
- */
- reset_cmd_timeout();
- }
- #endif
- len = readline (CFG_PROMPT);
- flag = 0; /* assume no special flags for now */
- if (len > 0)
- strcpy (lastcommand, console_buffer);
- else if (len == 0)
- flag |= CMD_FLAG_REPEAT;
- #ifdef CONFIG_BOOT_RETRY_TIME
- else if (len == -2) {
- /* -2 means timed out, retry autoboot
- */
- puts ("\nTimed out waiting for command\n");
- # ifdef CONFIG_RESET_TO_RETRY
- /* Reinit board to run initialization code again */
- do_reset (NULL, 0, 0, NULL);
- # else
- return; /* retry autoboot */
- # endif
- }
- #endif
- if (len == -1)
- puts ("<INTERRUPT>\n");
- else
- rc = run_command (lastcommand, flag);
- if (rc <= 0) {
- /* invalid command or not repeatable, forget it */
- lastcommand[0] = 0;
- }
- }
- #endif /*CFG_HUSH_PARSER*/
- }
复制代码至此,U-Boot 的启动流程就分析完毕了
相关代码位置:AR7241 架构代码:board/ar7240/ap99:
ap99.c 包含 USB 初始化等
board/ar7240/common:
ar7240_flash.c 包含 Flash 初始化代码
ar7240_pci.c 包含 PCI-E 设备初始化代码
ar7240_s26_phy.c:AR724X/AR9331 内置交换机初始化代码
lowlevel_init.S:lowlevel_init 函数所在的代码,用于初始化 CPU 频率
cpu/mips/ar7240:
ag7240.c:网络驱动
ar7240_serial.c:串口初始化及操作代码
meminit.c:内存初始化代码
U-Boot 通用代码:common:
cmd_*.c: U-Boot 命令代码
env_*.c:环境变量底层模块
command.c:命令解释模块
console.c:控制台模块
dlmalloc.c:内存管理模块
environment.c:环境变量公用模块
net:
eth.c:网络初始化模块
net.c:网络功能实现及调度模块
tftp.c:tftp 功能模块