免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1353 | 回复: 0
打印 上一主题 下一主题

bootloader讲解 之 《嵌入式Linux系统开发技术详解-基于ARM》 (3) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2010-02-21 20:18 |只看该作者 |倒序浏览

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//config.mk中定义。
另外,如果有复位向量也需要先从链接脚本中去掉。链接脚本是board//
u-boot.lds。
添加调试选项,在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,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}   /*从源地址[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, 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, [r0]        /* 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_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
              printf ("Usage:\n%s\n", cmdtp->usage);
              return 1;
       }
       addr = simple_strtoul(argv[1], NULL, 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%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[])
{
       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
              addr = load_addr;
       } else {
              addr = simple_strtoul(argv[1], 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[],
                   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], 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[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)
                       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 (&params);
#endif
#ifdef CONFIG_REVISION_TAG
      setup_revision_tag (&params);
#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);
}

do_bootm_linux()函数是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。这里引导的内核映像和ramdisk映像,必须是U-Boot格式的。U-Boot格式的映像可以通过mkimage工具来转换,其中包含了U-Boot可以识别的符号。



本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/110728/showart_2183506.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP