天天看點

U-Boot啟動核心分析

<script type=text/javascript> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript> </script> 先來引用一下這篇介紹“ARM Linux核心啟動要求”的文章ARM Linux Kernel Boot Requirements,是ARM Linux核心的維護者Russell King寫的。

  • CPU register settings
    • r0 = 0.
    • r1 = machine type number.
    • r2 = physical address of tagged list in system RAM.
  • CPU mode
    • All forms of interrupts must be disabled (IRQs and FIQs.)
    • The CPU must be in SVC mode. (A special exception exists for Angel.)
  • Caches, MMUs
    • The MMU must be off.
    • Instruction cache may be on or off.
    • Data cache must be off and must not contain any stale data.
  • Devices
    • DMA to/from devices should be quiesced.
  • The boot loader is expected to call the kernel image by jumping directly to the first instruction of the kernel image.

大緻就是以上條件了,請特别關注一下第一條,這個基本上就是U-Boot的go指令和bootm指令之間的本質差別所在了。先來看看bootm指令的實作,在common/cmd_bootm.c的第119行開始有:

#ifdef CONFIG_PPC static boot_os_Fcn do_bootm_linux; #else extern boot_os_Fcn do_bootm_linux; #endif

這裡的預編譯宏說明了,非PPC體系結構的CPU的do_bootm_linux()函數都不是在這個檔案内實作的(extern)。可想而知,這個函數的實作應該是和體系結構相關的,具體到arm體系結構的實作就是在lib_arm/armlinux.c這個檔案當中。可以看到從lib_arm/armlinux.c中的第77行開始就是do_bootm_linux()函數的實作。   其中第85行聲明了這樣一個函數指針theKernel:

void (*theKernel)(int zero, int arch, uint params);

看看它的名字和參數的命名我們也可以猜到這個其實就是核心的入口函數的指針了。幾個參數的命名也說明了上文提到的ARM Linux核心啟動要求的第一條,因為根據ACPS(ARM/Thumb Procedure Call Standard)的規定,這三個參數就是依次使用r0,r1和r2來傳遞的。   接下來第93行就是給這個函數指針指派:

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

可以看到theKernel被指派為hdr->ih_ep,這個hdr是指使用tools/mkimage工具程式制作uImage時加在linux.bin.gz前面的一個頭部,而ih_ep結構體成員儲存的就是使用mkimage時指定的-e參數的值,即核心的入口點(Entry Point)。 知道了hdr->ih_ep的意義之後,給theKernel賦這個值也就是理所當然的了。   最後是對核心入口函數的調用,發生在第270行:

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

調用的時候對參數進行指派,r0=0,r1=bd->bi_arch_number,r2=bd->bi_boot_params,一個都不少。至此U-Boot的使命完成,開始進入ARM Linux的美麗新世界。   ====================================================================   要知道哪個位址是啟動核心,哪個位址啟動檔案系統,要分析common/cmd_bootm.c中的函數do_bootm,因為引導kernel就是bootm這條指令的工作,do_bootm是指令bootm的執行函數。 bootm指令是用來引導經過u-boot的工具mkimage打包後的kernel image的,什麼叫做經過u-boot的工具mkimage打包後的kernel image,這個就要看mkimage的代碼,看看它做了些什麼,雖然我很希望大家不要偷懶,認真地去看看,但是我知道還是有很多人懶得去做這件,那麼我就j将分析mkimage代碼後得到的總結告訴大家,mkimage做了些什麼,怎麼用這個工具。

mkimage的用法

uboot源代碼的tools/目錄下有mkimage工具,這個工具可以用來制作不壓縮或者壓縮的多種可啟動映象檔案。

mkimage在制作映象檔案的時候,是在原來的可執行映象檔案的前面加上一個0x40位元組的頭,記錄參數所指定的資訊,這樣uboot才能識别這個映象是針對哪個CPU體系結構的,哪個OS的,哪種類型,加載記憶體中的哪個位置, 入口點在記憶體的那個位置以及映象名是什麼

[email protected]:/tftpboot# ./mkimage

Usage: ./mkimage -l image

-l ==> list image header information

./mkimage -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image

-A ==> set architecture to 'arch'

-O ==> set operating system to 'os'

-T ==> set image type to 'type'

-C ==> set compression type 'comp'

-a ==> set load address to 'addr' (hex)

-e ==> set entry point to 'ep' (hex)

-n ==> set image name to 'name'

-d ==> use image data from 'datafile'

-x ==> set XIP (execute in place)

參數說明:

-A 指定CPU的體系結構:

取值 表示的體系結構

alpha Alpha

arm A RM

x86 Intel x86

ia64 IA64

mips MIPS

mips64 MIPS 64 Bit

ppc PowerPC

s390 IBM S390

sh SuperH

sparc SPARC

sparc64 SPARC 64 Bit

m68k MC68000

-O 指定作業系統類型,可以取以下值:

openbsd、netbsd、freebsd、4_4bsd、linux、svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos

-T 指定映象類型,可以取以下值:

standalone、kernel、ramdisk、multi、firmware、script、filesystem

-C 指定映象壓縮方式,可以取以下值:

none 不壓縮

gzip 用gzip的壓縮方式

bzip2 用bzip2的壓縮方式

-a 指定映象在記憶體中的加載位址,映象下載下傳到記憶體中時,要按照用mkimage制作映象時,這個參數所指定的位址值來下載下傳

-e 指定映象運作的入口點位址,這個位址就是-a參數指定的值加上0x40(因為前面有個mkimage添加的0x40個位元組的頭)

-n 指定映象名

-d 指定制作映象的源檔案

現在我們來分析一下common/cmd_bootm.c中的函數do_bootm,這是bootm指令的處理函數。

……

image_header_t header;

ulong load_addr = CFG_LOAD_ADDR;

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

{

ulong iflag;

ulong addr;

ulong data, len, checksum;

ulong *len_ptr;

uint unc_len = 0x400000;

int i, verify;

char *name, *s;

int (*appl)(int, char *[]);

image_header_t *hdr = &header;

讀取uboot的環境變量verify,如果環境變量verify等于’n’,則局部變量verify指派成為0;如果環境變量verify為空(即沒有定義環境變量verify)或者環境變量verify不等于’n’,則局部變量verify指派成為1。

s = getenv ("verify");

verify = (s && (*s == 'n')) ? 0 : 1;

如果參數個數小于2(即隻是輸入了bootm),使用預設加載位址CFG_LOAD_ADDR;否則使用第二個參數作為加載位址。

if (argc < 2) {

addr = load_addr;

} else {

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

}

SHOW_BOOT_PROGRESS (1);

printf ("## Booting image at %08lx .../n", addr);

将mkimage添加到映象檔案頭部的64位元組提取到image_header_t 結構變量header中。

定義了CONFIG_HAS_DATAFLASH,表示系統中存在ATMEL的資料Flash。

#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));

判斷image header的magic是否比對,如果不比對,說明下載下傳過程中發生了錯誤。

if (ntohl(hdr->ih_magic) != IH_MAGIC) {

#ifdef __I386__

if (fake_header(hdr, (void*)addr, -1) != NULL) {

addr -= sizeof(image_header_t);

verify = 0;

} else

#endif

{

puts ("Bad Magic Number/n");

SHOW_BOOT_PROGRESS (-1);

return 1;

}

}

SHOW_BOOT_PROGRESS (2);

校驗image header的CRC以及image data的CRC,如果校驗不比對,說明下載下傳過程中發生了錯誤。

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);

print_image_hdr ((image_header_t *)addr);

data = addr + sizeof(image_header_t);

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");

SHOW_BOOT_PROGRESS (-3);

return 1;

}

puts ("OK/n");

}

SHOW_BOOT_PROGRESS (4);

判斷體系結構。

len_ptr = (ulong *)data;

#if defined(__PPC__)

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)

#else

# error Unknown CPU type

#endif

{

printf ("Unsupported Architecture 0x%x/n", hdr->ih_arch);

SHOW_BOOT_PROGRESS (-4);

return 1;

}

SHOW_BOOT_PROGRESS (5);

判斷image類型。

switch (hdr->ih_type) {

case IH_TYPE_STANDALONE:

name = "Standalone Application";

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]);

data += 8;

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);

iflag = disable_interrupts();

#ifdef CONFIG_AMIGAONEG3SE

icache_disable();

invalidate_l1_instruction_cache();

flush_data_cache();

dcache_disable();

#endif

判斷image壓縮類型

switch (hdr->ih_comp) {

case IH_COMP_NONE: 沒有壓縮

if(ntohl(hdr->ih_load) == addr) { 如果image header中訓示的加載位址和bootm指令中參數2指定的位址相同,則表示不需要copy,可以就地執行。

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

如果image header中訓示的加載位址和bootm指令中參數2指定的位址不相同,則表示要從image header中訓示的加載位址處把image data copy到bootm指令中參數2指定的位址處,然後再執行。

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

#endif

}

break;

case IH_COMP_GZIP:

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

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

(uchar *)data, (int *)&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);

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

default:

if (iflag)

enable_interrupts();

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

SHOW_BOOT_PROGRESS (-7);

return 1;

}

puts ("OK/n");

SHOW_BOOT_PROGRESS (7);

根據image 執行type來決定如何引導。

switch (hdr->ih_type) {

case IH_TYPE_STANDALONE:

if (iflag)

enable_interrupts();

if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {

char buf[32];

sprintf(buf, "%lX", len);

setenv("filesize", buf);

return 0;

}

appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);

(*appl)(argc-1, &argv[1]);

return 0;

case IH_TYPE_KERNEL:

case IH_TYPE_MULTI:

break; 下面将有代碼專門處理這兩種image類型

default:

if (iflag)

enable_interrupts();

printf ("Can't boot image type %d/n", hdr->ih_type);

SHOW_BOOT_PROGRESS (-8);

return 1;

}

SHOW_BOOT_PROGRESS (8);

根據image 的OS type來決定如何引導

switch (hdr->ih_os) {

default:

case IH_OS_LINUX:

#ifdef CONFIG_SILENT_CONSOLE

fixup_silent_linux();

#endif

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;

#ifdef CONFIG_LYNXKDI

case IH_OS_LYNXOS:

do_bootm_lynxkdi (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

#endif

case IH_OS_RTEMS:

do_bootm_rtems (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

#if (CONFIG_COMMANDS & CFG_CMD_ELF)

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;

#endif

#ifdef CONFIG_ARTOS

case IH_OS_ARTOS:

do_bootm_artos (cmdtp, flag, argc, argv,

addr, len_ptr, verify);

break;

#endif

}

SHOW_BOOT_PROGRESS (-9);

#ifdef DEBUG

puts ("/n## Control returned to monitor - resetting.../n");

do_reset (cmdtp, flag, argc, argv);

#endif

return 1;

}

  <script type=text/javascript> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript> </script>

繼續閱讀