LSYstudy 发表于 2011-10-13 16:42:42

UBOOT系统启动流程

系统的启动流程
      这里我们来讨论Uoot的启动流程,并将延伸至内核启动到start_kernel函数,我们主要讲start_kernel函数前系统都在做什么,系统是怎么样从开始运行,到解压内核,跳到内核运行,又怎么样从Uboot里将参数传到内核的。
Uboot的启动是从start.S文件开始的,系统一上电pc指针便指向这里开始程序的启动运行,在start.S文件中系统的工作主要有:
系统一上电便是reset异常,便跳到reset处进行处理,初始化Sdram,cache等,然后跳到board_init_f函数中运行。
Board_init_if(lib_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;
      printf("   \n3: System Boot system code via Flash.\n");
      do_bootm(cmdtp, 0, 2, argv); //从这里开始对内核进行解压,并跳到内核处运行。
      
      /* below only when boot from flash fali*/
      argv =&file_name_space;
      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, NULL, 16); //我们传过来的参数为2,所以就从地址就是argv,这个地址呢就是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, NULL, 16);
            }
            break;
       case IH_TYPE_KERNEL:
            name = "Kernel Image"; //执行这里。
            break;
       case IH_TYPE_MULTI:
            name = "Multi-File Image";
            len= ntohl(len_ptr);
            /* OS kernel is always the first image */
            data += 8; /* kernel_len + terminator */
            for (i=1; len_ptr; ++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_header,ih_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进入点
            .setpush
            /*
               * 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
            xort0, 0x1f
            mtc0       t0, CP0_STATUS

            .setnoreorder
            sll    zero,3                        # ehb
            .setreorder

            /*
               * 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)
            bnet0, 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代码执行。
            .setpop
            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之后便是跟内核原理紧密相关的启动过程,在这里就不介绍了,有兴趣的同事可以慢慢研究

ghbb88 发表于 2011-10-21 08:26:23

谢谢楼主哦,对我理解有一定的帮助!

bluelool 发表于 2011-10-25 22:20:23

mark

ITOP 发表于 2011-10-25 23:01:32

学习!!

xuxi2009 发表于 2011-10-25 23:14:40

标记

maj87 发表于 2011-10-31 08:39:59

Mark

willX 发表于 2011-11-2 11:06:55

学习!

xiaomengyichen 发表于 2012-12-12 23:33:59

写的非常好!谢谢!!!

lechoate 发表于 2012-12-18 17:18:35

大部分bootloader都是这种流程,楼主写得不错,很详细,对研究bootloader很有帮助,多谢了!

zjgugugugugu 发表于 2012-12-21 10:09:52

{:cry:}我不会。。

xiaomengyichen 发表于 2013-1-5 00:06:19

好帖!!!!

Privia 发表于 2013-1-5 00:47:10

{:biggrin:} 我也在学这个 MARK下 哈哈

samba_zhou 发表于 2013-1-5 08:50:31

现在正在研究这个

jacktau 发表于 2013-1-5 08:52:53

谢谢楼主的分析,学海无涯......

charlieholy 发表于 2013-3-20 11:00:58

mark                                             

spiritcity 发表于 2013-3-20 15:58:19

学习了..不错

lzbp 发表于 2013-4-7 11:19:49

墙贴啊啊啊啊啊!!

Lee2012 发表于 2013-4-9 09:22:43

{:smile:} 耐心学习!

along_mail 发表于 2013-4-9 11:25:35

mark 学习下
页: [1]
查看完整版本: UBOOT系统启动流程