11@ uboot第二階段分析(下)



============================================================對於uboot兩階段的小結,很重要!!!==================================
第一階段:
在腳本uboot.lds中 ENTRY(_start) _start的地址一般是TEXT_BASE
設置異常向量表
  進入svc管理模式 arm狀態
  關看門狗 關中斷 時鐘初始化
  cpu初始化(關mmu 關數據和指令緩存 cpu速率)和內存初始化 在函數cpu_init_crit中完成
  重定位
  設置棧
  清bss斷
  跳入到start_armboot函數

第二階段 就進入到lib_arm/board.c    中的start_kernel函數

分配gd   指針指向的空間和gd->bd   指針指向的空間
執行init_sequence        函數序列,其實主要目的是初始化並填充gd和gd->bd結構體
分配堆空間     mem_malloc_init ,這樣纔可以初始化環境變量,因爲環境變量是要從nand拷貝到內存中的堆空間裏
nand初始化      nand_init,因爲現在普及採用nand,若是用nor的話,在之前已經初始化了 flash_init    函數
環境變量初始化    env_reloacate 有四個很重要的環境變量參數:bootdelay(啓動延時),bootcmd(啓動命令),menucmd(菜單),bootargs(啓動參數,也即最原始的命令行參數)
串口設置的5個函數,這樣就能看到串口打印數據
混雜設備 misc_init_r函數


進入循環執行 main_loop函數,處理啓動命令或者用戶輸入的命令 該函數是在common/main.c     中
    第一種情況是 在bootdelay內不按空格鍵:s=getenv ("bootcmd");run_command (s, 0);    直接啓動內核了
    第二種輕狂就是 在bootdelay內按下空格鍵進入menu菜單裏: s = getenv("menucmd");run_command (s, 0);
     然後進入命令循環獲取用戶從串口裏打印的字符 len = readline         (CFG_PROMPT);run_command (lastcommand, flag); 
之後在run_command裏if ((cmdtp = find_cmd(argv[0])) == NULL)--->if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0)即根據     
命令的名字 找到該命令表結構體,調用它的cmd參數 最終還是回到do_xxx  run_command就是來解析命令的

run_command分別解析nand命令和bootm命令,nand命令負責把linux內核讀到內存中,bootm負責去啓動內核鏡像,調用do_bootm----->do_bootm_linux,啓動內核



/****************************   開始第二階段分析   **************************************/
 
開始第二階段
第二階段是在lib_arm/board.c      bootm.c兩個文件
start_armboot    是 U-Boot執行的第一個C語言函數,完成系統初始化工作,進入主循環,處理用戶輸入的命令。

看board.c            文件

#include <common.h>
#include <command.h>
#include <malloc.h>
#include <devices.h>
#include <version.h>
#include <net.h>
#include <serial.h>
#include <nand.h>
#include <onenand_uboot.h>

#ifdef CONFIG_DRIVER_SMC91111
#include "../drivers/net/smc91111.h"
#endif
#ifdef CONFIG_DRIVER_LAN91C96
#include "../drivers/net/lan91c96.h"
#endif

DECLARE_GLOBAL_DATA_PTR;

ulong monitor_flash_len;

#ifdef CONFIG_HAS_DATAFLASH
extern int  AT91F_DataflashInit(void);
extern void dataflash_print_info(void);
#endif

#ifndef CONFIG_IDENT_STRING
#define CONFIG_IDENT_STRING ""
#endif

const char version_string[] =
     U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;

#ifdef CONFIG_DRIVER_CS8900
extern void cs8900_get_enetaddr (uchar * addr);
#endif

#ifdef CONFIG_DRIVER_RTL8019
extern void rtl8019_get_enetaddr (uchar * addr);
#endif

#if defined(CONFIG_HARD_I2C) || \
    defined(CONFIG_SOFT_I2C)
#include <i2c.h>
#endif

/*
* Begin and End of memory area for malloc(), and current "brk"
*/
static ulong mem_malloc_start = 0;
static ulong mem_malloc_end = 0;
static ulong mem_malloc_brk = 0;

static
void mem_malloc_init (ulong dest_addr)
{
     mem_malloc_start = dest_addr;
     mem_malloc_end = dest_addr + CFG_MALLOC_LEN;
     mem_malloc_brk = mem_malloc_start;

     memset ((void *) mem_malloc_start, 0,
               mem_malloc_end - mem_malloc_start);
}

void *sbrk (ptrdiff_t increment)
{
     ulong old = mem_malloc_brk;
     ulong new = old + increment;

     if ((new < mem_malloc_start) || (new > mem_malloc_end)) {
          return (NULL);
     }
     mem_malloc_brk = new;

     return ((void *) old);
}


/************************************************************************
* Coloured LED functionality
************************************************************************
* May be supplied by boards if desired
*/
void inline __coloured_LED_init (void) {}
void inline coloured_LED_init (void) __attribute__((weak, alias("__coloured_LED_init")));
void inline __red_LED_on (void) {}
void inline red_LED_on (void) __attribute__((weak, alias("__red_LED_on")));
void inline __red_LED_off(void) {}
void inline red_LED_off(void)          __attribute__((weak, alias("__red_LED_off")));
void inline __green_LED_on(void) {}
void inline green_LED_on(void) __attribute__((weak, alias("__green_LED_on")));
void inline __green_LED_off(void) {}
void inline green_LED_off(void)__attribute__((weak, alias("__green_LED_off")));
void inline __yellow_LED_on(void) {}
void inline yellow_LED_on(void)__attribute__((weak, alias("__yellow_LED_on")));
void inline __yellow_LED_off(void) {}
void inline yellow_LED_off(void)__attribute__((weak, alias("__yellow_LED_off")));

/************************************************************************
* Init Utilities                                   *
************************************************************************
* Some of this code should be moved into the core functions,
* or dropped completely,
* but let's get it working (again) first...
*/

static int init_baudrate (void)
{
     char tmp[64];     /* long enough for environment variables */
     int i = getenv_r ("baudrate", tmp, sizeof (tmp));
     gd->bd->bi_baudrate = gd->baudrate = (i > 0)
               ? (int) simple_strtoul (tmp, NULL, 10)
               : CONFIG_BAUDRATE;

     return (0);
}

static int display_banner (void)
{
     printf ("\n\n%s\n\n", version_string);
     debug ("U-Boot code: %08lX -> %08lX  BSS: -> %08lX\n",
            _armboot_start, _bss_start, _bss_end);
#ifdef CONFIG_MODEM_SUPPORT
     debug ("Modem Support enabled\n");
#endif
#ifdef CONFIG_USE_IRQ
     debug ("IRQ Stack: %08lx\n", IRQ_STACK_START);
     debug ("FIQ Stack: %08lx\n", FIQ_STACK_START);
#endif

     return (0);
}

/*
* WARNING: this code looks "cleaner" than the PowerPC version, but
* has the disadvantage that you either get nothing, or everything.
* On PowerPC, you might see "DRAM: " before the system hangs - which
* gives a simple yet clear indication which part of the
* initialization if failing.
*/
static int display_dram_config (void)   //打印顯示ram的配置信息
{
     int i;

#ifdef DEBUG
     puts ("RAM Configuration:\n");

     for(i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
          printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
          print_size (gd->bd->bi_dram[i].size, "\n");
     }
#else
     ulong size = 0;

     for (i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
          size += gd->bd->bi_dram[i].size;
     }
     puts("DRAM:  ");
     print_size(size, "\n");
#endif

     return (0);
}

#ifndef CFG_NO_FLASH
static void display_flash_config (ulong size)
{
     puts ("Flash: ");
     print_size (size, "\n");
}
#endif /* CFG_NO_FLASH */

#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
static int init_func_i2c (void)
{
     puts ("I2C:   ");
     i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
     puts ("ready\n");
     return (0);
}
#endif

/*
* Breathe some life into the board...
*
* Initialize a serial port as console, and carry out some hardware
* tests.
*
* The first part of initialization is running from Flash memory;
* its main purpose is to initialize the RAM so that we
* can relocate the monitor code to RAM.
*/

/*
* All attempts to come up with a "common" initialization sequence
* that works for all boards and architectures failed: some of the
* requirements are just _too_ different. To get rid of the resulting
* mess of board dependent #ifdef'ed code we now make the whole
* initialization sequence configurable to the user.
*
* The requirements for any new initalization function is simple: it
* receives a pointer to the "global data" structure as it's only
* argument, and returns an integer return code, where 0 means
* "continue" and != 0 means "fatal error, hang the system".
*/
typedef int (init_fnc_t) (void);

int print_cpuinfo (void); /* test-only */

init_fnc_t *init_sequence[ ] = {
     cpu_init,          /* basic cpu dependent setup */
     board_init,          /* basic board dependent setup */
     interrupt_init,          /* set up exceptions */
     env_init,          /* initialize environment */
     init_baudrate,          /* initialze baudrate settings */
     serial_init,          /* serial communications setup */
     console_init_f,          /* stage 1 init of console */
     display_banner,          /* say that we are here */   打印uboot相關信息
#if defined(CONFIG_DISPLAY_CPUINFO)
     print_cpuinfo,          /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
     checkboard,          /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
     init_func_i2c,
#endif
     dram_init,     /* configure available RAM banks */ 
     //配置可用的ram,非常重要,在這裏開始進行配置DRAM信息,用到結構體bd->bi_dram[BANK_NR].start
                                                                                                                      bd->bi_dram[BANK_NR].size
     display_dram_config,  //打印顯示ram的配置信息
     NULL,
};




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,
};






void start_armboot (void)      //start_armboot函數 非常重要,c function第一個函數!!!
{
     init_fnc_t **init_fnc_ptr;
     char *s;
#if !defined(CFG_NO_FLASH) || defined (CONFIG_VFD) || defined(CONFIG_LCD)
     ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
     unsigned long addr;
#endif

//在重定位之後,即uboot的代碼從flash拷到sdram 此時連接腳本里的_start 等於TEXT_BASE 
     /* Pointer is writable since we allocated a register for it */
    
// 給全局變量gd分配空間大小且指定gd的位置 這裏gd是一個結構體,在uboot內存分佈中
     是CFG_GBL_DATA_SIZE一共128字節
     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指針所指向的空間清零
     gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));      //給gd中的bd指針分配空間大小
     memset (gd->bd, 0, sizeof (bd_t));                  //gd->bd 所指向的空間清零

     gd->flags |= GD_FLG_RELOC;

     monitor_flash_len = _bss_start - _armboot_start;      //uboot鏡像文件的大小

     for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { 
 //執行初始化序列函數 這裏是調用了一系列的c函數指針,進行初始化。
比如cpu_init初始化完成各個gpio管腳初始化,board_init完成arch_number設置和boot_params約定存放地址,還有串口初始化等。

          if ((*init_fnc_ptr)() != 0) {
               hang ();
          }
     }
              
               board/smdk2410/flash.c配置flash

#ifndef CFG_NO_FLASH     識別出來是哪一種flash nor還是nand 如果定義了CFG_NO_FLASH這個宏,說明是nand 否則是nor
     /* configure available FLASH banks */
     size = flash_init ();                           //nor型flash的初始化
     display_flash_config (size);
#endif /* CFG_NO_FLASH */

                     定義顯示類型 分vfd和lcd兩種。vfd一般不用,我們用lcd的
                     在這裏定義了幀緩衝,也就顯存的的地址和大小
     -----------------------------------------------------------------------------------------------------------

#ifdef CONFIG_VFD           
#     ifndef PAGE_SIZE
#       define PAGE_SIZE 4096  //定義頁大小4k
#     endif
     /*
     * reserve memory for VFD display (always full pages)
     */
     /* bss_end is defined in the board-specific linker script */
     addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
     size = vfd_setmem (addr);
     gd->fb_base = addr; 
#endif /* CONFIG_VFD */




#ifdef CONFIG_LCD      //在內存中配置一塊幀緩衝區
     /* board init may have inited fb_base */
     if (!gd->fb_base) {
#          ifndef PAGE_SIZE
#            define PAGE_SIZE 4096
#endif
/*
* reserve memory for LCD display (always full pages)
*/

/* bss_end is defined in the board-specific linker script */
     addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);  按頁對其方式保留顯存

     size = lcd_setmem (addr);          // 分配幀緩衝區的大小
     gd->fb_base = addr;                    //  幀緩衝區的物理起始地址
     }
#endif /* CONFIG_LCD */

--------------------------------------------------------------------------------------------------------------------

     /* armboot_start is defined in the board-specific linker script */
     mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);     //分配堆空間大小 這樣纔可以初始化環境變量
    

//初始化nand flash,這是在nand flash啓動的s3c2410移植u-boot的關鍵,根據flash時序編寫函數即可
//在include/configs/smdk2410.h中的command definition中增加CONFIG_COMMANDS和CFG_CMD_NAND命令
//nand型flash的初始化
#if defined(CONFIG_CMD_NAND)
     puts ("NAND:  ");        打印標誌:   NAND: 64MB
     nand_init();          /* go init the NAND */  //board/smdk2410/smdk2410.c,獲取nand的基地址和 大小信息

#endif

#if defined(CONFIG_CMD_ONENAND)  三星的一種特別的flash  onenand,類似於nand
     onenand_init();
#endif

#ifdef CONFIG_HAS_DATAFLASH
     AT91F_DataflashInit();
     dataflash_print_info();
#endif

/* initialize environment */
     env_relocate ();  
//環境變量的初始化 該函數是在commen/env_commen.c中定義的,在該文件中還定義了一個默認下的環境變量

default_enviornment[]  第一次啓動時,nand 默認裏面是沒有環境變量的,則根據板文件的宏選用默認的環境變量。
可以進行修改通過saveenv進行保存到nand裏。

//env_relocate將環境變量從存儲設備中讀取到全局變量env_t env_ptr的data裏面,由於該函數從以上堆空間
//中分配空間,所以我們的環境變量都放在了堆空間裏面了

env_relocate ----> env_ptr = (env_t *)malloc (CONFIG_ENV_SIZE); gd->env_addr = (ulong)&(env_ptr->data);此處是告訴
環境變量存放在內存中的地址自此, 環境變量已經存放在內存gd->env_addr處,這樣就可以獲取環境變量或者
修改環境變量保存到nand中去了

#ifdef CONFIG_VFD    //framebuffer初始化

     /* must do this after the framebuffer is allocated */
     drv_vfd_init();  //video的初始化
#endif /* CONFIG_VFD */

#ifdef CONFIG_SERIAL_MULTI  //多串口
     serial_initialize();
#endif


//從環境變量中獲取IP地址 以太網接口MAC地址 主要是初始化 gd->bd->bi_ip_addr和gd->bd->bi_enetaddr[]
-----------------------------------------------------------------
     /* IP Address */
     gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");     通過讀取環境變量的ip地址

     /* MAC Address */
     {
          int i;
          ulong reg;
          char *s, *e;
          char tmp[64];

          i = getenv_r ("ethaddr", tmp, sizeof (tmp));
          s = (i > 0) ? tmp : NULL;

          for (reg = 0; reg < 6; ++reg) {
               gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
               if (s)
                    s = (*e) ? e + 1 : e;
          }

#ifdef CONFIG_HAS_ETH1  如果要是有兩塊以太網卡
          i = getenv_r ("eth1addr", tmp, sizeof (tmp));
          s = (i > 0) ? tmp : NULL;

          for (reg = 0; reg < 6; ++reg) {
               gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
               if (s)
                    s = (*e) ? e + 1 : e;
          }
#endif
     }

----------------------------------------------------------------------------

     devices_init (); /* get the devices list going. */     註冊設備鏈表,其實也就只註冊了一個串口設備

#ifdef CONFIG_CMC_PU2
     load_sernum_ethaddr ();
#endif /* CONFIG_CMC_PU2 */
    
     jumptable_init ();

     console_init_r ();/* fully init console as a device */ 
//控制檯設備的初始化階段2  到這裏終於可以從控制檯上看到數據打印出來了



    
------------------------------------------------------------------------------------------------------
注意:從串口寄存器的設置到最終在終端上打印信息,是有以下函數組成的。


   在init_sequense裏的三個函數和
      init_baudrate,   設置 gd->bd->bi_baudrate
      serial_init,         直接調用serial_setbrg函數初始化UART寄存器:8個數據位,一個開始位,一個停止位,無校驗位。。。
      console_init_f,  控制檯前期初始化  設置gd->have_console=1
      devices_init,    調用drv_system_init 註冊串口設備
      console_init_r   控制檯後期初始化,將串口設備指向控制檯標準輸入設備,標準輸出設備,標準錯誤設備
 
     In:   serial
     Out: serial
     Err: serial  


打印這三行信息,表明串口作爲標準輸入設備,標準輸出設備,標準錯誤輸出設備,這樣就能打印信息了
默認情況下,鍵盤和鼠標默認爲標準輸入設備,顯示器默認爲標準輸出設備和標準錯誤輸出設備,printf爲標準格式輸出
scanf爲標準格式輸入。標準輸入,標準輸出設備的重定向即將串口設備作爲標準輸入設備,將串口做爲標準輸出設備和
標準錯誤輸出設備

static void drv_system_init (void) 
static void drv_system_init (void) 
    device_t dev;     //定義一個設備結構體 
 
    memset (&dev, 0, sizeof (dev));//爲剛剛定義的結構體分配內存 
 
    strcpy (dev.name, "serial");    //名稱 
    dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM;   設備的類型
 
#ifdef CONFIG_SERIAL_SOFTWARE_FIFO 
    dev.putc = serial_buffered_putc; 
    dev.puts = serial_buffered_puts; 
    dev.getc = serial_buffered_getc; 
    dev.tstc = serial_buffered_tstc; 
#else 
    dev.putc = serial_putc; 
    dev.puts = serial_puts; 
    dev.getc = serial_getc; 
    dev.tstc = serial_tstc; 
#endif 
                             填充該設備結構體並註冊
    device_register (&dev);//註冊函數  將該串口設備註冊到devlist
 
#ifdef CFG_DEVICE_NULLDEV 
    memset (&dev, 0, sizeof (dev)); 
 
    strcpy (dev.name, "nulldev"); 
    dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM; 
    dev.putc = nulldev_putc; 
    dev.puts = nulldev_puts; 
    dev.getc = nulldev_input; 
    dev.tstc = nulldev_input; 
 
    device_register (&dev); 
#endif 
                        
                        
                        
console_init_r     函數
                        
2    int console_init_r (void)
3    {
4        device_t *inputdev = NULL, *outputdev = NULL;  定義兩個設備
5        int i, items = ListNumItems (devlist);   //  取得設備鏈中的設備數 因爲只註冊了一個設備,即items=1

6        /* Scan devices looking for input and output devices */
7        for (i = 1;
8             (i <= items) && ((inputdev == NULL) || (outputdev == NULL));
9             i++
10            ) {
11            device_t *dev = ListGetPtrToItem (devlist, i);   從devlist中獲取該串口設備

12            if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {   執行
13                inputdev = dev;     即輸入設備爲該串口設備
14            }
15            if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {   執行
16                outputdev = dev;   即輸出設備也爲該串口設備
17            }
18        }
   
// 7~18行,在設備鏈中按註冊的順序查找輸入輸出設備,在設備註冊時 dev.flags表示此設備的類型。
// 比如這裏drv_system_init,此設備是第一個註冊的設備,且其dev.flags爲
// DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM 所以上面18行過後,輸入輸出設備都指定爲
// drv_system_init裏註冊的設備了
console_setfile 是最關鍵的地方,在這裏纔是真正的給我們的標準輸入設備,標準輸出設備,標準錯誤輸出設備填充結構

19        /* Initializes output console first */                               
20        if (outputdev != NULL) {     

通過console_setfile()函數可以看出,控制檯有一個包含 3 個 device_t 元素的數組stdio_devices,分別對應
     stdin,stdout,stderr。
通過 stdio_devices[file] = dev 就可以將dev設成設置控制檯的某個設備。這樣就實現了控制檯任意選擇設備的功能。
     這和 linux 的設計思想有點類似。
                                                                  
21            console_setfile (stdout, outputdev); 即stdio_devices[stdout]=outputdev
22            console_setfile (stderr, outputdev); 即stdio_devices[stderr]=outputdev
23        }
24        /* Initializes input console */
25        if (inputdev != NULL) {
26            console_setfile (stdin, inputdev); 即stdio_devices[stdin]=inputdev
27        }                      將控制檯的標準輸入設備,標準輸出設備,標註錯誤輸出設備均設爲串口設備

   
   
21~27行, console_setfile做如下幾件事:
1. 如果初始化該設備時註冊了device_t.start,即啓動設備的函數,則運行該函數,開啓該設備
2. 將設備的指針存入stdio_devices[file],這應該是標準輸入標準輸出、標準出錯。

      #define stdin         0
      #define stdout        1
      #define stderr        2
22行將標準出錯定爲輸出設備,這樣有錯誤信息就會通過輸出設備打印出來了

28        gd->flags |= GD_FLG_DEVINIT;    /* device initialization completed */

29        /* Print information */
30        puts ("In:    ");
31        if (stdio_devices[stdin] == NULL) {
32            puts ("No input devices available!\n");
33        } else {
34            printf ("%s\n", stdio_devices[stdin]->name);
35        }

36        puts ("Out:   ");
37        if (stdio_devices[stdout] == NULL) {
38            puts ("No output devices available!\n");
39        } else {
40            printf ("%s\n", stdio_devices[stdout]->name);
41        }

42        puts ("Err:   ");
43        if (stdio_devices[stderr] == NULL) {
44            puts ("No error devices available!\n");
45        } else {
46            printf ("%s\n", stdio_devices[stderr]->name);
47        }

30~47行,將信息打印出來,這裏打印出出的信息就爲
In:    serial
Out:   serial
Err:   serial

48        /* Setting environment variables */
49        for (i = 0; i < 3; i++) {
50            setenv (stdio_names[i], stdio_devices[i]->name);// 即stdin=serial
51        }                                                        stdout = serial

49~51行 將信息寫到環境變量中去
char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" };
這樣環境變量裏 stdin stdout stderr 都爲serial

    52        return (0);
    53    }

   ---------------------------------------------------------------------------------------------------------------------------------------                                             

#if defined(CONFIG_MISC_INIT_R)
     /* miscellaneous platform dependent initialisations */
     misc_init_r ();     //混雜設備的初始化 很重要
#endif

     /* enable exceptions */
     enable_interrupts ();  //使能中斷 即打開cpsr_c的第7位 即外中斷,該第7位值爲0,表示使能外中斷

     /* Perform network card initialisation if necessary */
    
     配置幾種類型的網卡
#ifdef CONFIG_DRIVER_TI_EMAC
extern void davinci_eth_set_mac_addr (const u_int8_t *addr);
     if (getenv ("ethaddr")) {
          davinci_eth_set_mac_addr(gd->bd->bi_enetaddr);
     }
#endif

#ifdef CONFIG_DRIVER_CS8900
     cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif

#if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
     if (getenv ("ethaddr")) {
          smc_set_mac_addr(gd->bd->bi_enetaddr);
     }
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */

     /* Initialize from environment */
     if ((s = getenv ("loadaddr")) != NULL) {
          load_addr = simple_strtoul (s, NULL, 16);
     }
#if defined(CONFIG_CMD_NET)
     if ((s = getenv ("bootfile")) != NULL) {
          copy_filename (BootFile, s, sizeof (BootFile));
     }
#endif



#ifdef BOARD_LATE_INIT
     board_late_init ();
#endif
#if defined(CONFIG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
     puts ("Net:   ");
#endif
     eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)
     debug ("Reset Ethernet PHY\n");
     reset_phy();
#endif
#endif

/*****************************************非常重要**************************************/

/* main_loop()      can return to retry autoboot, if so just run it again. */
// for(;;)與while(1) 是一樣的,for(;;)編譯成彙編後是無條件轉移,while(1)是要0和1進行一下比較的,
     所以從這個方向上看for(;;)是要比while(1)快的因爲少了一個比較指令,但現在的編譯器都是有一定的
     優化能力的,像while(1)這種會優化成和for(;;)一樣的彙編代碼

for (;;) {        

     main_loop ();       
//進入主循環  主循環函數處理執行用戶命令 main_loop 是在common/main.c       中定義和實現的

}

/* NOTREACHED - no way out of command loop except booting */
}



void hang (void)
{
     puts ("### ERROR ### Please RESET the board ###\n");
     for (;;);
}

#ifdef CONFIG_MODEM_SUPPORT
static inline void mdm_readline(char *buf, int bufsiz);

/* called from main loop (common/main.c) */
extern void  dbg(const char *fmt, ...);
int mdm_init (void)
{
     char env_str[16];
     char *init_str;
     int i;
     extern char console_buffer[];
     extern void enable_putc(void);
     extern int hwflow_onoff(int);

     enable_putc(); /* enable serial_putc() */

#ifdef CONFIG_HWFLOW
     init_str = getenv("mdm_flow_control");
     if (init_str && (strcmp(init_str, "rts/cts") == 0))
          hwflow_onoff (1);
     else
          hwflow_onoff(-1);
#endif

     for (i = 1;;i++) {
          sprintf(env_str, "mdm_init%d", i);
          if ((init_str = getenv(env_str)) != NULL) {
               serial_puts(init_str);
               serial_puts("\n");
               for(;;) {
                    mdm_readline(console_buffer, CFG_CBSIZE);
                    dbg("ini%d: [%s]", i, console_buffer);

                    if ((strcmp(console_buffer, "OK") == 0) ||
                         (strcmp(console_buffer, "ERROR") == 0)) {
                         dbg("ini%d: cmd done", i);
                         break;
                    } else /* in case we are originating call ... */
                         if (strncmp(console_buffer, "CONNECT", 7) == 0) {
                              dbg("ini%d: connect", i);
                              return 0;
                         }
               }
          } else
               break; /* no init string - stop modem init */

          udelay(100000);
     }

     udelay(100000);

     /* final stage - wait for connect */
     for(;i > 1;) { /* if 'i' > 1 - wait for connection
                      message from modem */
          mdm_readline(console_buffer, CFG_CBSIZE);
          dbg("ini_f: [%s]", console_buffer);
          if (strncmp(console_buffer, "CONNECT", 7) == 0) {
               dbg("ini_f: connected");
               return 0;
          }
     }

     return 0;
}

/* 'inline' - We have to do it fast */
static inline void mdm_readline(char *buf, int bufsiz)
{
     char c;
     char *p;
     int n;

     n = 0;
     p = buf;
     for(;;) {
          c = serial_getc();

          /*          dbg("(%c)", c); */

          switch(c) {
          case '\r':
               break;
          case '\n':
               *p = '\0';
               return;

          default:
               if(n++ > bufsiz) {
                    *p = '\0';
                    return; /* sanity check */
               }
               *p = c;
               p++;
               break;
          }
     }
}
#endif     /* CONFIG_MODEM_SUPPORT */
























以下是main_loop函數的分析 該文件在common/main.c中

void main_loop (void)
{
#ifndef CFG_HUSH_PARSER                                        // CFG_HUSH_PARSER沒有定義,所以執行
     static char lastcommand[CFG_CBSIZE] = { 0, };  CFG_CBSIZE爲256 用於記錄console buffer size
     int len;
     int rc = 1;
     int flag;
#endif

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)  
//  執行  定義指針s和int型bootdelay。用於uboot判斷無任何按鍵按下,就執行CONFIG_BOOTCOMMAND宏所對應的命令,這個命令通常用於加載啓動操作系統;
     char *s;
     int bootdelay;
#endif
#ifdef CONFIG_PREBOOT   沒定義,不執行
     char *p;
#endif
#ifdef CONFIG_BOOTCOUNT_LIMIT    沒定義 不執行
     unsigned long bootcount = 0;
     unsigned long bootlimit = 0;
     char *bcs;
     char bcs_set[16];
#endif /* CONFIG_BOOTCOUNT_LIMIT */

#if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO)  沒定義 但是我們可以定義,讓顯示開機log
     ulong bmp = 0;          /* default bitmap */
     extern int trab_vfd (ulong bitmap);

#ifdef CONFIG_MODEM_SUPPORT             沒定義
     if (do_mdm_init)
          bmp = 1;     /* alternate bitmap */
#endif
     trab_vfd (bmp);
#endif     /* CONFIG_VFD && VFD_TEST_LOGO */

#ifdef CONFIG_BOOTCOUNT_LIMIT        沒定義 不執行
     bootcount = bootcount_load();
     bootcount++;
     bootcount_store (bootcount);
     sprintf (bcs_set, "%lu", bootcount);
     setenv ("bootcount", bcs_set);
     bcs = getenv ("bootlimit");
     bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */

#ifdef CONFIG_MODEM_SUPPORT           暫時不支持通話 沒定義 不執行
     debug ("DEBUG: main_loop:   do_mdm_init=%d\n", do_mdm_init);
     if (do_mdm_init) {
          char *str = strdup(getenv("mdm_cmd"));
          setenv ("preboot", str);  /* set or delete definition */
          if (str != NULL)
               free (str);
          mdm_init(); /* wait for modem connection */
     }
#endif  /* CONFIG_MODEM_SUPPORT */

#ifdef CONFIG_VERSION_VARIABLE  沒定義 不執行
     {
          extern char version_string[];

          setenv ("ver", version_string);  /* set version variable */ 設置環境變量ver=“”
     }
#endif /* CONFIG_VERSION_VARIABLE */

#ifdef CFG_HUSH_PARSER   沒定義 不執行
     u_boot_hush_start ();
#endif

#ifdef CONFIG_AUTO_COMPLETE   該宏非常好用,可以讓我們自動補齊命令
     install_auto_complete();
#endif

     #ifdef CONFIG_PREBOOT  沒定義 不執行
          if ((p = getenv ("preboot")) != NULL) {
     # ifdef CONFIG_AUTOBOOT_KEYED
               int prev = disable_ctrlc(1);     /* disable Control C checking */
     # endif
    
     # ifndef CFG_HUSH_PARSER
               run_command (p, 0);
     # else
               parse_string_outer(p, FLAG_PARSE_SEMICOLON |
                             FLAG_EXIT_FROM_LOOP);
     # endif
    
     # ifdef CONFIG_AUTOBOOT_KEYED
               disable_ctrlc(prev);     /* restore Control C checking */
     # endif
          }
     #endif /* CONFIG_PREBOOT */ 

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
     s = getenv ("bootdelay");      //獲取環境變量bootdelay的值 即將環境變量bootdelay等號後面的值的地址保存在s ,即s保存字符串的地址,比如是5,即5此時是字符串
     bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;     //如果環境變量中沒有bootdelay參數,則就用默認的CONFIG_BOOTDELAY來當作倒數計時
                         bootdelay保存該環境變量的值
     debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);

# ifdef CONFIG_BOOT_RETRY_TIME    不執行
     init_cmd_timeout ();
# endif     /* CONFIG_BOOT_RETRY_TIME */

#ifdef CONFIG_POST
     if (gd->flags & GD_FLG_POSTFAIL) {
          s = getenv("failbootcmd");
     }
     else
#endif /* CONFIG_POST */
#ifdef CONFIG_BOOTCOUNT_LIMIT   不執行
     if (bootlimit && (bootcount > bootlimit)) {
          printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
                  (unsigned)bootlimit);
          s = getenv ("altbootcmd");
     }
     else
#endif /* CONFIG_BOOTCOUNT_LIMIT */

*******************************************************************************************************************************************
以下是兩種情況
第一種情況是 在bootdelay內不按空格鍵:     s=getenv ("bootcmd");run_command (s, 0);         直接啓動內核了
第二種情況就是 在bootdelay內按下空格鍵: s = getenv("menucmd");run_command (s, 0);      進入命令循環
  len = readline (CFG_PROMPT);run_command (lastcommand, flag);                         之後在run_command裏
  if ((cmdtp = find_cmd(argv[0])) == NULL)--->if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0)即根據命令的名字
  找到該命令表結構體,調用它的cmd參數 最終還是回到do_xxx
比如 run_command("bootm")---->根據“bootm”名字會在命令表裏去找到相應的命令表,最終用該命令表的
cmd參數即do_bootm去處理宏U_BOOT_CMD定義了一個段屬性爲.u_boot_cmd的命令表結構體 struct cmd_tbl_t

          s = getenv ("bootcmd");                      //啓動命令 非常重要  bootcmd = nand read  目的地址 源地址; bootm 目的地址

     debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

     if (bootdelay >= 0 && s && !abortboot (bootdelay)) {  //如果倒數計時之內沒有按空格鍵 則直接
     run_command (s, 0)                                                     直接啓動內核了
    
# ifdef CONFIG_AUTOBOOT_KEYED  不執行
          int prev = disable_ctrlc(1);     /* disable Control C checking */
# endif

# ifndef CFG_HUSH_PARSER
          run_command (s, 0);  //直接啓動內核了
# else
          parse_string_outer(s, FLAG_PARSE_SEMICOLON |
                        FLAG_EXIT_FROM_LOOP);
# endif

# ifdef CONFIG_AUTOBOOT_KEYED
          disable_ctrlc(prev);     /* restore Control C checking */
# endif
     }

# ifdef CONFIG_MENUKEY
     if (menukey == CONFIG_MENUKEY) {
         s = getenv("menucmd");                                     //否則如果在倒數計時之內按下了空格鍵,就會跑到這裏來
         if (s) {
# ifndef CFG_HUSH_PARSER
          run_command (s, 0);                                         //就會進入到菜單裏面 當然你需要自己去實現這個菜單
# else
          parse_string_outer(s, FLAG_PARSE_SEMICOLON |
                        FLAG_EXIT_FROM_LOOP);
# endif
         }
     }
#endif /* CONFIG_MENUKEY */
#endif     /* CONFIG_BOOTDELAY */

#ifdef CONFIG_AMIGAONEG3SE
     {
         extern void video_banner(void);
         video_banner();
     }
#endif

     /*
     * Main Loop for Monitor Command Processing                //進入循環 處理用戶輸入的命令
     */
#ifdef CFG_HUSH_PARSER
     parse_file_outer();
     /* This point is never reached */
     for (;;);
#else                                            //執行else分支 在這裏進入循環來處理各種用戶輸入的命令
     for (;;) {
#ifdef CONFIG_BOOT_RETRY_TIME
          if (rc >= 0) {
               /* Saw enough of a valid command to
               * restart the timeout.
               */
               reset_cmd_timeout();
          }
#endif
          len = readline (CFG_PROMPT);       //讀取串口裏用戶輸入的命令 一回車這些輸入的字符串就被考入到console_buffer裏 len表示獲取的字符串長度

          flag = 0;     /* assume no special flags for now */
          if (len > 0)
               strcpy (lastcommand, console_buffer);                  //將用戶輸入的命令字符串拷貝到lastcommand裏
          else if (len == 0)
               flag |= CMD_FLAG_REPEAT;
#ifdef CONFIG_BOOT_RETRY_TIME
          else if (len == -2) {
               /* -2 means timed out, retry autoboot
               */
               puts ("\nTimed out waiting for command\n");
# ifdef CONFIG_RESET_TO_RETRY
               /* Reinit board to run initialization code again */
               do_reset (NULL, 0, 0, NULL);
# else
               return;          /* retry autoboot */
# endif
          }
#endif

          if (len == -1)
               puts ("<INTERRUPT>\n");
          else
               rc = run_command (lastcommand, flag);                              //解析和處理用戶輸入的命令

          if (rc <= 0) {
               /* invalid command or not repeatable, forget it */
               lastcommand[0] = 0;
          }
     }
#endif /*CFG_HUSH_PARSER*/
}
       
  
  
  
  
  
  
  
       
----------------------------------------------------------------------------------------------------     
----------------------------------------------------------------------------------------------------      
----------------------------------------------------------------------------------------------------
       main_loop又臭又長,去掉宏註釋掉的部分就只剩下一點點了。如下:

void main_loop (void)
{
#ifndef CONFIG_SYS_HUSH_PARSER
    static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
    int len;
    int rc = 1;
    int flag;
#endif

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    char *s;
    int bootdelay;
#endif

#ifdef CONFIG_AUTO_COMPLETE
    install_auto_complete();                                    //安裝自動補全的函數,分析如下 。
#endif

#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    s = getenv ("bootdelay");
    bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

    debug ("### main_loop entered: bootdelay=%d/n/n", bootdelay);


        s = getenv ("bootcmd");                               //獲取引導命令。分析見下面。

    debug ("### main_loop: bootcmd=/"%s/"/n", s ? s : "<UNDEFINED>");

    if (bootdelay >= 0 && s && !abortboot (bootdelay)) {          //如果延時大於等於零,並且沒有在延時過程中接收到按鍵,則引導內核。abortboot函數的分析見下面。
        /*  重點1 */
       
        run_command (s, 0);         //運行引導內核的命令。這個命令是在配置頭文件中定義的。run_command的分析在下面。
    }

#endif    /* CONFIG_BOOTDELAY */

    for (;;) {                                                                    否則就進入uboot命令行執行各個uboot命令了
        len = readline (CONFIG_SYS_PROMPT);   //CONFIG_SYS_PROMPT的意思是回顯字符,一般是“>”。這是由配置頭文件定義的 readline讀入用戶輸入的字符串,存放在console_buffer

        flag = 0;    /* assume no special flags for now */
        if (len > 0)
            strcpy (lastcommand, console_buffer); //保存輸入的數據。
        else if (len == 0)
            flag |= CMD_FLAG_REPEAT;//如果輸入數據爲零,則重複執行上次的命令,如果上次輸入的是一個命令的話

        if (len == -1)
            puts ("<INTERRUPT>/n");
        else
            rc = run_command (lastcommand, flag); //執行命令 ,重點2。

        if (rc <= 0) {//執行失敗,則清空記錄
            /* invalid command or not repeatable, forget it */
            lastcommand[0] = 0;
        }
    }

}


2。自動補全
common/common.c

int var_complete(int argc, char *argv[], char last_char, int maxv, char *cmdv[])
{
    static char tmp_buf[512];
    int space;

    space = last_char == '/0' || last_char == ' ' || last_char == '/t';

    if (space && argc == 1)
        return env_complete("", maxv, cmdv, sizeof(tmp_buf), tmp_buf);

    if (!space && argc == 2)
        return env_complete(argv[1], maxv, cmdv, sizeof(tmp_buf), tmp_buf);

    return 0;
}

static void install_auto_complete_handler(const char *cmd,
        int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]))
{
    cmd_tbl_t *cmdtp;

    cmdtp = find_cmd(cmd);
    if (cmdtp == NULL)
        return;

    cmdtp->complete = complete; //命令結構體的complete指針指向傳入的函數。
}

void install_auto_complete(void)
{
#if defined(CONFIG_CMD_EDITENV)
    install_auto_complete_handler("editenv", var_complete);
#endif
    install_auto_complete_handler("printenv", var_complete);
    install_auto_complete_handler("setenv", var_complete);
#if defined(CONFIG_CMD_RUN)
    install_auto_complete_handler("run", var_complete);
#endif
}

可以看到將editenv、printenv、setenv和run的自動補全函數安裝爲 var_complete。
var_complete的功能是根據給出的前綴字符串,找出所有前綴相同的命令。

每個命令在內存中用一個cmd_tbl_t 表示。

include/command.h

struct cmd_tbl_s {
    char        *name;        /* 命令名,輸入的就是它            */
    int        maxargs;        /* 最大參數個數    */
    int        repeatable;    /* 允許自動重發,也就是在按下空格鍵之後執行最後一條命令。        */
                   
    int        (*cmd)(struct cmd_tbl_s *, int, int, char *[]); /* 實現命令的參數 */
    char        *usage;        /* 短的提示信息    */
#ifdef    CONFIG_SYS_LONGHELP
    char        *help;        /* 詳細的幫助信息。    */
#endif
#ifdef CONFIG_AUTO_COMPLETE
    /* do auto completion on the arguments */

    int        (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};

typedef struct cmd_tbl_s    cmd_tbl_t;

extern cmd_tbl_t  __u_boot_cmd_start;
extern cmd_tbl_t  __u_boot_cmd_end;

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) /            U_BOOT_CMD宏定義了一個cmd_tbl_t結構的uboot命令,用find_cmd(argv[0])) 參數是命令的名字,根據名字找到該命令結構cmd_tbl_t
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

#define U_BOOT_CMD_MKENT(name,maxargs,rep,cmd,usage,help) /
{#name, maxargs, rep, cmd, usage, help}

uboot中的命令使用U_BOOT_CMD這個宏聲明來註冊進系統,鏈接腳本會把所有的cmd_tbl_t結構體放在相鄰的地方。
鏈接腳本中的一些內容如下:
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
可見,__u_boot_cmd_start 和__u_boot_cmd_end 分別對應命令結構體在內存中開始和結束的地址。




3。abortboot        函數的分析
abortboot是uboot在引導期間的延時函數。期間可以按鍵進入uboot的命令行。
common/main.c
static __inline__ int abortboot(int bootdelay)   該函數 即在延時時間內,若掃描有鍵按下即返回1,若無鍵按下 則返回0
{
    int abort = 0;
    printf("Hit any key to stop autoboot: %2d ", bootdelay);

#if defined CONFIG_ZERO_BOOTDELAY_CHECK   //如果定義了這個宏,即使定義延時爲0,也會檢查一次是否有按鍵按下。只要在這裏執行之前按鍵,還是能進入uboot的命令行。
    if (bootdelay >= 0) {
        if (tstc()) {    /* we got a key press    */ 測試是否有按鍵按下
            (void) getc();  /* consume input    */接收按鍵值
            puts ("/b/b/b 0");
            abort = 1;    /* don't auto boot    */修改標記,停止自動引導
        }
    }
#endif

    while ((bootdelay > 0) && (!abort)) { //如果延時大於零並且停止標記沒有賦值則進入延時循環,直到延時完或者接收到了按 鍵
        int i;

        --bootdelay;
        /* delay 100 * 10ms */ 每秒中測試按鍵100次,之後延時10ms。
        for (i=0; !abort && i<100; ++i) {
            if (tstc()) {    /* we got a key press    */
                abort  = 1;    /* don't auto boot    */*/修改標記,停止自動引導
                bootdelay = 0;    /* no more delay    */延時歸零
                (void) getc();  /* consume input    */獲取按鍵
                break;
            }
            udelay(10000);//延時10000us,也就是10ms
        }

        printf("/b/b/b%2d ", bootdelay);//打印當前剩餘時間
    }

    putc('/n');
    return abort;//返回結果:1-停止引導,進入命令行; 0-引導內核。
}

可以看到uboot延時的單位是秒,如果想提高延時的精度,比如想進行10ms級的延時,將udelay(10000)改爲udelay(100)就可以了 。






run_command 重點
//////////////////////////////////////////////////////////////////////////////////////////////////////////
int run_command (const char *cmd, int flag)
{
    cmd_tbl_t *cmdtp;
    char cmdbuf[CONFIG_SYS_CBSIZE];    /* working copy of cmd        */
    char *token;            /* start of token in cmdbuf    */
    char *sep;            /* end of token (separator) in cmdbuf */
    char finaltoken[CONFIG_SYS_CBSIZE];
    char *str = cmdbuf;
    char *argv[CONFIG_SYS_MAXARGS + 1];    /* NULL terminated    */
    int argc, inquotes;
    int repeatable = 1;
    int rc = 0;

    clear_ctrlc();        /* forget any previous Control C */

    if (!cmd || !*cmd) {
        return -1;    /* empty command */  空命令
    }

    if (strlen(cmd) >= CONFIG_SYS_CBSIZE) {  //命令太長
        puts ("## Command too long!/n");
        return -1;
    }

    strcpy (cmdbuf, cmd);            //將命令拷貝到臨時命令緩衝cmdbuf

    /* Process separators and check for invalid
     * repeatable commands
     */
    while (*str) {  //str指向cmdbuf

        /*
         * Find separator, or string end
         * Allow simple escape of ';' by writing "/;"
         */
        for (inquotes = 0, sep = str; *sep; sep++) { 
        //尋找分割符或者命令尾部。相鄰的句子之間是用;隔開的。每次處理一句命令
            if ((*sep=='/'') &&
                (*(sep-1) != '//'))
                inquotes=!inquotes;

            if (!inquotes &&
                (*sep == ';') &&    /* separator        */
                ( sep != str) &&    /* past string start    */
                (*(sep-1) != '//'))    /* and NOT escaped    */
                break;
        }

        /*
         * Limit the token to data between separators
         */
        token = str;        //token指向命令的開頭
        if (*sep) {        //如果是分隔符的話,將分隔符替換爲空字符
            str = sep + 1;    /* start of command for next pass */str指向下一句的開頭
            *sep = '/0';
        }
        else
            str = sep;    /* no more commands for next pass */如果沒有其它命令了,str指向命令尾部

        /* find macros in this token and replace them */
        process_macros (token, finaltoken);      
       //將token指向的命令中的宏替換掉,如把$(kernelsize)替換成內核的大小

        /* Extract arguments */
        if ((argc = parse_line (finaltoken, argv)) == 0) {
     //將每一個參數用‘/0’隔開,argv中的每一個指針指向一個參數的起始地址。 返回值爲參數的個數
            rc = -1;    /* no command at all */
            continue;
        }

        /* Look up command in command table */
        if ((cmdtp = find_cmd(argv[0])) == NULL) { 
              //第一個參數就是要運行的命令,首先在命令表中找到它的命令結構體的指針
            printf ("Unknown command '%s' - try 'help'/n", argv[0]);
            rc = -1;    /* give up after bad command */
            continue;
        }

        /* found - check max args */
        if (argc > cmdtp->maxargs) { //檢查參數個數是否過多
            cmd_usage(cmdtp);
            rc = -1;
            continue;
        }

        /* OK - call function to do the command */
        if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {      //調用命令執行函數。這是最重要的一步。
            rc = -1;
        }

        repeatable &= cmdtp->repeatable;
        //設置命令自動重複執行的標誌。也就是隻按下enter鍵是否可以執行最近執行的命令 .

        /* Did the user stop this? */
        if (had_ctrlc ())  
  //檢查是否有ctrl+c按鍵按下,如果有,結束當前命令。本函數並沒有從中斷接收字符,接收ctrl+c的是一些執行命令的函數。
            return -1;    /* if stopped then not repeatable */
    }

    return rc ? rc : repeatable;
}
                              ---------------------------------------------------------------------------------------------------
其實 main_loop    函數就是處理兩個run_command     函數,一個是在開機延遲時間內若沒有按下任意鍵,則
直接啓動內核(bootcmd);一個是若按下了,則不會啓動內核,處理uboot命令





uboot最核心的東西就是命令,關於uboot命令的章節筆記本上都有,此處略。
現在來看uboot的最後一部分 啓動內核
啓動內核 s=getenv ("bootcmd");run_command (s, 0);
環境變量bootcmd = nand read  目的地址 源地址 大小; bootm 目的地址
只要是啓動內核,就一定要用到bootcmd,執行這兩條指令
nand read指令的實現是在cmd_nand.c文件中的do_nand函數中實現的,do_nand函數中有很多關於nand命令
比如 nand write,nand erase等等


***************************************************************************************************************
我們來看下關於nand相關的指令實現的函數 do_nand
int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
{
     int i, dev, ret = 0;
     ulong addr, off;
     size_t size;
     char *cmd, *s;
     nand_info_t *nand;
#ifdef CFG_NAND_QUIET
     int quiet = CFG_NAND_QUIET;
#else
     int quiet = 0;
#endif
     const char *quiet_str = getenv("quiet");

     /* at least two arguments please */
     if (argc < 2)  //r如果參數的個數小於2個 退出,也即參數的個數至少是兩個 如nand read
    
          goto usage;

     if (quiet_str)
          quiet = simple_strtoul(quiet_str, NULL, 0) != 0;

     cmd = argv[1]; //獲取nand 後面的那一個命令

     if (strcmp(cmd, "info") == 0) {  //命令 nand info

          putc('\n');
          for (i = 0; i < CFG_MAX_NAND_DEVICE; i++) {
               if (nand_info[i].name)
                    printf("Device %d: %s, sector size %u KiB\n",
                           i, nand_info[i].name,
                           nand_info[i].erasesize >> 10);
          }
          return 0;
     }

     if (strcmp(cmd, "device") == 0) { // 命令 nand device

          if (argc < 3) {
               if ((nand_curr_device < 0) ||
                   (nand_curr_device >= CFG_MAX_NAND_DEVICE))
                    puts("\nno devices available\n");
               else
                    printf("\nDevice %d: %s\n", nand_curr_device,
                           nand_info[nand_curr_device].name);
               return 0;
          }
          dev = (int)simple_strtoul(argv[2], NULL, 10);
          if (dev < 0 || dev >= CFG_MAX_NAND_DEVICE || !nand_info[dev].name) {
               puts("No such device\n");
               return 1;
          }
          printf("Device %d: %s", dev, nand_info[dev].name);
          puts("... is now current device\n");
          nand_curr_device = dev;

#ifdef CFG_NAND_SELECT_DEVICE
          /*
          * Select the chip in the board/cpu specific driver
          */
          board_nand_select_device(nand_info[dev].priv, dev);
#endif

          return 0;
     }

     if (strcmp(cmd, "bad") != 0 && strcmp(cmd, "erase") != 0 &&
         strncmp(cmd, "dump", 4) != 0 &&
         strncmp(cmd, "read", 4) != 0 && strncmp(cmd, "write", 5) != 0 &&
         strcmp(cmd, "scrub") != 0 && strcmp(cmd, "markbad") != 0 &&
         strcmp(cmd, "biterr") != 0 &&
         strcmp(cmd, "lock") != 0 && strcmp(cmd, "unlock") != 0 )
          goto usage;

     /* the following commands operate on the current device */
     if (nand_curr_device < 0 || nand_curr_device >= CFG_MAX_NAND_DEVICE ||
         !nand_info[nand_curr_device].name) {
          puts("\nno devices available\n");
          return 1;
     }
     nand = &nand_info[nand_curr_device];

     if (strcmp(cmd, "bad") == 0) {
          printf("\nDevice %d bad blocks:\n", nand_curr_device);
          for (off = 0; off < nand->size; off += nand->erasesize)
               if (nand_block_isbad(nand, off))
                    printf("  %08lx\n", off);
          return 0;
     }

     /*
     * Syntax is:
     *   0    1     2       3    4
     *   nand erase [clean] [off size]
     */
     if (strcmp(cmd, "erase") == 0 || strcmp(cmd, "scrub") == 0) {
          nand_erase_options_t opts;
          /* "clean" at index 2 means request to write cleanmarker */
          int clean = argc > 2 && !strcmp("clean", argv[2]);
          int o = clean ? 3 : 2;
          int scrub = !strcmp(cmd, "scrub");

          printf("\nNAND %s: ", scrub ? "scrub" : "erase");
          /* skip first two or three arguments, look for offset and size */
          if (arg_off_size(argc - o, argv + o, nand, &off, &size) != 0)
               return 1;

          memset(&opts, 0, sizeof(opts));
          opts.offset = off;
          opts.length = size;
          opts.jffs2  = clean;
          opts.quiet  = quiet;

          if (scrub) {
               puts("Warning: "
                    "scrub option will erase all factory set "
                    "bad blocks!\n"
                    "         "
                    "There is no reliable way to recover them.\n"
                    "         "
                    "Use this command only for testing purposes "
                    "if you\n"
                    "         "
                    "are sure of what you are doing!\n"
                    "\nReally scrub this NAND flash? <y/N>\n");

               if (getc() == 'y' && getc() == '\r') {
                    opts.scrub = 1;
               } else {
                    puts("scrub aborted\n");
                    return -1;
               }
          }
          ret = nand_erase_opts(nand, &opts);  //真正解析該命令的函數
          printf("%s\n", ret ? "ERROR" : "OK");

          return ret == 0 ? 0 : 1;
     }

     if (strncmp(cmd, "dump", 4) == 0) {
          if (argc < 3)
               goto usage;

          s = strchr(cmd, '.');
          off = (int)simple_strtoul(argv[2], NULL, 16);

          if (s != NULL && strcmp(s, ".oob") == 0)
               ret = nand_dump(nand, off, 1);
          else
               ret = nand_dump(nand, off, 0);

          return ret == 0 ? 1 : 0;

     }

     if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) { 
  //命令是 nand read 和nand write 命令的解析
          int read;

          if (argc < 4)
               goto usage;

          addr = (ulong)simple_strtoul(argv[2], NULL, 16);

          read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
          printf("\nNAND %s: ", read ? "read" : "write");
          if (arg_off_size(argc - 3, argv + 3, nand, &off, &size) != 0)
               return 1;

          s = strchr(cmd, '.');
          if (!s || !strcmp(s, ".jffs2") ||
              !strcmp(s, ".e") || !strcmp(s, ".i")) {
               if (read)
                    ret = nand_read_skip_bad(nand, off, &size,
                                   (u_char *)addr);
               else
                    ret = nand_write_skip_bad(nand, off, &size,
                                     (u_char *)addr);
          } else if (s != NULL && !strcmp(s, ".oob")) {
               /* out-of-band data */
               mtd_oob_ops_t ops = {
                    .oobbuf = (u8 *)addr,
                    .ooblen = size,
                    .mode = MTD_OOB_RAW
               };

               if (read)
                    ret = nand->read_oob(nand, off, &ops);
               else
                    ret = nand->write_oob(nand, off, &ops);
          } else {
               printf("Unknown nand command suffix '%s'.\n", s);
               return 1;
          }

          printf(" %d bytes %s: %s\n", size,
                 read ? "read" : "written", ret ? "ERROR" : "OK");

          return ret == 0 ? 0 : 1;
     }

     if (strcmp(cmd, "markbad") == 0) {
          addr = (ulong)simple_strtoul(argv[2], NULL, 16);

          int ret = nand->block_markbad(nand, addr);
          if (ret == 0) {
               printf("block 0x%08lx successfully marked as bad\n",
                      (ulong) addr);
               return 0;
          } else {
               printf("block 0x%08lx NOT marked as bad! ERROR %d\n",
                      (ulong) addr, ret);
          }
          return 1;
     }

     if (strcmp(cmd, "biterr") == 0) {
          /* todo */
          return 1;
     }

     if (strcmp(cmd, "lock") == 0) {
          int tight  = 0;
          int status = 0;
          if (argc == 3) {
               if (!strcmp("tight", argv[2]))
                    tight = 1;
               if (!strcmp("status", argv[2]))
                    status = 1;
          }
/*
* ! BROKEN !
*
* TODO: must be implemented and tested by someone with HW
*/
#if 0
          if (status) {
               ulong block_start = 0;
               ulong off;
               int last_status = -1;

               struct nand_chip *nand_chip = nand->priv;
               /* check the WP bit */
               nand_chip->cmdfunc (nand, NAND_CMD_STATUS, -1, -1);
               printf("device is %swrite protected\n",
                      (nand_chip->read_byte(nand) & 0x80 ?
                      "NOT " : ""));

               for (off = 0; off < nand->size; off += nand->writesize) {
                    int s = nand_get_lock_status(nand, off);

                    /* print message only if status has changed
                    * or at end of chip
                    */
                    if (off == nand->size - nand->writesize
                        || (s != last_status && off != 0))     {

                         printf("%08lx - %08lx: %8d pages %s%s%s\n",
                                block_start,
                                off-1,
                                (off-block_start)/nand->writesize,
                                ((last_status & NAND_LOCK_STATUS_TIGHT) ? "TIGHT " : ""),
                                ((last_status & NAND_LOCK_STATUS_LOCK) ? "LOCK " : ""),
                                ((last_status & NAND_LOCK_STATUS_UNLOCK) ? "UNLOCK " : ""));
                    }

                    last_status = s;
               }
          } else {
               if (!nand_lock(nand, tight)) {
                    puts("NAND flash successfully locked\n");
               } else {
                    puts("Error locking NAND flash\n");
                    return 1;
               }
          }
#endif
          return 0;
     }

     if (strcmp(cmd, "unlock") == 0) {
          if (arg_off_size(argc - 2, argv + 2, nand, &off, &size) < 0)
               return 1;

/*
* ! BROKEN !
*
* TODO: must be implemented and tested by someone with HW
*/
#if 0
          if (!nand_unlock(nand, off, size)) {
               puts("NAND flash successfully unlocked\n");
          } else {
               puts("Error unlocking NAND flash, "
                    "write and erase will probably fail\n");
               return 1;
          }
#endif
          return 0;
     }

usage:
     printf("Usage:\n%s\n", cmdtp->usage);
     return 1;
}







**********************************************************************************************************************************
bootm命令
bootm命令的實現
/* common/cmd_bootm.c */
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
……        ……
/* 檢查頭部 */
if (crc32 (0, (uchar *)data, len) != checksum) {
          puts ("Bad Header Checksum\n");
          SHOW_BOOT_PROGRESS (-2);
          return 1;
     }
……          ……
/*解壓縮*/
     switch (hdr->ih_comp) {
     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);  //將真正的內核移動到加載地址處hdr->ih_load
#endif     /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
          }
          break;
     case IH_COMP_GZIP:
          printf ("   Uncompressing %s ... ", name);
          if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
                   (uchar *)data, &len) != 0) {
               puts ("GUNZIP ERROR - must RESET board to recover\n");
               SHOW_BOOT_PROGRESS (-6);
               do_reset (cmdtp, flag, argc, argv);
          }
          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 */
     default:
          if (iflag)
               enable_interrupts();
          printf ("Unimplemented compression type %d\n", hdr->ih_comp);
          SHOW_BOOT_PROGRESS (-7);
          return 1;
     }
}
……           ……             ……

switch (hdr->ih_os) {
     default:               /* handled by (original) Linux case */
     case IH_OS_LINUX:                                     //驚奇!!!!!!!!!!!!!!!!!!!在這裏調用了do_bootm_linux函數
         do_bootm_linux  (cmdtp, flag, argc, argv,
                    addr, len_ptr, verify);
         break;
     case IH_OS_NETBSD:
         do_bootm_netbsd (cmdtp, flag, argc, argv,
                    addr, len_ptr, verify);
         break;
     case IH_OS_RTEMS:
         do_bootm_rtems (cmdtp, flag, argc, argv,
                    addr, len_ptr, verify);
         break;
     case IH_OS_VXWORKS:
         do_bootm_vxworks (cmdtp, flag, argc, argv,
                     addr, len_ptr, verify);
         break;
     case IH_OS_QNX:
         do_bootm_qnxelf (cmdtp, flag, argc, argv,
                     addr, len_ptr, verify);
         break;
     }
bootm命令調用do_bootm函數。這個函數專門用來引導各種操作系統映像,可以支持引導Linux、vxWorks、QNX等操作系統。引導Linux的時候,調用do_bootm_linux()  函數。
bootm命令主要做三件事情:

1,解析uImage頭部信息,獲取image_header結構體
2,將真正的內核移動到加載地址處,如果相等就不用移動
3,根據操作系統,選擇啓動內核的函數,如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)
{
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
… …                 … …
      /* we assume that the kernel is in place */
      printf ("\nStarting kernel ...\n\n");
… …                 … …
      theKernel (0, bd->bi_arch_number, bd->bi_boot_params);  /*啓動內核,傳遞啓動參數*/
}

do_bootm_linux()函數是專門引導Linux映像的函數,它還可以處理ramdisk文件系統的映像。這裏引導的內核映像和ramdisk映像,必須是U-Boot格式的。
U-Boot格式的映像可以通過mkimage工具來轉換,其中包含了U-Boot可以識別的符號。
以下是bootm.c裏的do_bootm_linux的源碼

int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
     bd_t     *bd = gd->bd;
     char     *s;
     int     machid = bd->bi_arch_number;  //機器ID
     void     (*theKernel)(int zero, int arch, uint params);

#ifdef CONFIG_CMDLINE_TAG
     char *commandline = getenv ("bootargs");  //獲取啓動參數的命令行
#endif

     theKernel = (void (*)(int, int, uint))images->ep; // the_kernel即鏡像文件的入口地址

     s = getenv ("machid");
     if (s) {
          machid = simple_strtoul (s, NULL, 16);
          printf ("Using machid 0x%x from environment\n", machid);
     }

     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);    //設置內存TAG,會用到bd->bi_dram[].start bd->bi_dram[].size
#endif
#ifdef CONFIG_CMDLINE_TAG
     setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
     if (images->rd_start && images->rd_end)
          setup_initrd_tag (bd, images->rd_start, images->rd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
     setup_videolfb_tag ((gd_t *) gd);
#endif
     setup_end_tag (bd);
#endif

     /* we assume that the kernel is in place */
     printf ("\nStarting kernel ...\n\n");

#ifdef CONFIG_USB_DEVICE
     {
          extern void udc_disconnect (void);
          udc_disconnect ();
     }
#endif

     cleanup_before_linux ();

     theKernel (0, machid, bd->bi_boot_params);     //啓動內核,一去不復返
     /* does not return */

     return 1;
}


第二階段小結:                                  bootcmd
主要是3個關鍵函數 start_armboot ----> main_loop ------> do_bootm_linux
當我們在倒數計時時按下空格鍵進入uboot命令界面,我們若輸入boot命令回車,便會調用bootcmd環境變量的兩條指令,同樣啓動內核
我們按下空格時出現的菜單 是需要自己去實現的,在common裏創建一個新文件cmd_menu.c裏去實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章