本節主要學習:
詳細分析UBOOT中"bootcmd=nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0"
中怎么實現bootm命令啟動內核.
其中bootm要做的事情:
a 讀取頭部,把內核拷貝到合適的地方(0X30008000)
b 在do_boom_linux()中把參數給內核准備好,並告訴內核參數的首地址
c 在do_boom_linux()中最后使用theKernel () 引導內核.
{注意:當在cmd_bootm.C中沒有定義宏CONFIG_PPC時,
系統使用./lib_arm/armlinux.C下的do_bootm_linux()函數(本uboot使用的是這個函數).
若定義了該宏,系統會使用./common/cmd_bootm.C下的do_bootm_linux()函數.}
1. bootm 0x30007FC0
為什么這里是從0x30007FC0啟動?
因為Flash上存的內核格式是:uImage
而uiamge由: 頭部(header) + 真正的內核 組成
在下面1.1節中講到頭部占用了64B字節,用來存放各個參數變量,所以真正的內核加載地址是在:
真正的內核開始地址=0x30007FC0+64=0X30008000,所以bootm啟動內核地址剛好位於nand命令加載的地址后面,不需要移動
1.1 uImage頭部結構體分析
頭部:由結構體image_header_t定義,該結構體大小為64B,位於./include/image.h
typedef struct image_header { uint32_t ih_magic; /* Image Header Magic Number(鏡像頭部幻數,為#define IH_MAGIC 0x27051956 ) */ //幻數:用來標記文件的格式 uint32_t ih_hcrc; /* Image Header CRC Checksum(鏡像頭部CRC校驗碼) */ uint32_t ih_time; /* Image Creation Timestamp(鏡像創建時間戳)*/ uint32_t ih_size; /* Image Data Size(鏡像數據大小(不算頭部) ) */ uint32_t ih_load; /* Data Load Address(鏡像數據將要載入的內存地址) */ uint32_t ih_ep; /* Entry Point Address(鏡像入口地址) */ uint32_t ih_dcrc; /* Image Data CRC Checksum(鏡像數據CRC校驗碼) */ uint8_t ih_os; /* Operating System(操作系統類型) */ uint8_t ih_arch; /* CPU architecture(CPU架構) */ uint8_t ih_type; /* Image Type(鏡像類型) */ uint8_t ih_comp; /* Compression Type(壓縮類型) */ uint8_t ih_name[IH_NMLEN]; /* Image Name(鏡像名字ih_name,共32字節 #define IH_NMLEN 32) */ } image_header_t;
1.2 bootm命令之do_bootm函數分析 (bootm命令位於./common/cmd_bootm.c,其中nand命令執行時調用的是do_bootm()函數)
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 = CFG_BOOTM_LEN; int i, verify; char *name, *s; int (*appl)(int, char *[]); image_header_t *hdr = &header; //定義頭部結構體指針hdr等於header的地址. s = getenv ("verify"); //讀取uboot環境變量verify verify = (s && (*s == 'n')) ? 0 : 1; //如果verify==n,局部變量verify=0,否則verify=1. if (argc < 2) { //如果argc==1(只輸入了bootm),則使用缺省加載地址load_addr addr = load_addr; } else { //否則使用argv[1](0x30007FC0)為加載地址 addr = simple_strtoul(argv[1], NULL, 16); } SHOW_BOOT_PROGRESS (1); printf ("## Booting image at %08lx ...\n", addr); //打印"## Booting image at 0x30007FC0 ...\n" #ifdef CONFIG_HAS_DATAFLASH if (addr_dataflash(addr)){ read_dataflash(addr, sizeof(image_header_t), (char *)&header); } else #endif memmove (&header, (char *)addr, sizeof(image_header_t)); //在加載地址中前64B大小的頭部結構體提取到image_header_t結構變量header中,為下面的分析校驗做准備 if (ntohl(hdr->ih_magic) != IH_MAGIC) //判斷幻數Magic number 是否匹配,不匹配說明下載過程中錯誤. { ... } else #endif /* __I386__ */ { 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, (uchar *)data , len) != checksum) { //判斷校驗和 puts ("Bad Header Checksum\n"); SHOW_BOOT_PROGRESS (-2); return 1; } SHOW_BOOT_PROGRESS (3); .... #if defined(__PPC__) //判斷體系結構,校驗CPU類型是否正確 if (hdr->ih_arch != IH_CPU_PPC) #elif defined(__ARM__) if (hdr->ih_arch != IH_CPU_ARM) #elif defined(__I386__) if (hdr->ih_arch != IH_CPU_I386) #elif defined(__mips__) if (hdr->ih_arch != IH_CPU_MIPS) #elif defined(__nios__) if (hdr->ih_arch != IH_CPU_NIOS) #elif defined(__M68K__) if (hdr->ih_arch != IH_CPU_M68K) #elif defined(__microblaze__) if (hdr->ih_arch != IH_CPU_MICROBLAZE) #elif defined(__nios2__) if (hdr->ih_arch != IH_CPU_NIOS2) #elif defined(__blackfin__) if (hdr->ih_arch != IH_CPU_BLACKFIN) #elif defined(__avr32__) if (hdr->ih_arch != IH_CPU_AVR32) #else # error Unknown CPU type //沒有找到CPU類型 #endif ... switch (hdr->ih_type) //判斷鏡像image類型 { ...} switch (hdr->ih_comp) //根據鏡像壓縮(compression)類型把內核鏡像解壓到指定的地址 { case IH_COMP_NONE: //使用的是沒有壓縮,執行該段case if(ntohl(hdr->ih_load) == data) //該data內核地址剛好位於ih_load加載地址,不需要移動,直接運行 { printf (" XIP %s ... ", name); //打印 } else //else執行內核移動,將內核data地址移到 hdr->ih_load (加載地址)中 { ... memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len); ...} break; case IH_COMP_GZIP: .... } ... switch (hdr->ih_os) //根據不同的操作系統類型來啟動內核 { case IH_OS_LINUX: //LINUX系統,執行該段case #ifdef CONFIG_SILENT_CONSOLE fixup_silent_linux(); #endif do_bootm_linux(cmdtp, flag, argc, argv,addr, len_ptr, verify); //執行do_bootm_linux()函數啟動內核 break; case IH_OS_NETBSD: //NETBSD系統 .... .... }
do_bootm()函數若執行無誤,最終會執行do_bootm_linux()函數
1.3 bootm命令之do_bootm_linux函數分析
進入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) { void (*theKernel)(int zero, int arch, uint params); //定義一個函數指針theKernel ... ... theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); //1.設置theKernel地址=ih_ep鏡像入口地址,用於后面啟動內核 ... ... char *commandline = getenv ("bootargs"); //commandline指向"bootargs"命令環境參數. 用於后面setup_commandline_tag的形參 //在本uboot界面中輸入print指令就能得到"bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0" //root=/dev/mtdblock3:表示根文件系統root位於第4個flsh分區(mtdblock3), mtdblock0=bootloader,mtd1=參數,mtd2=內核 //init=/linuxrc:指定內核啟動后運行的第一個腳本是當前目錄下linuxrc腳本 //console=ttySAC0:指定選擇串口0(ttySAC0)來打印信息、 ... ... /*2.設置tag 參數*/ setup_start_tag (bd); //在0X30000100地址保存start_tag數據,tag:用於u-boot給Linux kernel傳遞參數數據,因為內核啟動后不能使用uboot了. setup_memory_tags (bd); //保存memory_tag數據,讓LINUX知道內存多大 setup_commandline_tag (bd, commandline); //保存commandline_tag數據 setup_end_tag (bd); //初始化tag結構體結束 .... cleanup_before_linux (); //3.啟動內核之前需要做一些清理工作,禁止中斷,關閉cache theKernel (0, bd->bi_arch_number, bd->bi_boot_params); //4.通過ih_ep鏡像入口地址啟動內核,然后從0X30000010處讀取tag參數, //其中"bd->bi_arch_number"參數是向內核傳遞的機器ID,用於內核確定機器ID是否正確, bd->bi_arch_number是在start_armboot函數中board_init里賦了值 }
從以上代碼中可以看出啟動內核之前主要執行了兩步驟:
1.4 通過setup_...._tag函數為內核准備參數,
1.5 進入cleanup_before_linux函數清除中斷和cache
1.4 tag參數函數分析:
1.4.1 d setup_start_tag (bd)函數分析如下: (在上面的tag結構體的首地址為什么在0X30000100?)
通過搜索"setup_start_tag"得到該函數位於./lib_arm/armlinux.c中:
static void setup_start_tag (bd_t *bd) { params = (struct tag *) bd->bi_boot_params; //初始化(struct tag *)型全局變量params= bd->bi_boot_params=0x30000100, // 之后的memory_tag和commandline_tag等tag數據都保存在params后面的偏移地址. params->hdr.tag = ATAG_CORE; //存放srat常量:params->hdr.tag = ATAG_CORE=0x54410001, tag表示tag類型的常量。 params->hdr.size = tag_size (tag_core); //存放srat長度:params->hdr.size=5, size表示start_tag的結構大小。 //因為tag_size (tag_core)=((sizeof(struct tag_header) + sizeof(struct tag_core)) >> 2) //其中tag_header結構體里有2個4字節成員(size,tag), //tag_core結構體里有3個4字節成員(flags,pagesize,rootdev) //所以tag_size (tag_core)=(2*4+3*4)>>2=5; 單位是4字節 params->u.core.flags = 0; //存放params的(tag_core型)結構體成員u.core.flags=0 params->u.core.pagesize = 0;//存放params的(tag_core型)結構體成員u.core.pagesize=0 params->u.core.rootdev = 0;//存放params的(tag_core型)結構體成員u.core.rootdev=0 params = tag_next (params); //params指向下一個tag(setup_memory_tags),params=(0x30000100+size*4)=0x30000114 }
通過上面代碼,最終內存分布為:
1.4.2 do_bootm_linux函數中setup_memory_tags(bd)函數分析如下:
static void setup_memory_tags (bd_t *bd) { int i; for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { params->hdr.tag = ATAG_MEM; //存放內存tag常量: params->hdr.tag =ATAG_MEM= 0x54410002 params->hdr.size = tag_size (tag_mem32); //存放內存長度:params->hdr.size =4 (len+ATAG_MEM+u.mem.size+u.mem.start) params->u.mem.start = bd->bi_dram[i].start; //存放內存(sdram)的的首地址, // bd->bi_dram[i].start在start_armboot()函數中init_sequence->dram_init結構函數成員里被復制: // gd->bd->bi_dram[0].start = PHYS_SDRAM_1;其中"PHYS_SDRAM_1"在./include/configs/100ask24x0.h中定義為0X30000000(bank6首地址) //所以,這里存放內存(sdram)首地址:params->u.mem.start =0X30000000; params->u.mem.size = bd->bi_dram[i].size; //同上,gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;"PHYS_SDRAM_1_SIZE"被定義為0X04000000(64Mb) //所以,這里存放內存(sdram)長度: params->u.mem.size=0X04000000; params = tag_next (params); //params指向下一個tag(setup_commandline_tag),params=(0x30000114+size*4)=0x30000124 } }
通過上面代碼,最終內存分布為:
1.4.3 do_bootm_linux函數中setup_commandline_tag(bd)函數分析如下:
static void setup_commandline_tag (bd_t *bd, char *commandline) //commandline:指向"bootargs"命令環境參數 { char *p; if (!commandline) // 判斷bootargs是否為空, return; for (p = commandline; *p == ' '; p++); //去掉空格 if (*p == '\0') //判斷*p是否為空 return; params->hdr.tag = ATAG_CMDLINE; //存放命令行產量: params->hdr.tag =ATAG_MEM= 0x54410009 params->hdr.size = (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2; //存放命令行長度 params->hdr.size /* 其中 strlen (p) + 1 + 4: +1表示添加結束符'/0' */ /* +4 表示向上取整,比如當len=(4,5,6,7)時,size=(len+4)>>2=2; 實現4字節對齊 */ strcpy (params->u.cmdline.cmdline, p); //存放命令行參數:params->u.cmdline.cmdline=boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0 params = tag_next (params); //params指向下一個tag(setup_end_tag) }
通過上面代碼,最終內存分布為:
1.4.4 do_bootm_linux函數中setup_end_tag (bd)函數分析如下:
static void setup_end_tag (bd_t *bd) { params->hdr.tag = ATAG_NONE; //params->hdr.tag =ATAG_NONE=0 params->hdr.size = 0; //size=0 }
通過上面代碼,最終內存分布為:
1.5 進入cleanup_before_linux函數清除中斷和cache(./arm920t/cpu/cpu.c):
int cleanup_before_linux (void) { unsigned long i; disable_interrupts (); //禁止中斷 /* turn off I/D-cache */ //關閉 指令Icache和數據Dcache asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i)); i &= ~(C1_DC | C1_IC); asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i)); /* flush I/D-cache */ i = 0; asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i)); return (0); }