热度 18
2014-9-4 19:01
1739 次阅读|
0 个评论
6.3.2 软件跟踪 假如U-Boot没有任何串口打印信息,手头又没有硬件调试工具,那样怎么知道U-Boot执行到什么地方了呢?可以通过开发板上的LED指示灯判断。 开发板上最好设计安装八段数码管等LED,可以用来显示数字或者数字位。 U-Boot可以定义函数show_boot_progress (int status),用来指示当前启动进度。在include/common.h头文件中声明这个函数。 #ifdef CONFIG_SHOW_BOOT_PROGRESS void show_boot_progress (int status); #endif CONFIG_SHOW_BOOT_PROGRESS是需要定义的。这个在板子配置的头文件中定义。CSB226开发板对这项功能有完整实现,可以参考。在头文件include/configs/csb226.h中,有下列一行。 #define CONFIG_SHOW_BOOT_PROGRESS 1 函数show_boot_progress (int status)的实现跟开发板关系密切,所以一般在board目录下的文件中实现。看一下CSB226在board/csb226/csb226.c中的实现函数。 /** 设置CSB226板的0、1、2三个指示灯的开关状态 * csb226_set_led: - switch LEDs on or off * @param led: LED to switch (0,1,2) * @param state: switch on (1) or off (0) */ void csb226_set_led(int led, int state) { switch(led) { case 0: if (state==1) { GPCR0 |= CSB226_USER_LED0; } else if (state==0) { GPSR0 |= CSB226_USER_LED0; } break; case 1: if (state==1) { GPCR0 |= CSB226_USER_LED1; } else if (state==0) { GPSR0 |= CSB226_USER_LED1; } break; case 2: if (state==1) { GPCR0 |= CSB226_USER_LED2; } else if (state==0) { GPSR0 |= CSB226_USER_LED2; } break; } return; } /** 显示启动进度函数,在比较重要的阶段,设置三个灯为亮的状态(1, 5, 15)*/ void show_boot_progress (int status) { switch(status) { case 1: csb226_set_led(0,1); break; case 5: csb226_set_led(1,1); break; case 15: csb226_set_led(2,1); break; } return; } 这样,在U-Boot启动过程中就可以通过show_boot_progresss指示执行进度。比如hang()函数是系统出错时调用的函数,这里需要根据特定的开发板给定显示的参数值。 void hang (void) { puts ("### ERROR ### Please RESET the board ###\n"); #ifdef CONFIG_SHOW_BOOT_PROGRESS show_boot_progress(-30); #endif for (;;); } 6.3.3 U-Boot启动过程 尽管有了调试跟踪手段,甚至也可以通过串口打印信息了,但是不一定能够判断出错原因。如果能够充分理解代码的启动流程,那么对准确地解决和分析问题很有帮助。 开发板上电后,执行U-Boot的第一条指令,然后顺序执行U-Boot启动函数。函数调用顺序如图6.3所示。 看一下board/smsk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。第一个要链接的是cpu/arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中。下面详细分析一下程序跳转和函数的调用关系以及函数实现。 1.cpu/arm920t/start.S 这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。 图6.3 U-Boot启动代码流程图 _start: b reset //复位向量 ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq //中断向量 ldr pc, _fiq //中断向量 … /* the actual reset code */ reset: //复位启动子程序 /* 设置CPU为SVC32模式 */ mrs r0,cpsr bic r0,r0,#0x1f orr r0,r0,#0xd3 msr cpsr,r0 /* 关闭看门狗 */ /* 这些初始化代码在系统重起的时候执行,运行时热复位从RAM中启动不执行 */ #ifdef CONFIG_INIT_CRITICAL bl cpu_init_crit #endif relocate: /* 把U-Boot重新定位到RAM */ adr r0, _start /* r0是代码的当前位置 */ ldr r1, _TEXT_BASE /* 测试判断是从Flash启动,还是RAM */ cmp r0, r1 /* 比较r0和r1,调试的时候不要执行重定位 */ beq stack_setup /* 如果r0等于r1,跳过重定位代码 */ /* 准备重新定位代码 */ ldr r2, _armboot_start ldr r3, _bss_start sub r2, r3, r2 /* r2 得到armboot的大小 */ add r2, r0, r2 /* r2 得到要复制代码的末尾地址 */ copy_loop: /* 重新定位代码 */ ldmia r0!, {r3-r10} /*从源地址 复制 */ stmia r1!, {r3-r10} /* 复制到目的地址 */ cmp r0, r2 /* 复制数据块直到源数据末尾地址 */ ble copy_loop /* 初始化堆栈等 */ stack_setup: ldr r0, _TEXT_BASE /* 上面是128 KiB重定位的u-boot */ sub r0, r0, #CFG_MALLOC_LEN /* 向下是内存分配空间 */ sub r0, r0, #CFG_GBL_DATA_SIZE /* 然后是bdinfo结构体地址空间 */ #ifdef CONFIG_USE_IRQ sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) #endif sub sp, r0, #12 /* 为abort-stack预留3个字 */ clear_bss: ldr r0, _bss_start /* 找到bss段起始地址 */ ldr r1, _bss_end /* bss段末尾地址 */ mov r2, #0x00000000 /* 清零 */ clbss_l:str r2, /* bss段地址空间清零循环... */ add r0, r0, #4 cmp r0, r1 bne clbss_l /* 跳转到start_armboot函数入口,_start_armboot字保存函数入口指针 */ ldr pc, _start_armboot _start_armboot: .word start_armboot //start_armboot函数在lib_arm/board.c中实现 /* 关键的初始化子程序 */ cpu_init_crit: …… //初始化CACHE,关闭MMU等操作指令 /* 初始化RAM时钟。 * 因为内存时钟是依赖开发板硬件的,所以在board的相应目录下可以找到memsetup.S文件。 */ mov ip, lr bl memsetup //memsetup子程序在board/smdk2410/memsetup.S中实现 mov lr, ip mov pc, lr 2.lib_arm/board.c start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。 void start_armboot (void) { DECLARE_GLOBAL_DATA_PTR; ulong size; init_fnc_t **init_fnc_ptr; char *s; /* Pointer is writable since we allocated a register for it */ gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); /* compiler optimization barrier needed for GCC = 3.4 */ __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)); monitor_flash_len = _bss_start - _armboot_start; /* 顺序执行init_sequence数组中的初始化函数 */ for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } } /*配置可用的Flash */ size = flash_init (); display_flash_config (size); /* _armboot_start 在u-boot.lds链接脚本中定义 */ mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); /* 配置环境变量,重新定位 */ env_relocate (); /* 从环境变量中获取IP地址 */ gd-bd-bi_ip_addr = getenv_IPaddr ("ipaddr"); /* 以太网接口MAC 地址 */ …… devices_init (); /* 获取列表中的设备 */ jumptable_init (); console_init_r (); /* 完整地初始化控制台设备 */ enable_interrupts (); /* 使能例外处理 */ /* 通过环境变量初始化 */ if ((s = getenv ("loadaddr")) != NULL) { load_addr = simple_strtoul (s, NULL, 16); } /* main_loop()总是试图自动启动,循环不断执行 */ for (;;) { main_loop (); /* 主循环函数处理执行用户命令 -- common/main.c */ } /* NOTREACHED - no way out of command loop except booting */ } 3.init_sequence 数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。 init_fnc_t *init_sequence ) { ulong addr, rc; int rcode = 0; if (argc 2) { printf ("Usage:\n%s\n", cmdtp-usage); return 1; } addr = simple_strtoul(argv , NULL, 16); printf ("## Starting application at 0x%08lX ...\n", addr); /* * pass address parameter as argv (aka command name), * and all remaining args */ rc = ((ulong (*)(int, char * ); if (rc != 0) rcode = 1; printf ("## Application terminated, rc = 0x%lX\n", rc); return rcode; } go命令调用do_go()函数,跳转到某个地址执行的。如果在这个地址准备好了自引导的内核映像,就可以启动了。尽管go命令可以带变参,实际使用时一般不用来传递参数。 2.bootm命令的实现 /* common/cmd_bootm.c */ int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv ); image_header_t *hdr = header; s = getenv ("verify"); verify = (s (*s == 'n')) ? 0 : 1; if (argc 2) { addr = load_addr; } else { addr = simple_strtoul(argv , NULL, 16); } SHOW_BOOT_PROGRESS (1); printf ("## Booting image at %08lx ...\n", addr); /* Copy header so we can blank CRC field for re-calculation */ memmove (header, (char *)addr, sizeof(image_header_t)); if (ntohl(hdr-ih_magic) != IH_MAGIC) { puts ("Bad Magic Number\n"); SHOW_BOOT_PROGRESS (-1); return 1; } SHOW_BOOT_PROGRESS (2); data = (ulong)header; len = sizeof(image_header_t); checksum = ntohl(hdr-ih_hcrc); hdr-ih_hcrc = 0; if(crc32 (0, (char *)data, len) != checksum) { puts ("Bad Header Checksum\n"); SHOW_BOOT_PROGRESS (-2); return 1; } SHOW_BOOT_PROGRESS (3); /* for multi-file images we need the data part, too */ print_image_hdr ((image_header_t *)addr); data = addr + sizeof(image_header_t); len = ntohl(hdr-ih_size); if(verify) { puts (" Verifying Checksum ... "); if(crc32 (0, (char *)data, len) != ntohl(hdr-ih_dcrc)) { printf ("Bad Data CRC\n"); SHOW_BOOT_PROGRESS (-3); return 1; } puts ("OK\n"); } SHOW_BOOT_PROGRESS (4); len_ptr = (ulong *)data; …… switch (hdr-ih_os) { default: /* handled by (original) Linux case */ case IH_OS_LINUX: do_bootm_linux (cmdtp, flag, argc, argv, addr, len_ptr, verify); break; …… } bootm命令调用do_bootm函数。这个函数专门用来引导各种操作系统映像,可以支持引导Linux、vxWorks、QNX等操作系统。引导Linux的时候,调用do_bootm_linux()函数。 3.do_bootm_linux函数的实现 /* lib_arm/armlinux.c */ void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv , NULL, 16); printf ("## Loading Ramdisk Image at %08lx ...\n", addr); /* Copy header so we can blank CRC field for re-calculation */ memcpy (header, (char *) addr, sizeof (image_header_t)); if (ntohl (hdr-ih_magic) != IH_MAGIC) { printf ("Bad Magic Number\n"); SHOW_BOOT_PROGRESS (-10); do_reset (cmdtp, flag, argc, argv); } data = (ulong) header; len = sizeof (image_header_t); checksum = ntohl (hdr-ih_hcrc); hdr-ih_hcrc = 0; if(crc32 (0, (char *) data, len) != checksum) { printf ("Bad Header Checksum\n"); SHOW_BOOT_PROGRESS (-11); do_reset (cmdtp, flag, argc, argv); } SHOW_BOOT_PROGRESS (10); print_image_hdr (hdr); data = addr + sizeof (image_header_t); len = ntohl (hdr-ih_size); if(verify) { ulong csum = 0; printf (" Verifying Checksum ... "); csum = crc32 (0, (char *) data, len); if (csum != ntohl (hdr-ih_dcrc)) { printf ("Bad Data CRC\n"); SHOW_BOOT_PROGRESS (-12); do_reset (cmdtp, flag, argc, argv); } printf ("OK\n"); } SHOW_BOOT_PROGRESS (11); if ((hdr-ih_os != IH_OS_LINUX) || (hdr-ih_arch != IH_CPU_ARM) || (hdr-ih_type != IH_TYPE_RAMDISK)) { printf ("No Linux ARM Ramdisk Image\n"); SHOW_BOOT_PROGRESS (-13); do_reset (cmdtp, flag, argc, argv); } /* Now check if we have a multifile image */ } else if ((hdr-ih_type == IH_TYPE_MULTI) (len_ptr )) { ulong tail = ntohl (len_ptr ) % 4; int i; SHOW_BOOT_PROGRESS (13); /* skip kernel length and terminator */ data = (ulong) (len_ptr ); /* skip any additional image length fields */ for (i = 1; len_ptr ; ++i) data += 4; /* add kernel length, and align */ data += ntohl (len_ptr ); if (tail) { data += 4 - tail; } len = ntohl (len_ptr ); } else { /* no initrd image */ SHOW_BOOT_PROGRESS (14); len = data = 0; } if (data) { initrd_start = data; initrd_end = initrd_start + len; } else { initrd_start = 0; initrd_end = 0; } SHOW_BOOT_PROGRESS (15); debug ("## Transferring control to Linux (at address %08lx) ...\n", (ulong) theKernel); #if defined (CONFIG_SETUP_MEMORY_TAGS) || \ defined (CONFIG_CMDLINE_TAG) || \ defined (CONFIG_INITRD_TAG) || \ defined (CONFIG_SERIAL_TAG) || \ defined (CONFIG_REVISION_TAG) || \ defined (CONFIG_LCD) || \ defined (CONFIG_VFD) setup_start_tag (bd); #ifdef CONFIG_SERIAL_TAG setup_serial_tag (¶ms); #endif #ifdef CONFIG_REVISION_TAG setup_revision_tag (¶ms); #endif #ifdef CONFIG_SETUP_MEMORY_TAGS setup_memory_tags (bd); #endif #ifdef CONFIG_CMDLINE_TAG setup_commandline_tag (bd, commandline); #endif #ifdef CONFIG_INITRD_TAG if (initrd_start initrd_end) setup_initrd_tag (bd, initrd_start, initrd_end); #endif setup_end_tag (bd); #endif /* we assume that the kernel is in place */ printf ("\nStarting kernel ...\n\n"); cleanup_before_linux (); theKernel (0, bd-bi_arch_number, bd-bi_boot_params); }