Chinaunix首页 | 论坛 | 博客
  • 博客访问: 957441
  • 博文数量: 376
  • 博客积分: 154
  • 博客等级: 入伍新兵
  • 技术积分: 1558
  • 用 户 组: 普通用户
  • 注册时间: 2011-10-13 08:42
文章分类

全部博文(376)

文章存档

2014年(11)

2013年(88)

2012年(260)

2011年(17)

分类:

2012-10-18 13:43:52

原文地址:UBOOT系统启动流程 作者:梦飞雨

       系统的启动流程

这里我们来讨论Uoot的启动流程,并将延伸至内核启动到start_kernel函数,我们主要讲start_kernel函数前系统都在做什么,系统是怎么样从开始运行,到解压内核,跳到内核运行,又怎么样从Uboot里将参数传到内核的。

Uboot的启动是从start.S文件开始的,系统一上电pc指针便指向这里开始程序的启动运行,在start.S文件中系统的工作主要有:

系统一上电便是reset异常,便跳到reset处进行处理,初始化Sdram,cache等,然后跳到board_init_f函数中运行。

Board_init_iflib_mips/board.c:

        在此函数中对定时器进行初始化,环境变量初始化,初始化串口,然后根据Sdram进行计算(具体的计算过程看代码),计算出一个具体的地址,即SDRAM的高端地址,然后调用relocate_code函数,将Uboot程序Copy 到内存,copy完后即跳到in_ram(内存运行)中接着运行Uboot,最后调用board_init_r函数。

Relocate_code是一段汇编代码,位于start.S中,怎么实现copy就自已仔细看代码了啊,in_ram也在start.S中。

       接下来我们来主要看board_init_r函数:

              开始部分仍是对系统一些功能的初始化:

/* configure available FLASH banks */

       size = flash_init(); 初始化flash

gpio_init(); //初始gpio

/** leave this here (after malloc(), environment and PCI are working) **/

       /* Initialize devices */

       devices_init ();//初始化外设

       /* initialize the console (after the relocation and devices init) */

       console_init_r ();初始化console

cy_nvram_init();

mac_init();

进行一些系统初配置初始化后,Uboot就便等待三秒,等待用户输入字符,以进行不同的操作,即当按下4时会进入命令处理模式,按下CTRL+ESC时会进入tftpd,可以进行文件的上传。我们这里只探讨正常启动不探讨tftpd下载和命令处理模式。

        当我们启动时什么也不操作时,默认的启动类型为3,就会进入到这里:

if(BootType == '3') {

      char *argv[3];

      printf("   \n3: System Boot system code via Flash.\n");

      do_bootm(cmdtp, 0, 2, argv); //从这里开始对内核进行解压,并跳到内核处运行。

     

      /* below only when boot from flash fali*/

      argv[2] =  &file_name_space[0];

      memset(file_name_space,0,ARGV_LEN);

      eth_initialize(gd->bd);

      do_tftpd(cmdtp, 0, 4, argv); //启动tftpd

   }

如果kernel确实被写入了flash中,则do_bootm是不会返回的,则之后的do_tftpd就不会执行。当我们重新烧写Uboot后,必须重新烧写内核与文件系统,因此当重新烧写Uboot后,此时do_bootm就会出错,返回,则就开启了tftpd,进行下载模式。

接下来我们看看do_bootm函数,看它是怎么样解压内核,怎么样跳到内核执行的。

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

     ………………………….

       if (argc < 2) {

              addr = load_addr;

       } else {

              addr = simple_strtoul(argv[1], NULL, 16); //我们传过来的参数为2,所以就从地址就是argv[1],这个地址呢就是CFG_FLASH_BASE +0X40000,0xbc440000,这里便是kernel的起始地址

       }

       memmove (&header, (char *)addr, sizeof(image_header_t)); //kernel启去始地址处我们在做code.bin时,在前面通过mkimage加了一个image_header的头,之后才加上code pattern的。这里我们烧写的时侯去掉了code pattern,所以flash里开始处便是image_header头,这里就是将这个头读出来。

 

       if (ntohl(hdr->ih_magic) != IH_MAGIC) { //这里判断这个头正确不正确,通过一个IH_MAGIC来判断,每次重新烧写Uboot后,flash内核开始处,image_header就会被破坏,这个条件就不成立,于时就会在这里直接返回,之后就像上面所讲的进入了tftpd.

           {

              printf ("Bad Magic Number,%08X \n",ntohl(hdr->ih_magic));

              SHOW_BOOT_PROGRESS (-1);

              return 1;

           }

       }

 

       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); 将image_header头的内容打印出来

Image Type:   MIPS Linux Kernel Image (lzma compressed)

   Data Size:    3174400 Bytes =  3 MB

   Load Address: 8a00000

   Entry Point:  881ee040

       这个是我们当前的image_header头。

       data = addr + sizeof(image_header_t);//去掉image_header头,这里便是kernel开始的部分了。

       len  = ntohl(hdr->ih_size);

 

#ifdef CONFIG_HAS_DATAFLASH

       if (addr_dataflash(addr)){

              read_dataflash(data, len, (char *)CFG_LOAD_ADDR);

              data = CFG_LOAD_ADDR;

       }

#endif

 

       if (verify) {

              puts ("   Verifying Checksum ... ");

              if (crc32 (0, (char *)data, len) != ntohl(hdr->ih_dcrc)) {

                     printf ("Bad Data CRC\n");

              //kaiker   SHOW_BOOT_PROGRESS (-3);

                     return 1;

              }

              puts ("OK\n");

       }

       SHOW_BOOT_PROGRESS (4);

 

       len_ptr = (ulong *)data;

………………………………………….

       switch (hdr->ih_type) { 查看这部分的类型,目前为KERNEL

       case IH_TYPE_STANDALONE:

              name = "Standalone Application";

              /* A second argument overwrites the load address */

              if (argc > 2) {

                     hdr->ih_load = simple_strtoul(argv[2], NULL, 16);

              }

              break;

       case IH_TYPE_KERNEL:

              name = "Kernel Image"; //执行这里。

              break;

       case IH_TYPE_MULTI:

              name = "Multi-File Image";

              len  = ntohl(len_ptr[0]);

              /* OS kernel is always the first image */

              data += 8; /* kernel_len + terminator */

              for (i=1; len_ptr[i]; ++i)

                     data += 4;

              break;

       default: printf ("Wrong Image Type for %s command\n", cmdtp->name);

              SHOW_BOOT_PROGRESS (-5);

              return 1;

       }

       SHOW_BOOT_PROGRESS (6);

 

       /*

        * We have reached the point of no return: we are going to

        * overwrite all exception vector code, so we cannot easily

        * recover from any failures any more...

        */

 

       iflag = disable_interrupts();

 

       switch (hdr->ih_comp) { //判断压缩类型,刚才我们从image_header看出是lzma压缩的。

       case IH_COMP_NONE:

              if(ntohl(hdr->ih_load) == addr) {

                     printf ("   XIP %s ... ", name);

              } else {

#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)

                     size_t l = len;

                     void *to = (void *)ntohl(hdr->ih_load);

                     void *from = (void *)data;

 

                     printf ("   Loading %s ... ", name);

 

                     while (l > 0) {

                            size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;

                            WATCHDOG_RESET();

                            memmove (to, from, tail);

                            to += tail;

                            from += tail;

                            l -= tail;

                     }

#else       /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */

                     memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);

#endif     /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */

              }

              break;

       case IH_COMP_GZIP:

#if 1

              printf ("   Uncompressing %s ... ", name);

              if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,

                         (uchar *)data + sizeof(struct trx_header), &len) != 0) {

                     puts ("GUNZIP ERROR - must RESET board to recover\n");

                     SHOW_BOOT_PROGRESS (-6);

                     do_reset (cmdtp, flag, argc, argv);

              }

#endif

              break;

#ifdef CONFIG_BZIP2

       case IH_COMP_BZIP2:

              printf ("   Uncompressing %s ... ", name);

              /*

               * If we've got less than 4 MB of malloc() space,

               * use slower decompression algorithm which requires

               * at most 2300 KB of memory.

               */

              i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),

                                          &unc_len, (char *)data, len,

                                          CFG_MALLOC_LEN < (4096 * 1024), 0);

              if (i != BZ_OK) {

                     printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);

                     SHOW_BOOT_PROGRESS (-6);

                     udelay(100000);

                     do_reset (cmdtp, flag, argc, argv);

              }

              break;

#endif /* CONFIG_BZIP2 */

#ifdef CONFIG_LZMA //所以就跳到这里来了,

        case IH_COMP_LZMA:

                printf ("   Uncompressing %s ... ", name);

 

#ifdef CONFIG_UNCOMPRESS_TIME

                tBUncompress = get_ticks();

#endif

              unsigned int destLen = 0;

                     接来来开始解压,解压目的地址hdr->ih_load,我们从打印的image_header看出,是0x8a000000.,所以解压到这里。

                i = lzmaBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),

                                &destLen, (char *)data + sizeof(struct trx_header), len);

                if (i != LZMA_RESULT_OK) {

                        printf ("LZMA ERROR %d - must RESET board to recover\n", i);

                     return 1;

                        //SHOW_BOOT_PROGRESS (-6);

                        //udelay(100000);

                        //do_reset (cmdtp, flag, argc, argv);

                }

#endif /* CONFIG_LZMA */

       default:

              if (iflag)

                     enable_interrupts();

              printf ("Unimplemented compression type %d\n", hdr->ih_comp);

              SHOW_BOOT_PROGRESS (-7);

              return 1;

       }

………………………………….

       switch (hdr->ih_os) { //这里我们是linux内核,所以要调用do_bootm_linux函数。

       default:                 /* handled by (original) Linux case */

       case IH_OS_LINUX:

#ifdef CONFIG_SILENT_CONSOLE

           fixup_silent_linux();

#endif

           do_bootm_linux  (cmdtp, flag, argc, argv,

                          addr, len_ptr, verify);

           break;

…………………………………………………………………………………..

       }

       return 1;

}

接下来我们看看do_bootm_linux函数,现在以经解压完了,所以这里我们只关注它是怎么跳转到内核开始运行的。

do_bootm_linux (cmd_tbl_t *cmdtp, int flag,

              int   argc, char *argv[],

              ulong      addr,

              ulong      *len_ptr,

              int   verify)

{

…………………………………………………..

kernel = (void (*)(bd_t *, ulong, ulong, ulong, ulong))hdr->ih_ep;//image_headerih_ep,对于当前内核,我们可以从刚才打印imager_header参数时看出,为0x881ee040,这个地址是什么呢,它其时就是head.S,kernel_entry的地址,

……………………………………………….

(*kernel) (kbd, initrd_start, initrd_end, cmd_start, cmd_end); //这里就是跳到kernel的地方,即跳到kernel_entry处开始内核的运行了。之后就便是开始从head.S中的kernel_entry开始执行了。从而完成从Uboot到内核的交接。

}

接下来我们进入内核看看:

               * Kernel entry point

               */

              NESTED(kernel_entry, 16, sp) //kernel进入点

              .set  push

              /*

               * For the moment disable interrupts and mark the kernel mode.

               * A full initialization of the CPU's status register is done

               * later in per_cpu_trap_init().

               */

              mfc0       t0, CP0_STATUS

              or    t0, ST0_CU0|0x1f

              xor  t0, 0x1f

              mtc0       t0, CP0_STATUS

 

              .set  noreorder

              sll    zero,3                          # ehb

              .set  reorder

 

              /*

               * The firmware/bootloader passes argc/argp/envp

               * to us as arguments.  But clear bss first because

               * the romvec and other important info is stored there

               * by prom_init().

               */

              la     t0, _edata

              sw   zero, (t0)

              la     t1, (_end - 4)

1:

              addiu      t0, 4

              sw   zero, (t0)

              bne  t0, t1, 1b

 

              /*

               * Stack for kernel and init, current variable

               */

              la     $28, init_task_union

              addiu      t0, $28, KERNEL_STACK_SIZE-32

              subu sp, t0, 4*SZREG

              sw   t0, kernelsp

 

              jal    init_arch //跳到init_arch函数,从这里跳到C代码执行。

              .set  pop

              END(kernel_entry)

Init_arch函数:

 

 

 

asmlinkage void __init

init_arch(int argc, char **argv, char **envp, int *prom_vec)

{

       /* Determine which MIPS variant we are running on. */

       cpu_probe(); //CPU探测,读出CPU ID

 

       prom_init(argc, argv, envp, prom_vec);

 

       cpu_report();//打印CPU信息

 

       /*

        * Determine the mmu/cache attached to this machine,

        * then flush the tlb and caches.  On the r4xx0

        * variants this also sets CP0_WIRED to zero.

        */

       load_mmu();//

 

       start_kernel();

}

int __init prom_init()

{

 

/* change parameter by bobtseng, 2006.1.3. */

       mips_machgroup = MACH_GROUP_RT2880;

 

       set_io_port_base(KSEG1);

 

       prom_init_cmdline();  //初始化启动参数,将启动参数分解并存入argc数组里

       prom_init_sysclk(); //初始化CPU频率

       prom_init_serial_port();  /* Needed for Serial Console */ //初始化串口参数

       // prom_init_mac(); // remove for test, bobtseng 2006.1.3.

       prom_setup_printf(prom_get_ttysnum()); //可能有两个串口,选择哪一个

#if defined(CONFIG_RT2880_FPGA)

       prom_printf("\nTHIS IS FPGA\n");

#elif defined(CONFIG_RT2880_ASIC)

       prom_printf("\nTHIS IS ASIC\n");

#else

#error Please Choice Chip Type

#endif

 

       prom_meminit(); //初始化SDRAM,取出base地址和size大小,为mmu做准备

       return 0;

}

以上便是从Uboot的启动到内核start_kernel的运行过程,start_kernel之后便是跟内核原理紧密相关的启动过程,在这里就不介绍了,有兴趣的同事可以慢慢研究。这里仍有两个问题。

一个是我们正常的桌面linux(如Redhat),在系统启动的时侯,参数都是通过lilogrub启动程序传给内核的,即同理,启动参数也可以从Uboot传到内核,问题就是参数是怎么样传递的呢?

一个是在init_arch函数里,调用了prom_init函数,调用prom_init时带了四个能数,但是prom_init原形确不带参数,这个又是为什么呢?接下来我们就讲讲这两个问题。

a、  Uboot跳到内核是通过kernel­_entry的地址跳过来的,kernel_entry是汇编语言,Uboot在调用时确是C语言的实现过程,所以一般这里就不容易看出参数的传递规则了。

由于C在编译时还是要通过汇编这一关,因此我们也就只关注汇编语言中函数参数是怎么传递的,也就明白了这个问题。

       通常每种架构(intel, mips,arm)的CPU,都有各自的汇编指令,都有各自独特的寄存器,同时也都有很多的共性,原理上也都大同小异。我们通常编程都是用的高级语言,高级语言中都是有函数调用的,函数往往是带参数的,高级语言在编译成汇编时,参数是怎么样表示的。因此,针对汇编语言,只有指令,与操作数,操作数就只有寄存器与立即数。因此针对每种架构的CPU,专门有预留的寄存器用来传递函数参数的,在MIPS架构中,a0-a3就是这样的寄存器。

Uboot在解压完内核后,通过一个地址调用函数进入内核中运行,并且携带启动参数,由于进入内核是汇编语言,所以这此参数放在a0a3的寄存器中(这个是由MIPS架构决定的,a0-a3专门存放子程序的参数).

b 这个问题跟函数名前的__init 有关系,int __init prom_init()

 

#define __init            __attribute__ ((__section__ (".text.init")))

 

这个宏的定义将这段代码放在.text.init这里,通过__attribute__展开,在这里attribute只是gcc的一个属性,针对这个属性gcc编译时对此函数不做参数方面的检查(检查不是很严格)。所以编译就没问题。gcc由于attribute,所以编译时不检查函数参数,我们在调用子函数时可以带参数,也可以不带参数,也可以少带参数等,这个根据需要而定。我想应该是这样的。

链接时将此函数放入.text.init段里,之后运行时直接根据prom_init进行跳转运行。

 

阅读(3056) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~