uboot分析二
6.3? U-Boot的调试 新移植的U-Boot不能正常工作,这时就需要调试了。调试U-Boot离不开工具,只有理解U-Boot启动过程,才能正确地调试U-Boot源码。 6.3.1?硬件调试器硬件电路板制作完成以后,这时上面还没有任何程序,就叫作裸板。首要的工作是把程序或者固件加载到裸板上,这就要通过硬件工具来完成。习惯上,这种硬件工具叫作仿真器。 仿真器可以通过处理器的JTAG等接口控制板子,直接把程序下载到目标板内存,或者进行Flash编程。如果板上的Flash是可以拔插的,就可以通过专用的Flash烧写器来完成。在第4章介绍过目标板跟主机之间的连接,其中JTAG等接口就是专门用来连接仿真器的。 仿真器还有一个重要的功能就是在线调试程序,这对于调试Bootloader和硬件测试程序很有用。 从最简单的JTAG电缆,到ICE仿真器,再到可以调试Linux内核的仿真器。 复杂的仿真器可以支持与计算机间的以太网或者USB接口通信。 对于U-Boot的调试,可以采用BDI2000。BDI2000完全可以反汇编地跟踪Flash中的程序,也可以进行源码级的调试。 使用BDI2000调试U-boot的方法如下。 (1)配置BDI2000和目标板初始化程序,连接目标板。 (2)添加U-Boot的调试编译选项,重新编译。 U-Boot的程序代码是位置相关的,调试的时候尽量在内存中调试,可以修改连接定位地址TEXT_BASE。TEXT_BASE在board/<board_name>/config.mk中定义。 另外,如果有复位向量也需要先从链接脚本中去掉。链接脚本是board/<board_name>/ 添加调试选项,在config.mk文件中查找,DBGFLAGS,加上-g选项。然后重新编译U-Boot。 (3)下载U-Boot到目标板内存。 通过BDI2000的下载命令LOAD,把程序加载到目标板内存中。然后跳转到U-Boot入口。 (4)启动GDB调试。 启动GDB调试,这里是交叉调试的GDB。GDB与BDI2000建立链接,然后就可以设置断点执行了。 ? $ arm-linux-gdb u-boot (gdb)target remote 192.168.1.100:2001 (gdb)stepi (gdb)b start_armboot (gdb)c 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,#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,r2????? ??? /* r2?得到要复制代码的末尾地址?*/ copy_loop: /*?重新定位代码?*/ ?????? ldmia r0!,{r3-r10}?? /*从源地址[r0]复制?*/ ?????? stmia r1!,{r3-r10}?? /*?复制到目的地址[r1] */ ?????? cmp?? r0,r2????????? /*?复制数据块直到源数据末尾地址[r2] */ ?????? ble?? copy_loop ? ?????? /*?初始化堆栈等??? */ stack_setup: ?????? ldr?? r0,_TEXT_BASE????? ??? ??? /*?上面是128 KiB重定位的u-boot */ ?????? sub?? r0,#CFG_MALLOC_LEN???? /*?向下是内存分配空间?*/ ?????? sub?? r0,#CFG_GBL_DATA_SIZE /*?然后是bdinfo结构体地址空间? */ #ifdef CONFIG_USE_IRQ ?????? sub?? r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) #endif ?????? sub?? sp,#12???? /*?为abort-stack预留3个字?*/ clear_bss: ?????? ldr?? r0,_bss_start????? /*?找到bss段起始地址?*/ ?????? ldr?? r1,_bss_end??????? /* ?bss段末尾地址?? */ ?????? mov ? r2,#0x00000000???? /*?清零?*/ clbss_l:str r2,[r0]??????? /* bss段地址空间清零循环...? */ ?????? add?? 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.cstart_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,sizeof (gd_t)); ?????? gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); ?????? memset (gd->bd,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_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。 ? init_fnc_t *init_sequence[] = { ?????? cpu_init,???? ??? ??? /*?基本的处理器相关配置?-- cpu/arm920t/cpu.c */ ?????? board_init,???????????/*?基本的板级相关配置?-- board/smdk2410/smdk2410.c */ ?????? interrupt_init,?? ????/*?初始化例外处理?-- cpu/arm920t/s3c24x0/interrupt.c */ ?????? env_init,???? ??? ??? /*?初始化环境变量?-- common/cmd_flash.c */ ?????? init_baudrate,??? ??? /*?初始化波特率设置?-- lib_arm/board.c */ ?????? serial_init,????? ????/*?串口通讯设置?-- cpu/arm920t/s3c24x0/serial.c */ ?????? console_init_f,?? ??? /*?控制台初始化阶段1 -- common/console.c */ ?????? display_banner,?? ??? /*?打印u-boot信息?-- lib_arm/board.c */ ?????? dram_init,??????? ??? /*?配置可用的RAM -- board/smdk2410/smdk2410.c */ ?????? display_dram_config,?/*?显示RAM的配置大小?-- lib_arm/board.c */ ?????? NULL, }; 6.3.4? U-Boot与内核的关系U-Boot作为Bootloader,具备多种引导内核启动的方式。常用的go和bootm命令可以直接引导内核映像启动。U-Boot与内核的关系主要是内核启动过程中参数的传递。 1.go命令的实现? /* common/cmd_boot.c? */ int do_go (cmd_tbl_t *cmdtp,int flag,int argc,char *argv[]) { ?????? ulong addr,rc; ?????? int???? rcode = 0; ?????? if (argc < 2) { ????????????? printf ("Usage:n%sn",cmdtp->usage); ????????????? return 1; ?????? } ?????? addr = simple_strtoul(argv[1],16); ?????? printf ("## Starting application at 0x%08lX ...n",addr); ?????? /* ??????? * pass address parameter as argv[0] (aka command name), ??????? * and all remaining args ??????? */ ?????? rc = ((ulong (*)(int,char *[]))addr) (--argc,&argv[1]); ?????? if (rc != 0) rcode = 1; ? ?????? printf ("## Application terminated,rc = 0x%lXn",rc); ?????? return rcode; } ? go命令调用do_go()函数,跳转到某个地址执行的。如果在这个地址准备好了自引导的内核映像,就可以启动了。尽管go命令可以带变参,实际使用时一般不用来传递参数。 2.bootm命令的实现? /* common/cmd_bootm.c */ int do_bootm (cmd_tbl_t *cmdtp,char *argv[]) { ?????? ulong iflag; ?????? ulong addr; ?????? ulong data,len,checksum; ?????? ulong? *len_ptr; ?????? uint? unc_len = 0x400000; ?????? int?? i,verify; ?????? char? *name,*s; ?????? int?? (*appl)(int,char *[]); ?????? 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[1],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 Numbern"); ????????????? 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 Checksumn"); ????????????? 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,len) != ntohl(hdr->ih_dcrc)) { ???????????????????? printf ("Bad Data CRCn"); ???????????????????? SHOW_BOOT_PROGRESS (-3); ???????????????????? return 1; ????????????? } ????????????? puts ("OKn"); ?????? } ?????? 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,char *argv[], ?????????????????? ulong addr,ulong *len_ptr,int verify) { ?????? DECLARE_GLOBAL_DATA_PTR; ?????? ulong len = 0,checksum; ?????? ulong initrd_start,initrd_end; ?????? ulong data; ?????? void (*theKernel)(int zero,int arch,uint params); ?????? image_header_t *hdr = &header; ?????? bd_t *bd = gd->bd; #ifdef CONFIG_CMDLINE_TAG ?????? char *commandline = getenv ("bootargs"); #endif ?????? theKernel = (void (*)(int,int,uint))ntohl(hdr->ih_ep); ?????? /* Check if there is an initrd image */ ?????? if(argc >= 3) { ????????????? SHOW_BOOT_PROGRESS (9); ????????????? addr = simple_strtoul (argv[2],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 Numbern"); ????????????????????? SHOW_BOOT_PROGRESS (-10); ????????????????????? do_reset (cmdtp,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 Checksumn"); ???????????????????? SHOW_BOOT_PROGRESS (-11); ???????????????????? do_reset (cmdtp,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,len); ???????????????????? if (csum != ntohl (hdr->ih_dcrc)) { ??????????????????????????? printf ("Bad Data CRCn"); ??????????????????????????? SHOW_BOOT_PROGRESS (-12); ??????????????????????????? do_reset (cmdtp,argv); ???????????????????? } ???????????????????? printf ("OKn"); ????????????? } ????????????? 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 Imagen"); ???????????????????? SHOW_BOOT_PROGRESS (-13); ???????????????????? do_reset (cmdtp,argv); ????????????? } ????????????? /* Now check if we have a multifile image */ ?????? } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) { ?????????????? ulong tail = ntohl (len_ptr[0]) % 4; ?????????????? int i; ?????????????? SHOW_BOOT_PROGRESS (13); ?????????????? /* skip kernel length and terminator */ ??????? ???????data = (ulong) (&len_ptr[2]); ?????????????? /* skip any additional image length fields */ ?????????????? for (i = 1; len_ptr[i]; ++i) ?????????????????????? data += 4; ????????????? /* add kernel length,and align */ ????????????? data += ntohl (len_ptr[0]); ????????????? if (tail) { ?????????????????????? data += 4 - tail; ????????????? } ????????????? len = ntohl (len_ptr[1]); ?????? } 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 ...nn"); ????? cleanup_before_linux (); ? ????? theKernel (0,bd->bi_arch_number,bd->bi_boot_params); } ? do_bootm_linux()函数是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。这里引导的内核映像和ramdisk映像,必须是U-Boot格式的。U-Boot格式的映像可以通过mkimage工具来转换,其中包含了U-Boot可以识别的符号。 6.4??使用U-Boot U-Boot是“Monitor”。除了Bootloader的系统引导功能,它还有用户命令接口,提供了一些复杂的调试、读写内存、烧写Flash、配置环境变量等功能。掌握U-Boot的使用,将极大地方便嵌入式系统的开发。 6.4.1?烧写U-Boot到Flash新开发的电路板没有任何程序可以执行,也就不能启动,需要先将U-Boot烧写到Flash中。 如果主板上的EPROM或者Flash能够取下来,就可以通过编程器烧写。例如:计算机BIOS就存储在一块256KB的Flash上,通过插座与主板连接。 但是多数嵌入式单板使用贴片的Flash,不能取下来烧写。这种情况可以通过处理器的调试接口,直接对板上的Flash编程。 处理器调试接口是为处理器芯片设计的标准调试接口,包含BDM、JTAG和EJTAG 3种接口标准。JTAG接口在第4章已经介绍过;BDM(Background Debug Mode)主要应用在PowerPC8xx系列处理器上;EJTAG主要应用在MIPS处理器上。这3种硬件接口标准定义有所不同,但是功能基本相同,下面都统称为JTAG接口。 JTAG接口需要专用的硬件工具来连接。无论从功能、性能角度,还是从价格角度,这些工具都有很大差异。关于这些工具的选择,将在第6.4.1节详细介绍。 最简单方式就是通过JTAG电缆,转接到计算机并口连接。这需要在主机端开发烧写程序,还需要有并口设备驱动程序。开发板上电或者复位的时候,烧写程序探测到处理器并且开始通信,然后把Bootloader下载并烧写到Flash中。这种方式速率很慢,可是价格非常便宜。一般来说,平均每秒钟可以烧写100~200个字节。 烧写完成后,复位实验板,串口终端应该显示U-Boot的启动信息。 6.4.2? U-Boot的常用命令U-Boot上电启动后,敲任意键可以退出自动启动状态,进入命令行。 ? U-Boot 1.1.2 (Apr 26 2005 - 12:27:13) U-Boot code: 11080000 -> 1109614C? BSS: -> 1109A91C RAM Configuration: Bank #0: 10000000 32 MB Micron StrataFlash MT28F128J3 device initialized Flash: 32 MB In:??? serial Out:?? serial Err:?? serial Hit any key to stop autoboot:? 0 U-Boot> ? 在命令行提示符下,可以输入U-Boot的命令并执行。U-Boot可以支持几十个常用命令,通过这些命令,可以对开发板进行调试,可以引导Linux内核,还可以擦写Flash完成系统部署等功能。掌握这些命令的使用,才能够顺利地进行嵌入式系统的开发。 输入help命令,可以得到当前U-Boot的所有命令列表。每一条命令后面是简单的命令说明。 ? => help ??????? - alias for 'help' autoscr - run script from memory base??? - print or set address offset bdinfo? - print Board Info structure boot??? - boot default,i.e.,run 'bootcmd' bootd?? - boot default,run 'bootcmd' bootm?? - boot application image from memory bootp?? - boot image via network using BootP/TFTP protocol cmp???? - memory compare coninfo? - print console devices and information cp????? - memory copy crc32?? - checksum calculation dhcp??? - invoke DHCP client to obtain IP/boot params echo??? - echo args to console erase?? - erase FLASH memory flinfo? - print FLASH memory information go????? - start application at address 'addr' help??? - print online help iminfo? - print header information for application image imls??? - list all images found in flash itest?? ?- return true/false on integer compare loadb?? - load binary file over serial line (kermit mode) loads?? - load S-Record file over serial line loop?? - infinite loop on address range md??? - memory display mm??? - memory modify (auto-incrementing) mtest?? - simple RAM test mw????? - memory write (fill) nfs???? - boot image via network using NFS protocol nm????? - memory modify (constant address) printenv - print environment variables protect - enable or disable FLASH write protection rarpboot - boot image via network using RARP/TFTP protocol reset?? - Perform RESET of the CPU run???? - run commands in an environment variable saveenv - save environment variables to persistent storage setenv? - set environment variables sleep?? - delay execution for some time tftpboot - boot image via network using TFTP protocol version - print monitor version => ? U-Boot还提供了更加详细的命令帮助,通过help命令还可以查看每个命令的参数说明。由于开发过程的需要,有必要先把U-Boot命令的用法弄清楚。接下来,根据每一条命令的帮助信息,解释一下这些命令的功能和参数。 ? => help bootm bootm [addr [arg ...]] ??? - boot application image stored in memory ????????? passing arguments 'arg ...'; when booting a Linux kernel, ??? ??????'arg' can be the address of an initrd image ? bootm命令可以引导启动存储在内存中的程序映像。这些内存包括RAM和可以永久保存的Flash。 第1个参数addr是程序映像的地址,这个程序映像必须转换成U-Boot的格式。 第2个参数对于引导Linux内核有用,通常作为U-Boot格式的RAMDISK映像存储地址;也可以是传递给Linux内核的参数(缺省情况下传递bootargs环境变量给内核)。 ? => help bootp bootp [loadAddress] [bootfilename] bootp命令通过bootp请求,要求DHCP服务器分配IP地址,然后通过TFTP协议下载指定的文件到内存。 第1个参数是下载文件存放的内存地址。 第2个参数是要下载的文件名称,这个文件应该在开发主机上准备好。 ? => help cmp cmp [.b,.w,.l] addr1 addr2 count ???? - compare memory ? cmp命令可以比较2块内存中的内容。.b以字节为单位;.w以字为单位;.l以长字为单位。注意:cmp.b中间不能保留空格,需要连续敲入命令。 第1个参数addr1是第一块内存的起始地址。 第2个参数addr2是第二块内存的起始地址。 第3个参数count是要比较的数目,单位按照字节、字或者长字。 ? => help cp cp [.b,.l] source target count ?????? - copy memory ? cp命令可以在内存中复制数据块,包括对Flash的读写操作。 第1个参数source是要复制的数据块起始地址。 第2个参数target是数据块要复制到的地址。这个地址如果在Flash中,那么会直接调用写Flash的函数操作。所以U-Boot写Flash就使用这个命令,当然需要先把对应Flash区域擦干净。 第3个参数count是要复制的数目,根据cp.b cp.w cp.l分别以字节、字、长字为单位。 ? => help crc32 crc32 address count [addr] ???? - compute CRC32 checksum [save at addr]?? ? crc32命令可以计算存储数据的校验和。 第1个参数address是需要校验的数据起始地址。 第2个参数count是要校验的数据字节数。 第3个参数addr用来指定保存结果的地址。 ? => help echo echo [args..] ????? - echo args to console; c suppresses newline ? echo命令回显参数。 ? => help erase erase start end ????? - erase FLASH from addr 'start' to addr 'end' erase N:SF[-SL] ????? - erase sectors SF-SL in FLASH bank # N erase bank N ????? - erase FLASH bank # N erase all ????? - erase all FLASH banks ? erase命令可以擦Flash。 参数必须指定Flash擦除的范围。 按照起始地址和结束地址,start必须是擦除块的起始地址;end必须是擦除末尾块的结束地址。这种方式最常用。举例说明:擦除0x20000 – 0x3ffff区域命令为erase 20000 3ffff。 按照组和扇区,N表示Flash的组号,SF表示擦除起始扇区号,SL表示擦除结束扇区号。另外,还可以擦除整个组,擦除组号为N的整个Flash组。擦除全部Flash只要给出一个all的参数即可。 ? => help flinfo flinfo ?????? - print information for all FLASH memory banks flinfo N ?????? - print information for FLASH memory bank # N ? flinfo命令打印全部Flash组的信息,也可以只打印其中某个组。一般嵌入式系统的Flash只有一个组。 ? => help go go addr [arg ...] ????? - start application at address 'addr' ????????passing 'arg' as arguments ? go命令可以执行应用程序。 第1个参数是要执行程序的入口地址。 第2个可选参数是传递给程序的参数,可以不用。 ? => help iminfo iminfo addr [addr ...] ????? - print header information for application image starting at ???????? address 'addr' in memory; this includes verification of the ???????? image contents (magic number,header and payload checksums) ? iminfo可以打印程序映像的开头信息,包含了映像内容的校验(序列号、头和校验和)。 第1个参数指定映像的起始地址。 可选的参数是指定更多的映像地址。 ? => help loadb loadb [ off ] [ baud ] ???? - load binary file over serial line with offset 'off' and baudrate 'baud' ? loadb命令可以通过串口线下载二进制格式文件。 ? => help loads loads [ off ] ??? - load S-Record file over serial line with offset 'off' ? loads命令可以通过串口线下载S-Record格式文件。 ? => help mw mw [.b,.l] address value [count] ???? - write memory ? mw命令可以按照字节、字、长字写内存,.b .w .l的用法与cp命令相同。 第1个参数address是要写的内存地址。 第2个参数value是要写的值。 第3个可选参数count是要写单位值的数目。 ? => help nfs nfs [loadAddress] [host ip addr:bootfilename] ? nfs命令可以使用NFS网络协议通过网络启动映像。 ? => help nm nm [.b,.l] address ???? - memory modify,read and keep address ? nm命令可以修改内存,可以按照字节、字、长字操作。 参数address是要读出并且修改的内存地址。 ? => help printenv printenv ????? - print values of all environment variables printenv name ... ????? - print value of environment variable 'name' ? printenv命令打印环境变量。 可以打印全部环境变量,也可以只打印参数中列出的环境变量。 ? => help protect protect on? start end ????? - protect Flash from addr 'start' to addr 'end' protect on? N:SF[-SL] ????? - protect sectors SF-SL in Flash bank # N protect on? bank N ???? ?- protect Flash bank # N protect on? all ????? - protect all Flash banks protect off start end ??????- make Flash from addr 'start' to addr 'end' writable protect off N:SF[-SL] ?????- make sectors SF-SL writable in Flash bank # N protect off bank N ?????- make Flash bank # N writable protect off all ?????- make all Flash banks writable ? protect命令是对Flash写保护的操作,可以使能和解除写保护。 第1个参数on代表使能写保护;off代表解除写保护。 第2、3参数是指定Flash写保护操作范围,跟擦除的方式相同。 ? => help rarpboot rarpboot [loadAddress] [bootfilename] ? rarboot命令可以使用TFTP协议通过网络启动映像。也就是把指定的文件下载到指定地址,然后执行。 第1个参数是映像文件下载到的内存地址。 第2个参数是要下载执行的映像文件。 ? => help run run var [...] ????? - run the commands in the environment variable(s) 'var' ? run命令可以执行环境变量中的命令,后面参数可以跟几个环境变量名。 ? => help setenv setenv name value ... ????? - set environment variable 'name' to 'value ...' setenv name ????? - delete environment variable 'name' ? setenv命令可以设置环境变量。 第1个参数是环境变量的名称。 第2个参数是要设置的值,如果没有第2个参数,表示删除这个环境变量。 ? ? => help sleep sleep N ????? - delay execution for N seconds (N is _decimal_ !!!) ? sleep命令可以延迟N秒钟执行,N为十进制数。 ? => help tftpboot tftpboot [loadAddress] [bootfilename] ? tftpboot命令可以使用TFTP协议通过网络下载文件。按照二进制文件格式下载。另外使用这个命令,必须配置好相关的环境变量。例如serverip和ipaddr。 第1个参数loadAddress是下载到的内存地址。 第2个参数是要下载的文件名称,必须放在TFTP服务器相应的目录下。 这些U-Boot命令为嵌入式系统提供了丰富的开发和调试功能。在Linux内核启动和调试过程中,都可以用到U-Boot的命令。但是一般情况下,不需要使用全部命令。比如已经支持以太网接口,可以通过tftpboot命令来下载文件,那么还有必要使用串口下载的loadb吗?反过来,如果开发板需要特殊的调试功能,也可以添加新的命令。 在建立交叉开发环境和调试Linux内核等章节时,在ARM平台上移植了U-Boot,并且提供了具体U-Boot的操作步骤。 6.4.3? U-Boot的环境变量有点类似Shell,U-Boot也使用环境变量。可以通过printenv命令查看环境变量的设置。 ? U-Boot> printenv bootdelay=3 baudrate=115200 netmask=255.255.0.0 ethaddr=12:34:56:78:90:ab bootfile=uImage bootargs=console=ttyS0,115200 root=/dev/ram rw initrd=0x30800000,8M bootcmd=tftp 0x30008000 zImage;go 0x30008000 serverip=192.168.1.1 ipaddr=192.168.1.100 stdin=serial stdout=serial stderr=serial ? Environment size: 337/131068 bytes U-Boot> ? 表6.5是常用环境变量的含义解释。通过printenv命令可以打印出这些变量的值。 表6.5????????????????????????????????????????????????? U-Boot环境变量的解释说明
? U-Boot的环境变量都可以有缺省值,也可以修改并且保存在参数区。U-Boot的参数区一般有EEPROM和Flash两种设备。 环境变量的设置命令为setenv,在6.2.2节有命令的解释。 举例说明环境变量的使用。 ? =>setenv serverip? 192.168.1.1 =>setenv ipaddr? 192.168.1.100 =>setenv rootpath??"/usr/local/arm/3.3.2/rootfs" =>setenv bootargs ?"root=/dev/nfs rw nfsroot=$(serverip):$(rootpath) ip= =>setenv kernel_addr 30000000 =>setenv nfscmd ?"tftp $(kernel_addr) uImage; bootm $(kernel_addr)?" =>run nfscmd ? 上面定义的环境变量有serverip ipaddr rootpath bootargs kernel_addr。环境变量bootargs中还使用了环境变量,bootargs定义命令行参数,通过bootm命令传递给内核。环境变量nfscmd中也使用了环境变量,功能是把uImage下载到指定的地址并且引导起来。可以通过run命令执行nfscmd脚本。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- 正则表达式 – 如何删除非ascii字符并在非ascii字符使用Per
- xcode – 无效的代码签名应用提交
- flash为8M、16M、32M的esp8266串口烧写方式有哪些不同呢
- Monodevelop:从C#/ Visual Studio移植:双击消失
- ruby-on-rails – Ruby中基于Rack的服务器是什么
- postgresql – 在NGINX中使用Postgres Basic Auth将查询结果
- 如何为 PostgreSQL 增加系统表字段
- Swift可选类型的使用
- 想全面了解ajax,看此帖就足足的够了!
- c# – 如何使用JavaScriptSerializer强制使用camelcase?